Release: devnet v4 cut-over staged (Windows 0.2.0 packages, Mac app 0.2.0, seed igneumd-v4 unit, runbook)

Windows combined package 0.2.0 (proto-cuda/windows-app): v4 exes from target-integration, peers = seed then Mac,
fresh appdir devnet-v4, one miner and one worker per card with --identities 8, --evm-address (PAYOUT_EVM or derived
per vendor from the PC name), voting on (VOTE=0 opts out), --prepare-packs for the hot swap with --exit-on-seed-change
as the fallback, --yes on the node, STATUS regex tolerant of the v4 now= segment, version in the dashboard header.
Mac app 0.2.0 (packaging/mac): v4 binaries, Metal worker rebuilt for macOS 11, data folder devnet-v4, EVM payout,
identities in one process, synced= flag honoured. Seed (infra/seed-nodes): stage-v4.sh builds v4 on the VM as a
niced, memory-capped transient service and installs a disabled igneumd-v4 unit with a fresh data dir; switch-v4.sh
swaps the units (--back reverses); health.sh reports active-v4. Runbook: docs/plans/cutover-2026-10-04.md.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-04 01:48:56 +00:00
parent bcce17e8da
commit b2cebf78e9
22 changed files with 701 additions and 139 deletions

View file

@ -0,0 +1,163 @@
# Devnet v4 cut-over: runbook for the morning of 4 October 2026
Staged overnight by the release engineer (packages built, seed build installed, nothing switched). The cut-over is a
ten-minute operation in this order. Background: `docs/fork-divergence.md`, section "Integration 4 Oct 2026" (what v4
is, why it is a new chain, the test-network numbers). All Mac commands run from `/Users/joshm/Projects/igneum` unless
a `cd` says otherwise. Times are BST.
## What is staged
| Item | Where | What it carries |
|---|---|---|
| Windows combined package 0.2.0 | `~/Desktop/igneum-windows-v4.zip` (26,223,837 bytes) | v4 `igneumd.exe` and `igneum-miner.exe` (cross-build of `dc749905`), the three mingw DLLs, the launchers: peers = seed `188.245.5.161:26611` then Mac `192.168.68.64:26611`, appdir `%LOCALAPPDATA%\igneum\devnet-v4`, `MINERS=8` as `--identities 8` on ONE worker per card, `--evm-address` (`PAYOUT_EVM`, else derived from the PC name per vendor), voting on, `--prepare-packs` for the hot swap, `--exit-on-seed-change` kept as the fallback, `PLAIN=1` kept, version in the dashboard header |
| Windows node-only package | `/tmp/igneum-integration/igneum-node-windows-v4.zip` and `~/Desktop/igneum-node-windows-v4.zip` (32,946,401 bytes) | same exes, `src.zip`, `START-NODE.bat` with appdir `devnet-v4` and `EXTRA_ARGS=--addpeer=188.245.5.161:26611 --yes` |
| Mac app 0.2.0 | `packaging/mac/dist/Igneum-Miner-0.2.0.dmg` (19,918,617 bytes) | v4 `igneumd` and `igneum-miner` from `target-integration/release`, the Metal worker rebuilt from the hot-swap source with `-target arm64-apple-macos11` (`prepare 1`), `SEED_PEERS` = seed then Mac, data `~/Library/Application Support/Igneum/devnet-v4`, `--evm-address` and `--identities` |
| Seed node v4 | igneum-seed-1 (188.245.5.161): `/opt/igneum/v4/bin/{igneumd,igneum-miner,run-seed-v4.sh}`, `/var/lib/igneum-v4` (empty), `/etc/igneum/seed-v4.env`, unit `igneumd-v4` installed and DISABLED | built on the VM from `vendor/igneum-node-v4` at `dc749905` plus the working-tree `igneum-pow` (`infra/seed-nodes/stage-v4.sh`; log `infra/seed-nodes/build/stage-v4-igneum-seed-1.log`). The v3 unit `igneumd` keeps running on `/var/lib/igneum` until step (c) |
| Mac binaries | `vendor/igneum-node/target-integration/release/igneumd` (40,463,680 bytes), `igneum-miner` (7,916,096) | built from `dc749905` |
Not staged, by design: the live devnet (node 1 on 26610/26611, the observer peer on 26640/26641/28640, `observer.mjs`, the
Mac seed relay on 26680/26681) and the seed's v3 unit all still run the v3 chain.
## The order
### (a) the project lead stops the PC
In both windows on the PC: Ctrl+C (the mining window first, then the node window; or one window if START-IGNEUM.bat
was running: it stops the miners first, then the node). Wait for "Igneum has stopped" / "The node has stopped". Expected:
the PC's node leaves the Mac's peer list within a few seconds (step (e) checks `peers`). The old folder `C:\igneum`
(or wherever v3 was extracted) and `%LOCALAPPDATA%\igneum\devnet` are left as they are.
### (b) Main session cuts the Mac over
```
# 1. stop node 1 (caffeinate exits with it) and move the v3 database aside
kill -INT 72039; while kill -0 72039 2>/dev/null; do sleep 1; done
mv /tmp/igneum-devnet/node1 /tmp/igneum-devnet/node1-v3-2026-10-04
# 2. start igneumd v4: same appdir path, rpclisten and listen, plus the two peers (seed, PC)
cd /Users/joshm/Projects/igneum/vendor/igneum-node
nohup caffeinate -dims target-integration/release/igneumd --devnet --nodnsseed --disable-upnp --enable-unsynced-mining \
--appdir=/tmp/igneum-devnet/node1 --rpclisten=0.0.0.0:26610 --listen=0.0.0.0:26611 \
--addpeer=188.245.5.161:26611 --addpeer=192.168.68.67:26611 --nologfiles --yes > /tmp/igneum-devnet/node1-v4.out 2>&1 &
# the execution layer starts with it (eth_ JSON-RPC on 127.0.0.1:26790); expected in node1-v4.out within 5 s:
# "GRPC Server starting on: 0.0.0.0:26610", "P2P Server starting on: 0.0.0.0:26611", the genesis as the sink, 0 blocks
# 3. restart the observer peer from the same binary (fresh appdir) and observer.mjs
kill -INT 73692; kill -INT 74029
nohup target-integration/release/igneumd --devnet --nodnsseed --disable-upnp --appdir=/tmp/igneum-devnet/observer-v4 \
--rpclisten=127.0.0.1:26640 --rpclisten-json=127.0.0.1:28640 --listen=127.0.0.1:26641 --connect=127.0.0.1:26611 \
--nologfiles --yes > /tmp/igneum-devnet/observer-v4.out 2>&1 &
cd /Users/joshm/Projects/igneum && IGNEUM_RPC=ws://127.0.0.1:28640 nohup node tools/observer/observer.mjs > /tmp/igneum-devnet/observer-mjs-v4.out 2>&1 &
# block numbers restart at 0 on the new chain; LIVE_TABLE_PREFIX=v4_ keeps the v3 rows apart if wanted
# 4. the Mac seed relay (pid 72139, v3 binary): stop it; node 1 now dials the seed itself (--addpeer above), so the
# relay is not needed. If the seed should also be reached through a second path, start it again from v4:
kill -INT 72139
# RELAY_BIN=vendor/igneum-node/target-integration/release/igneumd infra/seed-nodes/addpeer-from-mac.sh relay start (optional; it needs
# its own fresh appdir: rm -rf /tmp/igneum-seed-relay first, or the v3 database refuses to open)
```
Where the Mac miner runs (the Metal worker, `/tmp/igneum-devnet/metal-worker.log`): restart it from
`target-integration/release/igneum-miner` with `--evm-address 0x...` (any address the project lead holds) once node 1 is up; an
old miner's blocks are rejected from the first epoch boundary (DAA 3,600).
Expected after (b): `igneum-miner watch 1 grpc://127.0.0.1:26610` prints `blocks=1 headers=1 ... peers=0 synced=true`
(the genesis only, `--enable-unsynced-mining` reports synced); the live page shows the new chain from block 0.
### (c) Seed: switch to v4
```
cd /Users/joshm/Projects/igneum/infra/seed-nodes
./stage-v4.sh igneum-seed-1 status # expect: build=done | ... | v4: installed igneumd 2.1.0, unit igneumd-v4 disabled/inactive | v3: unit igneumd enabled/active
./switch-v4.sh igneum-seed-1 # stop+disable igneumd, enable+start igneumd-v4, then one sync line every 10 s
```
Expected output of the switch: `igneumd: disabled/inactive igneumd-v4: enabled/active`, six journal lines ending in
`P2P Server starting on: 0.0.0.0:26611` and `WRPC Server starting on: 127.0.0.1:28610`, then lines like
`synced=False version=2.1.0 blocks=1 headers=1 daa=0 sink=... peers=0 active` turning into `synced=True ... peers=1`
within a minute once node 1 (which dials the seed) has connected and the few v4 blocks have crossed. `./health.sh` then
prints `unit=active-v4`. The seed never mines; it relays. Rollback of this step alone: `./switch-v4.sh igneum-seed-1 --back`.
### (d) the project lead starts the PC on v4
1. Extract `igneum-windows-v4.zip` to a FRESH folder, for example `C:\igneum-v4` (not over the old one: the old
package's built workers and packs must not be mixed in).
2. Double-click `START-IGNEUM.bat`. Settings at the top are already right (peers, `devnet-v4`, `MINERS=8`). If the project lead
wants a specific payout address, set `PAYOUT_EVM=0x...` first; otherwise the launcher derives one per card and prints it.
3. Firewall prompt for the new `igneumd.exe` path: tick Private networks, Allow access (new exe path = new prompt).
Expected on the dashboard (header "Igneum: node and miners devnet v4, package 0.2.0"): NODE card "starting", then
"syncing" with blocks climbing (the v4 chain is minutes old, so sync is seconds), "peer joined (1 peers)" then "(2 peers)"
in the events, "synced in N s", then "exporting the program pack", "building the RTX 5090 worker (about 30 s for CUDA)",
"started 8 RTX 5090 identities (1 miner)", "RTX 5090 worker ready, hot swap at the hour boundary" and the first
"RTX 5090 found a block" within a minute. The launcher log (`igneum-<stamp>.log` next to the bat) carries the EVM address
line and the full miner command line.
If the worker's ready event says "no hot swap (nvcc missing)", nvcc is not on the PATH of the launcher: the old exit-42
path rebuilds the worker at the boundary (about 30 s of no mining per hour), nothing else changes.
### (e) Checks
| Check | Command (Mac) | Expected |
|---|---|---|
| node 1 peers | `vendor/igneum-node/target-integration/release/igneum-miner watch 1 grpc://127.0.0.1:26610` | `peers=2` (seed and PC), `synced=true`, `blocks` climbing about 1 per second once the PC mines |
| PC synced, blocks flowing | the PC dashboard NODE card: `synced`, `blocks` equal to the Mac's within a few blocks; `chain N blocks/s` near 1.00 | the Mac's `watch` line shows the same `sink` as the PC's launcher log status block |
| seed synced | `infra/seed-nodes/health.sh` | `OK ... unit=active-v4 rpc=yes synced=True blocks=<same as node 1> peers=1..2` |
| live page | https://igneum.network/live | LIVE dot, the strip (blocks, miners, difficulty, peers) counting from the new genesis, the finality lock markers appear only after the first lock (next row) |
| first checkpoint lock | `IGNEUM_RPC=ws://127.0.0.1:28640 python3 infra/cloud-devnet/node/wrpc.py call getFinalityCheckpoints` | "window filling, N of 7,200" until the sink's DAA score reaches 7,200: the devnet weight window and `min_daa` are 7,200 DAA seconds, which is TWO HOURS of chain time after the v4 genesis (finality.rs `DEVNET`). The first lock lands at the first checkpoint whose DAA score is at least 7,200: expect it about 2 h after the chain starts, and `observer.mjs` then emits "checkpoint N locked (xx% of weight)" and the live page draws the lock marker. Locks every 30 s after that |
| hourly program swap | the PC events at the first boundary (DAA 3,600, about one hour after the v4 genesis; the pack for the next program is written 600 DAA s, 10 minutes, before it) | "the next hourly program is compiled and resident (hot swap ready)" about 10 minutes before the boundary, no miner restart at the boundary, hash rate unbroken. On the Mac miner's log: `prepared ...` then jobs on the new seed. Test-network numbers: first block of the new epoch accepted 0.5 to 2.2 s after the last of the old (fork-divergence) |
| EVM smoke | `cd tools/evm-smoke && IGNEUM_RPCS=http://127.0.0.1:26790 IGNEUM_MINER_KEY=<the private key of an --evm-address that has mined> node smoke.mjs` | `SMOKE SUMMARY: 84 checks passed, 1 failed` is the known result (the one failure wants duplicate sends in parallel blocks and a PoW network with one tip rarely offers any); fewer passes means the execution layer is not running. The quick check without a key: `curl -s -X POST http://127.0.0.1:26790 -H 'content-type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}'` returns `"0x116f"` (4463) and `eth_blockNumber` climbs |
Shorter finality window for the first day (optional). The two-hour wait is the window filling; to see locks sooner, every
node on the network must carry the same override file (it is a consensus parameter, so node 1, the observer peer, the seed
and the PC all need it, and a node without it rejects nothing but certifies on a different schedule, which the lock rule
treats as disagreement). The override file, with every finality field present (the parser takes the whole object):
```
{ "finality": { "checkpoint_interval": 30, "checkpoint_depth": 20, "weight_window": 1800, "dust": 5,
"presence_window": 20, "aggregators": 8, "equivocation_ban": 1800, "min_daa": 1800, "aggregator_fallback": 15 } }
```
Flag on every node: `--override-params-file=/path/override.json` (Mac: in the two `nohup` lines above; PC: `EXTRA_ARGS`
at the top of START-IGNEUM.bat, for example `--override-params-file=C:\igneum-v4\override.json`; seed: `EXTRA_ARGS=` in
`/etc/igneum/seed-v4.env`, then `systemctl restart igneumd-v4`). 1,800 DAA s = 30 minutes to the first lock. The
recommendation is to leave the default (7,200) and let the first lock arrive two hours in: that is the rule the network
will run and the window is the test. The 300-DAA window of the integration test was the same mechanism.
### (f) Rollback to v3 (two commands per machine)
Mac (node 1; the observer peer and `observer.mjs` the same way with their v3 commands from `docs/fork-divergence.md`):
```
pkill -INT -f 'target-integration/release/igneumd --devnet --nodnsseed --disable-upnp --enable-unsynced-mining --appdir=/tmp/igneum-devnet/node1'; sleep 3; mv /tmp/igneum-devnet/node1 /tmp/igneum-devnet/node1-v4-abandoned && mv /tmp/igneum-devnet/node1-v3-2026-10-04 /tmp/igneum-devnet/node1
cd vendor/igneum-node && nohup caffeinate -dims ./target/release/kaspad --devnet --nodnsseed --disable-upnp --enable-unsynced-mining --appdir=/tmp/igneum-devnet/node1 --rpclisten=0.0.0.0:26610 --listen=0.0.0.0:26611 --addpeer=192.168.68.67:26611 --nologfiles > /tmp/igneum-devnet/node1-v3-again.out 2>&1 &
```
(`pkill -f` with the full v4 command line only, never a bare name: the observer peer and the relay must not match.)
Seed: `infra/seed-nodes/switch-v4.sh igneum-seed-1 --back` (stops `igneumd-v4`, starts `igneumd` on the untouched
`/var/lib/igneum`).
PC: Ctrl+C in the v4 window, then `START-IGNEUM.bat` in the OLD folder (its `igneumd.exe`, `igneum-miner.exe` and
`%LOCALAPPDATA%\igneum\devnet` are untouched).
Mac app users: the 0.1.0 app and its `devnet` data folder are untouched by 0.2.0.
## What the old chain's data means
Nothing is lost. The v3 chain (3 October, 12,000+ blocks, the finality v2 locks, the hash-rate measurements) stays in
`/tmp/igneum-devnet/node1-v3-2026-10-04` on the Mac, `/var/lib/igneum` on the seed, `%LOCALAPPDATA%\igneum\devnet` on
the PC and `~/Library/Application Support/Igneum/devnet` on any Mac that ran 0.1.0. The v3 binaries (`target/release/kaspad`,
`target-finality/release/igneumd`, the old zips and `Igneum-Miner-0.1.0.dmg`) can read it. The live page's v3 rows stay in
the `live_*` tables unless `LIVE_TABLE_PREFIX=v4_` is set (then both chains keep their rows apart). The v3 chain is kept
aside for reference and rollback; it is not deleted by any step above. The v4 chain starts again from the same genesis
with block numbers from 0, so comparisons across the cut (block counts, DAA scores, difficulty) restart as well.
## Untested before the morning
| Item | Why |
|---|---|
| Windows package on the PC | no Windows machine on the Mac side; the PowerShell edits are checked by hand and by a bracket and quote balance pass (no `pwsh` on this Mac: the Homebrew cask is unavailable), not parsed by PowerShell. The CUDA worker's hot swap (`host.cu` with `prepare`, uncommitted working tree) has not run on the PC either; the exit-42 rebuild path stays underneath it |
| Mac app sync and mining | tested on this Mac with `MINERS=0` on ports 28900/28901 against a dead peer (launch, version line, EVM address line, node up, Terminal title escape, `--stop` in 4 s, ports free, live devnet untouched); no v4 node was running, so sync and the Metal miner were not exercised by the 0.2.0 bundle (the same binaries mined on the integration test network) |
| Seed v4 unit | built and installed, never started; `switch-v4.sh` is new code, its rollback too |
| First lock and the hourly swap on the real devnet values | 7,200 and 3,600 DAA; only the 300/60 test values ran |

View file

@ -16,8 +16,18 @@ cd infra/seed-nodes
./addpeer-from-mac.sh relay start # a non-mining relay igneumd on the Mac peers with the live node (LAN) and the seed; the
# live node runs in safe RPC mode, so addPeer over grpcurl is refused (see the script)
SEED_NAME=igneum-seed-2 SEED_LOCATION=ash SEED_TYPE=cpx11 ./create-seed.sh # the next seed; provision adds the first as --addpeer
./stage-v4.sh igneum-seed-1 # devnet v4 (4 Oct 2026): source tarball of vendor/igneum-node-v4 -> detached, niced, memory-capped
# build on the VM into /root/v4 -> /opt/igneum/v4/bin, /var/lib/igneum-v4 (fresh), unit igneumd-v4
# installed DISABLED; the v3 unit keeps running. `wait` and `status` modes.
./switch-v4.sh igneum-seed-1 # the cut-over: stop+disable igneumd, enable+start igneumd-v4, print its sync state; --back reverses
```
Devnet v4 is a new chain from the same genesis (docs/fork-divergence.md, "Compatibility"), so the seed runs it from a
fresh data directory, `/var/lib/igneum-v4`, with its own launcher (`node/run-seed-v4.sh`, same flags, binary from
`/opt/igneum/v4/bin`), env file (`/etc/igneum/seed-v4.env`, a copy of `seed.env`) and unit (`node/igneumd-v4.service`,
`Conflicts=igneumd.service`: the two bind the same ports). The v3 database in `/var/lib/igneum` stays as it is for the
rollback. `health.sh` reports `unit=active-v4` once the v4 unit serves. The runbook is `docs/plans/cutover-2026-10-04.md`.
Settings in `config.sh`: provider, name, type, location, image, ssh key (`~/.ssh/igneum_ed25519`), `SSH_SOURCE`
(`any`, `me` or a CIDR for port 22), `NETWORK_ARGS` (`--devnet`; a private network: `--devnet --devnet-suffix=20`),
`SEED_PEERS`, `BUILD_WHERE` (`seed` builds on the VM; `bin` reuses `../cloud-devnet/build/bin`). The Hetzner token is
@ -28,6 +38,6 @@ Node flags (`node/run-seed.sh`): `--devnet --appdir=/var/lib/igneum --rpclisten=
--yes --maxinpeers=128 --outpeers=8 [--addpeer=<other seeds>]`. No `--enable-unsynced-mining` (nothing mines here) and
no `--connect` (it would set the inbound limit to 0 and switch the address manager off).
Files: `create-seed.sh`, `provision-seed.sh`, `health.sh`, `addpeer-from-mac.sh`, `config.sh`, `lib.sh`,
`node/{install-seed.sh,run-seed.sh,igneumd.service}`, `seeds.txt`, `seeds.tsv`, `build/` (logs, ignored by nothing: keep
the build logs, they are small).
Files: `create-seed.sh`, `provision-seed.sh`, `health.sh`, `addpeer-from-mac.sh`, `stage-v4.sh`, `switch-v4.sh`, `config.sh`,
`lib.sh`, `node/{install-seed.sh,run-seed.sh,igneumd.service,build-v4-on-seed.sh,run-seed-v4.sh,igneumd-v4.service}`,
`seeds.txt`, `seeds.tsv`, `build/` (logs and the v4 source tarball, git-ignored).

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# Health check for every seed in seeds.tsv (or one name). One line per seed, exit 1 if any check fails.
# Checks: p2p port reachable from here (nc), unit active, RPC answers, synced flag, blocks and headers, connected
# Checks: p2p port reachable from here (nc), unit active (igneumd, or igneumd-v4 after switch-v4.sh: shown as active-v4), RPC answers, synced flag, blocks and headers, connected
# peers, known addresses (the peer-exchange table), disk and memory. Add --watch to repeat every 60 s.
. "$(dirname "$0")/lib.sh"
only="${1:-}"; [ "$only" = --watch ] && only=""
@ -10,7 +10,7 @@ check_one() {
local port=FAIL unit=? info= dag= peers=? known=? disk=? mem=? synced=? ver=? blocks=? headers=? sink=?
if nc -z -w 5 "$ip" "$P2P_PORT" >/dev/null 2>&1; then port=open; fi
local raw
raw=$(sssh "$ip" "systemctl is-active igneumd 2>/dev/null; echo '|'; python3 /opt/igneum/bin/wrpc.py call getInfo 2>/dev/null; echo '|'; python3 /opt/igneum/bin/wrpc.py call getBlockDagInfo 2>/dev/null; echo '|'; python3 /opt/igneum/bin/wrpc.py call getConnectedPeerInfo 2>/dev/null | python3 -c 'import json,sys; print(len(json.load(sys.stdin).get(\"peerInfo\",[])))' 2>/dev/null; echo '|'; python3 /opt/igneum/bin/wrpc.py call getPeerAddresses 2>/dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); print(len(j.get(\"knownAddresses\",[])), len(j.get(\"bannedAddresses\",[])))' 2>/dev/null; echo '|'; df -h / | awk 'NR==2 { print \$5 }'; echo '|'; free -m | awk 'NR==2 { print \$3 \"/\" \$2 \"MB\" }'" 2>/dev/null) || raw=""
raw=$(sssh "$ip" "if systemctl is-active igneumd-v4 >/dev/null 2>&1; then echo active-v4; else systemctl is-active igneumd 2>/dev/null; fi; echo '|'; python3 /opt/igneum/bin/wrpc.py call getInfo 2>/dev/null; echo '|'; python3 /opt/igneum/bin/wrpc.py call getBlockDagInfo 2>/dev/null; echo '|'; python3 /opt/igneum/bin/wrpc.py call getConnectedPeerInfo 2>/dev/null | python3 -c 'import json,sys; print(len(json.load(sys.stdin).get(\"peerInfo\",[])))' 2>/dev/null; echo '|'; python3 /opt/igneum/bin/wrpc.py call getPeerAddresses 2>/dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); print(len(j.get(\"knownAddresses\",[])), len(j.get(\"bannedAddresses\",[])))' 2>/dev/null; echo '|'; df -h / | awk 'NR==2 { print \$5 }'; echo '|'; free -m | awk 'NR==2 { print \$3 \"/\" \$2 \"MB\" }'" 2>/dev/null) || raw=""
unit=$(printf '%s' "$raw" | awk -F'|' 'NR==1 { gsub(/\n/, "", $1); print $1 }' | tr -d '\n')
info=$(printf '%s' "$raw" | awk 'BEGIN{RS="|"} NR==2' | tr -d '\n')
dag=$(printf '%s' "$raw" | awk 'BEGIN{RS="|"} NR==3' | tr -d '\n')
@ -26,7 +26,8 @@ check_one() {
read -r blocks headers sink <<< "$(printf '%s' "$dag" | python3 -c 'import json,sys; j=json.load(sys.stdin); print(j.get("blockCount"), j.get("headerCount"), str(j.get("sink",""))[:12])' 2>/dev/null || echo "? ? ?")"
fi
local ok=OK
if [ "$port" != open ] || [ "$unit" != active ] || [ -z "$info" ]; then ok=FAIL; fail=1; fi
case "$unit" in active|active-v4) ;; *) ok=FAIL; fail=1 ;; esac # active-v4: the devnet-v4 unit (igneumd-v4) serves instead of igneumd
if [ "$port" != open ] || [ -z "$info" ]; then ok=FAIL; fail=1; fi
printf '%s %-14s %-15s p2p=%s unit=%s rpc=%s synced=%s blocks=%s headers=%s sink=%s peers=%s known/banned=%s disk=%s mem=%s version=%s\n' \
"$ok" "$name" "$ip" "$port" "${unit:-?}" "$([ -n "$info" ] && echo yes || echo no)" "$synced" "${blocks:-?}" "${headers:-?}" "${sink:-?}" "$peers" "${known:-?}" "$disk" "$mem" "$ver"
}

View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Runs ON the seed VM as root, started detached by stage-v4.sh (a transient systemd service, niced, memory-capped, so
# the running v3 seed is never starved). Builds the devnet-v4 igneumd and igneum-miner from /root/v4/src.tar.gz
# (make-source.sh of vendor/igneum-node-v4 plus igneum-pow) in its own tree, /root/v4/src, with its own target
# directory. Leaves the binaries in /root/v4/out and touches /root/v4/DONE (or /root/v4/FAILED). The v3 tree under
# /root/src and the running unit are not touched. 4 October 2026.
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
log() { printf '%s %s\n' "$(date -u +%H:%M:%S)" "$*"; }
cd /root/v4
rm -f DONE FAILED
trap 'rc=$?; if [ $rc -ne 0 ]; then log "build FAILED (exit $rc)"; touch /root/v4/FAILED; fi' EXIT
if ! command -v cargo >/dev/null 2>&1 && [ ! -x "$HOME/.cargo/bin/cargo" ]; then
log "installing build dependencies"
apt-get update -qq
apt-get install -y -qq build-essential clang libclang-dev llvm-dev pkg-config libssl-dev protobuf-compiler libprotobuf-dev curl git >/dev/null
log "installing rust (stable) with rustup"
curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain stable >/dev/null
fi
# shellcheck disable=SC1091
. "$HOME/.cargo/env"
[ -f src.tar.gz ] || { log "no /root/v4/src.tar.gz"; exit 1; }
rm -rf src; tar -xzf src.tar.gz 2>/dev/null || tar -xzf src.tar.gz; log "unpacked src.tar.gz into /root/v4/src"
[ -d src/vendor/igneum-node ] || { log "no src/vendor/igneum-node in the tarball"; exit 1; }
cd src/vendor/igneum-node
jobs="${CARGO_BUILD_JOBS:-1}"
log "building igneumd and igneum-miner for devnet v4 (release, --features igneum-pow, $jobs job(s), nice 19); the v3 build took 25.5 min with 2 jobs on this VM, expect longer: the execution layer adds alloy and revm"
start=$(date +%s)
( while sleep 60; do printf '%s build running, %s s\n' "$(date -u +%H:%M:%S)" "$(( $(date +%s) - start ))"; done ) & ticker=$!
CARGO_BUILD_JOBS="$jobs" nice -n 19 cargo build --release -p kaspad -p igneum-miner --features igneum-pow 2>&1 | tail -n 40
kill $ticker 2>/dev/null || true
log "build finished in $(( $(date +%s) - start )) s"
mkdir -p /root/v4/out
cp target/release/igneumd target/release/igneum-miner /root/v4/out/
strip /root/v4/out/igneumd /root/v4/out/igneum-miner 2>/dev/null || true
/root/v4/out/igneumd --version | head -1 | tee /root/v4/out/version.txt
[ -f /root/v4/src.stamp ] && cat /root/v4/src.stamp >> /root/v4/out/version.txt
# the v4 miner's usage names --evm-address; a v3 miner does not
/root/v4/out/igneum-miner 2>&1 | grep -q -- '--evm-address' || { log "the built igneum-miner has no --evm-address: not the devnet-v4 source?"; exit 1; }
ls -la /root/v4/out
touch /root/v4/DONE
log "DONE"

View file

@ -0,0 +1,23 @@
[Unit]
Description=Igneum seed node, devnet v4 (igneumd from /opt/igneum/v4, p2p open, no mining)
After=network-online.target chrony.service
Wants=network-online.target
# The v3 unit (igneumd) listens on the same ports; the two never run together. switch-v4.sh swaps them.
Conflicts=igneumd.service
[Service]
Type=simple
User=igneum
Group=igneum
EnvironmentFile=/etc/igneum/seed-v4.env
ExecStart=/opt/igneum/v4/bin/run-seed-v4.sh
Restart=always
RestartSec=10
LimitNOFILE=65536
MemoryMax=3200M
StandardOutput=journal
StandardError=journal
SyslogIdentifier=igneumd-v4
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,20 @@
#!/usr/bin/env bash
# igneumd launcher on a seed VM, devnet v4 (4 October 2026). Reads /etc/igneum/seed-v4.env. The v3 launcher
# (run-seed.sh, /etc/igneum/seed.env, /var/lib/igneum) is untouched; this one runs the devnet-v4 binary from
# /opt/igneum/v4/bin with a fresh data directory, /var/lib/igneum-v4, because devnet v4 is a new chain from the same
# genesis and the v3 database does not open under it.
# Same flags as v3: no mining, no --connect (the address manager stays on and answers RequestAddresses, which is what
# a seed is for), --nodnsseed, UPnP off, RPC on loopback only, p2p on every interface, --externalip so peers learn the
# fixed address, generous inbound limit, --yes so the unit never waits on a terminal. The execution layer starts with
# the node and listens on 127.0.0.1:26790 (eth_ JSON-RPC, loopback only); EXTRA_ARGS=--evm-disable turns it off.
set -euo pipefail
. /etc/igneum/seed-v4.env
# shellcheck disable=SC2206
args=($NETWORK_ARGS --appdir=/var/lib/igneum-v4 --rpclisten=127.0.0.1:26610 --rpclisten-json=127.0.0.1:28610
--listen=0.0.0.0:26611 --externalip="$EXTERNAL_IP" --nodnsseed --disable-upnp --nologfiles --yes
--maxinpeers=128 --outpeers=8 --loglevel=info)
IFS=',' read -r -a peers <<< "${SEED_PEERS:-}"
for p in "${peers[@]}"; do [ -n "$p" ] && args+=(--addpeer="$p"); done
# shellcheck disable=SC2206
[ -n "${EXTRA_ARGS:-}" ] && args+=($EXTRA_ARGS)
exec /opt/igneum/v4/bin/igneumd "${args[@]}"

96
infra/seed-nodes/stage-v4.sh Executable file
View file

@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Stage devnet v4 on a seed without touching the running v3 seed. 4 October 2026.
# ./stage-v4.sh [seed-name] export vendor/igneum-node-v4 (+ igneum-pow) as a source tarball, upload it, start
# the build on the VM as a detached, niced, memory-capped transient service, wait
# for it, then install: /opt/igneum/v4/bin/{igneumd,igneum-miner,run-seed-v4.sh},
# /etc/igneum/seed-v4.env, /var/lib/igneum-v4 (fresh, empty), unit igneumd-v4
# installed and DISABLED (not started). The v3 unit keeps running the v3 chain.
# ./stage-v4.sh [seed-name] wait only wait for a build already started and install when it is done
# ./stage-v4.sh [seed-name] status one line: build state, installed version, unit states
# The build runs under `systemd-run` with MemoryHigh 1600M, MemoryMax 2400M, nice 19, idle IO, 1 cargo job (the VM has
# 2 vCPU and 4 GB; the v3 node uses about 1.5 GB; the 6 GB swap file from provision-seed.sh takes the overflow), so a
# dropped ssh session does not kill it and it cannot starve the running node. Expect 40 to 60 minutes (approximate: the
# v3 build took 25.5 min with 2 jobs on an idle VM; v4 adds the execution layer's crates).
# Switch with ./switch-v4.sh (not run by this script).
. "$(dirname "$0")/lib.sh"
name="${1:-$SEED_NAME}"; mode="${2:-start}"
case "$name" in start|wait|status) mode="$name"; name="$SEED_NAME" ;; esac
ip=$(seed_ip "$name"); [ -n "$ip" ] || die "$name not in seeds.tsv"
mkdir -p "$BUILD_DIR"
CD="$HERE/../cloud-devnet"
V4_SRC="${V4_SRC:-$REPO/vendor/igneum-node-v4}"
LOG="$BUILD_DIR/stage-v4-$name.log"
BUILD_UNIT=igneum-v4-build
remote_status() {
sssh "$ip" '
s="not started"; [ -f /root/v4/build.log ] && s="running"; [ -f /root/v4/DONE ] && s="done"; [ -f /root/v4/FAILED ] && s="failed"
systemctl is-active '"$BUILD_UNIT"' >/dev/null 2>&1 && s="running (unit active)"
last=""; [ -f /root/v4/build.log ] && last=$(grep -E "build running|build finished|FAILED|DONE|error\[" /root/v4/build.log | tail -1)
inst="not installed"; [ -x /opt/igneum/v4/bin/igneumd ] && inst="installed $(/opt/igneum/v4/bin/igneumd --version 2>/dev/null | head -1)"
u4=$(systemctl is-enabled igneumd-v4 2>/dev/null || echo absent); a4=$(systemctl is-active igneumd-v4 2>/dev/null || true)
u3=$(systemctl is-enabled igneumd 2>/dev/null || echo absent); a3=$(systemctl is-active igneumd 2>/dev/null || true)
echo "build=$s | $last | v4: $inst, unit igneumd-v4 $u4/$a4 | v3: unit igneumd $u3/$a3"'
}
install_v4() {
log "installing the v4 binaries, launcher, env file and the DISABLED unit igneumd-v4"
sscp "$HERE/node/run-seed-v4.sh" "$HERE/node/igneumd-v4.service" "$SSH_USER@$ip:/root/v4/"
sssh "$ip" '
set -e
[ -f /root/v4/DONE ] || { echo "no /root/v4/DONE: the build has not finished"; exit 1; }
mkdir -p /opt/igneum/v4/bin /var/lib/igneum-v4 /etc/igneum
cp /root/v4/out/igneumd /root/v4/out/igneum-miner /root/v4/run-seed-v4.sh /opt/igneum/v4/bin/
chmod +x /opt/igneum/v4/bin/*
chown -R igneum:igneum /var/lib/igneum-v4
if [ ! -f /etc/igneum/seed-v4.env ]; then cp /etc/igneum/seed.env /etc/igneum/seed-v4.env; fi
cp /root/v4/igneumd-v4.service /etc/systemd/system/igneumd-v4.service
systemctl daemon-reload
systemctl disable igneumd-v4 >/dev/null 2>&1 || true
echo "v4 binary: $(/opt/igneum/v4/bin/igneumd --version | head -1) at /opt/igneum/v4/bin (built from: $(sed -n 2p /root/v4/out/version.txt 2>/dev/null))"
echo "v4 data dir: /var/lib/igneum-v4 ($(ls -A /var/lib/igneum-v4 | wc -l) entries, fresh)"
echo "env: /etc/igneum/seed-v4.env"; sed "s/^/ /" /etc/igneum/seed-v4.env
echo "unit igneumd-v4: $(systemctl is-enabled igneumd-v4 2>/dev/null) / $(systemctl is-active igneumd-v4 2>/dev/null || true) (stays off until switch-v4.sh)"
echo "unit igneumd (v3): $(systemctl is-enabled igneumd 2>/dev/null) / $(systemctl is-active igneumd 2>/dev/null || true) (untouched)"
' | tee -a "$LOG"
}
wait_build() {
log "waiting for the build on $name (poll every 60 s; log on the VM: /root/v4/build.log; here: $LOG)"
while true; do
st=$(sssh "$ip" 'if [ -f /root/v4/DONE ]; then echo done; elif [ -f /root/v4/FAILED ]; then echo failed; else grep -E "build running|installing|unpacked|building" /root/v4/build.log 2>/dev/null | tail -1; fi' 2>/dev/null || echo "ssh?")
log " $st" | tee -a "$LOG"
case "$st" in
done) break ;;
failed) sssh "$ip" 'tail -40 /root/v4/build.log' | tee -a "$LOG"; die "build failed on $name (see above and $LOG)" ;;
esac
sleep 60
done
sssh "$ip" 'grep -E "build finished|igneumd 2" /root/v4/build.log | tail -2' | tee -a "$LOG"
install_v4
log "staged. Nothing was switched: $(remote_status)"
}
case "$mode" in
status) remote_status ;;
wait) wait_build ;;
start)
for try in $(seq 1 10); do sssh "$ip" true >/dev/null 2>&1 && break; log "waiting for ssh ($try)"; sleep 10; done
sssh "$ip" true || die "ssh to $ip failed"
if sssh "$ip" "systemctl is-active $BUILD_UNIT >/dev/null 2>&1"; then die "a v4 build is already running on $name (./stage-v4.sh $name wait)"; fi
[ -f "$V4_SRC/Cargo.toml" ] || die "no devnet-v4 worktree at $V4_SRC"
log "v4 source: $V4_SRC at $(git -C "$V4_SRC" rev-parse --short HEAD) ($(git -C "$V4_SRC" rev-parse --abbrev-ref HEAD)), igneum-pow at $(git -C "$POW_SRC" rev-parse --short HEAD) plus its uncommitted files (SRC_MODE=$SRC_MODE, the same overlay the Mac v4 binaries were built with)"
NODE_SRC="$V4_SRC" POW_SRC="$POW_SRC" SRC_MODE="$SRC_MODE" "$CD/make-source.sh" | tee -a "$LOG"
cp "$CD/build/src.tar.gz" "$BUILD_DIR/src-v4.tar.gz"; cp "$CD/build/src.stamp" "$BUILD_DIR/src-v4.stamp"
log "uploading the v4 source tarball ($(du -h "$BUILD_DIR/src-v4.tar.gz" | cut -f1)) and the build script to /root/v4 on $name"
sssh "$ip" 'mkdir -p /root/v4 && rm -f /root/v4/DONE /root/v4/FAILED'
sscp "$BUILD_DIR/src-v4.tar.gz" "$SSH_USER@$ip:/root/v4/src.tar.gz"
sscp "$BUILD_DIR/src-v4.stamp" "$SSH_USER@$ip:/root/v4/src.stamp"
sscp "$HERE/node/build-v4-on-seed.sh" "$SSH_USER@$ip:/root/v4/build-v4-on-seed.sh"
log "starting the build as transient service $BUILD_UNIT (nice 19, idle IO, MemoryHigh 1600M, MemoryMax 2400M, 1 cargo job)"
sssh "$ip" "systemctl reset-failed $BUILD_UNIT >/dev/null 2>&1 || true; free -m | head -2; systemd-run --unit=$BUILD_UNIT --collect -p Nice=19 -p IOSchedulingClass=idle -p MemoryHigh=1600M -p MemoryMax=2400M -p OOMScoreAdjust=900 -E CARGO_BUILD_JOBS=1 -E HOME=/root bash -c 'bash /root/v4/build-v4-on-seed.sh > /root/v4/build.log 2>&1'" | tee -a "$LOG"
sleep 5
sssh "$ip" "systemctl is-active $BUILD_UNIT; tail -3 /root/v4/build.log" | tee -a "$LOG"
wait_build ;;
*) die "usage: stage-v4.sh [seed-name] [start|wait|status]" ;;
esac

34
infra/seed-nodes/switch-v4.sh Executable file
View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
# Cut a seed over to devnet v4, or back. 4 October 2026. Run only after ./stage-v4.sh has installed igneumd-v4.
# ./switch-v4.sh [seed-name] stop and disable the v3 unit (igneumd), enable and start igneumd-v4 (fresh
# data dir /var/lib/igneum-v4), then print its sync state for up to 3 minutes
# ./switch-v4.sh [seed-name] --back the reverse: stop igneumd-v4, start the v3 unit on its untouched v3 database
# Both units bind the same ports (26610, 26611, 28610), so the stop comes first. The v3 database in /var/lib/igneum is
# never deleted by this script.
. "$(dirname "$0")/lib.sh"
name="${1:-$SEED_NAME}"; back=0
case "$name" in --back) back=1; name="$SEED_NAME" ;; esac
[ "${2:-}" = --back ] && back=1
ip=$(seed_ip "$name"); [ -n "$ip" ] || die "$name not in seeds.tsv"
if [ $back -eq 0 ]; then from=igneumd; to=igneumd-v4; label="devnet v4"; else from=igneumd-v4; to=igneumd; label="devnet v3 (rollback)"; fi
sssh "$ip" "test -f /etc/systemd/system/igneumd-v4.service && test -x /opt/igneum/v4/bin/igneumd" || die "igneumd-v4 is not staged on $name (./stage-v4.sh $name first)"
log "$name: switching to $label: stop+disable $from, enable+start $to"
sssh "$ip" "
set -e
systemctl disable --now $from >/dev/null 2>&1 || true
for i in 1 2 3 4 5 6 7 8 9 10; do systemctl is-active $from >/dev/null 2>&1 || break; sleep 1; done
systemctl enable --now $to
sleep 4
echo \"$from: \$(systemctl is-enabled $from 2>/dev/null || true)/\$(systemctl is-active $from 2>/dev/null || true) $to: \$(systemctl is-enabled $to 2>/dev/null)/\$(systemctl is-active $to 2>/dev/null)\"
journalctl -u $to --no-pager -n 6 -o cat
"
log "sync state of $to (one line every 10 s, up to 3 minutes; Ctrl+C any time):"
for i in $(seq 1 18); do
line=$(sssh "$ip" "python3 /opt/igneum/bin/wrpc.py call getInfo 2>/dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); print(\"synced=%s version=%s\" % (j.get(\"isSynced\"), j.get(\"serverVersion\")))' 2>/dev/null; python3 /opt/igneum/bin/wrpc.py call getBlockDagInfo 2>/dev/null | python3 -c 'import json,sys; j=json.load(sys.stdin); print(\"blocks=%s headers=%s daa=%s sink=%s\" % (j.get(\"blockCount\"), j.get(\"headerCount\"), j.get(\"virtualDaaScore\"), str(j.get(\"sink\",\"\"))[:12]))' 2>/dev/null; python3 /opt/igneum/bin/wrpc.py call getConnectedPeerInfo 2>/dev/null | python3 -c 'import json,sys; print(\"peers=%d\" % len(json.load(sys.stdin).get(\"peerInfo\",[])))' 2>/dev/null; systemctl is-active $to" 2>/dev/null | tr '\n' ' ')
log " $line"
case "$line" in *"synced=True"*"peers="[1-9]*) log "$to is synced with peers"; break ;; esac
sleep 10
done
log "done. Back with: ./switch-v4.sh $name $(if [ $back -eq 0 ]; then echo --back; else echo '(forward: no flag)'; fi). Health: ./health.sh $name"

View file

@ -1,20 +1,27 @@
# Igneum Miner for Mac (packaging/mac)
A DMG a miner can open on an Apple silicon Mac to run a node and the Metal miner, and show up on igneum.network/live.
Built 3 October 2026 from the binaries already compiled in this repo; nothing is compiled here. Renamed from "Igneum
Devnet" to "Igneum Miner" the same evening (bundle id network.igneum.miner, version 0.1.0); the devnet is still the only
network and its name appears in the status lines, never in a window title.
Built 3 October 2026 from the binaries already compiled in this repo; nothing is compiled here except the Metal worker
(one `swiftc` call, about 7 s). Renamed from "Igneum Devnet" to "Igneum Miner" the same evening (bundle id
network.igneum.miner, version 0.1.0); the devnet is still the only network and its name appears in the status lines,
never in a window title. Version 0.2.0 (4 October 2026) is the devnet v4 build: a new chain from genesis, so the data
folder moved to `devnet-v4`; the miner takes an EVM payout address (`--evm-address`, `PAYOUT_EVM`), runs every identity
through one process (`--identities`), votes on finality by default (`VOTE=0` turns it off) and hot-swaps the hourly
program through the worker's `prepare` command.
## Build
packaging/mac/build-dmg.sh
Output: `packaging/mac/dist/Igneum-Miner-0.1.0.dmg` (about 16 MB, lzfse). The script takes
`vendor/igneum-node/target-rename/release/igneumd` (falls back to `target/release/kaspad` renamed, and says so),
`vendor/igneum-node/target/release/igneum-miner` and `proto-metal/igneum-bench` (the plain build, the one whose
`--serve` protocol matches that miner), copies them into the bundle, strips the copies and signs them ad hoc again
(strip invalidates the linker signature and arm64 macOS refuses an unsigned binary), and checks that each copy still
runs. The originals are untouched. The icon is `brand/icons/igneum.icns` and the DMG art `brand/icons/dmg-background.tiff`
Output: `packaging/mac/dist/Igneum-Miner-0.2.0.dmg` (about 16 MB, lzfse). The script takes
`vendor/igneum-node/target-integration/release/igneumd` and `igneum-miner` (the devnet-v4 integration build of
`vendor/igneum-node-v4` at dc749905, `NODE=` and `MINER=` override) and rebuilds the Metal worker from
`proto-metal/main.swift` (the hot-swap source) with `-target arm64-apple-macos11`, so it loads on any Apple silicon macOS
and not only on the build machine's (the committed `proto-metal/igneum-bench-hotswap` says minos 16.0; `REBUILD_WORKER=0`
ships it as is, `WORKER=` names another one). It copies the three into the bundle, strips the copies and signs them ad
hoc again (strip invalidates the linker signature and arm64 macOS refuses an unsigned binary), and checks that each copy
still runs, that the worker says `prepare 1` and that the miner is the v4 one (`--evm-address` in its usage). The
originals are untouched. The icon is `brand/icons/igneum.icns` and the DMG art `brand/icons/dmg-background.tiff`
(both from `python3 brand/icons/make-icons.py`). The image is laid out by `dmgbuild` (`pip3 install dmgbuild`, settings in
`dmg/settings.py`: volume "Igneum Miner", hidden `.VolumeIcon.icns` with the custom-icon flag, background, icon slots app
(165,130), Applications (495,130), README and Stop command on the row below, no Finder scripting needed). Without dmgbuild
@ -47,11 +54,13 @@ a writable volume, so the app refuses (with a dialog) to run straight from the D
| Variable | Default | Meaning |
|---|---|---|
| `SEED_PEERS` | `192.168.68.64:26611` | the Mac node on the project lead's LAN; a public seed node replaces it; comma list allowed |
| `MINERS` | `1` | miner identities; 0 = node only. One is right: one worker owns the whole GPU |
| `SEED_PEERS` | `188.245.5.161:26611,192.168.68.64:26611` | the seed node, then the Mac node on the project lead's LAN; comma list |
| `MINERS` | `1` | miner identities, all inside one miner process (`--identities`); 0 = node only. One worker owns the whole GPU |
| `PAYOUT_EVM` | empty | EVM payout address for the block rewards; empty = 20 bytes of SHA-256 over `igneum-evm-payout-v0:mac-<hostname>`, printed at start |
| `VOTE` | `1` | finality voting; 0 adds `--no-vote` |
| `STATUS_SECS`, `UPLOAD_SECS` | 30, 60 | status line and log upload periods |
| `IGNEUM_RPC_PORT`, `IGNEUM_P2P_PORT` | 26610, 26611 | environment overrides (tests) |
| `IGNEUM_DATA`, `IGNEUM_LOGS` | `~/Library/Application Support/Igneum/devnet`, `~/Library/Logs/Igneum` | the only places written outside the bundle |
| `IGNEUM_DATA`, `IGNEUM_LOGS` | `~/Library/Application Support/Igneum/devnet-v4`, `~/Library/Logs/Igneum` | the only places written outside the bundle (0.1.0 wrote `devnet`, left alone) |
Order: checks (arm64, binaries, ports, no second copy via the pid file) > `caffeinate -dims -w <launcher>` >
`igneumd --devnet --appdir ... --rpclisten 127.0.0.1:26610 --listen 0.0.0.0:26611 --addpeer <each seed> --nodnsseed

View file

@ -11,7 +11,7 @@
<key>CFBundleVersion</key>
<string>VERSION_STAMP</string>
<key>CFBundleShortVersionString</key>
<string>0.1.0</string>
<string>0.2.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleExecutable</key>

View file

@ -1,39 +1,49 @@
#!/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).
# The app's launcher opens this script in Terminal. 3 October 2026 (was the devnet launcher, renamed the same day);
# 0.2.0 for devnet v4 on 4 October 2026 (fresh data folder devnet-v4, EVM payout address, one miner process with
# MINERS identities, finality voting on, hot swap through the worker's prepare command).
#
# 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,
# 2. Starts igneumd --devnet with the database under ~/Library/Application Support/Igneum/devnet-v4,
# 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.
# `igneum-miner watch`: the node's own synced flag when the line carries one, else peers > 0, blocks
# caught up with headers, and the block count moving.
# 4. Starts ONE igneum-miner with the Metal worker (igneum-bench --serve) and MINERS identities inside it
# (--identities; vote keys and payout labels mac-<hostname>-1..N, or mac-<hostname> alone for 1). One
# worker owns the whole GPU. Block rewards go to PAYOUT_EVM, or to an address derived from the hostname.
# 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,
# awake with caffeinate, restarts the node (5 to 60 s later) or the miner after a crash, restarts the
# miner at once when it exits with code 42 (the hourly program change on a worker without prepare).
# 6. Ctrl+C, closing the window (SIGHUP) or `igneum-miner.sh --stop` stops the miner, its worker,
# 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"
VERSION="0.2.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".
CHAIN="devnet v4"
# The seed node(s) to peer with: the public seed node first, then the project lead's Mac on the house LAN.
# A comma list is allowed: "1.2.3.4:26611,5.6.7.8:26611".
SEED_PEERS="${SEED_PEERS:-188.245.5.161:26611,192.168.68.64:26611}"
# Miner identities on this Mac. 1 is right for one GPU; 0 runs the node alone.
# Miner identities on this Mac, all inside one miner process. 1 is right for one GPU; 0 runs the node alone.
MINERS="${MINERS:-1}"
# EVM payout address (0x + 40 hex) for the block rewards of every identity. Empty = derived from the hostname
# (the same on every run of this Mac; printed at start). A block without an address pays nobody.
PAYOUT_EVM="${PAYOUT_EVM:-}"
# Finality voting: 1 = every identity signs every checkpoint (the v4 default); 0 = mine without voting.
VOTE="${VOTE:-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}"
# devnet-v4 is a fresh folder: v4 is a new chain from genesis (4 Oct 2026); the v3 folder (devnet) is left alone.
DATA="${IGNEUM_DATA:-$HOME/Library/Application Support/Igneum/devnet-v4}"
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"
@ -103,7 +113,7 @@ stop_mode() {
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 ;;
--help|-h) sed -n '2,22p' "$0"; echo; echo "usage: igneum-miner.sh [--stop] (settings: SEED_PEERS, MINERS, PAYOUT_EVM, VOTE, STATUS_SECS, UPLOAD_SECS at the top)"; exit 0 ;;
esac
# ---- 1. checks --------------------------------------------------------------------------------------------
@ -128,14 +138,24 @@ for port in "$RPC_PORT" "$P2P_PORT"; do
fi
done
case "$MINERS" in ''|*[!0-9]*) echo "MINERS must be a number (got '$MINERS')"; exit 1 ;; esac
case "$VOTE" in 0|1) ;; *) echo "VOTE must be 0 or 1 (got '$VOTE')"; exit 1 ;; esac
# The EVM payout address: PAYOUT_EVM, or 20 bytes of SHA-256 over a fixed tag and the hostname label.
if [ -n "$PAYOUT_EVM" ]; then
case "$PAYOUT_EVM" in 0x[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]) ;;
*) echo "PAYOUT_EVM must be 0x followed by 40 hex characters (got '$PAYOUT_EVM')"; exit 1 ;; esac
EVM_ADDRESS="$(printf '%s' "$PAYOUT_EVM" | tr 'A-F' 'a-f')"; EVM_SOURCE="PAYOUT_EVM"
else
EVM_ADDRESS="0x$(printf 'igneum-evm-payout-v0:%s' "$LABEL_BASE" | shasum -a 256 | cut -c1-40)"; EVM_SOURCE="derived from the hostname; set PAYOUT_EVM to pay elsewhere"
fi
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 "Igneum Miner $VERSION on $HOST, $CHAIN (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 "seed peers: $SEED_PEERS; identities: $MINERS; voting: $(if [ "$VOTE" = 1 ]; then echo on; else echo off; fi); logs: $LOGS"
log "EVM payout address for the block rewards: $EVM_ADDRESS ($EVM_SOURCE)"
log "node $("$NODE_EXE" --version 2>&1 | head -1); worker $(basename "$WORKER_EXE") (Metal)"
# ---- keep awake -------------------------------------------------------------------------------------------
@ -146,14 +166,15 @@ 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
NODE_BLOCKS=0; NODE_HEADERS=0; NODE_PEERS=0; NODE_DAA=0; NODE_READ_OK=0; NODE_FLAG=""
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)
# --yes answers the database-version question so the node never waits on a prompt nobody sees
args=("--$NETWORK" "--appdir=$DATA" "--rpclisten=127.0.0.1:$RPC_PORT" "--listen=0.0.0.0:$P2P_PORT" --nodnsseed --disable-upnp --nologfiles --yes)
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.
@ -195,6 +216,8 @@ read_node() {
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
# the v4 watch line carries the node's own flag: synced=true|false (empty on an older miner)
NODE_FLAG="$(printf '%s' "$line" | sed -n -E 's/.* synced=(true|false).*/\1/p')"
return 0
}
@ -202,6 +225,8 @@ read_node() {
# (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_FLAG" = "true" ] && [ "$NODE_PEERS" -gt 0 ]; then SYNC_PREV=$NODE_BLOCKS; return 0; fi
if [ "$NODE_FLAG" = "false" ]; then SYNC_PREV=$NODE_BLOCKS; return 1; fi
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
@ -216,15 +241,21 @@ check_sync() {
}
# ---- the miners -------------------------------------------------------------------------------------------
# Per identity i (1-based): label, pid, worker pid, log, restart bookkeeping, accepted blocks of earlier runs.
# One miner process (slot 1) carries every identity (--identities MINERS): one worker, one dataset and one
# program on the GPU. The slot arrays stay (label, pid, worker pid, log, restart bookkeeping, accepted blocks
# of earlier runs) so the loop below is unchanged; PROCS is the number of processes, 1 or 0.
MINER_LABEL=(); MINER_PID=(); MINER_WORKER=(); MINER_LOG=(); MINER_STARTS=(); MINER_RESTARTS=(); MINER_RESTART_AT=(); MINER_ACC_BASE=(); MINER_ID=()
PROCS=0; [ "$MINERS" -gt 0 ] && PROCS=1
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
while [ $i -le "$PROCS" ]; do
MINER_LABEL[$i]="$LABEL_BASE"
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
MINER_ARGS=(--worker "$WORKER_EXE" --status-secs "$STATUS_SECS" --exit-on-seed-change --evm-address "$EVM_ADDRESS")
[ "$MINERS" -gt 1 ] && MINER_ARGS+=(--identities "$MINERS")
[ "$VOTE" = 1 ] || MINER_ARGS+=(--no-vote)
start_miner() {
local i=$1 label seg
@ -233,10 +264,10 @@ start_miner() {
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 &
( trap '' HUP; exec "$MINER_EXE" mine "$RPC_URL" 1 100000000 "$label" "${MINER_ARGS[@]}" --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 $label started (pid ${MINER_PID[$i]}): igneum-miner mine $RPC_URL 1 100000000 $label ${MINER_ARGS[*]} --payout-label $label"
log "miner output: ${MINER_LOG[$i]}"
}
@ -263,20 +294,20 @@ stop_miner() {
start_all_miners() {
local i=1
while [ $i -le "$MINERS" ]; do start_miner $i; i=$(( i + 1 )); done
while [ $i -le "$PROCS" ]; 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
while [ $i -le "$PROCS" ]; 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
while [ $i -le "$PROCS" ]; do
if alive "${MINER_PID[$i]}"; then
note_worker "$i"
if [ -z "${MINER_ID[$i]}" ]; then
@ -326,7 +357,7 @@ print_status() {
miner_txt="no miner (MINERS=0)"
else
mh="n/a"; age="n/a"
while [ $i -le "$MINERS" ]; do
while [ $i -le "$PROCS" ]; 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)"
@ -365,7 +396,7 @@ 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
while [ $i -le "$PROCS" ]; 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
@ -393,7 +424,7 @@ cleanup() {
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
while [ $i -le "$PROCS" ]; 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"

View file

@ -3,19 +3,25 @@
# a link to /Applications, on a branded image (volume "Igneum Miner", coin volume icon, background with the drag arrow,
# icons placed). Reuses the binaries already built in the repo (nothing is compiled here); copies of the binaries are
# stripped and re-signed ad hoc (strip invalidates the linker signature, and arm64 macOS refuses to run an unsigned
# binary). The originals are not touched. 3 October 2026.
# binary). The originals are not touched. 3 October 2026; 0.2.0 for devnet v4 on 4 October 2026.
#
# packaging/mac/build-dmg.sh build
# NODE=<path> packaging/mac/build-dmg.sh use another node binary
# 0.2.0 defaults: igneumd and igneum-miner from vendor/igneum-node/target-integration/release (the devnet-v4 integration
# build of vendor/igneum-node-v4 at dc749905) and the Metal worker rebuilt from proto-metal/main.swift (the hot-swap
# source: its ready line says "prepare 1") with `-target arm64-apple-macos11`, so the worker loads on any Apple silicon
# macOS and not only on the build machine's (the committed proto-metal/igneum-bench-hotswap carries minos 16.0).
# WORKER=<path> skips that rebuild; REBUILD_WORKER=0 uses proto-metal/igneum-bench-hotswap as is.
# Needs: brand/icons/igneum.icns and dmg-background.tiff (python3 brand/icons/make-icons.py) and dmgbuild
# (pip3 install dmgbuild) for the icon layout. Without dmgbuild a plain hdiutil image is built and said so.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
VERSION="0.1.0"
NODE="${NODE:-$ROOT/vendor/igneum-node/target-rename/release/igneumd}"
MINER="${MINER:-$ROOT/vendor/igneum-node/target/release/igneum-miner}"
WORKER="${WORKER:-$ROOT/proto-metal/igneum-bench}"
VERSION="0.2.0"
NODE="${NODE:-$ROOT/vendor/igneum-node/target-integration/release/igneumd}"
MINER="${MINER:-$ROOT/vendor/igneum-node/target-integration/release/igneum-miner}"
WORKER="${WORKER:-}"
REBUILD_WORKER="${REBUILD_WORKER:-1}"
ICONS="$ROOT/brand/icons"
BUILD="$HERE/build"
DIST="$HERE/dist"
@ -32,8 +38,8 @@ if [ ! -x "$NODE" ]; then
echo "no node binary at $NODE"; exit 1
fi
fi
for f in "$MINER" "$WORKER"; do [ -x "$f" ] || { echo "missing: $f"; exit 1; }; done
for f in "$NODE" "$MINER" "$WORKER"; do
[ -x "$MINER" ] || { echo "missing: $MINER"; exit 1; }
for f in "$NODE" "$MINER"; do
file "$f" | grep -q 'arm64' || { echo "$f is not an arm64 binary"; exit 1; }
done
[ -f "$ICONS/igneum.icns" ] || { echo "no $ICONS/igneum.icns; run: python3 brand/icons/make-icons.py"; exit 1; }
@ -41,6 +47,22 @@ done
rm -rf "$BUILD"
mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources/bin" "$DIST"
# the Metal worker: rebuilt from main.swift for macOS 11 and later (about 7 s), else the committed hot-swap binary
if [ -z "$WORKER" ]; then
if [ "$REBUILD_WORKER" = 1 ] && command -v swiftc >/dev/null 2>&1; then
mkdir -p "$BUILD/worker"
echo "building the Metal worker from proto-metal/main.swift with -target arm64-apple-macos11"
(cd "$ROOT/proto-metal" && nice -n 19 swiftc -O -target arm64-apple-macos11 -o "$BUILD/worker/igneum-bench" main.swift -framework Metal)
WORKER="$BUILD/worker/igneum-bench"
else
WORKER="$ROOT/proto-metal/igneum-bench-hotswap"
echo "note: using $WORKER as built (its load command says minos $(otool -l "$WORKER" | awk '/minos/ { print $2; exit }'); a rebuild with -target arm64-apple-macos11 needs swiftc)"
fi
fi
[ -x "$WORKER" ] || { echo "missing: $WORKER"; exit 1; }
file "$WORKER" | grep -q 'arm64' || { echo "$WORKER is not an arm64 binary"; exit 1; }
echo "worker: $WORKER (minos $(otool -l "$WORKER" | awk '/minos/ { print $2; exit }'))"
# the bundle
sed "s/VERSION_STAMP/$STAMP/" "$HERE/app/Info.plist" > "$APP/Contents/Info.plist"
plutil -lint "$APP/Contents/Info.plist" >/dev/null
@ -65,6 +87,8 @@ done
v="$("$APP/Contents/Resources/bin/igneumd" --version 2>&1 || true)"; echo "node: ${v%%$'\n'*}"
v="$("$APP/Contents/Resources/bin/igneum-miner" 2>&1 || true)"; case "$v" in usage*) ;; *) echo "igneum-miner copy does not run: $v"; exit 1 ;; esac
v="$(echo quit | "$APP/Contents/Resources/bin/igneum-bench" --serve 2>&1)"; case "$v" in "ready metal"*) echo "worker: $v" ;; *) echo "igneum-bench copy does not serve: $v"; exit 1 ;; esac
case "$v" in *"prepare 1"*) ;; *) echo "note: this worker has no prepare support (no hot swap at the hour boundary); the miner falls back to exit 42" ;; esac
v="$("$APP/Contents/Resources/bin/igneum-miner" 2>&1 || true)"; case "$v" in *"--evm-address"*) ;; *) echo "igneum-miner copy is not the devnet-v4 miner (no --evm-address in its usage)"; exit 1 ;; esac
# the rest of the image
cp "$HERE/dmg/README.txt" "$STAGE/README.txt"

