packaging/mac: build-dmg.sh assembles Igneum Devnet.app (shell launcher that opens igneum-devnet.sh in Terminal, stripped ad-hoc-signed copies of igneumd, igneum-miner and the Metal worker), README.txt, Stop Igneum.command and an Applications link into dist/igneum-devnet-mac.dmg (17 MB, lzfse). The script starts the node peered to SEED_PEERS, waits for sync, runs one miner identity mac-<hostname> on the Metal worker, prints a status line every 30 s, uploads logs every 60 s, keeps the Mac awake and stops everything in order on Ctrl+C, window close, --stop or the Stop command. Tested on this Mac from the mounted DMG against a test node on 27310/27311 peered to the live node: sync in 22 s, 24 accepted blocks, listed on igneum.network/live, clean stop with no stray process on SIGINT, SIGTERM and Stop Igneum.command. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
6.8 KiB
Igneum Devnet for Mac (packaging/mac)
A DMG a friend can open on an Apple silicon Mac to run a devnet 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.
Build
packaging/mac/build-dmg.sh
Output: packaging/mac/dist/igneum-devnet-mac.dmg (about 17 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 comes from site/icon-512.png. build/ and dist/ are ignored by git.
What is on the DMG
| Item | What it is |
|---|---|
Igneum Devnet.app |
the launcher bundle (below) |
README.txt |
9 lines for the friend (dmg/README.txt) |
Stop Igneum.command |
stops a copy whose window was closed without Ctrl+C (app/Stop Igneum.command) |
Applications |
symlink, for the drag |
The app bundle
Igneum Devnet.app/Contents/MacOS/Igneum Devnet app/launcher.sh: removes quarantine from Resources, opens the script in Terminal
Igneum Devnet.app/Contents/Resources/igneum-devnet.sh the run script (app/igneum-devnet.sh)
Igneum Devnet.app/Contents/Resources/bin/igneumd, igneum-miner, igneum-bench
Igneum Devnet.app/Contents/Resources/AppIcon.icns
Igneum Devnet.app/Contents/Info.plist app/Info.plist (CFBundleVersion = build stamp)
Why a shell launcher and not an AppleScript applet: open -a Terminal <script> needs no Automation consent;
tell application "Terminal" to do script would prompt "Igneum Devnet wants to control Terminal" on first run.
The launcher strips com.apple.quarantine from its own Resources first, because after Gatekeeper lets the app
through, every binary inside would still be checked on its first exec and refused as unidentified. That only works on
a writable volume, so the app refuses (with a dialog) to run straight from the DMG.
The run script (settings at the top)
| 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 |
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 |
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 --disable-upnp --nologfiles > every 5 s igneum-miner watch 1 until synced (peers > 0, blocks >= headers > 1 and
the count moving between readings, or stable for 60 s; this build's watch line has no synced= field) > igneum-miner mine grpc://127.0.0.1:26610 1 100000000 mac-<hostname> --worker igneum-bench --status-secs 30 --exit-on-seed-change --payout-label mac-<hostname> > loop: status every 30 s, uploads every 60 s (labels mac-<host>, nodelog-mac-<host>,
miner-mac-<host>, run id mac-<host>-<stamp>), node crash restarted after 5 to 60 s with the miners stopped until it
is synced again, miner exit 42 (hourly program change) restarted at once, other miner exits after 5 to 60 s.
Stopping: Ctrl+C (SIGINT), the window closing (SIGHUP) or SIGTERM set a flag; the loop then stops the miners and their
workers (TERM, 8 s, KILL), the node (TERM, 30 s, KILL), caffeinate, uploads the logs once more and prints a summary.
Children are started with SIGHUP ignored (bash already makes background children of a script ignore SIGINT), so the
launcher controls the order. igneum-devnet.sh --stop and Stop Igneum.command signal the launcher from the pid file
and fall back to pkill -f 'Igneum Devnet.app/Contents/Resources/bin/'.
The miner identity shows on igneum.network/live as the first 8 hex of its vote key hash; the window prints it.
Gatekeeper (expected on macOS 15 and 26)
The app is unsigned and not notarized. Double-click gives "Apple could not verify ... is free of malware" with no
Open button. Right-click > Open offers Open on older systems; on macOS 15 and later the route is System Settings >
Privacy & Security > "Open Anyway" (the button appears after the first refusal), then open the app again. Both are in
README.txt. Removing the flag by hand also works: xattr -dr com.apple.quarantine "/Applications/Igneum Devnet.app".
The first time igneumd listens on 0.0.0.0:26611 the macOS firewall (if on) asks to allow incoming connections; Allow.
Test (3 October 2026, this Mac, macOS 26.6.2, M5 Max)
Mounted the DMG and ran the script from the mounted bundle with SEED_PEERS=127.0.0.1:26611 MINERS=1 IGNEUM_RPC_PORT=27310 IGNEUM_P2P_PORT=27311 IGNEUM_DATA=/tmp/igneum-mac-test/data IGNEUM_LOGS=/tmp/igneum-mac-test/logs
(the live node as the seed, own node on 27300+, the Metal miner against that node).
| Check | Result |
|---|---|
| fresh database to synced | 22 s (0 blocks, 5,017 headers at 5 s; 9,988 blocks at 22 s); existing database 12 to 13 s |
| miner | started at sync, id 0aa660fe printed, first block 6 to 11 s later, 24 accepted blocks in 3 min 17 s, 0 rejected, 0 mismatched |
| rate | 16 to 18 MH/s wall, 40 MH/s inside jobs (the Mac was running two cargo builds and a census) |
| status line | every 30 s: status: accepted 22 blocks, 17.76 MH/s, template 0.89 s old | node 10205 blocks, 1 peers, synced | up 00:03:02 |
| uploads | 12 uploads in the intake under run id mac-MacBook-Pro-20261003-224420 (node tools/logs.mjs) |
| live page | 0aa660fe listed in igneum.network/api/live miners within a minute |
| Ctrl+C (SIGINT) | miners, worker, node gone in 5 s, summary printed, no stray process, pid and run files removed, ports free |
| SIGTERM | same, 4 s |
| Stop Igneum.command | node-only run stopped in 2 s; a second run says "Igneum is not running" |
Two things the test caught and fixed: an unquoted $NODE_EXE in the version line (the bundle path has a space), and
uname -m as the Apple silicon check (an x86_64 process, Rosetta, reports x86_64; now sysctl hw.optional.arm64).
Note for harness tests: a script started as a background job of a non-interactive shell has SIGINT ignored at entry
and cannot trap it; run it in the foreground or through perl -e '$SIG{INT}="DEFAULT"; exec ...'.