igneum/relay
2026-10-05 23:03:45 +00:00
..
api app: GPU hot-plug (re-detection every minute, WM_DEVICECHANGE on Windows), faulty cards listed with the problem code, integrated GPUs off by default, the card list in the console 2026-10-05 17:45:05 +00:00
clients Relay: its own key (relay-key) replaces the intake key for the Mac tools and clients; relay token rotated 4 Oct 2026 (round 4, X23); prove package excludes cross-build folders 2026-10-04 18:25:19 +00:00
lib Merge pack-loop (9b254b5) into release-0.3.10: a pack's seed words are its program attempt's words, not the bare seed's (the epoch 34 outage) 2026-10-05 19:23:59 +00:00
playbooks Merge ca2-analysis (4d0f7ec) into release-0.3.11: docs and standalone probe sources the evidence rows cite (C38); nothing a built artefact reads 2026-10-05 23:03:45 +00:00
test Merge pack-loop (9b254b5) into release-0.3.10: a pack's seed words are its program attempt's words, not the bare seed's (the epoch 34 outage) 2026-10-05 19:23:59 +00:00
.gitignore Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
apple-touch-icon.png Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
favicon-32.png Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
favicon.ico Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
index.html Brand: one master mark (black square, no circle) for every icon, favicon and profile picture 2026-10-04 11:39:55 +00:00
package-lock.json Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
package.json Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
README.md relay: /wake long-poll for the apps' remote jobs (public GET held 45 s, authenticated POST of the stamp) 2026-10-05 08:22:02 +00:00
robots.txt Relay: text, files and runnable tasks between the Mac, the PCs and the phone (relay.igneum.network) 2026-10-04 10:04:30 +00:00
ui.html app: GPU hot-plug (re-detection every minute, WM_DEVICECHANGE on Windows), faulty cards listed with the problem code, integrated GPUs off by default, the card list in the console 2026-10-05 17:45:05 +00:00
vercel.json relay: /wake long-poll for the apps' remote jobs (public GET held 45 s, authenticated POST of the stamp) 2026-10-05 08:22:02 +00:00

Igneum relay and console

Text, files and tasks between the project lead's devices without Gmail: the Mac, PC1, PC2 and the phone post to one feed and read from it. Vercel project igneum-relay, served at https://relay.igneum.network. Built 4 October 2026. Since the evening of 4 October 2026 the same private page is the Igneum console (ui.html): seven tabs, phone first, refreshed every 15 s. The relay feed and drop box are its last tab.

Scope since 4 October 2026 (afternoon): the relay stays for the Mac and for humans (notes, files, tasks for a person or a Claude session on a PC). Commands and files for the PCs themselves go over the line to the Igneum Miner app instead: signed jobs published next to the update manifest (packaging/ota/publish-jobs.sh, read back with tools/jobs.mjs, documented in packaging/ota/README.md, "Remote jobs"). The app jobs replace the PC agent (igneum-agent.bat): PC 2 has no Claude session and nobody at the keyboard, and both PCs report the same hostname (DESKTOP-KMCV30N), which the relay's registration cannot tell apart; the app's per-install machine id can. The playbooks under relay/playbooks/ stay as the relay form of the same runs (shard-test.ps1 is the model for a run job) and are parse-checked by windows.yml.

The console

Tab Shows Source
Machines one card per machine: app and node version, height (daa), synced, hash rate, accepted blocks, peers, faults, power and temperatures, last seen; red after 3 min without an upload, grey "stopped (quit|update)" when the app's last upload ends on its own quit lines; a worker card whose STATUS line is over 180 s old is marked stale and left out of the machine total; the OTA state is the newest update line the app logged (downloading, downloaded, staged, installing, updated, failed, current); worker labels nvidia, amd, mac, metal, opencl, other and intel Neon miner_logs (the log intake in site/api/log.mjs): newest upload per label, the last 20 KB parsed server side (miner STATUS lines, node log, the app's stability: lines)
Jobs the signed jobs file with per-machine status (queued, running, done + exit code) and the result line; tap a run for the full upload igneum-jobs.json on the downloads host (fetched server side with DL_TOKEN), results from miner_logs rows whose run_id is job-<id>-<machine>
Builds the OTA manifest (version, notes, platforms, sizes), the last CI fetch, build events, the downloads folder listing igneum-app-latest.json and igneum-windows-ci.json on the downloads host; console_items kind build (posted by packaging/windows/fetch-ci-artifacts.sh and packaging/ota/publish-manifest.sh) and key dl (tools/console.mjs sync-dl)
Chain blocks, identities, hash estimate, difficulty, last lock, finality state, peers, blocks per minute sparkline, events, Hetzner results https://igneum.network/api/live fetched server side; console_items key hetzner (sync-hetzner)
Work log what the agents and the main session post, merged with every relay item, newest first console_items kinds log, build, note; the relay feed
Results the bench log entries (heading + first paragraph), newest first; the FUD ledger counts by status console_items kind bench and key ledger, written by tools/console.mjs sync-bench from docs/bench-log.md and docs/fud-ledger.md
Relay the feed and the drop box, unchanged relay_items, relay_machines

The console function is api/console.mjs, reached through the rewrite /r/<token>/c/<fn>. Every GET answer is cached 10 s in the function instance. No secret reaches the client: the token in the path is the only auth, and DL_TOKEN (the downloads folder) lives in the project env and is used only server side. No GitHub token anywhere: build events come from the Mac-side scripts.

Mac: node tools/console.mjs post --kind log --title "..." --body "..." writes one work-log item (kinds log, build, note); log, machines, chain, jobs, builds, results print the tabs; sync-bench, sync-dl, sync-hetzner or sync push the file-derived data; url prints the link.