View file

@ -1,4 +1,4 @@
Igneum Miner 0.1.0 for Mac (Apple silicon only: M1 or later). Test network; nothing is bought or sold.
Igneum Miner 0.2.0 for Mac (Apple silicon only: M1 or later). Devnet v4, 4 October 2026. Test network; nothing is bought or sold.
Nobody from Igneum will ever ask for your seed.
1. Drag "Igneum Miner" into Applications. It will not start from this disk image.
2. First time: in Applications, right-click "Igneum Miner" and choose Open, then Open again. The app is unsigned.
@ -6,5 +6,7 @@ Nobody from Igneum will ever ask for your seed.
3. A Terminal window called "Igneum Miner" opens. It starts the node, shows "syncing, N blocks" until it has the chain
(usually under a minute), then starts the Metal miner. Every 30 s one status line: blocks, MH/s, node blocks and peers.
4. Your miner appears on igneum.network/live within about a minute of its first block, under the id the window prints.
5. To stop: press Ctrl+C in that window (miners stop first, then the node). If the window was closed, open "Stop Igneum Miner".
The Mac stays awake while the window is open. Data: ~/Library/Application Support/Igneum, logs: ~/Library/Logs/Igneum.
Block rewards go to the EVM address the window prints (set PAYOUT_EVM in the environment to use your own).
5. To stop: press Ctrl+C in that window (miner stops first, then the node). If the window was closed, open "Stop Igneum Miner".
Devnet v4 is a new chain: the data lives in ~/Library/Application Support/Igneum/devnet-v4 (an older devnet folder there is left alone).
The Mac stays awake while the window is open. Logs: ~/Library/Logs/Igneum.

