Windows
The binary under Task Scheduler, or a service wrapper.
View as MarkdownWindows has no built-in supervisor for an arbitrary executable, so the choice is Task Scheduler, which is already there, or a service wrapper, which behaves more like a real service. Both need the same two directories underneath them.
Run every block below from an elevated PowerShell.
Download and verify
$version = "v0.1.0"
$arch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "amd64" }
$asset = "local-worker-windows-$arch.exe"
$base = "https://github.com/savedhq/local-worker/releases/download/$version"
curl.exe -fsSLO "$base/$asset"
curl.exe -fsSLO "$base/SHA256SUMS"
$expected = (Select-String -Path SHA256SUMS -Pattern ([regex]::Escape($asset) + '$')).Line.Split()[0]
$actual = (Get-FileHash ".\$asset" -Algorithm SHA256).Hash.ToLower()
if ($expected -ne $actual) { throw "checksum mismatch" }
"OK"curl.exe has shipped with Windows since 10 build 1803. The .exe matters: bare curl is a
PowerShell alias for Invoke-WebRequest and takes different arguments.
Two directories
The executable and the config do not live in the same place. The config is the one that holds secrets, and it is the one the working directory has to point at.
New-Item -ItemType Directory -Force "$env:ProgramFiles\saved" | Out-Null
Move-Item -Force ".\$asset" "$env:ProgramFiles\saved\local-worker.exe"
New-Item -ItemType Directory -Force "$env:ProgramData\saved" | Out-Null
Copy-Item .\config.yaml "$env:ProgramData\saved\config.yaml"config.yaml holds your source passwords and the worker key. Strip inherited permissions so
ordinary users cannot read it:
icacls "$env:ProgramData\saved" /inheritance:r `
/grant:r "SYSTEM:(OI)(CI)(F)" "Administrators:(OI)(CI)(F)"The (OI)(CI) flags push the same restriction onto the files inside, so the config inherits
it rather than needing its own rule.
Run it once by hand
The worker reads config.yaml from its working directory and takes no path argument.
Push-Location "$env:ProgramData\saved"
& "$env:ProgramFiles\saved\local-worker.exe"
Pop-LocationCheck the external tool resolved lines and that backups= matches what this worker should
serve, then stop it with Ctrl-C. See
what healthy looks like.
Register the task
The worker logs to stderr, and Task Scheduler captures neither stream. Running it through
cmd.exe with a redirect is what gives you something to read afterwards.
$exe = "$env:ProgramFiles\saved\local-worker.exe"
$log = "$env:ProgramData\saved\worker.log"
$action = New-ScheduledTaskAction -Execute "cmd.exe" `
-Argument ('/c ""{0}" 2>> "{1}""' -f $exe, $log) `
-WorkingDirectory "$env:ProgramData\saved"
$trigger = New-ScheduledTaskTrigger -AtStartup
$settings = New-ScheduledTaskSettingsSet `
-ExecutionTimeLimit ([TimeSpan]::Zero) `
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) `
-MultipleInstances IgnoreNew `
-AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName "saved-worker" -Action $action -Trigger $trigger `
-Settings $settings -User "SYSTEM" -RunLevel HighestStart-ScheduledTask -TaskName "saved-worker"
Get-ScheduledTaskInfo -TaskName "saved-worker"
Get-Content "$env:ProgramData\saved\worker.log" -Tail 20 -WaitFour of those settings are load-bearing:
| Setting | Why |
|---|---|
-ExecutionTimeLimit [TimeSpan]::Zero | The default kills a task after three days. The worker is meant to run indefinitely |
-MultipleInstances IgnoreNew | One process per worker credential. A second copy breaks runs |
-AllowStartIfOnBatteries | Without it a task does not start on battery power at all |
-AtStartup with -User SYSTEM | Runs whether or not anyone logs in |
SYSTEM authenticates to the network as the machine account. That is fine for a source
reached with a password in config.yaml, and not fine for one that expects a domain user.
For those, register with -User "DOMAIN\svc-saved" -Password $cred and grant that account
Log on as a batch job, then re-run the icacls line for it.
Or use a service wrapper
Task Scheduler restarts a crashed worker but gives you no service to query, no dependency ordering and no log rotation. WinSW and NSSM both wrap an executable as a real Windows service and handle the logging properly.
Whichever you use, two settings carry over unchanged: the working directory must be
%ProgramData%\saved, and the service account must be able to read config.yaml and reach
your sources.