|
| 1 | +#!/bin/bash |
| 2 | + |
| 3 | +# Source-only helpers shared by the deploy and leaf installers. |
| 4 | +# Keep this file free of top-level work: both entrypoints load it before their |
| 5 | +# own validation and installation steps. |
| 6 | +MONEROOCEAN_COMMON_API_VERSION=1 |
| 7 | + |
| 8 | +is_test_mode() { |
| 9 | + [ "${POOL_DEPLOY_TEST_MODE:-0}" = "1" ] |
| 10 | +} |
| 11 | + |
| 12 | +retry_command() { |
| 13 | + local attempt |
| 14 | + for attempt in 1 2 3 4 5; do |
| 15 | + if "$@"; then |
| 16 | + return 0 |
| 17 | + fi |
| 18 | + [ "$attempt" -eq 5 ] || sleep $((attempt * 5)) |
| 19 | + done |
| 20 | + return 1 |
| 21 | +} |
| 22 | + |
| 23 | +install_node_dependencies() { |
| 24 | + if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then |
| 25 | + retry_command npm ci "$@" |
| 26 | + else |
| 27 | + retry_command npm install "$@" |
| 28 | + fi |
| 29 | +} |
| 30 | + |
| 31 | +configure_journald_retention() { |
| 32 | + install -d -m 755 /etc/systemd/journald.conf.d |
| 33 | + cat >/etc/systemd/journald.conf.d/90-moneroocean-retention.conf <<'EOF' |
| 34 | +[Journal] |
| 35 | +SystemMaxUse=100M |
| 36 | +SystemKeepFree=1G |
| 37 | +SystemMaxFileSize=10M |
| 38 | +EOF |
| 39 | +} |
| 40 | + |
| 41 | +configure_needrestart_pm2_guard() { |
| 42 | + install -d -m 755 /etc/needrestart/conf.d |
| 43 | + rm -f /etc/needrestart/conf.d/moneroocean-critical.conf |
| 44 | + cat >/etc/needrestart/conf.d/moneroocean-pm2.conf <<'EOF' |
| 45 | +# Keep unattended package maintenance from restarting the pool process manager. |
| 46 | +# Restart PM2 deliberately during a maintenance window to load updated libraries. |
| 47 | +$nrconf{override_rc}->{qr(^pm2-user\.service$)} = 0; |
| 48 | +EOF |
| 49 | +} |
| 50 | + |
| 51 | +clone_repo_once() { |
| 52 | + local repo="$1" |
| 53 | + local dest="$2" |
| 54 | + if [ -d "$dest/.git" ]; then |
| 55 | + return 0 |
| 56 | + fi |
| 57 | + retry_command git clone "$repo" "$dest" |
| 58 | +} |
| 59 | + |
| 60 | +configure_overcommit() { |
| 61 | + install -d -m 755 /etc/sysctl.d |
| 62 | + cat >/etc/sysctl.d/90-monero-overcommit.conf <<'EOF' |
| 63 | +vm.overcommit_memory = 2 |
| 64 | +vm.overcommit_ratio = 150 |
| 65 | +EOF |
| 66 | + if ! sysctl -p /etc/sysctl.d/90-monero-overcommit.conf; then |
| 67 | + if is_test_mode; then |
| 68 | + echo "Skipping active overcommit sysctl apply in test mode" |
| 69 | + return 0 |
| 70 | + fi |
| 71 | + return 1 |
| 72 | + fi |
| 73 | +} |
| 74 | + |
| 75 | +configure_pool_conntrack() { |
| 76 | + install -d -m 755 /etc/modules-load.d /etc/sysctl.d |
| 77 | + printf 'nf_conntrack\n' >/etc/modules-load.d/moneroocean-conntrack.conf |
| 78 | + cat >/etc/sysctl.d/92-moneroocean-conntrack.conf <<EOF |
| 79 | +# Leave headroom for daemon RPC and management traffic during miner reconnect bursts. |
| 80 | +net.netfilter.nf_conntrack_max = $POOL_CONNTRACK_MAX |
| 81 | +EOF |
| 82 | + if is_test_mode; then |
| 83 | + echo "Skipping active conntrack module load and sysctl apply in test mode" |
| 84 | + return 0 |
| 85 | + fi |
| 86 | + modprobe nf_conntrack |
| 87 | + sysctl -p /etc/sysctl.d/92-moneroocean-conntrack.conf |
| 88 | + if [ "$(sysctl -n net.netfilter.nf_conntrack_max)" != "$POOL_CONNTRACK_MAX" ]; then |
| 89 | + echo "nf_conntrack_max did not apply: expected $POOL_CONNTRACK_MAX, got $(sysctl -n net.netfilter.nf_conntrack_max)" >&2 |
| 90 | + return 1 |
| 91 | + fi |
| 92 | +} |
| 93 | + |
| 94 | +configure_pool_health_guard() { |
| 95 | + local guard_dir=/usr/local/libexec/moneroocean |
| 96 | + install -d -o root -g root -m 755 "$guard_dir" |
| 97 | + install -o root -g root -m 755 /home/user/nodejs-pool/pool_health_guard.sh "$guard_dir/pool-health-guard" |
| 98 | + install -o root -g root -m 644 /home/user/nodejs-pool/deployment/pool-health-guard.service /lib/systemd/system/pool-health-guard.service |
| 99 | + install -o root -g root -m 644 /home/user/nodejs-pool/deployment/pool-health-guard.timer /lib/systemd/system/pool-health-guard.timer |
| 100 | + systemctl daemon-reload |
| 101 | + systemctl enable pool-health-guard.timer |
| 102 | + if ! is_test_mode; then |
| 103 | + systemctl restart pool-health-guard.timer |
| 104 | + fi |
| 105 | +} |
| 106 | + |
| 107 | +configure_swap() { |
| 108 | + if awk 'NR > 1 {found = 1} END {exit found ? 0 : 1}' /proc/swaps; then |
| 109 | + return 0 |
| 110 | + fi |
| 111 | + if grep -Eq '^[^#]+[[:space:]]+[^[:space:]]+[[:space:]]+swap[[:space:]]' /etc/fstab; then |
| 112 | + swapon -a |
| 113 | + return 0 |
| 114 | + fi |
| 115 | + if [ ! -f /swapfile ] || [ "$(stat -c %s /swapfile 2>/dev/null || echo 0)" -lt 1073741824 ]; then |
| 116 | + rm -f /swapfile |
| 117 | + fallocate -l 1G /swapfile || dd if=/dev/zero of=/swapfile bs=1M count=1024 |
| 118 | + fi |
| 119 | + chmod 600 /swapfile |
| 120 | + if ! awk 'NR > 1 && $1 == "/swapfile" {found = 1} END {exit found ? 0 : 1}' /proc/swaps; then |
| 121 | + mkswap -f /swapfile |
| 122 | + chmod 600 /swapfile |
| 123 | + if [ "$(awk 'NR > 1 {total += $3} END {print total + 0}' /proc/swaps)" -eq 0 ]; then |
| 124 | + if ! swapon /swapfile; then |
| 125 | + if is_test_mode; then |
| 126 | + echo "Skipping active swap enable in test mode" |
| 127 | + else |
| 128 | + return 1 |
| 129 | + fi |
| 130 | + fi |
| 131 | + fi |
| 132 | + fi |
| 133 | + if ! grep -Eq '^[^#]*[[:space:]]/swapfile[[:space:]]' /etc/fstab; then |
| 134 | + echo " /swapfile none swap sw 0 0" >>/etc/fstab |
| 135 | + fi |
| 136 | +} |
| 137 | + |
| 138 | +default_tari_memory_high() { |
| 139 | + local mem_kb |
| 140 | + mem_kb="$(awk '/MemTotal:/ {print $2}' /proc/meminfo)" |
| 141 | + if [ "$mem_kb" -ge $((30 * 1024 * 1024)) ]; then |
| 142 | + echo 18G |
| 143 | + else |
| 144 | + echo 12G |
| 145 | + fi |
| 146 | +} |
| 147 | + |
| 148 | +validate_systemd_memory_limit() { |
| 149 | + local value="$1" |
| 150 | + local name="$2" |
| 151 | + if [[ ! "$value" =~ ^(infinity|max|[0-9]+([.][0-9]+)?[KMGTPE]?)$ ]]; then |
| 152 | + echo "Invalid $name value: $value" >&2 |
| 153 | + exit 1 |
| 154 | + fi |
| 155 | +} |
| 156 | + |
| 157 | +rpc_synced() { |
| 158 | + local url="$1" |
| 159 | + local method="$2" |
| 160 | + local response |
| 161 | + response="$(curl -fsS -H 'Content-Type: application/json' --data "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"$method\",\"params\":{}}" "$url")" || return 1 |
| 162 | + printf '%s' "$response" | python3 -c ' |
| 163 | +import json |
| 164 | +import sys |
| 165 | +
|
| 166 | +method = sys.argv[1] |
| 167 | +payload = json.load(sys.stdin) |
| 168 | +result = payload.get("result") or {} |
| 169 | +if method == "get_info": |
| 170 | + sys.exit(0 if result.get("status") == "OK" and result.get("synchronized") is True and result.get("busy_syncing") is not True else 1) |
| 171 | +if method == "GetTipInfo": |
| 172 | + metadata = result.get("metadata") or {} |
| 173 | + synced = result.get("initial_sync_achieved") |
| 174 | + height = int(metadata.get("best_block_height") or 0) |
| 175 | + sys.exit(0 if synced is True and height > 0 else 1) |
| 176 | +sys.exit(1) |
| 177 | +' "$method" |
| 178 | +} |
| 179 | + |
| 180 | +ensure_rust_toolchain() { |
| 181 | + if [ -s "$HOME/.cargo/env" ]; then |
| 182 | + . "$HOME/.cargo/env" |
| 183 | + fi |
| 184 | + if ! command -v cargo >/dev/null 2>&1; then |
| 185 | + retry_command bash -lc 'set -o pipefail; curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable' |
| 186 | + . "$HOME/.cargo/env" |
| 187 | + fi |
| 188 | + retry_command rustup update stable |
| 189 | +} |
| 190 | + |
| 191 | +checkout_repo_ref() { |
| 192 | + local repo="$1" |
| 193 | + local dest="$2" |
| 194 | + local ref="$3" |
| 195 | + if [ -e "$dest" ] && [ ! -d "$dest/.git" ]; then |
| 196 | + mv "$dest" "$dest.pre-source.$(date +%Y%m%d%H%M%S)" |
| 197 | + fi |
| 198 | + clone_repo_once "$repo" "$dest" |
| 199 | + cd "$dest" |
| 200 | + retry_command git fetch --tags origin |
| 201 | + git checkout --force "$ref" |
| 202 | +} |
| 203 | + |
| 204 | +ensure_tari_user() { |
| 205 | + id -u "$TARI_USER" >/dev/null 2>&1 || useradd -m -d "$TARI_HOME" -s /bin/sh "$TARI_USER" |
| 206 | + install -d -m 755 -o "$TARI_USER" -g "$TARI_USER" "$TARI_HOME" |
| 207 | +} |
| 208 | + |
| 209 | +configure_monero_hugepages() { |
| 210 | + local gid |
| 211 | + groupadd --system "$HUGEPAGES_GROUP" 2>/dev/null || true |
| 212 | + usermod -a -G "$HUGEPAGES_GROUP" monerodaemon |
| 213 | + gid="$(getent group "$HUGEPAGES_GROUP" | cut -d: -f3)" |
| 214 | + test -n "$gid" |
| 215 | + install -d -m 755 /etc/sysctl.d |
| 216 | + cat >/etc/sysctl.d/91-moneroocean-hugepages.conf <<EOF |
| 217 | +vm.nr_hugepages = $MONERO_RANDOMX_HUGEPAGES |
| 218 | +vm.hugetlb_shm_group = $gid |
| 219 | +EOF |
| 220 | + echo 1 >/proc/sys/vm/compact_memory 2>/dev/null || true |
| 221 | + if ! sysctl -p /etc/sysctl.d/91-moneroocean-hugepages.conf; then |
| 222 | + if is_test_mode; then |
| 223 | + echo "Skipping active hugepage sysctl apply in test mode" |
| 224 | + return 0 |
| 225 | + fi |
| 226 | + return 1 |
| 227 | + fi |
| 228 | + if [ "$(sysctl -n vm.nr_hugepages)" -lt "$MONERO_RANDOMX_HUGEPAGES" ]; then |
| 229 | + echo "Warning: requested $MONERO_RANDOMX_HUGEPAGES hugepages but only $(sysctl -n vm.nr_hugepages) are available until reboot or more memory compaction" |
| 230 | + fi |
| 231 | +} |
| 232 | + |
| 233 | +write_monero_service() { |
| 234 | + local block_notify_arg="" |
| 235 | + if [ "${1:-}" = "enable-block-notify" ]; then |
| 236 | + block_notify_arg=" --block-notify '/bin/bash /home/user/nodejs-pool/block_notify.sh'" |
| 237 | + fi |
| 238 | + cat >/lib/systemd/system/monero.service <<EOF |
| 239 | +[Unit] |
| 240 | +Description=Monero Daemon |
| 241 | +After=network.target |
| 242 | +
|
| 243 | +[Service] |
| 244 | +Environment=MALLOC_ARENA_MAX=2 |
| 245 | +SupplementaryGroups=$HUGEPAGES_GROUP |
| 246 | +LimitMEMLOCK=infinity |
| 247 | +ExecStart=/usr/local/src/monero/build/release/bin/monerod --rpc-bind-ip=127.0.0.1 --rpc-bind-port=18083 --hide-my-port --prune-blockchain --enable-dns-blocklist --no-zmq --out-peers 64 --non-interactive --log-level '$MONERO_LOG_CATEGORIES'$block_notify_arg |
| 248 | +Restart=always |
| 249 | +User=monerodaemon |
| 250 | +Nice=10 |
| 251 | +CPUQuota=400% |
| 252 | +
|
| 253 | +[Install] |
| 254 | +WantedBy=multi-user.target |
| 255 | +EOF |
| 256 | +} |
| 257 | + |
| 258 | +write_tari_service() { |
| 259 | + cat >/lib/systemd/system/xtm.service <<EOF |
| 260 | +[Unit] |
| 261 | +Description=Tari Daemon |
| 262 | +After=network.target |
| 263 | +
|
| 264 | +[Service] |
| 265 | +# Tari SubmitBlock JSON bodies can exceed grpc-json-proxy's 1 MiB default when |
| 266 | +# the block carries a large proof body. |
| 267 | +ExecStart=/bin/bash -c "(sleep 2; /usr/bin/node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18146 18142 --max-body-bytes 16777216) & (sleep 2; /usr/bin/node /usr/local/src/grpc-json-proxy/grpc-json-proxy.js /usr/local/src/grpc-json-proxy/base_node.proto 18148 18142 --max-body-bytes 16777216) & /usr/local/src/tari/target/release/minotari_node --non-interactive-mode --watch status --disable-splash-screen" |
| 268 | +Restart=always |
| 269 | +User=$TARI_USER |
| 270 | +Environment=HOME=$TARI_HOME |
| 271 | +Nice=10 |
| 272 | +CPUQuota=400% |
| 273 | +MemoryHigh=$TARI_MEMORY_HIGH |
| 274 | +MemorySwapMax=$TARI_MEMORY_SWAP_MAX |
| 275 | +
|
| 276 | +[Install] |
| 277 | +WantedBy=multi-user.target |
| 278 | +EOF |
| 279 | +} |
| 280 | + |
| 281 | +write_tari_merge_mining_service() { |
| 282 | + local dependencies="$1" |
| 283 | + cat >/lib/systemd/system/xtm_mm.service <<EOF |
| 284 | +[Unit] |
| 285 | +Description=Tari Merge Mining Daemon |
| 286 | +After=network.target $dependencies |
| 287 | +PartOf=$dependencies |
| 288 | +
|
| 289 | +[Service] |
| 290 | +ExecStart=/usr/local/src/tari/target/release/minotari_merge_mining_proxy --non-interactive-mode |
| 291 | +Restart=always |
| 292 | +RestartSec=3s |
| 293 | +StartLimitBurst=0 |
| 294 | +User=$TARI_USER |
| 295 | +Environment=HOME=$TARI_HOME |
| 296 | +Nice=10 |
| 297 | +CPUQuota=400% |
| 298 | +MemoryHigh=$TARI_MM_MEMORY_HIGH |
| 299 | +MemorySwapMax=$TARI_MM_MEMORY_SWAP_MAX |
| 300 | +
|
| 301 | +[Install] |
| 302 | +WantedBy=multi-user.target |
| 303 | +EOF |
| 304 | +} |
0 commit comments