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>
460 lines
22 KiB
Bash
Executable file
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
|