igneum/packaging/mac/README.md
igneum-labs 543833c50d Mac packaging: Igneum Devnet.app and DMG for Apple silicon miners
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>
2026-10-03 21:54:22 +00:00

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 ...'.