igneum/packaging/mac/app/igneum-miner.sh
igneum-labs 3baf399615 Packaging: Igneum Miner for Mac and Windows, one-tap installs with the coin on every file and window
Mac: bundle renamed Igneum Miner.app (network.igneum.miner, 0.1.0, LSMinimumSystemVersion 11.0, igneum.icns),
Terminal title Igneum Miner, seed line in the first-run text, branded DMG (volume icon, background, icon slots via
dmgbuild), dist/Igneum-Miner-0.1.0.dmg at 16.2 MB, tested synced against the live node on 27400/27401.
Windows: packaging/windows with windres .rc files and embed-resources.sh (relink commands for the next cross-build),
Igneum-Miner.iss (Program Files, Start Menu group, firewall rule, uninstall stops the processes, licence, seed line),
BUILD-INSTALLER.bat and build-installer.ps1 for the project lead's PC (winget Inno Setup 6, rcedit fallback for the exe icon).
Icons: brand/icons/make-icons.py makes igneum.icns, igneum.ico, the Inno wizard art and the DMG background.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 22:26:57 +00:00

460 lines
22 KiB
Bash
Executable file

#!/bin/bash
# Igneum Miner for Apple silicon Macs: the node and the Metal miner in one Terminal window. The devnet is the only
# network for now. Lives in "Igneum Miner.app/Contents/Resources" next to bin/ (igneumd, igneum-miner, igneum-bench).
# The app's launcher opens this script in Terminal. 3 October 2026 (was the devnet launcher, renamed the same day).
#
# What it does, in order:
# 1. Checks the Mac (Apple silicon), the binaries, the ports, and that no other copy is running.
# 2. Starts igneumd --devnet with the database under ~/Library/Application Support/Igneum/devnet,
# RPC on 127.0.0.1:26610, p2p on 0.0.0.0:26611, peered to SEED_PEERS (--addpeer, never --connect,
# so the seed node can dial back in). The node's output goes to ~/Library/Logs/Igneum/node-<stamp>.log.
# 3. Waits until the node is synced ("syncing, N blocks" every 5 s meanwhile). The reading is
# `igneum-miner watch`: peers > 0, blocks caught up with headers, and the block count moving.
# 4. Starts MINERS identities of igneum-miner with the Metal worker (igneum-bench --serve), identity and
# payout label mac-<hostname> (mac-<hostname>-2, -3 ... for more). One identity is plenty: one worker
# owns the whole GPU.
# 5. Prints a plain status line every 30 s, uploads the logs to the intake every 60 s, keeps the Mac
# awake with caffeinate, restarts the node (5 to 60 s later) or a miner after a crash, restarts a
# miner at once when it exits with code 42 (the hourly program change).
# 6. Ctrl+C, closing the window (SIGHUP) or `igneum-miner.sh --stop` stops the miners, their workers,
# then the node, then uploads the logs one last time.
#
# Nothing is written outside the app except the data directory and the log directory below.
# ---- settings ---------------------------------------------------------------------------------------------
VERSION="0.1.0"
NETWORK="devnet"
# The seed node(s) to peer with. For now the project lead's Mac on the house LAN; a public seed node address
# (host:port) replaces it when one exists. A comma list is allowed: "1.2.3.4:26611,5.6.7.8:26611".
SEED_PEERS="${SEED_PEERS:-192.168.68.64:26611}"
# Miner identities on this Mac. 1 is right for one GPU; 0 runs the node alone.
MINERS="${MINERS:-1}"
STATUS_SECS="${STATUS_SECS:-30}"
UPLOAD_SECS="${UPLOAD_SECS:-60}"
RPC_PORT="${IGNEUM_RPC_PORT:-26610}"
P2P_PORT="${IGNEUM_P2P_PORT:-26611}"
DATA="${IGNEUM_DATA:-$HOME/Library/Application Support/Igneum/devnet}"
LOGS="${IGNEUM_LOGS:-$HOME/Library/Logs/Igneum}"
# The log intake (site/api/log.mjs). The key only authorises log uploads and is meant to ship here.
LOG_URL="https://igneum-six.vercel.app/api/log"
LOG_KEY="***INTAKE-KEY-REMOVED***"
LIVE_PAGE="igneum.network/live"
# ---- paths and names --------------------------------------------------------------------------------------
RES="$(cd "$(dirname "$0")" && pwd)"
BIN="$RES/bin"
NODE_EXE="$BIN/igneumd"
MINER_EXE="$BIN/igneum-miner"
WORKER_EXE="$BIN/igneum-bench"
RPC_URL="grpc://127.0.0.1:$RPC_PORT"
PIDFILE="$DATA/igneum-miner.pid"
RUNFILE="$DATA/igneum-miner.run"
STAMP="$(date +%Y%m%d-%H%M%S)"
HOST="$(scutil --get LocalHostName 2>/dev/null || hostname -s)"
HOST="$(printf '%s' "$HOST" | tr -c 'A-Za-z0-9-' '-' | sed -e 's/^-*//' -e 's/-*$//')"
[ -n "$HOST" ] || HOST="mac"
LABEL_BASE="mac-$HOST"
RUN_ID="$LABEL_BASE-$STAMP"
LAUNCHER_LOG="$LOGS/igneum-$STAMP.log"
NODE_LOG=""
START_AT=$SECONDS
log() {
local line
line="$(date '+%Y-%m-%d %H:%M:%S') $*"
printf '%s\n' "$line" 2>/dev/null || true
[ -d "$LOGS" ] && printf '%s\n' "$line" >> "$LAUNCHER_LOG" 2>/dev/null
}
alive() { [ -n "$1" ] && kill -0 "$1" 2>/dev/null; }
uptime_txt() {
local s=$(( SECONDS - START_AT ))
printf '%02d:%02d:%02d' $(( s / 3600 )) $(( s % 3600 / 60 )) $(( s % 60 ))
}
# The Terminal window and tab title: the product name, never the script path (OSC 0 sets both in Terminal.app).
[ -t 1 ] && printf '\033]0;Igneum Miner\007'
# ---- --stop: stop a running instance (also used by "Stop Igneum Miner.command") ----------------------------
stop_mode() {
local pid="" waited=0 left
[ -f "$PIDFILE" ] && pid="$(cat "$PIDFILE" 2>/dev/null)"
if alive "$pid"; then
echo "stopping Igneum Miner (launcher pid $pid): miners first, then the node"
kill -TERM "$pid" 2>/dev/null
while alive "$pid" && [ $waited -lt 60 ]; do sleep 1; waited=$(( waited + 1 )); done
if alive "$pid"; then echo "the launcher did not stop in 60 s; forcing"; kill -KILL "$pid" 2>/dev/null; fi
else
echo "no running launcher found (no live pid in $PIDFILE)"
fi
# Anything left from this app (a launcher killed with SIGKILL leaves its children behind)
left="$(pgrep -f 'Igneum Miner.app/Contents/Resources/bin/' 2>/dev/null)"
if [ -n "$left" ]; then
echo "stopping leftover processes: $(echo $left | tr '\n' ' ')"
pkill -TERM -f 'Igneum Miner.app/Contents/Resources/bin/' 2>/dev/null
sleep 5
pkill -KILL -f 'Igneum Miner.app/Contents/Resources/bin/' 2>/dev/null
fi
rm -f "$PIDFILE" "$RUNFILE"
echo "Igneum Miner is stopped."
exit 0
}
case "$1" in
--stop) stop_mode ;;
--help|-h) sed -n '2,20p' "$0"; echo; echo "usage: igneum-miner.sh [--stop] (settings: SEED_PEERS, MINERS, STATUS_SECS, UPLOAD_SECS at the top)"; exit 0 ;;
esac
# ---- 1. checks --------------------------------------------------------------------------------------------
# sysctl rather than uname -m: a Terminal running under Rosetta reports x86_64 from uname on an Apple silicon Mac.
if [ "$(sysctl -n hw.optional.arm64 2>/dev/null)" != "1" ]; then
echo "Igneum Miner needs an Apple silicon Mac (M1 or later). This Mac reports $(uname -m) and no arm64 support. Nothing was started."
exit 1
fi
for f in "$NODE_EXE" "$MINER_EXE" "$WORKER_EXE"; do
if [ ! -x "$f" ]; then echo "missing or not executable: $f. The app is incomplete; copy it again from the DMG."; exit 1; fi
done
mkdir -p "$DATA" "$LOGS" || { echo "cannot create $DATA or $LOGS"; exit 1; }
if [ -f "$PIDFILE" ] && alive "$(cat "$PIDFILE" 2>/dev/null)"; then
echo "Igneum Miner is already running in another window (launcher pid $(cat "$PIDFILE")). Press Ctrl+C there, or open Stop Igneum Miner."
exit 1
fi
for port in "$RPC_PORT" "$P2P_PORT"; do
busy="$(lsof -nP -iTCP:"$port" -sTCP:LISTEN -t 2>/dev/null | head -1)"
if [ -n "$busy" ]; then
echo "port $port is already in use by pid $busy ($(ps -p "$busy" -o comm= 2>/dev/null)). Is a node already running? Open Stop Igneum Miner first."
exit 1
fi
done
case "$MINERS" in ''|*[!0-9]*) echo "MINERS must be a number (got '$MINERS')"; exit 1 ;; esac
echo "$$" > "$PIDFILE"
printf 'pid=%s\nscript=%s\nrpc=%s\nrun=%s\n' "$$" "$RES/igneum-miner.sh" "$RPC_URL" "$RUN_ID" > "$RUNFILE"
log "Igneum Miner $VERSION on $HOST, network $NETWORK (run $RUN_ID)"
log "Nobody from Igneum will ever ask for your seed. Nothing is bought or sold on this network."
log "node: RPC 127.0.0.1:$RPC_PORT, p2p 0.0.0.0:$P2P_PORT, data $DATA"
log "seed peers: $SEED_PEERS; miners: $MINERS; logs: $LOGS"
log "node $("$NODE_EXE" --version 2>&1 | head -1); worker $(basename "$WORKER_EXE") (Metal)"
# ---- keep awake -------------------------------------------------------------------------------------------
caffeinate -dims -w $$ >/dev/null 2>&1 &
CAFFEINATE_PID=$!
log "keeping the Mac awake while this window runs (caffeinate)"
# ---- the node ---------------------------------------------------------------------------------------------
NODE_PID=""; NODE_STARTS=0; NODE_RESTARTS=0; NODE_RESTART_AT=""; NODE_STARTED_AT=0
NODE_SYNCED=0; NODE_SYNCED_AT=""; SYNC_PREV=""; SYNC_STABLE_SINCE=""; NOPEER_WARNED=0
NODE_BLOCKS=0; NODE_HEADERS=0; NODE_PEERS=0; NODE_DAA=0; NODE_READ_OK=0
start_node() {
local args p seg
NODE_STARTS=$(( NODE_STARTS + 1 ))
seg=""; [ $NODE_STARTS -gt 1 ] && seg="-r$NODE_STARTS"
NODE_LOG="$LOGS/node-$STAMP$seg.log"
args=("--$NETWORK" "--appdir=$DATA" "--rpclisten=127.0.0.1:$RPC_PORT" "--listen=0.0.0.0:$P2P_PORT" --nodnsseed --disable-upnp --nologfiles)
for p in $(printf '%s' "$SEED_PEERS" | tr ',' ' '); do args+=("--addpeer=$p"); done
# Children ignore SIGHUP (the window closing) so the launcher can stop them in order; bash already makes
# background children ignore SIGINT (Ctrl+C) in a script.
( trap '' HUP; exec "$NODE_EXE" "${args[@]}" ) > "$NODE_LOG" 2>&1 &
NODE_PID=$!
NODE_STARTED_AT=$SECONDS
NODE_RESTART_AT=""
NODE_SYNCED=0; SYNC_PREV=""; SYNC_STABLE_SINCE=""; NODE_READ_OK=0
log "igneumd started (pid $NODE_PID): ${args[*]}"
log "node output: $NODE_LOG"
}
stop_node() {
local waited=0
alive "$NODE_PID" || return 0
kill -TERM "$NODE_PID" 2>/dev/null
while alive "$NODE_PID" && [ $waited -lt 30 ]; do sleep 1; waited=$(( waited + 1 )); done
if alive "$NODE_PID"; then log "the node did not stop in 30 s; forcing"; kill -KILL "$NODE_PID" 2>/dev/null; fi
wait "$NODE_PID" 2>/dev/null
}
# One reading from `igneum-miner watch 1 <rpc>`: the first "node1 blocks=..." line. The watch command sleeps
# 10 s after its sample, so it runs in the background and is stopped once the line is there.
read_node() {
local tmp="$LOGS/.watch-$$.tmp" pid n=0 line
NODE_READ_OK=0
alive "$NODE_PID" || return 1
"$MINER_EXE" watch 1 "$RPC_URL" > "$tmp" 2>/dev/null &
pid=$!
while [ $n -lt 32 ]; do
if grep -q ' node1 blocks=' "$tmp" 2>/dev/null; then break; fi
alive "$pid" || break
sleep 0.25; n=$(( n + 1 ))
done
kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null
line="$(grep ' node1 blocks=' "$tmp" 2>/dev/null | head -1)"
rm -f "$tmp"
[ -n "$line" ] || return 1
set -- $(printf '%s' "$line" | sed -E 's/.* blocks=([0-9]+) headers=([0-9]+) daa=([0-9]+) tips=([0-9]+) peers=([0-9]+).*/\1 \2 \3 \4 \5/')
[ "$#" -eq 5 ] || return 1
NODE_BLOCKS=$1; NODE_HEADERS=$2; NODE_DAA=$3; NODE_PEERS=$5; NODE_READ_OK=1
return 0
}
# Synced: a peer, blocks caught up with headers, more than genesis, and the count moving between two readings
# (or stable for 60 s with a peer, for a devnet that is not producing blocks right now).
check_sync() {
read_node || return 1
if [ "$NODE_PEERS" -gt 0 ] && [ "$NODE_BLOCKS" -ge "$NODE_HEADERS" ] && [ "$NODE_BLOCKS" -gt 1 ]; then
[ -n "$SYNC_STABLE_SINCE" ] || SYNC_STABLE_SINCE=$SECONDS
if { [ -n "$SYNC_PREV" ] && [ "$NODE_BLOCKS" -gt "$SYNC_PREV" ]; } || [ $(( SECONDS - SYNC_STABLE_SINCE )) -ge 60 ]; then
SYNC_PREV=$NODE_BLOCKS
return 0
fi
else
SYNC_STABLE_SINCE=""
fi
SYNC_PREV=$NODE_BLOCKS
return 1
}
# ---- the miners -------------------------------------------------------------------------------------------
# Per identity i (1-based): label, pid, worker pid, log, restart bookkeeping, accepted blocks of earlier runs.
MINER_LABEL=(); MINER_PID=(); MINER_WORKER=(); MINER_LOG=(); MINER_STARTS=(); MINER_RESTARTS=(); MINER_RESTART_AT=(); MINER_ACC_BASE=(); MINER_ID=()
i=1
while [ $i -le "$MINERS" ]; do
if [ $i -eq 1 ]; then MINER_LABEL[$i]="$LABEL_BASE"; else MINER_LABEL[$i]="$LABEL_BASE-$i"; fi
MINER_PID[$i]=""; MINER_WORKER[$i]=""; MINER_LOG[$i]=""; MINER_STARTS[$i]=0; MINER_RESTARTS[$i]=0; MINER_RESTART_AT[$i]=""; MINER_ACC_BASE[$i]=0; MINER_ID[$i]=""
i=$(( i + 1 ))
done
MINERS_UP=0
start_miner() {
local i=$1 label seg
label="${MINER_LABEL[$i]}"
MINER_STARTS[$i]=$(( ${MINER_STARTS[$i]} + 1 ))
seg=""; [ "${MINER_STARTS[$i]}" -gt 1 ] && seg="-r${MINER_STARTS[$i]}"
MINER_LOG[$i]="$LOGS/miner-$label-$STAMP$seg.log"
MINER_RESTART_AT[$i]=""
( trap '' HUP; exec "$MINER_EXE" mine "$RPC_URL" 1 100000000 "$label" --worker "$WORKER_EXE" --status-secs "$STATUS_SECS" --exit-on-seed-change --payout-label "$label" ) > "${MINER_LOG[$i]}" 2>&1 &
MINER_PID[$i]=$!
MINER_WORKER[$i]=""
log "miner $label started (pid ${MINER_PID[$i]}): igneum-miner mine $RPC_URL 1 100000000 $label --worker igneum-bench --status-secs $STATUS_SECS --exit-on-seed-change --payout-label $label"
log "miner output: ${MINER_LOG[$i]}"
}
# The worker is the miner's child; remembered so it can be stopped if the miner dies without closing it.
note_worker() {
local i=$1
[ -z "${MINER_WORKER[$i]}" ] && alive "${MINER_PID[$i]}" && MINER_WORKER[$i]="$(pgrep -P "${MINER_PID[$i]}" 2>/dev/null | head -1)"
}
accepted_in() { [ -f "$1" ] && grep -c ' ACCEPTED block' "$1" 2>/dev/null || echo 0; }
stop_miner() {
local i=$1 waited=0
note_worker "$i"
alive "${MINER_PID[$i]}" && kill -TERM "${MINER_PID[$i]}" 2>/dev/null
alive "${MINER_WORKER[$i]}" && kill -TERM "${MINER_WORKER[$i]}" 2>/dev/null
while { alive "${MINER_PID[$i]}" || alive "${MINER_WORKER[$i]}"; } && [ $waited -lt 8 ]; do sleep 1; waited=$(( waited + 1 )); done
alive "${MINER_PID[$i]}" && kill -KILL "${MINER_PID[$i]}" 2>/dev/null
alive "${MINER_WORKER[$i]}" && kill -KILL "${MINER_WORKER[$i]}" 2>/dev/null
wait "${MINER_PID[$i]}" 2>/dev/null
MINER_ACC_BASE[$i]=$(( ${MINER_ACC_BASE[$i]} + $(accepted_in "${MINER_LOG[$i]}") ))
MINER_PID[$i]=""; MINER_WORKER[$i]=""
}
start_all_miners() {
local i=1
while [ $i -le "$MINERS" ]; do start_miner $i; i=$(( i + 1 )); done
MINERS_UP=1
}
stop_all_miners() {
local i=1
while [ $i -le "$MINERS" ]; do stop_miner $i; i=$(( i + 1 )); done
MINERS_UP=0
}
# Miner bookkeeping every second: the live id once it is known, exits and restarts.
tend_miners() {
local i=1 rc delay
while [ $i -le "$MINERS" ]; do
if alive "${MINER_PID[$i]}"; then
note_worker "$i"
if [ -z "${MINER_ID[$i]}" ]; then
MINER_ID[$i]="$(sed -n 's/.*vote_key_hash=\([0-9a-f]\{8\}\).*/\1/p' "${MINER_LOG[$i]}" 2>/dev/null | head -1)"
[ -n "${MINER_ID[$i]}" ] && log "miner ${MINER_LABEL[$i]} shows on $LIVE_PAGE as ${MINER_ID[$i]} once it finds a block"
fi
elif [ -n "${MINER_PID[$i]}" ]; then
wait "${MINER_PID[$i]}" 2>/dev/null; rc=$?
alive "${MINER_WORKER[$i]}" && kill -TERM "${MINER_WORKER[$i]}" 2>/dev/null
MINER_ACC_BASE[$i]=$(( ${MINER_ACC_BASE[$i]} + $(accepted_in "${MINER_LOG[$i]}") ))
MINER_PID[$i]=""; MINER_WORKER[$i]=""
if [ "$rc" -eq 42 ]; then
log "miner ${MINER_LABEL[$i]}: the hourly program changed (exit 42); restarting it now"
MINER_RESTART_AT[$i]=$SECONDS
else
delay=$(( RANDOM % 56 + 5 ))
MINER_RESTARTS[$i]=$(( ${MINER_RESTARTS[$i]} + 1 ))
MINER_RESTART_AT[$i]=$(( SECONDS + delay ))
log "miner ${MINER_LABEL[$i]} exited with code $rc; restarting in $delay s (restart ${MINER_RESTARTS[$i]}). Last lines:"
tail -3 "${MINER_LOG[$i]}" 2>/dev/null | while IFS= read -r l; do log " miner: $l"; done
fi
elif [ -n "${MINER_RESTART_AT[$i]}" ] && [ "$SECONDS" -ge "${MINER_RESTART_AT[$i]}" ] && [ "$NODE_SYNCED" -eq 1 ]; then
start_miner $i
fi
i=$(( i + 1 ))
done
}
# ---- status -----------------------------------------------------------------------------------------------
STATUS_COUNT=0
print_status() {
local i=1 acc=0 a st mh age node_txt miner_txt sync_txt
STATUS_COUNT=$(( STATUS_COUNT + 1 ))
if alive "$NODE_PID"; then
if read_node; then
if [ "$NODE_SYNCED" -eq 1 ]; then sync_txt="synced"; else sync_txt="syncing"; fi
node_txt="node $NODE_BLOCKS blocks, $NODE_PEERS peers, $sync_txt"
else
node_txt="node running (pid $NODE_PID), no reading from the RPC yet"
fi
elif [ -n "$NODE_RESTART_AT" ]; then
node_txt="node restarting in $(( NODE_RESTART_AT - SECONDS )) s"
else
node_txt="node not running"
fi
if [ "$MINERS" -eq 0 ]; then
miner_txt="no miner (MINERS=0)"
else
mh="n/a"; age="n/a"
while [ $i -le "$MINERS" ]; do
acc=$(( acc + ${MINER_ACC_BASE[$i]} + $(accepted_in "${MINER_LOG[$i]}") ))
if alive "${MINER_PID[$i]}"; then
st="$(grep ' STATUS ' "${MINER_LOG[$i]}" 2>/dev/null | tail -1)"
if [ -n "$st" ]; then
a="$(printf '%s' "$st" | sed -E 's/.* hash=([0-9.]+) MH\/s wall.* template_age=([0-9.]+)s.*/\1 \2/')"
set -- $a; mh="${1:-n/a}"; age="${2:-n/a}"
fi
fi
i=$(( i + 1 ))
done
if [ "$MINERS_UP" -eq 1 ]; then
miner_txt="accepted $acc blocks, $mh MH/s, template $age s old"
else
miner_txt="accepted $acc blocks, miner waiting for the node"
fi
fi
log "status: $miner_txt | $node_txt | up $(uptime_txt)"
}
# ---- uploads ----------------------------------------------------------------------------------------------
# JSON {label, machine, run_id, lines} with the last 256 KB of the file, as site/api/log.mjs expects.
upload_one() {
local file=$1 label=$2 json="$LOGS/.upload-$$-$RANDOM.json" code
[ -s "$file" ] || return 0
{
printf '{"label":"%s","machine":"%s","run_id":"%s","lines":"' "$label" "$HOST" "$RUN_ID"
tail -c 262144 "$file" | tr -d '\000-\010\013\014\016-\037' | awk 'BEGIN{ORS=""} { gsub(/\\/,"\\\\"); gsub(/"/,"\\\""); gsub(/\t/,"\\t"); printf "%s\\n", $0 }'
printf '"}'
} > "$json"
code="$(curl -sS --max-time 60 -X POST "$LOG_URL" -H "Content-Type: application/json" -H "x-igneum-key: $LOG_KEY" --data-binary "@$json" -o /dev/null -w '%{http_code}' 2>&1)"
rm -f "$json"
printf '%s' "$code"
}
upload_logs() {
local i=1 r
r="$(upload_one "$LAUNCHER_LOG" "$LABEL_BASE")"; [ "$r" = "200" ] || log "upload of the launcher log failed: $r"
r="$(upload_one "$NODE_LOG" "nodelog-$LABEL_BASE")"; [ "$r" = "200" ] || [ -z "$r" ] || log "upload of the node log failed: $r"
while [ $i -le "$MINERS" ]; do
r="$(upload_one "${MINER_LOG[$i]}" "miner-${MINER_LABEL[$i]}")"; [ "$r" = "200" ] || [ -z "$r" ] || log "upload of the miner log failed: $r"
i=$(( i + 1 ))
done
}
UPLOAD_PID=""
upload_logs_bg() {
alive "$UPLOAD_PID" && return 0 # the previous upload is still running
upload_logs &
UPLOAD_PID=$!
}
# ---- stop -------------------------------------------------------------------------------------------------
STOPPING=0; STOP_REASON=""
on_signal() { STOPPING=1; STOP_REASON="$1"; }
trap 'on_signal "Ctrl+C"' INT
trap 'on_signal "stop requested"' TERM
trap 'on_signal "window closed"' HUP
cleanup() {
trap '' INT TERM HUP
local acc=0 i=1
log "$STOP_REASON: stopping the miners, then the node"
[ "$MINERS" -gt 0 ] && stop_all_miners
stop_node
alive "$CAFFEINATE_PID" && kill "$CAFFEINATE_PID" 2>/dev/null
alive "$UPLOAD_PID" && wait "$UPLOAD_PID" 2>/dev/null
while [ $i -le "$MINERS" ]; do acc=$(( acc + ${MINER_ACC_BASE[$i]} )); i=$(( i + 1 )); done
log "SUMMARY after $(uptime_txt): node started $NODE_STARTS time(s), restarts $NODE_RESTARTS, $STATUS_COUNT status lines, $acc accepted blocks; data stays in $DATA"
upload_logs
rm -f "$PIDFILE" "$RUNFILE"
log "stopped. This window can be closed."
}
# ---- run --------------------------------------------------------------------------------------------------
start_node
sleep 2
if ! alive "$NODE_PID"; then
log "igneumd exited at once. Last lines of $NODE_LOG:"
tail -8 "$NODE_LOG" 2>/dev/null | while IFS= read -r l; do log " node: $l"; done
log "Nothing to restart. If it mentions the database, delete \"$DATA\" and start again (the chain resyncs from the seed in seconds)."
alive "$CAFFEINATE_PID" && kill "$CAFFEINATE_PID" 2>/dev/null
rm -f "$PIDFILE" "$RUNFILE"
exit 1
fi
log "running. Press Ctrl+C to stop. Status every $STATUS_SECS s, logs uploaded every $UPLOAD_SECS s."
log "node: syncing, waiting for the first peer ($SEED_PEERS)"
LAST_SYNC_CHECK=$SECONDS
LAST_STATUS=$SECONDS
LAST_UPLOAD=$SECONDS
while [ "$STOPPING" -eq 0 ]; do
sleep 1
[ "$STOPPING" -eq 0 ] || break
# the node: crash, restart
if [ -n "$NODE_PID" ] && ! alive "$NODE_PID" && [ -z "$NODE_RESTART_AT" ]; then
wait "$NODE_PID" 2>/dev/null; rc=$?
NODE_RESTARTS=$(( NODE_RESTARTS + 1 ))
delay=$(( RANDOM % 56 + 5 ))
NODE_RESTART_AT=$(( SECONDS + delay ))
NODE_SYNCED=0
log "igneumd exited with code $rc after $(( SECONDS - NODE_STARTED_AT )) s; restarting in $delay s (restart $NODE_RESTARTS). Last lines:"
tail -5 "$NODE_LOG" 2>/dev/null | while IFS= read -r l; do log " node: $l"; done
if [ "$MINERS_UP" -eq 1 ]; then log "stopping the miners until the node is back and synced (their connection died with it)"; stop_all_miners; fi
fi
if [ -n "$NODE_RESTART_AT" ] && [ "$SECONDS" -ge "$NODE_RESTART_AT" ]; then start_node; fi
# sync, then the miners
if alive "$NODE_PID" && [ "$NODE_SYNCED" -eq 0 ] && [ $(( SECONDS - LAST_SYNC_CHECK )) -ge 5 ]; then
LAST_SYNC_CHECK=$SECONDS
if check_sync; then
NODE_SYNCED=1; NODE_SYNCED_AT=$SECONDS
log "node synced: $NODE_BLOCKS blocks, $NODE_PEERS peer(s), $(( SECONDS - NODE_STARTED_AT )) s after the start"
if [ "$MINERS" -gt 0 ] && [ "$MINERS_UP" -eq 0 ]; then start_all_miners; fi
elif [ "$NODE_READ_OK" -eq 1 ]; then
log "node: syncing, $NODE_BLOCKS blocks ($NODE_HEADERS headers, $NODE_PEERS peers)"
if [ "$NOPEER_WARNED" -eq 0 ] && [ "$NODE_PEERS" -eq 0 ] && [ $(( SECONDS - NODE_STARTED_AT )) -ge 90 ]; then
NOPEER_WARNED=1
log "no peer after 90 s: is the seed node reachable at $SEED_PEERS? The node keeps retrying (backoff up to 8 minutes)."
fi
elif [ $(( SECONDS - NODE_STARTED_AT )) -ge 60 ]; then
log "node: no answer from the RPC at $RPC_URL yet (still opening its database?); see $NODE_LOG"
fi
fi
[ "$MINERS" -gt 0 ] && tend_miners
if [ $(( SECONDS - LAST_STATUS )) -ge "$STATUS_SECS" ]; then LAST_STATUS=$SECONDS; print_status; fi
if [ $(( SECONDS - LAST_UPLOAD )) -ge "$UPLOAD_SECS" ]; then LAST_UPLOAD=$SECONDS; upload_logs_bg; fi
done
cleanup
exit 0