修复bootstrap注册崩溃:StartWhenAvailable属任务设置而非触发器
现场:用户以管理员跑 -SetEnv -StartNow 时脚本抛 SetValueInvocationException(触发器上没有 StartWhenAvailable 属性), 而它是在"已注销旧任务、尚未注册新任务"之间抛的,等于把 runner 任务删没了。 - StartWhenAvailable 改设到 settings(MSFT_TaskSettings 继承属性, CIM 属性列表里看不见但可直接赋值,实测 StartWhenAvailable=True) - 任务对象改由 New-WatchdogTaskPlan 统一构建,先构建、后注销旧任务 - Register-ScheduledTask 失败时显式告警"旧任务已删、CI 裸奔"并 exit 1 - 新增 -DryRun:免管理员即可全量校验 action/trigger/settings - -Status 增补上次运行时间与返回码,并提示"非管理员看不见任务" 同时更正根因:旧计划任务其实一直存在(非管理员会话看不见,先前误判为 "从未注册"),但它跑的是单次 run_runner.cmd——daemon 属"正常退出"而非 "失败",计划任务的失败重启根本接不到,必须靠看门狗循环重拉。
This commit is contained in:
@@ -57,6 +57,11 @@
|
||||
.PARAMETER Status
|
||||
Only report: task state, daemon process, Gitea reachability.
|
||||
|
||||
.PARAMETER DryRun
|
||||
Build and validate the action / trigger / settings objects and print what
|
||||
WOULD be done, writing and registering nothing. This is also the only way
|
||||
to verify this script without elevation.
|
||||
|
||||
.PARAMETER Force
|
||||
With -SetEnv, overwrite existing machine-level values.
|
||||
|
||||
@@ -74,6 +79,7 @@ param(
|
||||
[switch]$SetEnv,
|
||||
[switch]$StartNow,
|
||||
[switch]$Status,
|
||||
[switch]$DryRun,
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
@@ -154,13 +160,63 @@ function Find-MsvcCl {
|
||||
return $null
|
||||
}
|
||||
|
||||
# Builds the task objects in ONE place so -DryRun validates exactly what the
|
||||
# real run registers.
|
||||
#
|
||||
# MEASURED TRAP (2026-10-11, hit on the user's machine): `StartWhenAvailable`
|
||||
# is a TASK SETTINGS property (MSFT_TaskSettings, inherited by
|
||||
# MSFT_TaskSettings3) -- it does NOT exist on triggers. Assigning it to the
|
||||
# trigger aborts the script with a SetValueInvocationException along the lines
|
||||
# of "setting StartWhenAvailable threw an exception: property
|
||||
# 'StartWhenAvailable' is not found on this object" (message localized),
|
||||
# and, worse, that happened AFTER the old task had already been unregistered,
|
||||
# leaving CI with no task at all. Hence: build everything first, only then
|
||||
# unregister the old task.
|
||||
function New-WatchdogTaskPlan {
|
||||
param([string]$Launcher, [string]$WorkDir)
|
||||
|
||||
$action = New-ScheduledTaskAction `
|
||||
-Execute 'powershell.exe' `
|
||||
-Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$Launcher`"" `
|
||||
-WorkingDirectory $WorkDir
|
||||
|
||||
# Boot trigger + a 1 minute grace period: Gitea may still be starting when
|
||||
# the session-0 boot fires (and the watchdog waits for it anyway).
|
||||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||||
$trigger.Delay = 'PT1M'
|
||||
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
||||
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) `
|
||||
-MultipleInstances IgnoreNew
|
||||
# StartWhenAvailable also covers "the machine was off when this was missed".
|
||||
$settings.StartWhenAvailable = $true
|
||||
|
||||
return [pscustomobject]@{ Action = $action; Trigger = $trigger; Settings = $settings }
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# -Status: read-only report
|
||||
# ---------------------------------------------------------------------------
|
||||
if ($Status) {
|
||||
Info "task $TaskName"
|
||||
$t = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if ($t) { Ok ("task state: {0}" -f $t.State) } else { Warn 'task not registered' }
|
||||
if ($t) {
|
||||
Ok ("task state: {0}" -f $t.State)
|
||||
$li = Get-ScheduledTaskInfo -TaskName $TaskName -ErrorAction SilentlyContinue
|
||||
if ($li) {
|
||||
Info ("last run: {0} | last result: {1} | next run: {2}" -f `
|
||||
$li.LastRunTime, ('0x{0:X}' -f $li.LastTaskResult), $li.NextRunTime)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Warn "task $TaskName not found"
|
||||
if (-not (Test-Admin)) {
|
||||
Warn ' this session is NOT elevated: a non-admin session cannot always see'
|
||||
Warn ' every task, so re-run -Status elevated before concluding it is missing'
|
||||
}
|
||||
}
|
||||
|
||||
$proc = Get-Process | Where-Object { $_.ProcessName -match 'gitea-runner|act_runner' }
|
||||
if ($proc) { Ok ("daemon running: pid(s) " + ($proc.Id -join ',')) } else { Warn 'daemon not running' }
|
||||
@@ -179,8 +235,9 @@ if ($Status) {
|
||||
# ---------------------------------------------------------------------------
|
||||
# Preconditions
|
||||
# ---------------------------------------------------------------------------
|
||||
if (-not (Test-Admin)) {
|
||||
if (-not $DryRun -and -not (Test-Admin)) {
|
||||
Fail 'not elevated -- rerun from an elevated PowerShell (this writes a scheduled task and machine env vars)'
|
||||
Fail 'use -DryRun to validate the whole plan (action/trigger/settings) without elevation'
|
||||
exit 2
|
||||
}
|
||||
|
||||
@@ -210,6 +267,27 @@ if ($registeredAddress -and $registeredAddress.TrimEnd('/') -ne $GiteaUrl.TrimEn
|
||||
$GiteaUrl = $registeredAddress
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 0. build the task plan up front; -DryRun stops here without writing anything
|
||||
# ---------------------------------------------------------------------------
|
||||
$plan = New-WatchdogTaskPlan -Launcher $launcher -WorkDir $RunnerDir
|
||||
|
||||
if ($DryRun) {
|
||||
Info 'dry run -- writing and registering NOTHING'
|
||||
Info ("would write launcher : {0}" -f $launcher)
|
||||
Info ("would run : {0} {1}" -f $plan.Action.Execute, $plan.Action.Arguments)
|
||||
Info ("would trigger : {0} delay={1}" -f $plan.Trigger.CimClass.CimClassName, $plan.Trigger.Delay)
|
||||
Info ("would settings : StartWhenAvailable={0} RestartCount={1} RestartInterval={2} ExecTimeLimit={3} MultipleInstances={4}" -f `
|
||||
$plan.Settings.StartWhenAvailable, $plan.Settings.RestartCount, $plan.Settings.RestartInterval,
|
||||
$plan.Settings.ExecutionTimeLimit, $plan.Settings.MultipleInstances)
|
||||
Info 'would register as : user SYSTEM, RunLevel Highest'
|
||||
if (Test-Admin) { Ok 'elevation: OK (admin)' }
|
||||
else { Warn 'elevation: NOT admin -- the real run must be started from an elevated shell' }
|
||||
if (Test-Gitea $GiteaUrl) { Ok "Gitea reachable at $GiteaUrl" }
|
||||
else { Warn "Gitea not reachable at $GiteaUrl -- the watchdog would keep waiting" }
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 1. machine-level toolchain (SYSTEM cannot see the interactive user's PATH)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -313,27 +391,20 @@ if ($existing) {
|
||||
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
|
||||
}
|
||||
|
||||
$action = New-ScheduledTaskAction `
|
||||
-Execute 'powershell.exe' `
|
||||
-Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$launcher`"" `
|
||||
-WorkingDirectory $RunnerDir
|
||||
|
||||
# -AtStartup needs the "System" account; a boot trigger plus StartWhenAvailable
|
||||
# also covers "machine was off when the task was created".
|
||||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||||
$trigger.Delay = 'PT1M'
|
||||
$trigger.StartWhenAvailable = $true
|
||||
|
||||
$settings = New-ScheduledTaskSettingsSet `
|
||||
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
||||
-ExecutionTimeLimit ([TimeSpan]::Zero) `
|
||||
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) `
|
||||
-MultipleInstances IgnoreNew
|
||||
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger `
|
||||
-Settings $settings -User 'SYSTEM' -RunLevel Highest `
|
||||
-Description 'Gitea Actions runner watchdog (STM32F4-Base CI). Supervises gitea-runner daemon: waits for Gitea, restarts the daemon when it exits. Generated by scripts/ci_runner_bootstrap.ps1.' |
|
||||
Out-Null
|
||||
try {
|
||||
Register-ScheduledTask -TaskName $TaskName -Action $plan.Action -Trigger $plan.Trigger `
|
||||
-Settings $plan.Settings -User 'SYSTEM' -RunLevel Highest `
|
||||
-Description 'Gitea Actions runner watchdog (STM32F4-Base CI). Supervises the gitea-runner daemon: waits for Gitea, restarts the daemon when it exits. Generated by scripts/ci_runner_bootstrap.ps1.' |
|
||||
Out-Null
|
||||
}
|
||||
catch {
|
||||
Fail "Register-ScheduledTask failed: $($_.Exception.Message)"
|
||||
if ($existing) {
|
||||
Fail "the previous task was ALREADY removed -- fix the cause and re-run this script immediately"
|
||||
}
|
||||
Fail 'until this succeeds the runner has no auto-start and CI stays dead'
|
||||
exit 1
|
||||
}
|
||||
Ok "scheduled task registered: $TaskName (at boot +1m, as SYSTEM, unlimited runtime, restart x999/1m)"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user