130 lines
9.8 KiB
JavaScript
130 lines
9.8 KiB
JavaScript
// node --test relay/test/service.test.mjs The relay agent as a logon task and the start-app kind (MF-11, 7 October
|
|
// 2026). Structural checks of the manifest, the installer, the hidden loop, the agent's branch and the Mac tool; the
|
|
// PowerShell 5.1 parse of the .ps1 files is windows.yml's job.
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { KINDS, AGENT_KINDS } from '../lib/relay.mjs';
|
|
|
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
const read = p => readFileSync(join(ROOT, p), 'utf8');
|
|
|
|
test('the service manifest: at logon, the run level the installer chooses, interactive, restarted on failure, no time limit, one instance, hidden window', () => {
|
|
const x = read('relay/clients/IgneumRelayService.xml');
|
|
assert.match(x, /<LogonTrigger>[\s\S]*<UserId>__USER__<\/UserId>[\s\S]*<\/LogonTrigger>/);
|
|
assert.match(x, /<Principal id="Author">[\s\S]*<UserId>__USER__<\/UserId>[\s\S]*<LogonType>InteractiveToken<\/LogonType>[\s\S]*<RunLevel>__RUNLEVEL__<\/RunLevel>/);
|
|
const restart = /<RestartOnFailure>\s*<Interval>PT(\d+)M<\/Interval>\s*<Count>(\d+)<\/Count>\s*<\/RestartOnFailure>/.exec(x);
|
|
assert.ok(restart, 'RestartOnFailure with Interval and Count');
|
|
assert.ok(Number(restart[1]) <= 5 && Number(restart[2]) >= 100, `restart every ${restart[1]} min, ${restart[2]} times`);
|
|
assert.match(x, /<ExecutionTimeLimit>PT0S<\/ExecutionTimeLimit>/, 'no time limit');
|
|
assert.match(x, /<MultipleInstancesPolicy>IgnoreNew<\/MultipleInstancesPolicy>/);
|
|
assert.match(x, /<DisallowStartIfOnBatteries>false<\/DisallowStartIfOnBatteries>/);
|
|
assert.match(x, /<StopIfGoingOnBatteries>false<\/StopIfGoingOnBatteries>/);
|
|
assert.match(x, /<StartWhenAvailable>true<\/StartWhenAvailable>/);
|
|
assert.match(x, /<Command>powershell\.exe<\/Command>\s*<Arguments>-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "__AGENT_DIR__\\igneum-agent-service\.ps1"<\/Arguments>/);
|
|
assert.match(x, /<URI>\\IgneumRelayService<\/URI>/);
|
|
});
|
|
|
|
test('the installer fills the manifest and registers, runs and reads back the task; it carries no secret', () => {
|
|
const s = read('relay/clients/install-agent.ps1');
|
|
// F14 (the founder's ruling, 8 October 2026): the agent runs nothing beside a public engine; an engine without the product
|
|
// field (pre-2.0.2) runs as before. Known-failed first.
|
|
const agentLab = read('relay/clients/igneum-agent.ps1');
|
|
assert.match(agentLab, /function App-Edition/, 'the engine says what it is (api/state edition, product, channel)');
|
|
assert.match(agentLab, /\$e\.edition -eq 'lab' -and \$e\.product -eq 'Igneum Miner Lab' -and \$e\.channel -eq 'igneum-2\.0-devnet-lab'/, 'the window lane\'s exact strings (reviewb-202 ef3c8402): all three must read lab');
|
|
assert.match(agentLab, /if \(-not \$why\) \{ \$why = Lab-Refusal \}/, 'the refusal sits in the task path before anything runs');
|
|
assert.match(agentLab, /if \(-not \$e\.edition -and -not \$e\.product\) \{ return '' \}/, 'no field: an older engine, allowed');
|
|
assert.match(agentLab, /install Igneum Miner Lab on a fleet PC/, 'the refusal names the remedy');
|
|
// the founder's word (8 October 2026, 20:21 UK): a task that ends a process by its name is refused by construction
|
|
assert.match(agentLab, /function Kill-By-Name-Refusal/, 'the agent refuses kill-by-name task bodies');
|
|
const shapes = [['Stop-Process -Na', 'me igneum-app -Force'], ['task', 'kill /IM igneumd.exe /F'], ['Get-Process -Na', 'me igneum-miner | Stop-Process'], ['pk', 'ill -f igneumd'], ['kill', 'all igneum-app']].map(p => p.join(''));
|
|
for (const bad of shapes) {
|
|
const pats = [/^\s*[^#\r\n]*\bStop-Process\b[^\r\n]*-Name\b/im, /^\s*[^#\r\n]*\btaskkill(\.exe)?\b[^\r\n]*\/IM\b/im, /^\s*[^#\r\n]*\bGet-Process\b[^\r\n]*-Name\b[^\r\n]*\|\s*Stop-Process/im, /^\s*[^#\r\n]*\b(pkill|killall)\b/im];
|
|
assert.ok(pats.some(p => p.test(bad)), `refused shape: ${bad}`);
|
|
}
|
|
assert.ok(![/\bStop-Process\b[^\r\n]*-Name\b/im].some(p => p.test('Stop-Process -Id $p.ProcessId -Force')), 'a pid is the allowed way');
|
|
assert.match(agentLab, /\$kb = Kill-By-Name-Refusal \(\[string\]\$task\.body\)/, 'the check runs inside Check-Task before any body runs');
|
|
|
|
assert.doesNotMatch(s, /#Requires -RunAsAdministrator/, 'a per-user task needs no administrator (PC 2 has nobody to click a prompt)');
|
|
assert.match(s, /\.Replace\('__RUNLEVEL__', \$level\)/);
|
|
assert.match(s, /\$level = 'LeastPrivilege'[\s\S]*if \(\$Highest\) \{ \$level = 'HighestAvailable' \}/);
|
|
// the stale one-shot tasks (PC 2, 7 October 2026: a logon task at a path that was not there popped a dialog at every boot) go first
|
|
assert.match(s, /IgneumRelayAgent\*/);
|
|
assert.match(s, /schtasks\.exe \/Delete \/F \/TN \$t/);
|
|
assert.match(s, /RunOnce/);
|
|
assert.match(s, /\.Replace\('__USER__', \$user\)\.Replace\('__AGENT_DIR__', \$Here\)/);
|
|
assert.match(s, /schtasks\.exe \/Create \/F \/TN \$TaskName \/XML \$tmp/);
|
|
assert.match(s, /\$TaskName = 'IgneumRelayService'/);
|
|
assert.match(s, /schtasks\.exe \/Run \/TN \$TaskName/);
|
|
assert.match(s, /schtasks\.exe \/Query \/TN \$TaskName/);
|
|
assert.match(s, /machine-secret\.txt/, 'the zip must carry the machine secret for the agent to run anything');
|
|
assert.doesNotMatch(s, /__RELAY_(URL|KEY|TOKEN)__|x-relay-token|x-igneum-key/, 'the installer touches no secret');
|
|
assert.match(s, /-Remove/);
|
|
});
|
|
|
|
test('the hidden loop keeps the agent running for ever, 15 s after any exit, logs without the idle line, and a missing agent path is a log line, never a dialog', () => {
|
|
const s = read('relay/clients/igneum-agent-service.ps1');
|
|
// the task runs the loop by its full path under -WindowStyle Hidden (the manifest), and the loop itself opens nothing:
|
|
// a missing igneum-agent.ps1 is one line in the log and a 60 s wait, no `cmd /c start`, no message box, no exit
|
|
const missing = s.slice(s.indexOf('if (-not (Test-Path $Agent))'), s.indexOf('Note \'starting igneum-agent.ps1\''));
|
|
assert.match(missing, /Note \('no agent at ' \+ \$Agent \+ '; waiting'\); Start-Sleep -Seconds 60; continue/);
|
|
assert.doesNotMatch(s, /cmd(\.exe)? \/c start|MessageBox|Read-Host|\[System\.Windows\.Forms/);
|
|
assert.match(s, /-File \$Agent/, 'the agent runs by its full path');
|
|
assert.match(s, /ForEach-Object \{ Add-Content -Path \$log -Value \$_ \}/, 'one write per line: the log is never held open');
|
|
assert.match(s, /\$Agent = Join-Path \$Here 'igneum-agent\.ps1'/);
|
|
assert.match(s, /while \(\$true\) \{/);
|
|
assert.match(s, /powershell\.exe -NoProfile -ExecutionPolicy Bypass -File \$Agent/);
|
|
assert.match(s, /Start-Sleep -Seconds 15/);
|
|
assert.match(s, /-notmatch 'idle as '/);
|
|
assert.match(s, /agent-service\.log/);
|
|
assert.doesNotMatch(s, /api\/(quit|pause|resume)/);
|
|
});
|
|
|
|
test('the agent runs a start-app task through its own Start-App after the tag check, never the body, never elevated, never a quit', () => {
|
|
const s = read('relay/clients/igneum-agent.ps1');
|
|
const run = s.slice(s.indexOf('function Run-Task'), s.indexOf('Log ("igneum relay agent on'));
|
|
const check = run.indexOf('$why = Check-Task $task');
|
|
const nonce = run.indexOf('Add-Content -Path $NonceFile -Value $task.flags.nonce');
|
|
const branch = run.indexOf("if (\"$($task.kind)\" -eq 'start-app') {");
|
|
const script = run.indexOf('[IO.File]::WriteAllText($script');
|
|
assert.ok(check > 0 && nonce > check && branch > nonce && script > branch, 'tag check, nonce recorded, the start-app branch, and only then a script is written');
|
|
assert.match(run, /\$r = Start-App[\s\S]*Post-Result \$task \(\[int\]\$r\.code\) \$log \$r\.note[\s\S]*return\s*\}/);
|
|
const start = s.slice(s.indexOf('function Start-App'), s.indexOf('function Sha256-Hex'));
|
|
assert.match(start, /\/RL LIMITED/, 'an elevated agent starts the app at the limited run level');
|
|
assert.match(start, /Start-Process -FilePath \$exe -ArgumentList '--launch'/);
|
|
assert.match(start, /api\/state/);
|
|
assert.doesNotMatch(start, /api\/(quit|pause|resume|cards)/);
|
|
assert.doesNotMatch(start, /\$task\.body|Invoke-Expression/);
|
|
assert.match(s, /agent = 'igneum-agent\.ps1 v3'/);
|
|
// the registration carries the app's per-install id, so two PCs with one Windows hostname are two machines
|
|
assert.match(s, /Api-Post 'register' @\{ hostname = \(Machine-Hostname\); info = \$info \}/);
|
|
assert.match(s, /igneum\\app\\machine-id/);
|
|
assert.match(s, /\$id\.Substring\(0, 8\)/);
|
|
// the one-shot reboot arm is still the only task the agent removes: the service task is never touched
|
|
const disarm = s.slice(s.indexOf('function Disarm-Restart'), s.indexOf('function Sha256-Hex'));
|
|
assert.doesNotMatch(disarm, /IgneumRelayService/);
|
|
assert.match(disarm, /schtasks\.exe \/Delete \/F \/TN 'IgneumRelayAgent'/);
|
|
});
|
|
|
|
test('the start-app body for older agents does the same thing and never quits the installed app', () => {
|
|
const s = read('relay/playbooks/start-app.ps1');
|
|
assert.match(s, /api\/state/);
|
|
assert.match(s, /\/RL LIMITED/);
|
|
assert.doesNotMatch(s, /api\/(quit|pause|resume|cards)/);
|
|
assert.doesNotMatch(s, /__DL_BASE__/);
|
|
});
|
|
|
|
test('the kind is known to the relay, the Mac tool signs it, and the zip carries the service files', () => {
|
|
assert.ok(KINDS.has('start-app'));
|
|
assert.deepEqual(AGENT_KINDS, ['run', 'start-app']);
|
|
const m = read('tools/relay.mjs');
|
|
assert.match(m, /cmd === 'start-app'/);
|
|
assert.match(m, /kind: 'start-app', body, flags: \{ elevated: false/);
|
|
const sa = m.slice(m.indexOf("cmd === 'start-app'"), m.indexOf("cmd === 'keygen'"));
|
|
assert.match(sa, /signRunTask\(o\)/, 'start-app is signed and tagged like run');
|
|
assert.match(sa, /'start-app\.ps1'\)/);
|
|
const mk = read('relay/clients/make-clients.sh');
|
|
for (const f of ['igneum-agent-service.ps1', 'install-agent.ps1', 'IgneumRelayService.xml']) assert.match(mk, new RegExp(f.replace('.', '\\.')), `${f} is not in the zip`);
|
|
});
|