mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
feat(infra): supervise Windows packaging runner with Task Scheduler
This commit is contained in:
1 parent
e44061c934
commit
cfa52b0bac
6 files changed
+257
-2
No files matched your search
@@ -0,0 +1,26 @@
|
||||
# Repeating Task Scheduler triggers recover an exited launcher without relying on action exit-code retries.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$log = Join-Path $PSScriptRoot ('run-' + [DateTime]::UtcNow.ToString('yyyyMMddTHHmmssfffZ') + '-' + $PID + '.log')
|
||||
try {
|
||||
@{ Pid = $PID; Started = (Get-Process -Id $PID).StartTime.ToUniversalTime().Ticks } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $PSScriptRoot 'process.json')
|
||||
$config = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'config.json') -Raw | ConvertFrom-Json
|
||||
$attemptFile = Join-Path $PSScriptRoot 'attempts.txt'
|
||||
$attempts = [int](Get-Content -LiteralPath $attemptFile -Raw)
|
||||
if ($attempts -lt 0 -or $config.RestartCount -lt 1 -or $config.RestartCount -gt 999) { throw 'Invalid restart budget; reinstall the task.' }
|
||||
if ($attempts -gt $config.RestartCount) {
|
||||
$null = Disable-ScheduledTask -TaskName $config.TaskName -TaskPath '\'
|
||||
Set-Content -LiteralPath $log -Value 'Restart budget exhausted. Task disabled; inspect runner logs, fix the cause and run start.'
|
||||
exit 1
|
||||
}
|
||||
Set-Content -LiteralPath $attemptFile -Value ($attempts + 1)
|
||||
& (Join-Path $PSHOME 'pwsh.exe') -NoProfile -NonInteractive -File (Join-Path $PSScriptRoot 'start.ps1') -RunnerRoot $config.RunnerRoot -ConfigRoot $config.ConfigRoot *> $log
|
||||
$code = $LASTEXITCODE
|
||||
Add-Content -LiteralPath $log -Value "Runner launcher exited with code $code."
|
||||
# run.cmd maps several listener failures to zero; a persistent task must stay alive until explicitly stopped.
|
||||
exit 1
|
||||
} catch {
|
||||
$message = 'Runner task failed: ' + $_.Exception.Message
|
||||
[Console]::Error.WriteLine($message)
|
||||
Add-Content -LiteralPath $log -Value $message
|
||||
exit 1
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
# Manage a registered packaging runner in the current user's interactive Windows session.
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][ValidateSet('install','start','stop','status','uninstall')][string]$Action,
|
||||
[Parameter(Mandatory=$true)][string]$RunnerRoot,
|
||||
[string]$ConfigRoot,
|
||||
[ValidateRange(1,1440)][int]$RestartMinutes = 1,
|
||||
[ValidateRange(1,999)][int]$RestartCount = 10
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (-not $IsWindows) { throw 'This command requires PowerShell 7 on Windows.' }
|
||||
if ($RunnerRoot -notmatch '^[A-Za-z]:[\\/]') { throw 'RunnerRoot must be an absolute local directory.' }
|
||||
$RunnerRoot = [IO.Path]::GetFullPath($RunnerRoot).TrimEnd('\','/')
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $RunnerRoot '.runner') -PathType Leaf)) { throw 'Register the runner before managing its task.' }
|
||||
for ($ancestor = [IO.DirectoryInfo]::new($RunnerRoot); $null -ne $ancestor; $ancestor = $ancestor.Parent) {
|
||||
if ($ancestor.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'RunnerRoot cannot contain linked directories.' }
|
||||
}
|
||||
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
||||
$digest = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData([Text.Encoding]::UTF8.GetBytes($RunnerRoot.ToUpperInvariant())))
|
||||
$taskName = 'DSH-Packaging-' + $digest.Substring(0,16)
|
||||
$serviceDir = Join-Path $RunnerRoot 'dsh-task'
|
||||
$task = Get-ScheduledTask -TaskPath '\' | Where-Object TaskName -eq $taskName
|
||||
if ($task -and $task.Description -ne "DSH packaging runner: $RunnerRoot; owner: $sid") { throw 'Task ownership differs; inspect it in Task Scheduler.' }
|
||||
|
||||
if ($Action -eq 'install') {
|
||||
if ($task) { throw 'Task already installed; stop and uninstall before reinstalling.' }
|
||||
if (-not $ConfigRoot -or $ConfigRoot -notmatch '^[A-Za-z]:[\\/]') { throw 'Install requires an absolute ConfigRoot.' }
|
||||
$ConfigRoot = [IO.Path]::GetFullPath($ConfigRoot)
|
||||
$pwsh = Join-Path $PSHOME 'pwsh.exe'
|
||||
& $pwsh -NoProfile -File (Join-Path $PSScriptRoot 'start-windows-packaging-runner.ps1') -RunnerRoot $RunnerRoot -ConfigRoot $ConfigRoot -CheckOnly
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Runner preflight failed; no task was installed.' }
|
||||
if (Test-Path -LiteralPath $serviceDir) {
|
||||
if ((Get-Item -LiteralPath $serviceDir).Attributes -band [IO.FileAttributes]::ReparsePoint) { throw 'Task directory cannot be a link.' }
|
||||
} else {
|
||||
$null = New-Item -ItemType Directory -Path $serviceDir
|
||||
}
|
||||
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'start-windows-packaging-runner.ps1') -Destination (Join-Path $serviceDir 'start.ps1')
|
||||
Copy-Item -LiteralPath (Join-Path $PSScriptRoot 'run-windows-runner-task.ps1') -Destination (Join-Path $serviceDir 'run.ps1')
|
||||
@{ RunnerRoot = $RunnerRoot; ConfigRoot = $ConfigRoot; TaskName = $taskName; RestartCount = $RestartCount } | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $serviceDir 'config.json') -Encoding utf8
|
||||
Set-Content -LiteralPath (Join-Path $serviceDir 'attempts.txt') -Value '0'
|
||||
$arguments = '-NoProfile -NonInteractive -WindowStyle Hidden -File "' + (Join-Path $serviceDir 'run.ps1') + '"'
|
||||
$execution = New-ScheduledTaskAction -Execute $pwsh -Argument $arguments -WorkingDirectory $RunnerRoot
|
||||
$principal = New-ScheduledTaskPrincipal -UserId $sid -LogonType Interactive -RunLevel Limited
|
||||
$trigger = @(
|
||||
(New-ScheduledTaskTrigger -AtLogOn -User $sid),
|
||||
(New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes($RestartMinutes) -RepetitionInterval (New-TimeSpan -Minutes $RestartMinutes))
|
||||
)
|
||||
$settings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit ([TimeSpan]::Zero) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
|
||||
$settings.Enabled = $false
|
||||
$null = Register-ScheduledTask -TaskName $taskName -TaskPath '\' -Action $execution -Principal $principal -Trigger $trigger -Settings $settings -Description "DSH packaging runner: $RunnerRoot; owner: $sid"
|
||||
Write-Output "Installed disabled task $taskName. Run start while logged in; reboot requires desktop login. Logs: $serviceDir"
|
||||
exit 0
|
||||
}
|
||||
if (-not $task) { throw 'Task is not installed for this runner directory.' }
|
||||
switch ($Action) {
|
||||
'status' {
|
||||
$info = Get-ScheduledTaskInfo -InputObject $task
|
||||
[pscustomobject]@{ TaskName = $taskName; State = $task.State; LastRunTime = $info.LastRunTime; LastTaskResult = $info.LastTaskResult; Launches = (Get-Content -LiteralPath (Join-Path $serviceDir 'attempts.txt') -Raw).Trim(); Logs = $serviceDir }
|
||||
}
|
||||
'start' {
|
||||
if ($task.State -eq 'Running') { Write-Output "$taskName is already running."; break }
|
||||
Set-Content -LiteralPath (Join-Path $serviceDir 'attempts.txt') -Value '0'
|
||||
$null = Enable-ScheduledTask -InputObject $task
|
||||
Start-ScheduledTask -InputObject $task
|
||||
Write-Output "Start requested for $taskName. Verify Connected to GitHub and Listening for Jobs in $serviceDir."
|
||||
}
|
||||
'stop' {
|
||||
# Operators pause workflow dispatch first; this local check cannot reserve an idle GitHub runner.
|
||||
$workers = Get-CimInstance Win32_Process -Filter "Name = 'Runner.Worker.exe'"
|
||||
foreach ($worker in $workers) {
|
||||
if (-not $worker.ExecutablePath -or $worker.ExecutablePath.StartsWith($RunnerRoot + '\', [StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw 'A runner job is active or its location is unreadable. Wait for completion before stopping.'
|
||||
}
|
||||
}
|
||||
$null = Disable-ScheduledTask -InputObject $task
|
||||
if ($task.State -eq 'Running') {
|
||||
$record = Get-Content -LiteralPath (Join-Path $serviceDir 'process.json') -Raw | ConvertFrom-Json
|
||||
$owner = Get-Process -Id $record.Pid -ErrorAction SilentlyContinue
|
||||
if (-not $owner -or $owner.StartTime.ToUniversalTime().Ticks -ne $record.Started) {
|
||||
throw 'Running task process identity is unavailable. Automatic starts are disabled; inspect the task before stopping it.'
|
||||
}
|
||||
$processes = @(Get-CimInstance Win32_Process)
|
||||
$ownedIds = [Collections.Generic.HashSet[int]]::new()
|
||||
$null = $ownedIds.Add($owner.Id)
|
||||
do {
|
||||
$added = $false
|
||||
foreach ($process in $processes) {
|
||||
if ($ownedIds.Contains([int]$process.ParentProcessId) -and $ownedIds.Add([int]$process.ProcessId)) { $added = $true }
|
||||
}
|
||||
} while ($added)
|
||||
$owned = @(foreach ($ownedId in $ownedIds) { Get-Process -Id $ownedId -ErrorAction SilentlyContinue })
|
||||
& "$env:SystemRoot/System32/taskkill.exe" /PID $owner.Id /T /F
|
||||
if ($LASTEXITCODE -ne 0 -and -not $owner.HasExited) { throw 'Runner task process tree could not be stopped; inspect it before restarting.' }
|
||||
foreach ($process in $owned) {
|
||||
if (-not $process.WaitForExit(30000)) { throw 'Runner task child remains alive; inspect it before restarting.' }
|
||||
}
|
||||
}
|
||||
Stop-ScheduledTask -InputObject $task
|
||||
$deadline = [DateTime]::UtcNow.AddSeconds(30)
|
||||
do {
|
||||
$state = (Get-ScheduledTask -TaskName $taskName -TaskPath '\').State
|
||||
if ($state -ne 'Running') { break }
|
||||
Start-Sleep -Milliseconds 200
|
||||
} while ([DateTime]::UtcNow -lt $deadline)
|
||||
if ($state -eq 'Running') { throw 'Task remains running; inspect its processes before further maintenance.' }
|
||||
Write-Output "Stopped and disabled $taskName. Run start to enable automatic starts again."
|
||||
}
|
||||
'uninstall' {
|
||||
if ($task.State -ne 'Disabled') { throw 'Stop the task before uninstalling.' }
|
||||
Unregister-ScheduledTask -InputObject $task -Confirm:$false
|
||||
Write-Output "Uninstalled $taskName. Registration, configuration and logs remain in $RunnerRoot."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
# Opt-in Windows Task Scheduler acceptance: isolated fake runner, no GitHub registration or signing access.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (-not $IsWindows) { throw 'Requires PowerShell 7 on Windows.' }
|
||||
$root = Join-Path ([IO.Path]::GetTempPath()) ('dsh-runner-task-' + [guid]::NewGuid().ToString('N'))
|
||||
$null = New-Item -ItemType Directory -Path $root
|
||||
$source = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '../scripts'))
|
||||
$runner = Join-Path $root 'runner with spaces'
|
||||
$null = New-Item -ItemType Directory -Path $runner
|
||||
Set-Content -LiteralPath (Join-Path $runner '.runner') -Value '{}'
|
||||
foreach ($file in @('windows-runner-task.ps1','run-windows-runner-task.ps1')) { Copy-Item -LiteralPath (Join-Path $source $file) -Destination $root }
|
||||
$manager = Join-Path $root 'windows-runner-task.ps1'
|
||||
@'
|
||||
param([string]$RunnerRoot, [string]$ConfigRoot, [switch]$CheckOnly)
|
||||
if ($CheckOnly) { exit 0 }
|
||||
Add-Content -LiteralPath (Join-Path $RunnerRoot 'starts.txt') -Value $PID
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $RunnerRoot 'failed-once'))) {
|
||||
Set-Content -LiteralPath (Join-Path $RunnerRoot 'failed-once') -Value 'yes'
|
||||
exit 0
|
||||
}
|
||||
Write-Output 'FAKE RUNNER READY'
|
||||
while ($true) { Start-Sleep -Seconds 1 }
|
||||
'@ | Set-Content -LiteralPath (Join-Path $root 'start-windows-packaging-runner.ps1')
|
||||
function Invoke-Manager([string]$Operation, [switch]$ExpectFailure) {
|
||||
& (Join-Path $PSHOME 'pwsh.exe') -NoProfile -File $manager -Action $Operation -RunnerRoot $runner -ConfigRoot $root -RestartCount 2
|
||||
if (($LASTEXITCODE -eq 0) -eq [bool]$ExpectFailure) { throw "Unexpected exit $LASTEXITCODE for $Operation" }
|
||||
}
|
||||
function Wait-Condition([scriptblock]$Condition, [string]$Description, [int]$Seconds = 30) {
|
||||
$deadline = [DateTime]::UtcNow.AddSeconds($Seconds)
|
||||
do {
|
||||
if (& $Condition) { return }
|
||||
Start-Sleep -Milliseconds 200
|
||||
} while ([DateTime]::UtcNow -lt $deadline)
|
||||
throw "Timed out: $Description"
|
||||
}
|
||||
$installed = $false
|
||||
try {
|
||||
Invoke-Manager install
|
||||
$installed = $true
|
||||
$config = Get-Content -LiteralPath (Join-Path $runner 'dsh-task/config.json') -Raw | ConvertFrom-Json
|
||||
$task = Get-ScheduledTask -TaskName $config.TaskName -TaskPath '\'
|
||||
if ($task.State -ne 'Disabled' -or $task.Settings.ExecutionTimeLimit -ne 'PT0S' -or $task.Settings.MultipleInstances -ne 'IgnoreNew') { throw 'Unexpected task scheduling settings.' }
|
||||
Invoke-Manager install -ExpectFailure
|
||||
Invoke-Manager start
|
||||
$starts = Join-Path $runner 'starts.txt'
|
||||
Wait-Condition { (Test-Path -LiteralPath $starts) -and @(Get-Content -LiteralPath $starts).Count -ge 2 } 'automatic retry after unexpected clean exit' 110
|
||||
$childId = [int]@(Get-Content -LiteralPath $starts)[-1]
|
||||
$child = Get-Process -Id $childId
|
||||
Invoke-Manager start
|
||||
Invoke-Manager status
|
||||
if (@(Get-Content -LiteralPath $starts).Count -ne 2) { throw 'Duplicate runner started.' }
|
||||
Invoke-Manager uninstall -ExpectFailure
|
||||
$recordFile = Join-Path $runner 'dsh-task/process.json'
|
||||
$originalRecord = Get-Content -LiteralPath $recordFile -Raw
|
||||
$record = $originalRecord | ConvertFrom-Json
|
||||
$record.Started = 0
|
||||
$record | ConvertTo-Json | Set-Content -LiteralPath $recordFile
|
||||
Invoke-Manager stop -ExpectFailure
|
||||
if ($child.HasExited) { throw 'Stale process record terminated a process.' }
|
||||
Set-Content -LiteralPath $recordFile -Value $originalRecord
|
||||
Invoke-Manager stop
|
||||
Wait-Condition { $child.Refresh(); $child.HasExited } 'scheduler child termination'
|
||||
Invoke-Manager start
|
||||
Wait-Condition { @(Get-Content -LiteralPath $starts).Count -eq 3 } 'explicit restart'
|
||||
$child = Get-Process -Id ([int]@(Get-Content -LiteralPath $starts)[-1])
|
||||
Invoke-Manager stop
|
||||
Wait-Condition { $child.Refresh(); $child.HasExited } 'restarted child termination'
|
||||
Set-Content -LiteralPath (Join-Path $runner 'dsh-task/attempts.txt') -Value '3'
|
||||
$null = Enable-ScheduledTask -TaskName $config.TaskName -TaskPath '\'
|
||||
Start-ScheduledTask -TaskName $config.TaskName -TaskPath '\'
|
||||
Wait-Condition { (Get-ScheduledTask -TaskName $config.TaskName -TaskPath '\').State -eq 'Disabled' } 'exhausted restart budget disables task'
|
||||
if (@(Get-Content -LiteralPath $starts).Count -ne 3) { throw 'Exhausted budget launched another runner.' }
|
||||
Invoke-Manager uninstall
|
||||
$installed = $false
|
||||
Invoke-Manager status -ExpectFailure
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $runner '.runner'))) { throw 'Registration removed.' }
|
||||
Write-Output "PASS: install, duplicate refusal, automatic retry, start, status, stop, child teardown, retry exhaustion, uninstall. Evidence: $root"
|
||||
} finally {
|
||||
if ($installed) {
|
||||
Invoke-Manager stop
|
||||
Invoke-Manager uninstall
|
||||
}
|
||||
foreach ($process in @(Get-CimInstance Win32_Process -Filter "Name = 'pwsh.exe'")) {
|
||||
if ($process.CommandLine -and $process.CommandLine.Contains($root)) {
|
||||
$owned = Get-Process -Id $process.ProcessId -ErrorAction SilentlyContinue
|
||||
if ($owned) {
|
||||
Stop-Process -InputObject $owned
|
||||
if (-not $owned.WaitForExit(30000)) { throw 'Fixture process did not exit.' }
|
||||
}
|
||||
}
|
||||
}
|
||||
# Logs stay available for diagnosing scheduler failures; the fixture owns no registered runner credentials.
|
||||
Write-Output "Fixture retained: $root"
|
||||
}
|
||||
@@ -18,8 +18,11 @@
|
||||
en: 0c261f3948178d55
|
||||
zh: 1086cd882468ad8f
|
||||
/windows-signing-and-runner-operations/machine-setup-and-runner-startup:
|
||||
en: ba3de07cc9553056
|
||||
zh: 887e652c40d57091
|
||||
en: bb889e9514678d1c
|
||||
zh: 48cbec1e907d6a2b
|
||||
/windows-signing-and-runner-operations/machine-setup-and-runner-startup/supervised-startup:
|
||||
en: 45ed84267ca7bdf7
|
||||
zh: 6750dc58cce42023
|
||||
/windows-signing-and-runner-operations/signing-configuration:
|
||||
en: 4b5548818375eb9a
|
||||
zh: 8c975adbb1dca9c8
|
||||
|
||||
@@ -34,6 +34,16 @@ Use a runner-owned checkout and keep configuration, certificates and caches outs
|
||||
|
||||
Run `pwsh -NoProfile -File apps/desktop/scripts/setup-windows-packaging.ps1 -Root <absolute-machine-root>` to prepare private `packaging/{presets,secrets,certificates}` and `cache/windows-signatures` directories and copy templates without overwriting existing files. Add `-CheckOnly` to inspect without writes. Fill the [preset templates](../config/windows-packaging/test.env.example) and [signing template](../config/windows-packaging/windows-signing.env.example) on the machine. Run `start-windows-packaging-runner.ps1 -RunnerRoot <registered-runner-directory> -ConfigRoot <machine-root>/packaging -CheckOnly` from the same scripts directory to inspect PowerShell 7.6.6 under the runner's `tools` directory, Node, Python, Git, tar and Visual C++ tools; packaging `--check` performs the remaining toolchain validation. Omit `-CheckOnly` to start the existing runner with an explicit config root and scrubbed inherited environment. Registration and tool installation are separate; these scripts never create tokens, alter system security settings or clear signing failure records. Use the signing cache maintenance command below to migrate existing entries before changing the preset's cache path.
|
||||
|
||||
### Supervised startup
|
||||
|
||||
Run `pwsh -NoProfile -File apps/desktop/scripts/windows-runner-task.ps1 -Action install -RunnerRoot <registered-runner-directory> -ConfigRoot <machine-root>/packaging` as the runner's Windows user, using its installed PowerShell 7. The installer checks the environment and copies startup scripts into `runner/dsh-task`; registration, tools and secrets remain separate. It registers a disabled, limited-permission interactive Task Scheduler task without storing an account password. Run the same command with `-Action start` or `-Action status`; only installation needs `-ConfigRoot`. The task starts after this user's desktop login, including after reboot, and stops on logout. It does not provide unattended boot or unlock a signing Token.
|
||||
|
||||
The task permits one instance and has no execution time limit. A repeating trigger checks every `-RestartMinutes` minutes (default `1`) and restarts an exited launcher, including an unexpected zero exit. The persisted budget permits the initial launch plus `-RestartCount` restarts (default `10`) between explicit starts; starting an already running task does not reset it. GitHub's launcher owns runner updates; task recovery does not rerun a failed workflow or clear signing interlocks. After retries are exhausted, inspect `runner/dsh-task/run-*.log`, the runner's `_diag` logs and `status`, correct the cause, then run `start`. A running task alone does not prove connectivity: verify `Connected to GitHub` and `Listening for Jobs`, then verify assignment in Actions. Logs are retained for operator-managed cleanup.
|
||||
|
||||
Before maintenance, pause new workflow dispatch and wait for the runner to become idle. `-Action stop` refuses an active worker for this runner, disables future starts and stops its task process tree; the local check cannot prevent a concurrent GitHub assignment. `-Action uninstall` requires a stopped, disabled task and retains registration, configuration and logs. To update installed scripts, stop, uninstall and install again. When migrating a manually launched runner, stop its existing launcher only after it is idle, then start the task; never run both launchers together.
|
||||
|
||||
Operators can run `pwsh -NoProfile -File apps/desktop/tests/windows-runner-task.ps1` on Windows to exercise installation, automatic recovery, duplicate-start prevention, stop, child-process teardown and uninstall against an isolated fake runner. This opt-in test creates a temporary task and retains diagnostic files; it does not contact GitHub or access signing hardware. Real-machine acceptance additionally checks an idle runner coming online, an Actions job being assigned, and login after an operator-approved reboot. Signed packaging and logout/reboot acceptance require separate operator verification.
|
||||
|
||||
<a id="signing-configuration"></a>
|
||||
|
||||
## Signing configuration
|
||||
|
||||
@@ -34,6 +34,16 @@ description: "配置和维护 Windows 打包 runner、签名凭据、EV Token
|
||||
|
||||
运行 `pwsh -NoProfile -File apps/desktop/scripts/setup-windows-packaging.ps1 -Root <absolute-machine-root>` 创建私有 `packaging/{presets,secrets,certificates}` 和 `cache/windows-signatures` 目录并复制模板,不覆盖现有文件。添加 `-CheckOnly` 只检查不写入。在机器上填写 [preset 模板](../config/windows-packaging/test.env.example)和[签名模板](../config/windows-packaging/windows-signing.env.example)。从同一 scripts 目录运行 `start-windows-packaging-runner.ps1 -RunnerRoot <registered-runner-directory> -ConfigRoot <machine-root>/packaging -CheckOnly`,检查 runner 的 `tools` 目录下 PowerShell 7.6.6、Node、Python、Git、tar 和 Visual C++ 工具;打包 `--check` 完成其余工具链检查。省略 `-CheckOnly`,以显式配置目录和清理后的继承环境启动现有 runner。注册和工具安装单独进行;这些脚本不创建 token、不改系统安全设置、不清除签名失败记录。修改 preset 缓存路径前,使用下文签名缓存维护命令迁移已有条目。
|
||||
|
||||
### 受监督启动
|
||||
|
||||
以 runner 的 Windows 用户运行 `pwsh -NoProfile -File apps/desktop/scripts/windows-runner-task.ps1 -Action install -RunnerRoot <registered-runner-directory> -ConfigRoot <machine-root>/packaging`,使用该机器安装的 PowerShell 7。安装器检查环境,并将启动脚本复制到 `runner/dsh-task`;注册、工具和凭据仍单独管理。它注册一个初始禁用、权限受限的交互式计划任务,不保存账户密码。将同一命令的操作改为 `-Action start` 或 `-Action status`;只有安装需要 `-ConfigRoot`。任务在该用户登录桌面后启动,重启后也需要登录,注销时停止。它不提供无人值守开机,也不解锁签名 Token。
|
||||
|
||||
任务只允许一个实例,且没有执行时限。周期触发器每隔 `-RestartMinutes` 分钟(默认 `1`)检查一次,并恢复已退出的启动器,包括意外的零退出码。持久预算允许首次启动及 `-RestartCount` 次重启(默认 `10`),直到再次显式启动;对正在运行的任务执行 start 不会重置预算。GitHub 启动器负责 runner 更新;任务恢复不会重跑失败的 workflow,也不会清除签名互锁。重试耗尽后,检查 `runner/dsh-task/run-*.log`、runner 的 `_diag` 日志和 `status`,修复原因后运行 `start`。任务运行不等于连接成功:确认出现 `Connected to GitHub` 和 `Listening for Jobs`,再到 Actions 确认任务分配。日志保留,由操作者管理清理。
|
||||
|
||||
维护前暂停新 workflow 的触发,等待 runner 空闲。`-Action stop` 在该 runner 存在活跃 worker 时拒绝停止,否则禁用后续启动并停止任务进程树;本地检查无法阻止 GitHub 同时分配任务。`-Action uninstall` 要求任务已停止并禁用,保留注册、配置和日志。更新已安装脚本时,依次停止、卸载、重新安装。迁移手动启动的 runner 时,只在空闲后停止原启动器,再启动计划任务;不要同时运行两个启动器。
|
||||
|
||||
操作者可在 Windows 上运行 `pwsh -NoProfile -File apps/desktop/tests/windows-runner-task.ps1`,使用隔离的假 runner 验证安装、自动恢复、防止重复启动、停止、子进程退出和卸载。此显式运行的测试创建临时任务并保留诊断文件,不连接 GitHub 或访问签名硬件。真实机器验收还需检查空闲 runner 上线、Actions job 分配,以及经操作者批准的重启后登录。签名打包和注销/重启验收需要操作者单独验证。
|
||||
|
||||
<a id="signing-configuration"></a>
|
||||
|
||||
## 签名配置
|
||||
|
||||
Reference in new issue
Block a user