The app log (label win-<id8> or mac-<id8>) reaches the intake since b8b349a (4 Oct 2026, 0.3.3); apps before that show app ?. The parsers (labels, miner tail, app tail, the stale mark) live in relay/lib/parse.mjs with no dependencies, and relay/lib/auth.mjs holds the constant-time secret compare; node --test relay/test/parse.test.mjs relay/test/auth.test.mjs runs their tests, and CI runs them in the site job.

The secret is the path

The web page lives at /r/<token>/ and every API call sits under /r/<token>/api/<fn>. The token is 20 base32 characters generated once and stored at ~/.config/igneum/relay-token on the Mac (and as RELAY_TOKEN in the project). Anyone with the link can read and post, so the link stays with the project lead. Scripts may present the log intake key in x-igneum-key instead (RELAY_KEY, the same value as ~/.config/igneum/log-intake-key). There is no other login. Blob file URLs carry a random segment and a random suffix; they are not listed anywhere.

What is stored where

Thing Where Limit
Items (text, title, who, kind, flags, read and done marks) Neon table relay_items (database igneum) body 1 MB
Machines (name, hostname, role, GPU and WSL facts, last seen) Neon table relay_machines
Files Vercel Blob store igneum-relay (public URLs with random path and suffix, London) 50 MB per file through a client token; 4 MB when pushed through the function
The token and key ~/.config/igneum/relay-token, ~/.config/igneum/relay-key (the relay's own key since 4 October 2026, round 4 X23; the log-intake key no longer opens the relay); project env never in the repo

Kinds: text (a note), file, task (for a person or a Claude session on a PC), run (a script the agent executes), result (what a task produced, linked by task_id). Roles: miner, prover, bench, mac, phone.

API (all under /r/<token>/api/)

Call Does
GET feed?since=&before=&machine=&limit= items newest first (200 by default) plus every machine with its unread count
GET item?id= one item with its full body
GET file?id=[&download=1] 302 to the file
`GET inbox?machine=PC1&kind=run task&ack=1`
GET machines names, roles, hostnames, last seen
POST drop JSON {from,to,kind,title,body,file_name,file_url,size,task_id,flags}; or raw bytes with Content-Type: application/octet-stream and x-file-name (4 MB cap)
POST task same fields; kind task or run; run needs one named machine and flags {elevated, reboot_continue}
POST upload {name,size} returns a one-hour Blob client token and put_url; PUT the bytes there, then drop with the returned url
POST ack {ids} POST done {id,exit_code} POST delete {id} marks
POST register {hostname,info} a machine checks in; returns its name, role and whether it is named
POST name {hostname,name} POST role {name,role} naming and roles, from the Mac

Wake (api/wake.mjs, 0.3.6, 5 October 2026): GET /wake?since=<stamp> is public (the apps hold no token) and rate limited, 30 a minute per IP. It holds up to 45 s and answers {stamp, at, added, changed, held_ms} the moment the stored stamp differs from since, else the unchanged stamp at the deadline; without since it answers at once. POST /r/<token>/wake {stamp, added} (or POST /wake with x-relay-token or x-igneum-key) records the stamp; packaging/ota/publish-jobs.sh sends it after every verified deploy, with the ids it added. One row per stamp in Neon table relay_wake (created by the first POST); tools/jobs.mjs status reads the rows for the woken latency. The function's maxDuration is 60 s (vercel.json). Tests: relay/test/wake.test.mjs drives the handler with a fake database and clock.

Mac

node tools/relay.mjs (feed), read <id>, drop "<text>"|<file>, task PC2 "title" [file], run PC2 "title" script.ps1 [--elevated] [--reboot-continue], watch, inbox PC1, machines, role PC2 prover, name DESKTOP-XYZ PC2, ack|done|rm <id>, url. Playbooks live in relay/playbooks/; run fills __DL_BASE__ in from ~/.config/igneum/dl-token.

PCs

relay/clients/make-clients.sh bakes the URL, key and token into copies of the clients and writes ~/Desktop/igneum-relay-clients.zip. Unzip anywhere on the PC. send.bat for people and Claude sessions (see CLAUDE-PC.md), igneum-agent.bat for the automatic runner: double-click once, leave it open. It registers the PC (hostname, GPUs, WSL, nvcc), polls every 20 s, runs each run task in order, posts a result (exit code, last 64 KB inline, full log as a file when longer) and marks it done. A script that prints RELAY-REBOOT triggers shutdown /r /t 10; with reboot_continue the agent re-arms (scheduled task at logon with highest privileges, RunOnce as a fallback) and re-runs the task after the restart with RELAY_PASS incremented. The PC must sign in by itself for that to be unattended.

An unknown hostname that registers appears in the feed with a "name this machine" box, or node tools/relay.mjs name <hostname> PC2. PC1 is DESKTOP-KMCV30N.

Deploy

cd relay && npx --yes vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes --scope igneum

Env on the project: DATABASE_URL, RELAY_KEY, RELAY_TOKEN, BLOB_READ_WRITE_TOKEN (added by vercel blob create-store), DL_TOKEN (the downloads folder token, for the console; added 4 Oct 2026). DNS: relay CNAME cname.vercel-dns.com in the deSEC zone.

Untested until a PC runs it (4 Oct 2026)

send.ps1, igneum-agent.ps1 and the five PowerShell playbooks were written and syntax-reviewed on the Mac (no pwsh here). The bash twin agent.sh and send.sh ran end to end against the live relay. Expect a first-run fix on Windows: Start-Process -Wait exit codes through the wrapper, wsl --install --no-launch on pass 2, the RunOnce path after a reboot.