View file

@ -1,10 +1,14 @@
Igneum devnet for Windows: the node and the miners in one window. Extract the whole zip to a short path such as C:\igneum first.
1. START-IGNEUM.bat. It starts igneumd (database under %LOCALAPPDATA%\igneum\devnet, RPC 127.0.0.1:26610, p2p 0.0.0.0:26611, peered to the Mac at 192.168.68.64:26611), waits until the node is synced, then finds the GPUs, builds the CUDA and OpenCL workers on the first run (CUDA Toolkit 12.8 or newer plus Visual Studio with the MSVC v143 x64 component) and starts MINERS=8 miner identities per GPU vendor against the node on this PC. Settings are at the top of the bat.
Firewall: the first time, Windows Defender Firewall asks about igneumd.exe. Tick Private networks and click Allow access (that lets the Mac node dial this PC). If you clicked Cancel, run ALLOW-FIREWALL.bat once.
2. The window is a dashboard, redrawn every 2 s: the NODE card first (state in colour: starting, syncing, synced, restarting, stopped; blocks, headers, blue score, peers, the chain's blocks per second, the difficulty direction, the node's uptime), then one card per GPU (hash rate in big digits, blocks found, one cell per miner), the network share line, the last events (node started, synced in N s, peer joined or left, node restarted, blocks found, hourly program rebuilt, miner restarted, upload failed) and a footer. Set PLAIN=1 at the top of the bat for a scrolling log instead; the launcher uses it by itself when the window is not a real console.
Igneum devnet v4 for Windows (package 0.2.0, 4 October 2026): the node and the miners in one window. Extract the whole zip to a fresh short path such as C:\igneum-v4 first (a new folder, not over the old package).
Devnet v4 is a new chain from genesis. The node keeps its database in a fresh folder, %LOCALAPPDATA%\igneum\devnet-v4; the old chain's folder (%LOCALAPPDATA%\igneum\devnet) is not touched and can be deleted later. Every node and miner on the network moved to v4 together; an old igneumd.exe or igneum-miner.exe cannot follow this chain.
1. START-IGNEUM.bat. It starts igneumd (RPC 127.0.0.1:26610, p2p 0.0.0.0:26611, peered to the seed node 188.245.5.161:26611 and the Mac 192.168.68.64:26611), waits until the node is synced, then finds the GPUs, builds the CUDA and OpenCL workers on the first run (CUDA Toolkit 12.8 or newer plus Visual Studio with the MSVC v143 x64 component) and starts ONE miner per GPU vendor with MINERS=8 identities inside it (one worker owns the card; each identity has its own vote key and payout label). Settings are at the top of the bat.
Firewall: the first time, Windows Defender Firewall asks about igneumd.exe. Tick Private networks and click Allow access (that lets the other nodes dial this PC). If you clicked Cancel, run ALLOW-FIREWALL.bat once.
2. The window is a dashboard, redrawn every 2 s, with the version in the header: the NODE card first (state in colour: starting, syncing, synced, restarting, stopped; blocks, headers, blue score, peers, the chain's blocks per second, the difficulty direction, the node's uptime), then one card per GPU (hash rate in big digits, blocks found, identities, one cell per miner process), the network share line, the last events (node started, synced in N s, peer joined or left, node restarted, blocks found, worker ready, the next hourly program compiled, hourly program rebuilt, miner restarted, upload failed) and a footer. Set PLAIN=1 at the top of the bat for a scrolling log instead; the launcher uses it by itself when the window is not a real console.
3. Ctrl+C in the window stops the miners first, then the node, then uploads the logs one last time; the window stays open with a summary. If the window was closed instead, STOP-IGNEUM.bat stops everything cleanly.
A node crash is restarted after a random 5 to 60 s; the miners are restarted once the node is back and synced (their connection dies with the node). Every hour the program changes: the miners exit with code 42, the launcher re-exports the pack, rebuilds the worker and restarts them. The PC is held awake while the window runs; for the night also set Sleep to Never in Settings > System > Power.
Logs land next to the bat (igneum-<stamp>.log for the launcher, node-<stamp>.log for the node, nvidia-<i>-<stamp>.log and amd-<i>-<stamp>.log per miner) and are uploaded every 60 s with upload-log.bat under the labels igneum-<PC>, nodelog-<PC>, nvidia-<PC>-<i>, amd-<PC>-<i>.
Payout: block rewards go to an EVM address. PAYOUT_EVM at the top of the bat sets one for the whole PC; left empty, the launcher derives one address per GPU vendor from this PC's name (the same on every run; it is printed in the launcher log and the status block). Finality voting is on: every identity signs every checkpoint (VOTE=0 switches it off).
The hourly program change: every hour the lottery program changes. The v4 worker compiles the next program in the background as soon as the node announces it (about 10 minutes before the boundary) and swaps at the boundary with no pause ("hot swap"; the dashboard says "the next hourly program is compiled"). If nvcc is not on the PATH the worker cannot do that, and the miner exits with code 42 at the boundary instead: the launcher re-exports the pack, rebuilds the worker and restarts the miner, as before.
A node crash is restarted after a random 5 to 60 s; the miners are restarted once the node is back and synced (their connection dies with the node). The PC is held awake while the window runs; for the night also set Sleep to Never in Settings > System > Power.
Logs land next to the bat (igneum-<stamp>.log for the launcher, node-<stamp>.log for the node, nvidia-<stamp>.log and amd-<stamp>.log per miner process) and are uploaded every 60 s with upload-log.bat under the labels igneum-<PC>, nodelog-<PC>, nvidia-<PC>, amd-<PC>.
Separate entry points, same code (igneum-common.ps1): START-NODE.bat runs the node alone (its card and events), START-MINING.bat runs the miners alone against a node that is already up (NODE_HOST=auto picks 127.0.0.1 when one runs here, else the Mac).
If the node exits at once with a database error (an older build's database), delete %LOCALAPPDATA%\igneum\devnet and start again; it resyncs from the Mac in seconds.
The node also serves the execution layer's Ethereum JSON-RPC on 127.0.0.1:26790 (chain id 4463); --evm-disable in EXTRA_ARGS turns it off.
If the node exits at once with a database error, delete %LOCALAPPDATA%\igneum\devnet-v4 and start again; it resyncs from the seed node and the Mac in seconds.
Files: START-IGNEUM.bat, START-NODE.bat, START-MINING.bat, STOP-IGNEUM.bat, ALLOW-FIREWALL.bat and their .ps1 files, igneum-common.ps1, igneumd.exe with libstdc++-6.dll, libgcc_s_seh-1.dll and libwinpthread-1.dll (keep them next to it), igneum-miner.exe, upload-log.bat, proto-cuda\ and proto-opencl\ (the worker sources and build.bat).

View file

@ -1,29 +1,42 @@
@echo off
rem Igneum for Windows: the node and the miners in one window. Double-click this file.
rem It starts igneumd (your own node, peered to the Mac), waits until it is synced, then finds the GPUs in this PC, builds
rem the matching GPU workers on first run (NVIDIA: CUDA, AMD: OpenCL) and runs one igneum-miner per worker against the
rem node on this PC. The window shows a dashboard: the NODE card first, then one card per GPU, the last events.
rem Logs land next to this file and are uploaded every 60 seconds. Stop with Ctrl+C (miners first, then the node); the
rem window stays open at the end. If the window was closed instead, STOP-IGNEUM.bat stops everything.
rem Igneum for Windows, devnet v4 (package 0.2.0): the node and the miners in one window. Double-click this file.
rem It starts igneumd (your own node, peered to the seed node and the Mac), waits until it is synced, then finds the GPUs
rem in this PC, builds the matching GPU workers on first run (NVIDIA: CUDA, AMD: OpenCL) and runs one igneum-miner per
rem worker against the node on this PC, with MINERS identities inside that one miner. The window shows a dashboard: the
rem NODE card first, then one card per GPU, the last events. Logs land next to this file and are uploaded every 60
rem seconds. Stop with Ctrl+C (miners first, then the node); the window stays open at the end. If the window was closed
rem instead, STOP-IGNEUM.bat stops everything.
rem Devnet v4 is a new chain from genesis (4 October 2026): the database lives in a fresh folder (igneum\devnet-v4) and
rem the old one (igneum\devnet) is left alone. Every node and miner on the network moved together.
rem ---- node settings: edit these lines only ------------------------------------------------------------------------
rem The Mac node (its p2p port). This node dials it at start and keeps retrying; the Mac can dial back as well.
rem The peers this node dials at start and keeps retrying (--addpeer, comma separated): the seed node first, then the
rem Mac. Both can dial back as well.
set "PEER_NODES=188.245.5.161:26611,192.168.68.64:26611"
rem The Mac node (its p2p port), for the reachability line and START-MINING.bat's NODE_HOST=auto fallback.
set "MAC_NODE=192.168.68.64:26611"
rem gRPC for the miners on this PC.
set "RPC_LISTEN=127.0.0.1:26610"
rem p2p on all interfaces, so the Mac node can connect in. Windows Firewall asks once: tick Private networks, Allow access.
rem p2p on all interfaces, so the other nodes can connect in. Windows Firewall asks once: tick Private networks, Allow access.
set "P2P_LISTEN=0.0.0.0:26611"
rem Where the chain database lives.
set "APPDIR=%LOCALAPPDATA%\igneum\devnet"
rem 1 = hand out block templates before this node is synced and start the miners at once (the Mac node mines alone like
rem this). 0 = the miners start once this node has caught up with the Mac.
rem Where the chain database lives. devnet-v4 is a fresh folder: the v3 database in igneum\devnet is not touched.
set "APPDIR=%LOCALAPPDATA%\igneum\devnet-v4"
rem 1 = hand out block templates before this node is synced and start the miners at once. 0 = the miners start once
rem this node has caught up with its peers.
set "UNSYNCED_MINING=0"
rem Anything else to pass to igneumd (for example --loglevel=debug).
rem Anything else to pass to igneumd (for example --loglevel=debug, or --override-params-file=C:\igneum\override.json).
rem The execution layer starts with the node (Ethereum JSON-RPC on 127.0.0.1:26790); --evm-disable turns it off.
set "EXTRA_ARGS="
rem ---- miner settings ----------------------------------------------------------------------------------------------
rem Devnet payout address (igneumdev:...). Leave empty for one address per identity, derived from this PC's name.
rem EVM payout address (0x + 40 hex) for the block rewards of every identity on this PC. Leave empty for one address
rem per GPU vendor, derived from this PC's name (shown in the launcher log and the dashboard). A block without an
rem address pays an address nobody holds.
set "PAYOUT_EVM="
rem Devnet UTXO payout address (igneumdev:...). Leave empty for one address per identity, derived from this PC's name.
set "PAYOUT_ADDRESS="
rem Finality voting: 1 = every identity signs every checkpoint (the default on v4). 0 = mine without voting.
set "VOTE=1"
rem NVIDIA: CUDA device index (0 = first card) and the nvcc architecture (sm_120 = RTX 50 series; "native" lets nvcc pick).
set "NVIDIA_DEVICE=0"
set "CUDA_ARCH=sm_120"
@ -31,14 +44,17 @@ rem AMD: by default the first OpenCL GPU whose vendor string contains AMD_VENDOR
rem Set AMD_DEVICE to a flat device index from the worker's --list output to override.
set "AMD_VENDOR=Advanced Micro Devices"
set "AMD_DEVICE="
rem Miner identities per GPU vendor. Each identity runs its own worker process (about 1.3 GiB of GPU memory each) with its
rem own vote key and payout address derived from this PC's name and the index, and its own random nonce start.
rem Miner identities per GPU vendor. On v4 one miner process and ONE worker per card serve all of them (--identities):
rem one dataset and one program on the card, jobs alternate identities, each identity has its own vote key and payout
rem label derived from this PC's name and the index. Set ONE_PROCESS_PER_IDENTITY=1 for the old layout (one worker
rem process per identity, about 1.3 GiB of GPU memory each).
set "MINERS=8"
set "ONE_PROCESS_PER_IDENTITY=0"
rem Set to 0 to skip a vendor even if the hardware is present.
set "USE_NVIDIA=1"
set "USE_AMD=1"
rem Nonces per job (one job = one block template; a multiple of 32). Empty = the miner's default of 16,777,216, which an
rem RTX 5090 identity finishes in under a second. The integrated AMD GPU needs 100 s for that, so it mines stale templates
rem RTX 5090 finishes in under a second. The integrated AMD GPU needs 100 s for that, so it mines stale templates
rem and reports late; 2,097,152 keeps its jobs near 10 s.
set "NVIDIA_JOB_NONCES="
set "AMD_JOB_NONCES=2097152"

