Real screenshots of the live 0.3.14 app on the Apple M5 Max (paused, Ember Tune off, as the project lead left it), shot headless at 1440x900 and 390 px, dark and light, the rail foot, the address and the machine id masked: miner-dashboard.webp (the hero, same name), miner-earnings, miner-prove, miner-settings, miner-dashboard-light, miner-phone. miner.html: a page-by-page section (the card row cell by cell, three page shots, light and the phone), the lead and the meta copy name the four pages and pounds a day at the user's price, the Ember Tune feat carries the app's one sentence and the measured PC 1 rows from the 6 Oct log (5090: 311.0 to 226.8 W for 0.15% of rate; 4070: 106.0 to 75.6 W for none; the floor, not the optimum), the Power control feat (one approval, then the helper task, no prompts), the dev fee moved to Earnings as in the app. index.html: the same hero shot, two pills. build.mjs: the caption no longer says 'until 0.3.6' (0.3.14 still says Igneum Miner). The 9070 XT row, the market price for pounds earned and the bench anchor (the run 6 entry is on the ship0314 branch) are marked as owed or coming. relay/playbooks/site-capture-pc1.ps1: a read-only run job that shoots the installed app's UI on PC 1 with Edge headless and drops the PNGs on the relay (no quit, pause, resume or settings change; passes the playbook checks). docs/plans/site-miner-2026-10-06.png: before and after. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| api | ||
| clients | ||
| lib | ||
| playbooks | ||
| test | ||
| .gitignore | ||
| apple-touch-icon.png | ||
| favicon-32.png | ||
| favicon.ico | ||
| index.html | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| robots.txt | ||
| ui.html | ||
| vercel.json | ||
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.