Merge agent-watchdog 79b80c89 into master (gate: green on 56157f5d, recorded by tools/ci/pre-push.sh; landed on the box mirror)

This commit is contained in:
igneum-labs 2026-10-09 08:47:47 +00:00
commit 03a13ceaa4
4 changed files with 76 additions and 7 deletions

View file

@ -1,6 +1,6 @@
# The relay agent as a logon task (IgneumRelayService, registered by install-agent.ps1 from IgneumRelayService.xml).
# This loop keeps igneum-agent.ps1 running with no window: the Task Scheduler restarts this loop on failure (PT1M, 999
# times), and the loop restarts the agent 15 s after any exit, so the agent outlives the app, a crash of its own, and a
# times), and the loop restarts the agent 10 s after any exit, so the agent outlives the app, a crash of its own, and a
# reboot (the task fires at logon; the PC signs in by itself). MF-11, 7 October 2026: PC 2's agent had been dead since
# 6 October and its one-shot logon task exited at every boot, so neither a signed job nor a relay task could start the app.
# Output goes to %LOCALAPPDATA%\igneum-relay\logs\agent-service.log (5 MB, one rotation); the idle line is left out.
@ -19,7 +19,7 @@ while ($true) {
# one Add-Content per line, so the file is never held open: a reader (a job's Get-Content -Tail) is refused while a
# pipeline holds it (PC 2, 7 October 2026, 15:38 local: "being used by another process")
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $Agent 2>&1 | ForEach-Object { "$_" } | Where-Object { $_ -notmatch 'idle as ' } | ForEach-Object { Add-Content -Path $log -Value $_ }
Note ('agent exited with code ' + $LASTEXITCODE + '; again in 15 s')
} catch { Note ('agent failed to start: ' + $_.Exception.Message + '; again in 15 s') }
Start-Sleep -Seconds 15
Note ('agent exited with code ' + $LASTEXITCODE + '; again in 10 s')
} catch { Note ('agent failed to start: ' + $_.Exception.Message + '; again in 10 s') }
Start-Sleep -Seconds 10
}

View file

