relay: the lab-install kind (main's ruling 9 Oct 2026): node tools/relay.mjs lab-install <PC> <lab Setup> <sha256> <machine id> queues the one task an agent accepts beside a public engine: a run whose comment-only body pins the lab Setup's name and sha256 and the engine machine id of one of the two house rigs, signed by the lab OTA key (flags.lab_sig) beside the run key and the machine tag; relay/lib/guard.mjs checkLabInstall is the publisher's and the relay's check, relay/clients/lab-install.ps1 the agent's (plus the engine's own id beside it). Tests: relay/test/lab-install.test.mjs (5), relay/clients/lab-install.test.ps1 (9 of 9 on the Windows canary VM). Every other machine refuses as F14 says; the public OTA channel is untouched

This commit is contained in:
igneum-labs 2026-10-09 11:20:02 +00:00
parent b9a1b63716
commit 311eabdb31
6 changed files with 213 additions and 2 deletions

View file

@ -26,6 +26,7 @@ $RelayToken = '__RELAY_TOKEN__'
$DlBase = '__DL_BASE__'
if ($env:RELAY_DL_BASE) { $DlBase = $env:RELAY_DL_BASE }
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
if (Test-Path (Join-Path $Here 'lab-install.ps1')) { . (Join-Path $Here 'lab-install.ps1') } # the one kind accepted beside a public engine (main's ruling, 9 Oct 2026)
$StateDir = Join-Path $env:LOCALAPPDATA 'igneum-relay'
$TaskDir = Join-Path $StateDir 'tasks'
$LogDir = Join-Path $StateDir 'logs'
@ -245,7 +246,8 @@ function Run-Task($task, [int] $pass) {
$rebootAllowed = $rebootContinue -or [bool]$task.flags.reboot
Log ("task #" + $id + " '" + $task.title + "' pass " + $pass + $(if ($elevated) { " elevated" } else { "" }) + $(if ($rebootContinue) { " reboot_continue" } elseif ($rebootAllowed) { " reboot" } else { "" }))
$why = Check-Task $task
if (-not $why) { $why = Lab-Refusal }
$labInstall = [bool]$task.flags.lab_install
if (-not $why) { if ($labInstall -and (Get-Command LabInstall-Refusal -ErrorAction SilentlyContinue)) { $why = LabInstall-Refusal $task $script:Machine (LabInstall-EngineMachine) } else { $why = Lab-Refusal } }
if ($why) {
Log ("task #" + $id + " REFUSED: " + $why)
Add-Content -Path $log -Value ("REFUSED: " + $why)
@ -254,6 +256,15 @@ function Run-Task($task, [int] $pass) {
return
}
Add-Content -Path $NonceFile -Value $task.flags.nonce
if ($labInstall -and (Get-Command Lab-Install -ErrorAction SilentlyContinue)) {
# nothing of the body runs: the agent's own Lab-Install (download, sha, the Setup scheduled detached) is the whole task
$r = Lab-Install $task $DlBase
Log ("task #" + $id + " " + $r.text)
Add-Content -Path $log -Value $r.text
Post-Result $task ([int]$r.code) $log $r.note
try { Api-Post 'done' @{ id = $id; exit_code = [int]$r.code } | Out-Null } catch { Log ("done failed: " + $_.Exception.Message) }
return
}
if ("$($task.kind)" -eq 'start-app') {
# nothing of the body runs: the agent's own Start-App is the whole task
$r = Start-App

View file

@ -0,0 +1,66 @@
# lab-install (main's ruling, 9 October 2026, 12:2x UK): the ONE task kind the relay agent accepts beside a PUBLIC-edition engine.
# F14 (the founder, 8 October 2026) stands for everything else: the public miner carries no remote execution. This exception
# exists so the project's two house rigs come back as lab machines after a public update with no hand, and it is narrow by
# construction: the task is a `run` whose body is comment lines only (an older agent executes nothing from it), signed by the
# relay run key and tagged with the machine secret like every run, AND signed by the lab OTA key (flags.lab_sig, the relay's
# check), AND its pinned values live in the signed body: the lab Setup's file name and sha256 and the target's engine machine
# id, which must be one of the two baked house rigs AND the id the engine beside this agent reports. Every other machine
# refuses it exactly as F14 says, so no public user can ever be pushed a lab build; the public OTA channel is untouched.
# The agent runs nothing from the body: Lab-Install downloads the pinned Setup from the agent's own downloads base, checks the
# sha, and schedules the Setup detached (a one-shot task), because the Setup's last step re-registers this very agent as a
# service and ends the running copy. The result says "scheduled"; the read-back (edition lab beside the new agent) is the
# publisher's next task, which the fresh lab-side agent accepts as an ordinary run.
$LabInstallMachines = @('ae432dc7', '1ccfe586') # PC 1, PC 2: the engine machine ids (api/state.machine) of the house rigs
$LabInstallNames = @('PC1', 'PC2') # their relay names (the agent's own $script:Machine)
function LabInstall-Fields([string] $body) {
# the pinned values, one per comment line: "# key=value"; anything else in the body refuses the task
$f = @{}
foreach ($line in ($body -split "`r?`n")) {
$t = $line.Trim(); if (-not $t) { continue }
if ($t -eq '# lab-install') { $f['kind'] = 'lab-install'; continue }
if ($t -match '^#\s*(setup_name|setup_sha|machine_id)=(\S+)\s*$') { $f[$Matches[1]] = $Matches[2]; continue }
return $null # a line that is not a pinned value: not a lab-install body
}
return $f
}
function LabInstall-Refusal($task, [string] $machineName, [string] $engineMachineId) {
# '' when the task may run as a lab install, else why not. Every check is a refusal; none is a warning.
$f = LabInstall-Fields ([string]$task.body)
if ($null -eq $f -or $f['kind'] -ne 'lab-install') { return 'lab-install: the body is not the pinned form (comment lines only: # lab-install, # setup_name=, # setup_sha=, # machine_id=)' }
foreach ($k in @('setup_name', 'setup_sha', 'machine_id')) { if (-not $f[$k]) { return ('lab-install: no ' + $k + ' pinned in the signed body') } }
if ($f['setup_name'] -notmatch '^Igneum-Miner-Lab-Setup-[0-9][0-9.]*(-[0-9a-f]{8})?\.exe$') { return ('lab-install: setup_name "' + $f['setup_name'] + '" is not a lab Setup file name') }
if ($f['setup_sha'] -notmatch '^[0-9a-f]{64}$') { return 'lab-install: setup_sha is not 64 hex' }
if ($f['machine_id'] -notmatch '^[0-9a-f]{8}$') { return 'lab-install: machine_id is not 8 hex' }
if ($LabInstallMachines -notcontains $f['machine_id']) { return ('lab-install: machine id ' + $f['machine_id'] + ' is not one of the two house rigs; F14 stands (the public miner carries no remote execution)') }
if ($LabInstallNames -notcontains $machineName) { return ('lab-install: this agent (' + $machineName + ') is not a house rig; F14 stands') }
if (-not $engineMachineId) { return 'lab-install: no engine answers api/state beside this agent, so its machine id cannot be read; nothing installs blind' }
if ($engineMachineId -ne $f['machine_id']) { return ('lab-install: the engine beside this agent is machine ' + $engineMachineId + ', not the pinned ' + $f['machine_id']) }
$sig = [string]$task.flags.lab_sig
if ($sig -notmatch '^[0-9a-f]{128}$') { return 'lab-install: no flags.lab_sig (the Ed25519 signature by the lab OTA key, 128 hex; the relay verifies it against the lab public key)' }
return ''
}
function LabInstall-EngineMachine() {
$urlFile = Join-Path $env:LOCALAPPDATA 'igneum\app\app.url'
if (-not (Test-Path $urlFile)) { return '' }
try { $u = (Get-Content $urlFile -Raw).Trim(); $st = Invoke-RestMethod -Uri ($u + 'api/state') -TimeoutSec 5 -UseBasicParsing; return [string]$st.machine } catch { return '' }
}
function Lab-Install($task, [string] $dlBase) {
# returns @{ code; text; note }. Downloads the pinned Setup, checks the sha, schedules it detached; nothing of the body runs.
$f = LabInstall-Fields ([string]$task.body)
$art = Join-Path $env:LOCALAPPDATA 'igneum\artefacts'; New-Item -ItemType Directory -Force -Path $art | Out-Null
$setup = Join-Path $art $f['setup_name']
try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri ($dlBase + '/' + $f['setup_name']) -OutFile $setup -UseBasicParsing -TimeoutSec 900 } catch { return @{ code = 3; text = ('lab-install: download failed: ' + $_.Exception.Message); note = 'download failed' } }
$got = (Get-FileHash -LiteralPath $setup -Algorithm SHA256).Hash.ToLower()
if ($got -ne $f['setup_sha']) { Remove-Item -LiteralPath $setup -Force -ErrorAction SilentlyContinue; return @{ code = 3; text = ('lab-install: the Setup sha256 ' + $got + ' is not the pinned ' + $f['setup_sha'] + '; removed, nothing installs'); note = 'sha mismatch' } }
$log = Join-Path $art ($f['setup_name'] + '.install.log')
$tn = 'Igneum lab install'
$tr = '"' + $setup + '" /VERYSILENT /NORESTART /SUPPRESSMSGBOXES /LOG="' + $log + '"'
& schtasks.exe /Create /F /SC ONCE /ST 23:59 /RL LIMITED /TN $tn /TR $tr 2>&1 | Out-Null
$r = & schtasks.exe /Run /TN $tn 2>&1 | Out-String
if ($r -notmatch 'SUCCESS') { return @{ code = 4; text = ('lab-install: the one-shot task did not start: ' + $r.Trim()); note = 'schtasks failed' } }
return @{ code = 0; text = ('lab-install: ' + $f['setup_name'] + ' sha256 ' + $got + ' scheduled detached at ' + (Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + ' (task "' + $tn + '", log ' + $log + '); the Setup stops the public app, installs Igneum Miner Lab and re-registers this agent as a service beside it; read edition=lab with the next task'); note = 'scheduled' }
}

View file

@ -0,0 +1,30 @@
# The agent-side test of the lab-install kind (relay/clients/lab-install.ps1): three refusals and one acceptance, run on a Windows
# box: powershell -NoProfile -ExecutionPolicy Bypass -File relay\clients\lab-install.test.ps1 (exit 0 = every case as expected).
# The relay-side twin (the lab key's Ed25519 signature) is relay/test/lab-install.test.mjs; this side checks what the agent can
# check without a signature primitive: the pinned body form, the Setup name and sha shape, the two house rigs' machine ids
# against the engine beside it, the lab_sig's presence, and the result of a tampered body.
$ErrorActionPreference = 'Stop'
. (Join-Path (Split-Path -Parent $MyInvocation.MyCommand.Path) 'lab-install.ps1')
$sha = ('ab' * 32); $sig = ('cd' * 64)
function Mk([string] $name, [string] $s, [string] $mid, [string] $lsig) {
@{ to = 'PC1'; kind = 'run'; body = ("# lab-install`n# setup_name=" + $name + "`n# setup_sha=" + $s + "`n# machine_id=" + $mid + "`n"); flags = @{ lab_install = $true; lab_sig = $lsig; nonce = ('ef' * 16) } }
}
$fails = 0
function Expect([string] $case, [string] $got, [string] $want) {
if ($want -eq '' -and $got -eq '') { Write-Output ('ok ' + $case); return }
if ($want -ne '' -and $got -like ('*' + $want + '*')) { Write-Output ('ok ' + $case + ' -> refused: ' + $got.Substring(0, [Math]::Min(90, $got.Length))); return }
Write-Output ('FAIL ' + $case + ': got "' + $got + '", wanted "' + $want + '"'); $script:fails++
}
Expect 'acceptance: PC 1, the pinned lab Setup, the engine beside reads ae432dc7, lab_sig present' (LabInstall-Refusal (Mk 'Igneum-Miner-Lab-Setup-2.0.3-41abccce.exe' $sha 'ae432dc7' $sig) 'PC1' 'ae432dc7') ''
Expect 'refusal 1: no lab signature on the task (the lab key did not sign it)' (LabInstall-Refusal (Mk 'Igneum-Miner-Lab-Setup-2.0.3.exe' $sha 'ae432dc7' '') 'PC1' 'ae432dc7') 'no flags.lab_sig'
Expect 'refusal 2: a Setup sha that is not 64 hex' (LabInstall-Refusal (Mk 'Igneum-Miner-Lab-Setup-2.0.3.exe' 'not-a-sha' 'ae432dc7' $sig) 'PC1' 'ae432dc7') 'setup_sha is not 64 hex'
Expect 'refusal 3a: a foreign machine id (not a house rig)' (LabInstall-Refusal (Mk 'Igneum-Miner-Lab-Setup-2.0.3.exe' $sha 'deadbeef' $sig) 'PC1' 'deadbeef') 'not one of the two house rigs'
Expect 'refusal 3b: a house rig id pinned but the engine beside this agent is another machine' (LabInstall-Refusal (Mk 'Igneum-Miner-Lab-Setup-2.0.3.exe' $sha 'ae432dc7' $sig) 'PC1' '04383a0c') 'not the pinned'
Expect 'refusal 3c: this agent is not a house rig (the canary VM)' (LabInstall-Refusal (Mk 'Igneum-Miner-Lab-Setup-2.0.3.exe' $sha 'ae432dc7' $sig) 'win-canary' 'ae432dc7') 'not a house rig'
Expect 'refusal 3d: no engine answers (nothing installs blind)' (LabInstall-Refusal (Mk 'Igneum-Miner-Lab-Setup-2.0.3.exe' $sha 'ae432dc7' $sig) 'PC1' '') 'no engine answers'
Expect 'refusal: a public Setup name is not a lab Setup' (LabInstall-Refusal (Mk 'Igneum-Miner-Setup-2.0.3.exe' $sha 'ae432dc7' $sig) 'PC1' 'ae432dc7') 'not a lab Setup file name'
$t = Mk 'Igneum-Miner-Lab-Setup-2.0.3.exe' $sha 'ae432dc7' $sig; $t.body = "# lab-install`nStart-Process evil.exe`n"
Expect 'refusal: a body with a line that is not a pinned value (nothing of a body ever runs)' (LabInstall-Refusal $t 'PC1' 'ae432dc7') 'not the pinned form'
if ($fails -gt 0) { Write-Output ('RESULT lab-install agent test: ' + $fails + ' FAIL'); exit 1 }
Write-Output 'RESULT lab-install agent test: 9 of 9 as expected (1 acceptance, 8 refusals)'
exit 0

View file

@ -122,3 +122,44 @@ export function machineForSecret(secret, rows) {
const hit = rows.find(r => r.secret_hash && sameSecret(h, r.secret_hash));
return hit ? { name: hit.name } : { error: 'unknown machine secret' };
}
// lab-install (main's ruling, 9 October 2026, 12:2x UK): the one task an agent accepts beside a PUBLIC engine, so the two house rigs
// come back as lab machines after a public update with no hand. A `run` whose body is comment lines only (an older agent executes
// nothing from it), pinning the lab Setup's file name and sha256 and the target's engine machine id (api/state.machine), signed by
// the run key and tagged with the machine secret like every run, AND signed by the lab OTA key over the same canon (flags.lab_sig).
// The machine id must be one of the two baked house rigs; every other machine refuses it as F14 says. relay/clients/lab-install.ps1
// makes the same checks on the agent's side (plus the engine's own id beside it); this is the publisher's and the relay's copy.
export const LAB_INSTALL_MACHINES = ['ae432dc7', '1ccfe586']; // PC 1, PC 2
export const LAB_INSTALL_NAMES = ['PC1', 'PC2'];
export const LAB_SETUP_NAME = /^Igneum-Miner-Lab-Setup-[0-9][0-9.]*(-[0-9a-f]{8})?\.exe$/;
export function labInstallBody({ setupName, setupSha, machineId }) {
return `# lab-install\n# setup_name=${setupName}\n# setup_sha=${setupSha}\n# machine_id=${machineId}\n`;
}
export function labInstallFields(body) {
const f = {};
for (const raw of String(body || '').split(/\r?\n/)) {
const t = raw.trim(); if (!t) continue;
if (t === '# lab-install') { f.kind = 'lab-install'; continue; }
const m = /^#\s*(setup_name|setup_sha|machine_id)=(\S+)\s*$/.exec(t);
if (!m) return null;
f[m[1]] = m[2];
}
return f;
}
/** '' when the task may be queued (and may run on the named rig), else why not. */
export function checkLabInstall(o, labPubHex, allow = { names: LAB_INSTALL_NAMES, machines: LAB_INSTALL_MACHINES }) {
const f = labInstallFields(o.body);
if (!f || f.kind !== 'lab-install') return 'the body is not the pinned form (comment lines only)';
for (const k of ['setup_name', 'setup_sha', 'machine_id']) if (!f[k]) return `no ${k} pinned in the signed body`;
if (!LAB_SETUP_NAME.test(f.setup_name)) return `setup_name ${f.setup_name} is not a lab Setup file name`;
if (!/^[0-9a-f]{64}$/.test(f.setup_sha)) return 'setup_sha is not 64 hex';
if (!/^[0-9a-f]{8}$/.test(f.machine_id)) return 'machine_id is not 8 hex';
if (!allow.machines.includes(f.machine_id)) return `machine id ${f.machine_id} is not one of the two house rigs; F14 stands`;
if (!allow.names.includes(String(o.to))) return `${o.to} is not a house rig; F14 stands`;
const fl = o.flags && typeof o.flags === 'object' ? o.flags : {};
if (fl.lab_install !== true) return 'flags.lab_install is not true';
if (!isSig(fl.lab_sig)) return 'no flags.lab_sig (the Ed25519 signature by the lab OTA key)';
if (!isPub(labPubHex)) return 'no lab public key to verify against';
if (!verifyRun(runCanon({ to: o.to, nonce: fl.nonce, body: o.body, flags: fl }), fl.lab_sig, labPubHex)) return 'the lab signature does not verify against the lab public key';
return '';
}

View file

@ -0,0 +1,44 @@
// lab-install (main's ruling, 9 October 2026): the one task an agent accepts beside a PUBLIC engine. Three refusals and one
// acceptance on the publisher's and the relay's check (relay/lib/guard.mjs checkLabInstall), the same rules the agent makes
// on its side (relay/clients/lab-install.ps1, its own test lab-install.test.ps1 on a Windows box).
import test from 'node:test';
import assert from 'node:assert/strict';
import { keygen, signRun, runCanon, machineTag, newNonce, newSecret, labInstallBody, checkLabInstall, labInstallFields } from '../lib/guard.mjs';
const lab = keygen(); // the lab OTA key pair (seed, pub) as hex
const other = keygen(); // another key: the public OTA key, anyone's key
const run = keygen();
const secret = newSecret();
const SHA = 'ab'.repeat(32);
function task({ to = 'PC1', machineId = 'ae432dc7', setupName = 'Igneum-Miner-Lab-Setup-2.0.3-41abccce.exe', setupSha = SHA, labSeed = lab.seed, labInstall = true } = {}) {
const body = labInstallBody({ setupName, setupSha, machineId });
const o = { to, kind: 'run', body, flags: { elevated: false, reboot_continue: false, reboot: false, shell: 'powershell', script: 'lab-install.ps1', lab_install: labInstall } };
o.flags.nonce = newNonce();
o.flags.mac = machineTag(secret, runCanon({ to, nonce: o.flags.nonce, body, flags: o.flags }));
o.flags.sig = signRun(runCanon({ to, nonce: o.flags.nonce, body, flags: o.flags }), run.seed);
o.flags.lab_sig = signRun(runCanon({ to, nonce: o.flags.nonce, body, flags: o.flags }), labSeed);
return o;
}
test('acceptance: a house rig, the pinned lab Setup, signed by the lab key', () => {
assert.equal(checkLabInstall(task(), lab.pub), '');
});
test('refusal 1: signed by another key (the public OTA key, anyone else)', () => {
assert.match(checkLabInstall(task({ labSeed: other.seed }), lab.pub), /lab signature does not verify/);
});
test('refusal 2: a wrong or unpinned Setup sha', () => {
assert.match(checkLabInstall(task({ setupSha: 'not-a-sha' }), lab.pub), /setup_sha is not 64 hex/);
const o = task(); o.body = labInstallBody({ setupName: 'Igneum-Miner-Lab-Setup-2.0.3.exe', setupSha: 'cd'.repeat(32), machineId: 'ae432dc7' }); // the body changed after signing
assert.match(checkLabInstall(o, lab.pub), /lab signature does not verify/);
});
test('refusal 3: a foreign machine id or a machine that is not a house rig (F14 stands)', () => {
assert.match(checkLabInstall(task({ machineId: 'deadbeef' }), lab.pub), /not one of the two house rigs/);
assert.match(checkLabInstall(task({ to: 'win-canary' }), lab.pub), /not a house rig/);
});
test('the body form: comment lines only; a line that is not a pinned value is not a lab-install body', () => {
assert.equal(labInstallFields('# lab-install\n# setup_name=Igneum-Miner-Lab-Setup-2.0.3.exe\n# setup_sha=' + SHA + '\n# machine_id=ae432dc7\n').machine_id, 'ae432dc7');
assert.equal(labInstallFields('# lab-install\nStart-Process evil.exe\n'), null);
assert.match(checkLabInstall(task({ setupName: 'Igneum-Miner-Setup-2.0.3.exe' }), lab.pub), /not a lab Setup file name/);
assert.match(checkLabInstall(task({ labInstall: false }), lab.pub), /lab_install is not true/);
});

View file

@ -9,6 +9,7 @@
// a script the igneum-agent runs: signed with ~/.config/igneum/relay-run-key
// (Ed25519, checked by the relay) and tagged with the machine's secret
// (~/.config/igneum/relay-machines/<machine>, checked by the agent); X23
// node tools/relay.mjs lab-install <machine> <lab Setup name> <sha256> <engine machine id> the one task an agent runs beside a PUBLIC engine (the two house rigs only)
// node tools/relay.mjs start-app <machine> the agent there starts the installed Igneum Miner and reports whether an
// engine answered (MF-11); signed and tagged like run, body = relay/playbooks/start-app.ps1
// node tools/relay.mjs keygen make the run key pair once; prints the public key for RELAY_RUN_PUB
@ -24,7 +25,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, statSync, chmodSync
import { homedir, tmpdir, hostname } from 'node:os';
import { basename, join, resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { keygen as edKeygen, signRun, runCanon, machineTag, newNonce, newSecret, secretHash } from '../relay/lib/guard.mjs';
import { keygen as edKeygen, signRun, runCanon, machineTag, newNonce, newSecret, secretHash, labInstallBody, checkLabInstall, LAB_INSTALL_NAMES, LAB_INSTALL_MACHINES } from '../relay/lib/guard.mjs';
process.stdout.on('error', e => { if (e.code === 'EPIPE') process.exit(0); throw e; });
const CFG = join(homedir(), '.config', 'igneum');
@ -147,6 +148,24 @@ try {
signRunTask(o);
const r = await api('task', { body: o }); console.log(`queued #${r.id} start-app for ${to}: the agent starts the installed app and reports in about a minute (node tools/relay.mjs watch)`);
}
else if (cmd === 'lab-install') {
// the one kind an agent accepts beside a PUBLIC engine (main's ruling, 9 October 2026): a run whose body is comment lines only,
// pinning the lab Setup's name and sha256 and the target's engine machine id (one of the two house rigs), signed by the run
// key and tagged like every run AND signed by the lab OTA key (flags.lab_sig). relay/lib/guard.mjs checkLabInstall is the
// same check the agent makes on its side (relay/clients/lab-install.ps1), run here before anything is queued.
const [, to, setupName, setupSha, machineId] = pos;
if (!to || !setupName || !setupSha || !machineId) throw new Error('lab-install <machine> <Igneum-Miner-Lab-Setup-<v>[-<sha8>].exe> <setup sha256> <engine machine id (8 hex)>');
const body = labInstallBody({ setupName, setupSha, machineId });
const o = { from: flags.from || 'Mac', to, title: flags.title || `lab-install: ${setupName} (${setupSha.slice(0, 12)}) on ${to}/${machineId}`, kind: 'run', body, flags: { elevated: false, reboot_continue: false, reboot: false, shell: 'powershell', script: 'lab-install.ps1', lab_install: true, timeout_minutes: 30 } };
signRunTask(o);
const labSeed = cfg('lab-signing/lab-signing-key');
if (!/^[0-9a-f]{64}$/.test(labSeed)) throw new Error('no lab signing key at ~/.config/igneum/lab-signing/lab-signing-key');
o.flags.lab_sig = signRun(runCanon({ to: o.to, nonce: o.flags.nonce, body: o.body, flags: o.flags }), labSeed);
const labPub = cfg('lab-signing/lab-signing-key.pub');
const why = checkLabInstall(o, labPub, { names: LAB_INSTALL_NAMES, machines: LAB_INSTALL_MACHINES });
if (why) throw new Error(`lab-install refused before queueing: ${why}`);
const r = await api('task', { body: o }); console.log(`queued #${r.id} lab-install for ${to}: ${o.title}`);
}
else if (cmd === 'keygen') {
if (existsSync(RUN_KEY)) throw new Error(`${RUN_KEY} exists; not overwriting a signing key`);
const { seed, pub } = edKeygen();