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:
parent
b9a1b63716
commit
311eabdb31
6 changed files with 213 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
66
relay/clients/lab-install.ps1
Normal file
66
relay/clients/lab-install.ps1
Normal 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' }
|
||||
}
|
||||
30
relay/clients/lab-install.test.ps1
Normal file
30
relay/clients/lab-install.test.ps1
Normal 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
|
||||
|
|
@ -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 '';
|
||||
}
|
||||
|
|
|
|||
44
relay/test/lab-install.test.mjs
Normal file
44
relay/test/lab-install.test.mjs
Normal 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/);
|
||||
});
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in a new issue