@ -6,8 +6,13 @@
# the task. Every IgneumRelayAgent* task (the one-shot reboot arms of X25) and the RunOnce key go first: on PC 2 a stale
# one pointed at a path that was not there and popped "Windows cannot find 'igneum-agent'" at every boot (7 October 2026).
# A failure here is a line on stdout and an exit code, never a dialog.
# powershell -ExecutionPolicy Bypass -File install-agent.ps1 [-Highest] [-Remove]
param([switch]$Highest, [switch]$Remove)
# The watchdog (watchdog.ps1 beside this file; 9 October 2026, the founder's rule that no machine needs a hand to
# recover): two more tasks, IgneumRelayWatchdog every 5 minutes and IgneumRelayWatchdogLogon a minute after logon, start
# the service task when the agent's pid is dead (the logon one as a loop checking every 10 s, so the agent is back within
# 15 s) and register it again when it is missing, so the agent outlives the
# engine's update, restart, quit and a reboot. -NoWatchdog skips them (the watchdog itself calls this script with it).
# powershell -ExecutionPolicy Bypass -File install-agent.ps1 [-Highest] [-Remove] [-NoWatchdog]
param([switch]$Highest, [switch]$Remove, [switch]$NoWatchdog)
$ErrorActionPreference = 'Continue'
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
$TaskName = 'IgneumRelayService'
@ -30,11 +35,19 @@ try {
if ((Test-Path $k) -and ((Get-ItemProperty -Path $k -ErrorAction SilentlyContinue).PSObject.Properties.Name -contains 'IgneumRelayAgent')) { Remove-ItemProperty -Path $k -Name 'IgneumRelayAgent' -ErrorAction SilentlyContinue; Write-Host 'removed the RunOnce key IgneumRelayAgent' }
} catch { }
if ($Remove) {
foreach ($w in @('IgneumRelayWatchdog', 'IgneumRelayWatchdogLogon')) { & schtasks.exe /Delete /F /TN $w 2>&1 | Out-Null }
& schtasks.exe /End /TN $TaskName 2>&1 | Out-Null
& schtasks.exe /Delete /F /TN $TaskName 2>&1 | Out-Null
Write-Host ('removed the ' + $TaskName + ' task (an agent window started by hand is not touched)')
exit 0
}
# A stale lock (9 October 2026, both PCs at 09:0x UK): an agent copy that holds the mutex but no longer polls (it sat waiting
# on an app a task had relaunched) made the bat refuse a second copy, and a hand was needed. Every shell running
# igneum-agent.ps1 or its service loop is ended here BY PID before the task is registered, so the install always starts
# clean; nothing of the Miner is touched (the engine and its workers are not powershell shells of ours).
$stale = @(Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^(powershell|pwsh)\.exe$' -and $_.CommandLine -match 'igneum-agent(-service)?\.ps1' -and $_.ProcessId -ne $PID })
foreach ($p in $stale) { Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue; Write-Host ('ended the old agent shell pid ' + $p.ProcessId + ' (it held the lock)') }
if ($stale.Count -gt 0) { Start-Sleep -Seconds 2 }
foreach ($f in @('IgneumRelayService.xml', 'igneum-agent-service.ps1', 'igneum-agent.ps1', 'machine-secret.txt')) {
if (-not (Test-Path (Join-Path $Here $f))) { Write-Host ('missing ' + $f + ' beside this script (unzip the whole client zip from make-clients.sh --machine <name>)'); exit 2 }
}
@ -53,6 +66,16 @@ try {
Write-Host ('registered ' + $TaskName + ' for ' + $user + ' at ' + $level + ' from ' + $Here)
} finally { Remove-Item -Path $tmp -Force -ErrorAction SilentlyContinue }
& schtasks.exe /Run /TN $TaskName 2>&1 | ForEach-Object { "$_" } | Write-Host
if (-not $NoWatchdog) {
$wd = Join-Path $Here 'watchdog.ps1'
if (Test-Path $wd) {
$tr = 'powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "' + $wd + '"'
$w1 = & schtasks.exe /Create /F /TN 'IgneumRelayWatchdog' /SC MINUTE /MO 5 /RL LIMITED /TR $tr 2>&1 | ForEach-Object { "$_" }
$w2 = & schtasks.exe /Create /F /TN 'IgneumRelayWatchdogLogon' /SC ONLOGON /DELAY 0001:00 /RL LIMITED /TR ($tr + ' -Loop') 2>&1 | ForEach-Object { "$_" }
Write-Host ('watchdog tasks: ' + (($w1 + $w2) -join ' | '))
& schtasks.exe /Run /TN 'IgneumRelayWatchdogLogon' 2>&1 | Out-Null
} else { Write-Host ('no watchdog.ps1 beside this script; the watchdog tasks are not registered') }
}
Start-Sleep -Seconds 3
& schtasks.exe /Query /TN $TaskName /V /FO LIST 2>&1 | ForEach-Object { "$_" } | Where-Object { $_ -match '^(TaskName|Status|Logon Mode|Last Run Time|Last Result|Task To Run|Run As User|Schedule Type):' } | Write-Host
Write-Host 'the agent now runs under the task; close any igneum-agent.bat window (the task copy exits while that one holds the mutex, and comes back 15 s after it closes)'

View file

@ -31,7 +31,7 @@ fi
mkdir -p "$OUT"
NAME="igneum-relay-clients${MACHINE:+-$MACHINE}"
STAGE="$(mktemp -d)/$NAME"; mkdir -p "$STAGE"; umask 077
for f in send.bat send.ps1 send.sh igneum-agent.bat igneum-agent.ps1 igneum-agent-service.ps1 install-agent.ps1 IgneumRelayService.xml agent.sh CLAUDE-PC.md; do
for f in send.bat send.ps1 send.sh igneum-agent.bat igneum-agent.ps1 igneum-agent-service.ps1 install-agent.ps1 watchdog.ps1 IgneumRelayService.xml agent.sh CLAUDE-PC.md; do
sed -e "s#__RELAY_URL__#$URL#g" -e "s#__RELAY_KEY__#$KEY#g" -e "s#__RELAY_TOKEN__#$TOKEN#g" -e "s#__DL_BASE__#$DL_BASE#g" "$HERE/$f" > "$STAGE/$f"
done
[ -n "$SECRET" ] && printf '%s\n' "$SECRET" > "$STAGE/machine-secret.txt"

View file

@ -0,0 +1,46 @@
# Relay agent watchdog (9 October 2026, the founder's standing rule: no machine needs a hand to recover). Registered by
# install-agent.ps1 as two scheduled tasks beside IgneumRelayService: IgneumRelayWatchdog every 5 minutes and
# IgneumRelayWatchdogLogon one minute after this user's logon. When the agent's pid file names no live process and no
# igneum-agent.ps1 shell runs, it starts the IgneumRelayService task; when that task is missing, it registers it again
# with install-agent.ps1 from this folder. The agent is only ever started by the task scheduler, never by a job of the
# engine (an agent started under a job died with the engine's update on PC 1, 9 October 09:08 UK). It ends nothing.
# watchdog.ps1 one check (the five-minute task): the agent, then the loop below when none runs
# watchdog.ps1 -Loop the loop (the logon task): one check every 10 s for 55 minutes, so a dead agent is back within
# 15 s (the founder's bar); it exits after 55 minutes and the five-minute task starts it again
param([switch]$Loop)
$ErrorActionPreference = 'Continue'
$Here = Split-Path -Parent $MyInvocation.MyCommand.Path
$state = Join-Path $env:LOCALAPPDATA 'igneum-relay'
New-Item -ItemType Directory -Force -Path $state | Out-Null
$log = Join-Path $state 'watchdog.log'
function L($s) { Add-Content -Path $log -Value ((Get-Date).ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ') + ' ' + $s) }
$live = Join-Path $state 'agent.live'
function Agent-Alive {
if (Test-Path -LiteralPath $live) { try { $t = (Get-Content -LiteralPath $live -Raw).Trim(); if ($t -match '^\d+$' -and (Get-Process -Id ([int]$t) -ErrorAction SilentlyContinue)) { return $true } } catch { } }
return [bool](Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^(powershell|pwsh)\.exe$' -and $_.CommandLine -match 'igneum-agent\.ps1' } | Select-Object -First 1)
}
function Check {
if (Agent-Alive) { return }
$q = & schtasks.exe /Query /TN 'IgneumRelayService' 2>&1 | Out-String
if ($q -match 'IgneumRelayService') {
& schtasks.exe /Run /TN 'IgneumRelayService' 2>&1 | Out-Null
L 'agent not running: IgneumRelayService task started'
} else {
$inst = Join-Path $Here 'install-agent.ps1'
if (Test-Path -LiteralPath $inst) { & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $inst -NoWatchdog 2>&1 | Out-Null; L ('IgneumRelayService task missing: registered again from ' + $inst) } else { L ('IgneumRelayService task missing and no install-agent.ps1 beside ' + $Here + '; nothing done') }
}
}
if ($Loop) {
# one loop at a time: a second one exits
$mine = $PID
$other = Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $mine -and $_.Name -match '^(powershell|pwsh)\.exe$' -and $_.CommandLine -match 'watchdog\.ps1' -and $_.CommandLine -match '-Loop' } | Select-Object -First 1
if ($other) { exit 0 }
$until = (Get-Date).AddMinutes(55)
while ((Get-Date) -lt $until) { Check; Start-Sleep -Seconds 10 }
exit 0
}
Check
# the loop itself: started here when none runs (after a logon it is the logon task's; after 55 minutes it is this task's)
$loopUp = Get-CimInstance Win32_Process | Where-Object { $_.Name -match '^(powershell|pwsh)\.exe$' -and $_.CommandLine -match 'watchdog\.ps1' -and $_.CommandLine -match '-Loop' } | Select-Object -First 1
if (-not $loopUp) { & schtasks.exe /Run /TN 'IgneumRelayWatchdogLogon' 2>&1 | Out-Null; L 'watchdog loop not running: IgneumRelayWatchdogLogon task started' }
exit 0