View file

@ -1,16 +1,22 @@
@echo off
rem Igneum devnet mining test for Windows, the miners only. Double-click this file.
rem Igneum devnet v4 mining for Windows (package 0.2.0), the miners only. Double-click this file.
rem It finds the GPUs in this PC, builds the matching GPU workers on first run (NVIDIA: CUDA, AMD: OpenCL), and runs
rem one igneum-miner per worker against a node that is already running (START-NODE.bat here, or the Mac). Logs land next
rem to this file and are uploaded every 60 seconds. START-IGNEUM.bat runs the node and the miners together instead.
rem The window shows a dashboard: hash rate, blocks found and one line per miner. Stop with Ctrl+C; the window stays open at the end.
rem one igneum-miner per worker (MINERS identities inside it) against a node that is already running (START-NODE.bat
rem here, or the Mac). Logs land next to this file and are uploaded every 60 seconds. START-IGNEUM.bat runs the node
rem and the miners together instead. The window shows a dashboard: hash rate, blocks found and one cell per miner.
rem Stop with Ctrl+C; the window stays open at the end.
rem ---- settings: edit these lines only ----------------------------------------------------------
rem auto = a node on this PC (START-NODE.bat, 127.0.0.1) when one is running, else the Mac node at 192.168.68.64. Or an address.
set "NODE_HOST=auto"
set "NODE_PORT=26610"
rem Devnet payout address (igneumdev:...). Leave empty for one address per identity, derived from this PC's name.
rem EVM payout address (0x + 40 hex) for the block rewards of every identity on this PC. Leave empty for one address
rem per GPU vendor, derived from this PC's name (shown in the launcher log and the dashboard).
set "PAYOUT_EVM="
rem Devnet UTXO payout address (igneumdev:...). Leave empty for one address per identity, derived from this PC's name.
set "PAYOUT_ADDRESS="
rem Finality voting: 1 = every identity signs every checkpoint (the default on v4). 0 = mine without voting.
set "VOTE=1"
rem NVIDIA: CUDA device index (0 = first card) and the nvcc architecture (sm_120 = RTX 50 series; "native" lets nvcc pick).
set "NVIDIA_DEVICE=0"
set "CUDA_ARCH=sm_120"
@ -18,9 +24,12 @@ rem AMD: by default the first OpenCL GPU whose vendor string contains AMD_VENDOR
rem Set AMD_DEVICE to a flat device index from the worker's --list output to override.
set "AMD_VENDOR=Advanced Micro Devices"
set "AMD_DEVICE="
rem Miner identities per GPU vendor. Each identity runs its own worker process (about 1.3 GiB of GPU memory each) with its
rem own vote key and payout address derived from this PC's name and the index, and its own random nonce start.
rem Miner identities per GPU vendor. On v4 one miner process and ONE worker per card serve all of them (--identities):
rem one dataset and one program on the card, jobs alternate identities, each identity has its own vote key and payout
rem label derived from this PC's name and the index. Set ONE_PROCESS_PER_IDENTITY=1 for the old layout (one worker
rem process per identity, about 1.3 GiB of GPU memory each).
set "MINERS=8"
set "ONE_PROCESS_PER_IDENTITY=0"
rem Set to 0 to skip a vendor even if the hardware is present.
set "USE_NVIDIA=1"
set "USE_AMD=1"

View file

@ -1,22 +1,25 @@
@echo off
rem Igneum devnet node for Windows, the node only. Double-click this file.
rem Runs igneumd.exe (the Igneum node) on this PC: the miners mine against it on localhost and it peers with the Mac
rem node. The window shows the NODE card; the node's own output goes to node-<stamp>.log next to this file.
rem Igneum devnet v4 node for Windows (package 0.2.0), the node only. Double-click this file.
rem Runs igneumd.exe (the Igneum node) on this PC: the miners mine against it on localhost and it peers with the seed
rem node and the Mac. The window shows the NODE card; the node's own output goes to node-<stamp>.log next to this file.
rem Stop with Ctrl+C; the window stays open at the end. START-IGNEUM.bat runs the node and the miners together instead.
rem Devnet v4 is a new chain from genesis (4 October 2026): the database lives in igneum\devnet-v4, the old one is left alone.
rem ---- settings: edit these lines only ----------------------------------------------------------
rem The Mac node (its p2p port). This node dials it at start and keeps retrying; the Mac can dial back as well.
rem The peers this node dials at start and keeps retrying (--addpeer, comma separated): the seed node first, then the Mac.
set "PEER_NODES=188.245.5.161:26611,192.168.68.64:26611"
rem The Mac node (its p2p port), for the reachability line.
set "MAC_NODE=192.168.68.64:26611"
rem gRPC for the miners on this PC (START-MINING.bat with NODE_HOST=auto looks here first).
set "RPC_LISTEN=127.0.0.1:26610"
rem p2p on all interfaces, so the Mac node can connect in. Windows Firewall asks once: tick Private networks, Allow access.
rem p2p on all interfaces, so the other nodes can connect in. Windows Firewall asks once: tick Private networks, Allow access.
set "P2P_LISTEN=0.0.0.0:26611"
rem Where the chain database lives.
set "APPDIR=%LOCALAPPDATA%\igneum\devnet"
rem 1 = hand out block templates before this node is synced (the Mac node runs like this because it mines alone).
rem 0 = the miners wait until this node has caught up with the Mac.
rem Where the chain database lives. devnet-v4 is a fresh folder: the v3 database in igneum\devnet is not touched.
set "APPDIR=%LOCALAPPDATA%\igneum\devnet-v4"
rem 1 = hand out block templates before this node is synced. 0 = the miners wait until this node has caught up.
set "UNSYNCED_MINING=0"
rem Anything else to pass to igneumd (for example --loglevel=debug).
rem Anything else to pass to igneumd (for example --loglevel=debug, or --override-params-file=C:\igneum\override.json).
rem The execution layer starts with the node (Ethereum JSON-RPC on 127.0.0.1:26790); --evm-disable turns it off.
set "EXTRA_ARGS="
rem Set PLAIN=1 for a scrolling log instead of the dashboard.
set "PLAIN=0"

View file

@ -1,5 +1,7 @@
# Igneum for Windows: the shared module. Dot-sourced by start-igneum.ps1 (node and miners in one window),
# start-mining.ps1 (miners only) and start-node.ps1 (node only), so the logic lives once. 3 October 2026.
# Devnet v4, package 0.2.0 (4 October 2026): peers list (seed node and Mac), fresh appdir devnet-v4, one miner process
# with --identities per card, EVM payout address, finality voting on, hot swap through the worker's prepare command.
# The caller sets $launcherName ('igneum', 'launcher' or 'node-launcher') before dot-sourcing; it names the launcher log.
#
# Sections: settings, logging and events, the node (igneumd), the chain reader (igneum-miner watch), GPUs and tools,
@ -7,8 +9,9 @@
#
# Lines this module reads:
# igneum-miner watch: <unix> node1 blocks=N headers=N daa=N tips=N peers=N difficulty=X sink=<12 hex> blue=N synced=true|false
# miner log: <unix> ACCEPTED block nonce=0x... | <unix> STATUS '<label>' [worker]: <up>s jobs=N accepted=N ... synced=true
# <unix> worker: ready cuda|opencl <device> ... | <unix> SEED CHANGE at daa ...
# miner log: <unix> ACCEPTED block nonce=0x... | <unix> STATUS '<label>' [worker]: <up>s jobs=N accepted=N ...
# hash=X MH/s wall (Y MH/s inside jobs) [now=... (the v4 miner's interval rates)] template_age=Zs synced=true
# <unix> worker: ready cuda|opencl <device> ... prepare 1|0 | <unix> SEED CHANGE at daa ...
# miner stderr: template error: ... (the node is away: the miner retries every 200 ms but its gRPC client never
# reconnects, rpc/grpc/client/src/lib.rs GrpcClient::connect passes reconnect=false, so the
# launcher restarts the miners once the node is back and synced)
@ -18,6 +21,8 @@ $ErrorActionPreference = 'Continue'
$root = $PSScriptRoot
Set-Location $root
if (-not $launcherName) { $launcherName = 'launcher' }
$packageVersion = '0.2.0'
$chainVersion = 'devnet v4'
# ---- settings (environment variables set by the bat; defaults match the bats) ---------------------------------------------
@ -28,9 +33,11 @@ function Get-Setting([string]$name, [string]$default) {
# node
$macNode = Get-Setting 'MAC_NODE' '192.168.68.64:26611'
# The peers igneumd dials with --addpeer (comma separated): the seed node first, then the Mac.
$peerNodes = @((Get-Setting 'PEER_NODES' "188.245.5.161:26611,$macNode") -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
$rpcListen = Get-Setting 'RPC_LISTEN' '127.0.0.1:26610'
$p2pListen = Get-Setting 'P2P_LISTEN' '0.0.0.0:26611'
$appDir = Get-Setting 'APPDIR' (Join-Path $env:LOCALAPPDATA 'igneum\devnet')
$appDir = Get-Setting 'APPDIR' (Join-Path $env:LOCALAPPDATA 'igneum\devnet-v4')
$unsyncedMining = ($env:UNSYNCED_MINING -eq '1')
$extraArgs = $env:EXTRA_ARGS
$rpcHost = ($rpcListen -split ':')[0]
@ -45,6 +52,8 @@ $nodeHost = Get-Setting 'NODE_HOST' 'auto'
$nodePort = Get-Setting 'NODE_PORT' '26610'
$nodeUrl = ''
$payout = $env:PAYOUT_ADDRESS
$payoutEvm = $env:PAYOUT_EVM # 0x + 40 hex; empty = one address per vendor process, derived from the PC name
$vote = ($env:VOTE -ne '0') # finality voting is on unless VOTE=0
$nvidiaDevice = Get-Setting 'NVIDIA_DEVICE' '0'
$cudaArch = Get-Setting 'CUDA_ARCH' 'sm_120'
$amdVendor = Get-Setting 'AMD_VENDOR' 'Advanced Micro Devices'
@ -55,6 +64,9 @@ $vcvars = Get-Setting 'VCVARS' 'C:\Program Files\Microsoft Visual Studio\18\Comm
$vcvarsVer = Get-Setting 'VCVARS_VER' '14.30'
$minersPerVendor = 1
if ($env:MINERS -and [int]$env:MINERS -ge 1) { $minersPerVendor = [int]$env:MINERS }
# v4: one miner process per card serves every identity (--identities N). ONE_PROCESS_PER_IDENTITY=1 restores the old
# layout (N processes, N workers, about 1.3 GiB of GPU memory each).
$onePerIdentity = ($env:ONE_PROCESS_PER_IDENTITY -eq '1')
# Nonces per job, per vendor (a multiple of 32). Empty = the miner's default of 16,777,216. A job is one template, so a
# job should take well under a minute: a slow GPU with a long job mines a stale template and reports late.
$nvidiaJobNonces = $env:NVIDIA_JOB_NONCES
@ -68,9 +80,25 @@ $uploader = Join-Path $root 'upload-log.bat'
$miner = Join-Path $root 'igneum-miner.exe'
$nodeExe = Join-Path $root 'igneumd.exe'
$packDir = Join-Path $root 'proto-cuda\packs\devnet'
# Where the v4 miner writes the pack of the NEXT program (--prepare-packs): the worker compiles it in the background and
# swaps at the epoch boundary with no exit (hot swap). One folder per epoch and day under here.
$preparePackDir = Join-Path $root 'proto-cuda\packs\prepare'
$started = Get-Date
$gpuNames = @()
$vendors = @{} # $vendors[name] = @{ name; card; exe; instances; rebuilds; building }
$vendors = @{} # $vendors[name] = @{ name; card; exe; instances; rebuilds; building; evm }
# The EVM payout address of a vendor's miner process: PAYOUT_EVM when set, else 20 bytes of SHA-256 over a fixed tag,
# the PC name and the vendor, so it is the same on every run of this PC and different per card. Printed in the log and
# the dashboard. (The v4 miner takes one --evm-address per process; with --identities every identity of that process
# pays there.)
function Get-EvmAddress([string]$vendorName) {
if ($payoutEvm) { return $payoutEvm.Trim().ToLower() }
$sha = [System.Security.Cryptography.SHA256]::Create()
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes("igneum-evm-payout-v0:$machine:$vendorName"))
$sha.Dispose()
return '0x' + (($bytes[0..19] | ForEach-Object { $_.ToString('x2') }) -join '')
}
if ($payoutEvm -and $payoutEvm.Trim() -notmatch '^0x[0-9a-fA-F]{40}$') { Write-Host "PAYOUT_EVM must be 0x followed by 40 hex characters (got '$payoutEvm')"; exit 1 }
# The node target the miners and the chain reader dial. 'auto' = a node on this PC when one answers, else the Mac.
function Set-NodeTarget([string]$targetHost, [string]$targetPort) {
@ -183,7 +211,10 @@ $script:node = @{ on = $false; managed = $false; proc = $null; log = $null; err
# Start-Process joins the argument list with spaces and does not quote, so values that may contain spaces are quoted here.
function Get-NodeArgs {
$a = @('--devnet', ('"--appdir=' + $appDir + '"'), "--rpclisten=$rpcListen", "--listen=$p2pListen", "--addpeer=$macNode", '--nodnsseed', '--disable-upnp', '--nologfiles')
# --yes answers the database-version question so the node never waits on a console nobody sees.
$a = @('--devnet', ('"--appdir=' + $appDir + '"'), "--rpclisten=$rpcListen", "--listen=$p2pListen")
foreach ($p in $peerNodes) { $a += "--addpeer=$p" }
$a += @('--nodnsseed', '--disable-upnp', '--nologfiles', '--yes')
if ($unsyncedMining) { $a += '--enable-unsynced-mining' }
if ($extraArgs) { $a += ($extraArgs -split '\s+' | Where-Object { $_ -ne '' }) }
return $a
@ -568,6 +599,7 @@ function Build-Worker([string]$name, [string]$seeds) {
function Initialize-Vendors($plan) {
$script:phase = 'exporting the program pack from the node'
Draw-Dashboard
New-Item -ItemType Directory -Force -Path $preparePackDir | Out-Null
$seeds = Export-Pack
if (-not $seeds) { return $false }
foreach ($name in $plan) {
@ -576,18 +608,26 @@ function Initialize-Vendors($plan) {
$exe = Build-Worker $name $seeds
if (-not $exe) { Log "$name skipped: no worker binary"; continue }
$instances = @()
for ($i = 1; $i -le $minersPerVendor; $i++) {
$suffix = if ($minersPerVendor -gt 1) { "-$i" } else { '' }
# v4 default: ONE process per vendor carries every identity (--identities N, label <vendor>-<PC>, identities
# <vendor>-<PC>-1..N inside the miner). ONE_PROCESS_PER_IDENTITY=1: N processes, labels <vendor>-<PC>-<i>.
$procCount = if ($onePerIdentity) { $minersPerVendor } else { 1 }
$identitiesPerProc = if ($onePerIdentity) { 1 } else { $minersPerVendor }
for ($i = 1; $i -le $procCount; $i++) {
$suffix = if ($procCount -gt 1) { "-$i" } else { '' }
$instances += @{ vendor = $name; index = $i; label = "$name-$machine$suffix"; file = "$name$suffix-$stamp"; log = $null; err = $null;
runId = "$name-$machine$suffix-$stamp"; proc = $null; restarts = 0; starts = 0; startedAt = $null; restartAt = $null; exitCode = $null;
logPos = [long]0; logRem = ''; errPos = [long]0; errRem = ''; accepted = 0; found = (New-Object System.Collections.ArrayList);
status = $null; ready = $false; readyAt = $null; lastLineAt = $null; errors = 0; lastError = ''; lastErrorAt = $null; rejected = 0; lastNoNodeAt = $null }
status = $null; ready = $false; readyAt = $null; lastLineAt = $null; errors = 0; lastError = ''; lastErrorAt = $null; rejected = 0; lastNoNodeAt = $null;
identities = $identitiesPerProc; prepare = $null }
}
$script:vendors[$name] = @{ name = $name; card = (Get-CardName $name); exe = $exe; instances = $instances; rebuilds = 0; building = $false }
$script:vendors[$name] = @{ name = $name; card = (Get-CardName $name); exe = $exe; instances = $instances; rebuilds = 0; building = $false; evm = (Get-EvmAddress $name) }
}
$script:phase = ''
if ($vendors.Count -eq 0) { Log 'no worker could be built; see the launcher log'; return $false }
Log ("identities per vendor: $minersPerVendor (each identity runs its own worker: about 1.3 GiB of GPU memory per instance)")
if ($onePerIdentity) { Log ("identities per vendor: $minersPerVendor, one worker process each (about 1.3 GiB of GPU memory per process; ONE_PROCESS_PER_IDENTITY=1)") }
else { Log ("identities per vendor: $minersPerVendor, all through ONE miner and ONE worker per card (--identities $minersPerVendor)") }
foreach ($v in $vendors.Values) { Log ("EVM payout for the $($v.card) rewards: $($v.evm)" + $(if ($payoutEvm) { ' (PAYOUT_EVM)' } else { " (derived from the PC name and the vendor; set PAYOUT_EVM to pay elsewhere)" })) }
Log ("finality voting: " + $(if ($vote) { 'on (every identity signs every checkpoint)' } else { 'off (VOTE=0)' }))
Log ("job nonces: nvidia " + $(if ($nvidiaJobNonces) { $nvidiaJobNonces } else { 'miner default (16,777,216)' }) + ", amd $amdJobNonces")
return $true
}
@ -605,7 +645,13 @@ function Start-Miner($v, $inst) {
$inst.status = $null; $inst.ready = $false; $inst.readyAt = $null; $inst.restartAt = $null; $inst.lastNoNodeAt = $null; $inst.lastErrorAt = $null
# --worker-args takes ONE argument (quoted); the miner splits it on spaces and turns underscores into spaces.
# Start-Process joins the list with spaces and does not quote, so paths and the worker-args value are quoted here.
$margs = @('mine', $nodeUrl, '1', '100000000', $inst.label, '--worker', ('"' + $v.exe + '"'), '--status-secs', '30', '--exit-on-seed-change')
# --exit-on-seed-change stays as the fallback: a worker whose ready line says "prepare 1" never exits, the miner
# writes the next program's pack under --prepare-packs and the worker swaps at the boundary; a worker without
# prepare (no nvcc on PATH) exits 42 at the boundary and the launcher rebuilds it as before.
$margs = @('mine', $nodeUrl, '1', '100000000', $inst.label, '--worker', ('"' + $v.exe + '"'), '--status-secs', '30', '--exit-on-seed-change',
'--prepare-packs', ('"' + $preparePackDir + '"'), '--evm-address', $v.evm)
if ($inst.identities -gt 1) { $margs += @('--identities', [string]$inst.identities) }
if (-not $vote) { $margs += '--no-vote' }
if ($payout) { $margs += @('--address', $payout) } else { $margs += @('--payout-label', $inst.label) }
if ($v.name -eq 'nvidia') {
if ($nvidiaJobNonces) { $margs += @('--job-nonces', $nvidiaJobNonces) }
@ -628,10 +674,20 @@ function Stop-Vendor($v) {
function Stop-All { foreach ($v in $vendors.Values) { Stop-Vendor $v } }
function Get-VendorIdentities($v) { $n = 0; foreach ($inst in $v.instances) { $n += $inst.identities }; return $n }
# "8 RTX 5090 identities (1 miner)" or, one process per identity, "8 RTX 5090 miners"
function Get-VendorCountText($v) {
$ids = Get-VendorIdentities $v
if ($v.instances.Count -eq 1 -and $ids -gt 1) { return "$ids $($v.card) identities (1 miner)" }
if ($v.instances.Count -eq 1) { return "1 $($v.card) miner" }
return "$($v.instances.Count) $($v.card) miners"
}
function Start-AllMiners {
foreach ($v in $vendors.Values) { foreach ($inst in $v.instances) { Start-Miner $v $inst; Start-Sleep -Milliseconds 500 } }
$startText = @()
foreach ($v in $vendors.Values) { $startText += "$($v.instances.Count) $($v.card) miners" }
foreach ($v in $vendors.Values) { $startText += (Get-VendorCountText $v) }
Add-Event ('started ' + ($startText -join ' and ')) 'ok'
}
@ -682,7 +738,8 @@ function Convert-UnixToLocal([double]$secs) {
}
function Get-UnixNow { return [double][DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() / 1000.0 }
$statusRegex = ' STATUS .*?: ([0-9]+)s jobs=([0-9]+) accepted=([0-9]+) rejected=([0-9]+) mismatched=([0-9]+) extra=([0-9]+) rate=[0-9.]+ blocks/s hash=([0-9.]+) MH/s wall \(([0-9.]+) MH/s inside jobs\) template_age=([0-9.]+)s synced=(\w+)'
# The v4 miner adds "now=... (interval rates, N jobs, seed walk N calls)" between the wall rate and template_age; optional here.
$statusRegex = ' STATUS .*?: ([0-9]+)s jobs=([0-9]+) accepted=([0-9]+) rejected=([0-9]+) mismatched=([0-9]+) extra=([0-9]+) rate=[0-9.]+ blocks/s hash=([0-9.]+) MH/s wall \(([0-9.]+) MH/s inside jobs\)(?: now=.*?\))? template_age=([0-9.]+)s synced=(\w+)'
function Update-Instance($v, $inst) {
foreach ($l in (Read-NewLines $inst 'log')) {
@ -698,12 +755,20 @@ function Update-Instance($v, $inst) {
age = [double]$Matches[9]; synced = ($Matches[10] -eq 'true') }
} elseif ($l -match ' worker: ready ') {
$inst.ready = $true; $inst.readyAt = Get-Date
# v4 hot swap: "prepare 1" on the ready line means the worker compiles the next program itself and swaps at
# the boundary with no exit; "prepare 0" (no nvcc on PATH) means the old exit-42 rebuild path.
if ($l -match ' prepare 1') { $inst.prepare = $true } elseif ($l -match ' prepare 0') { $inst.prepare = $false }
if ($inst.starts -eq 1) { Add-Event ("$($v.card) worker ready" + $(if ($inst.prepare -eq $true) { ', hot swap at the hour boundary' } elseif ($inst.prepare -eq $false) { ', no hot swap (nvcc missing): rebuild at the boundary' } else { '' })) 'ok' }
} elseif ($l -match ' worker: prepared ') {
Add-Event "$($v.card): the next hourly program is compiled and resident (hot swap ready)" 'build'
} elseif ($l -match ' worker: prepare-failed ') {
Add-Event "$($v.card): the worker could not compile the next program (see the miner log); it compiles inline at the boundary" 'error'
}
}
foreach ($l in (Read-NewLines $inst 'err')) {
if ($l -match ' rejected nonce=') { $inst.rejected += 1 }
elseif ($l -match '^template error') { $inst.lastNoNodeAt = Get-Date }
elseif ($l -match 'submit error|template error|WORKER MISMATCH|worker error|worker exited|above target|panicked|CUDA error|FAIL') {
elseif ($l -match 'submit error|template error|WORKER MISMATCH|worker error|worker exited|could not prepare|above target|panicked|CUDA error|FAIL') {
$inst.errors += 1; $inst.lastError = $l; $inst.lastErrorAt = Get-Date
}
}
@ -842,7 +907,7 @@ function Print-Status {
Log ("{0}: accepted {1}, no STATUS line yet ({2}), restarts {3}, {4}" -f $inst.label, $inst.accepted, $since, $inst.restarts, $st.text)
}
}
Log ("{0} TOTAL ({1}): {2} identities, accepted {3} blocks ({4} in the last 10 min), {5:N2} MH/s, rebuilds {6}, up {7}" -f $v.name, $v.card, $v.instances.Count, $acc, $last10, $total, $v.rebuilds, $up)
Log ("{0} TOTAL ({1}): {2} identities in {8} process(es), accepted {3} blocks ({4} in the last 10 min), {5:N2} MH/s, rebuilds {6}, up {7}, EVM payout {9}" -f $v.name, $v.card, (Get-VendorIdentities $v), $acc, $last10, $total, $v.rebuilds, $up, $v.instances.Count, $v.evm)
}
$cs = Get-ChainStats
if ($cs) {
@ -922,11 +987,11 @@ function Build-Frame {
# header: the mark, three rows
$connected = $script:chain.ok
$counts = @()
foreach ($v in $vendors.Values) { $counts += "$($v.instances.Count) $($v.card) miners" }
foreach ($v in $vendors.Values) { $counts += (Get-VendorCountText $v) }
for ($r = 0; $r -lt 3; $r++) {
$L = New-Segs
Add-Seg $L (' ' + (Get-BigRow 'IGNEUM' $r) + ' ') 'DarkYellow'
if ($r -eq 0) { Add-Seg $L $script:title 'White' }
if ($r -eq 0) { Add-Seg $L $script:title 'White'; Add-Seg $L " $chainVersion, package $packageVersion" 'DarkGray' }
elseif ($r -eq 1) {
Add-Seg $L 'PC ' 'DarkGray'; Add-Seg $L $machine 'Gray'; Add-Seg $L ' node ' 'DarkGray'; Add-Seg $L "${nodeHost}:${nodePort}" 'Gray'; Add-Seg $L ' ' 'DarkGray'
if ($nodeOn) {
@ -994,6 +1059,8 @@ function Build-Frame {
}
$L = New-Segs
Add-Seg $L (' ' + $v.card) 'White'
$ids = Get-VendorIdentities $v
if ($v.instances.Count -eq 1 -and $ids -gt 1) { Add-Seg $L " $ids identities, one worker" 'DarkGray' }
if ($v.building) { Add-Seg $L " building the new hourly program$dots" 'Yellow' }
elseif ($reported -lt $v.instances.Count -and $reported -gt 0) { Add-Seg $L " $reported of $($v.instances.Count) miners reporting" 'DarkGray' }
elseif ($reported -eq 0) { Add-Seg $L ' no report from the miners yet (the first job has to finish)' 'Yellow' }
@ -1111,7 +1178,7 @@ function Draw-Dashboard {
Draw-Frame $frame
$total = 0.0; $acc = 0
foreach ($v in $vendors.Values) { foreach ($inst in $v.instances) { if ($inst.status) { $total += $inst.status.wall }; $acc += $inst.accepted } }
$titleTxt = if ($vendors.Count -gt 0) { ('Igneum {0} MH/s {1} blocks' -f (Format-Rate $total), $acc) } else { 'Igneum' }
$titleTxt = if ($vendors.Count -gt 0) { ('Igneum v4 {0} MH/s {1} blocks' -f (Format-Rate $total), $acc) } else { 'Igneum v4' }
if ($script:node.on) { $titleTxt += ' node ' + (Get-NodeState).text }
$Host.UI.RawUI.WindowTitle = $titleTxt
} catch {

View file

@ -1,16 +1,17 @@
#!/usr/bin/env bash
# Builds igneum-windows.zip (the node and the miners in one window on a Windows PC) on the Mac.
# Builds igneum-windows-v4.zip (the node and the miners in one window on a Windows PC) on the Mac.
# Contents: the launchers and igneum-common.ps1 from this directory, upload-log.bat, the firewall scripts, igneumd.exe
# with its three mingw runtime DLLs, igneum-miner.exe (the one that prints blue= and synced= in `watch`, needed for the
# sync wait), and the worker sources from proto-cuda/ and proto-opencl/. No src.zip: the node exe ships prebuilt.
# Needs the cross-compiled exes in vendor/igneum-node-win/target/x86_64-pc-windows-gnu/release/ (cargo build --release
# --target x86_64-pc-windows-gnu with Homebrew mingw-w64) or the two Windows packages they were shipped in.
# Usage: windows-app/make-package.sh [output zip, default ~/Desktop/igneum-windows.zip]
# Default exes (4 Oct 2026, devnet v4): the devnet-v4 cross-build in vendor/igneum-node/target-integration/
# x86_64-pc-windows-gnu/release/ (built from vendor/igneum-node-v4 at dc749905 by proto-cuda/windows-node/cross-build.sh
# with CARGO_TARGET_DIR=target-integration). IGNEUM_WIN_RELEASE points at another release directory.
# Usage: windows-app/make-package.sh [output zip, default ~/Desktop/igneum-windows-v4.zip]
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
OUT="${1:-$HOME/Desktop/igneum-windows.zip}"
REL="${IGNEUM_WIN_RELEASE:-$ROOT/vendor/igneum-node-win/target/x86_64-pc-windows-gnu/release}"
OUT="${1:-$HOME/Desktop/igneum-windows-v4.zip}"
REL="${IGNEUM_WIN_RELEASE:-$ROOT/vendor/igneum-node/target-integration/x86_64-pc-windows-gnu/release}"
NODE_EXE="${IGNEUMD_EXE:-$REL/igneumd.exe}"
MINER_EXE="${IGNEUM_MINER_EXE:-$REL/igneum-miner.exe}"
DLL_DIR="${IGNEUM_DLL_DIR:-}" # a directory holding the three DLLs; empty = the Homebrew mingw toolchain

View file

@ -1,14 +1,15 @@
# Igneum for Windows: the node and the miners in one window. Started by START-IGNEUM.bat, which sets the settings as
# environment variables (node: MAC_NODE, RPC_LISTEN, P2P_LISTEN, APPDIR, UNSYNCED_MINING, EXTRA_ARGS; miners:
# PAYOUT_ADDRESS, NVIDIA_DEVICE, CUDA_ARCH, AMD_VENDOR, AMD_DEVICE, MINERS, USE_NVIDIA, USE_AMD, NVIDIA_JOB_NONCES,
# AMD_JOB_NONCES, PLAIN, VCVARS, VCVARS_VER). 3 October 2026. The functions live in igneum-common.ps1.
# environment variables (node: PEER_NODES, MAC_NODE, RPC_LISTEN, P2P_LISTEN, APPDIR, UNSYNCED_MINING, EXTRA_ARGS; miners:
# PAYOUT_EVM, PAYOUT_ADDRESS, VOTE, NVIDIA_DEVICE, CUDA_ARCH, AMD_VENDOR, AMD_DEVICE, MINERS, ONE_PROCESS_PER_IDENTITY,
# USE_NVIDIA, USE_AMD, NVIDIA_JOB_NONCES, AMD_JOB_NONCES, PLAIN, VCVARS, VCVARS_VER). 3 October 2026; devnet v4 (package
# 0.2.0) 4 October 2026. The functions live in igneum-common.ps1.
#
# Order:
# 1. igneumd starts (devnet, database under %LOCALAPPDATA%\igneum\devnet, RPC 127.0.0.1:26610, p2p 0.0.0.0:26611,
# peered to the Mac), unless a node already answers on the RPC port: then that node is used and only watched.
# 1. igneumd starts (devnet v4, database under %LOCALAPPDATA%\igneum\devnet-v4, RPC 127.0.0.1:26610, p2p 0.0.0.0:26611,
# peered to the seed node and the Mac), unless a node already answers on the RPC port: then that node is used and only watched.
# 2. The GPUs and the tools are listed while the node opens its database.
# 3. The dashboard waits for the node to report synced (one reading every 5 s: "node: syncing, N blocks").
# 4. The pack is exported from the node, the workers are built, MINERS identities per GPU vendor start.
# 4. The pack is exported from the node, the workers are built, one miner per GPU vendor starts with MINERS identities.
# 5. The loop: readings every 30 s, a status block every 30 s, uploads every 60 s, restarts (seed change, crashes),
# the node restarted after a crash with a random 5 to 60 s delay; the miners are restarted once it is back and
# synced, because their gRPC client never reconnects.
@ -17,9 +18,9 @@
$launcherName = 'igneum'
. (Join-Path $PSScriptRoot 'igneum-common.ps1')
$script:title = 'Igneum devnet: node and miners'
$script:title = 'Igneum: node and miners'
Set-NodeTarget $rpcHost $rpcPort # the miners always dial the node on this PC
Log "igneum launcher on $machine (run $stamp): node $rpcListen, p2p $p2pListen, Mac $macNode, appdir $appDir, unsynced mining $(if ($unsyncedMining) { 'on' } else { 'off' }), miners $minersPerVendor per vendor"
Log "igneum launcher $packageVersion ($chainVersion) on $machine (run $stamp): node $rpcListen, p2p $p2pListen, peers $($peerNodes -join ' '), appdir $appDir, unsynced mining $(if ($unsyncedMining) { 'on' } else { 'off' }), identities $minersPerVendor per vendor"
Log "If Windows Firewall asks about igneumd.exe: tick Private networks and click Allow access (the Mac node dials this PC on port $p2pPort). If you clicked Cancel once, run ALLOW-FIREWALL.bat."
# ---- 1. the node ----------------------------------------------------------------------------------------------------------

View file

@ -1,6 +1,7 @@
# Igneum devnet mining test, Windows launcher: the miners only. Started by START-MINING.bat, which sets the settings as
# environment variables (NODE_HOST, NODE_PORT, PAYOUT_ADDRESS, NVIDIA_DEVICE, CUDA_ARCH, AMD_VENDOR, AMD_DEVICE,
# USE_NVIDIA, USE_AMD, MINERS, NVIDIA_JOB_NONCES, AMD_JOB_NONCES, PLAIN, VCVARS, VCVARS_VER). 3 October 2026.
# environment variables (NODE_HOST, NODE_PORT, PAYOUT_EVM, PAYOUT_ADDRESS, VOTE, NVIDIA_DEVICE, CUDA_ARCH, AMD_VENDOR,
# AMD_DEVICE, USE_NVIDIA, USE_AMD, MINERS, ONE_PROCESS_PER_IDENTITY, NVIDIA_JOB_NONCES, AMD_JOB_NONCES, PLAIN, VCVARS,
# VCVARS_VER). 3 October 2026; devnet v4 (package 0.2.0) 4 October 2026.
# The functions live in igneum-common.ps1; START-IGNEUM.bat runs the same code with the node in the same window.
#
# Order: GPUs and tools, igneum-miner.exe, the node must answer, the pack is exported and the workers built (cached by
@ -10,9 +11,9 @@
$launcherName = 'launcher'
. (Join-Path $PSScriptRoot 'igneum-common.ps1')
$script:title = 'Igneum devnet mining test'
$script:title = 'Igneum: miners only'
$note = Resolve-NodeHost # NODE_HOST=auto: a node on this PC first, else the Mac
Log "igneum-mine-test launcher $machine node $nodeUrl$note (run $stamp)"
Log "igneum mining launcher $packageVersion ($chainVersion) on $machine, node $nodeUrl$note (run $stamp)"
$plan = Find-Hardware
if ($plan.Count -eq 0) { Log 'nothing to run. Fix the missing tool above and start again.'; exit 1 }

View file

@ -2,7 +2,8 @@
# variables (MAC_NODE, RPC_LISTEN, P2P_LISTEN, APPDIR, UNSYNCED_MINING, EXTRA_ARGS, PLAIN). 3 October 2026.
# The functions live in igneum-common.ps1; START-IGNEUM.bat runs the same code with the miners in the same window.
#
# Starts igneumd.exe --devnet with --appdir, --rpclisten, --listen, --addpeer=<Mac>, --nodnsseed, --disable-upnp and
# Starts igneumd.exe --devnet with --appdir, --rpclisten, --listen, one --addpeer per PEER_NODES entry (the seed node and
# the Mac), --nodnsseed, --disable-upnp, --yes and
# --nologfiles (the node's output goes to node-<stamp>.log, a new segment per start). The window shows the NODE card
# (a reading every 5 s while syncing, then every 30 s), a status line goes to the launcher log every 30 s, the logs are
# uploaded every 60 s, a crash restarts the node after 5 to 60 s, Ctrl+C stops it.
@ -14,9 +15,9 @@
$launcherName = 'node-launcher'
. (Join-Path $PSScriptRoot 'igneum-common.ps1')
$script:title = 'Igneum devnet node'
$script:title = 'Igneum: node only'
Set-NodeTarget $rpcHost $rpcPort
Log "igneum node launcher on $machine (run $stamp): Mac node $macNode, RPC $rpcListen, p2p $p2pListen, appdir $appDir, unsynced mining $(if ($unsyncedMining) { 'on' } else { 'off' })"
Log "igneum node launcher $packageVersion ($chainVersion) on $machine (run $stamp): peers $($peerNodes -join ' '), RPC $rpcListen, p2p $p2pListen, appdir $appDir, unsynced mining $(if ($unsyncedMining) { 'on' } else { 'off' })"
if (-not (Test-Path $nodeExe)) { Log 'igneumd.exe is missing from this folder. Extract the whole zip first.'; exit 1 }
$ver = ''