Let the shell user install packages with pacman

The root user now owns the whole MSYS2 installation and can run pacman. The
login path the journey depends on - the sshd, the login shell zsh and cat - is
kept as a protected copy in C:\msys2\opt\kik that only SYSTEM and the
administrators may write, and the sshd runs as SYSTEM from there (the new
KiK sshd task; the msys2-sshd service is disabled). Replacing the user's own
/usr/bin/sshd therefore cannot touch the running server.

opt/kik is two levels below C:\msys2, so its runtime resolves the same
installation root and the login shell and the user's tools share one cygwin
runtime. A separate second installation does not work: two runtimes in one
process tree corrupt each other's file descriptors and pacman cannot lock its
database.

/etc/passwd and /etc/ssh are read-only to the user, /opt/kik is first on PATH
so cat README survives even if the user deletes their own /usr/bin/cat, and
the restore task rebuilds the protected copy and re-applies the permissions
every 30 minutes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Zebreus 2026-10-25 21:37:55 +01:00
commit b228fec07e

View file

@ -1,10 +1,19 @@
# Installs the scheduled task that puts the shell server's home directory back into shape every 30 minutes: the
# prompt, the home directory and the four text files. The task runs as SYSTEM, so the shell server's root user can
# neither stop it nor change the copies it restores from.
# Sets up the shell server so the website's root user can install packages with pacman, while the basic user
# journey - logging in with the key, the prompt, the home directory and the four text files - cannot be broken.
#
# The whole MSYS2 installation belongs to the root user: they may write everywhere and run pacman. The login path
# the journey depends on - the sshd, the login shell zsh and cat - is kept as a protected copy in C:\msys2\opt\kik
# that only SYSTEM and the administrators may write. The sshd runs as SYSTEM from that copy (the scheduled task
# "KiK sshd"), so replacing /usr/bin/sshd does not touch the running server. A second scheduled task,
# "KiK restore shell files", puts the home directory, the four texts and the protected copy back every 30 minutes.
#
# opt/kik works because its MSYS2 runtime resolves the same installation root (C:\msys2) as /usr/bin, so the login
# shell and the user's own tools share one cygwin runtime. A separate second installation cannot: two runtimes in
# one process tree break each other's file descriptors, and pacman then cannot even lock its database.
#
# Run this in an elevated PowerShell on the server, with this script and the ../resources directory next to it:
# powershell -ExecutionPolicy Bypass -File .\install_restore_task.ps1
# Remove the task and the copies again with:
# Remove the tasks and the protected copy again (the installation goes back to a normal cygrunsrv sshd) with:
# powershell -ExecutionPolicy Bypass -File .\install_restore_task.ps1 -Uninstall
[CmdletBinding()]
param(
@ -13,24 +22,40 @@ param(
)
$ErrorActionPreference = 'Stop'
$msys = 'C:\msys2'
$kik = "$msys\opt\kik"
$installDir = 'C:\ProgramData\kik-restore'
$copiesDir = Join-Path $installDir 'files'
$taskName = 'KiK restore shell files'
$binDir = Join-Path $installDir 'bin'
$sshdBat = Join-Path $installDir 'run-sshd.bat'
$restoreTask = 'KiK restore shell files'
$sshdTask = 'KiK sshd'
$service = 'msys2-sshd'
# The programs the journey needs, kept out of the user's reach. sshd-session and sshd-auth are the per-connection
# helpers OpenSSH 10 re-executes; zsh is the login shell; cat reads the four texts.
$loginBinaries = "$msys\usr\bin\sshd.exe", "$msys\usr\lib\ssh\sshd-session.exe", "$msys\usr\lib\ssh\sshd-auth.exe",
"$msys\usr\bin\zsh.exe", "$msys\usr\bin\cat.exe"
# The copies the task restores from and the script it runs both live in $installDir, which only SYSTEM and the
# administrators may read or write.
$restoreScript = @'
# Puts the shell server's home directory back into shape: the directory itself, the shell configuration that gives
# the prompt, the keys that let the website in, and the four text files. Runs as SYSTEM every 30 minutes; the shell
# server's root user can neither stop it nor change the copies it restores from.
# Puts the shell server back into shape and keeps the protected login path in place. Runs as SYSTEM every 30 minutes
# and at startup; the root user can neither stop it nor change the copies it restores from.
param([string]$HomeDir = 'C:\msys2\home\root')
$msys = 'C:\msys2'
$kik = "$msys\opt\kik"
$log = Join-Path $PSScriptRoot 'restore.log'
$copies = Join-Path $PSScriptRoot 'files\home'
$binDir = Join-Path $PSScriptRoot 'bin'
# The account of the shell server's root user, whose id the files in its home directory belong to.
$passwd = Get-Content 'C:\msys2\etc\passwd' -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
$passwd = Get-Content "$msys\etc\passwd" -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
$sid = if ($passwd) { ($passwd -split ':')[4].Split(',')[-1] }
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
function Write-Log($message) {
"$(Get-Date -Format s) $message" | Out-File -FilePath $log -Append
}
@ -47,6 +72,23 @@ function Remove-Anything($path) {
}
}
# A path that only SYSTEM and the administrators own. $userRights is what the root user may do: 'None' (the ssh keys
# and config), 'Read' (the account database) or 'ReadAndExecute' (the login programs it has to run).
function Set-LockedAcl($path, $forDirectory, $userRights) {
$acl = Get-Acl -LiteralPath $path
$acl.SetAccessRuleProtection($true, $false)
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
$acl.SetOwner($admins)
$inherit = if ($forDirectory) { 'ContainerInherit,ObjectInherit' } else { 'None' }
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($system, 'FullControl', $inherit, 'None', 'Allow')))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($admins, 'FullControl', $inherit, 'None', 'Allow')))
if ($userRights -ne 'None' -and $sid) {
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($user, $userRights, $inherit, 'None', 'Allow')))
}
Set-Acl -LiteralPath $path -AclObject $acl
}
# The website logs in with a key in the authorized_keys of the user. The user may keep keys of their own, so only
# the key of the website is put back.
function Restore-Keys($target, $source) {
@ -65,11 +107,9 @@ $readOnly = '.zshrc', '.zshenv', 'authorized_keys'
# The permissions of the home directory and of the files in it that have to stay: the user may read them and write
# the ones that may change, and delete their own files, but not the home directory itself and not the files the
# website needs. Only SYSTEM and the administrators own them, so the user cannot take the permissions away again.
function Set-ProtectedAcl($path, $forDirectory, $writable) {
function Set-HomeAcl($path, $forDirectory, $writable) {
if (-not $sid) { return }
try {
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
$acl = Get-Acl -LiteralPath $path
$acl.SetAccessRuleProtection($true, $false)
@ -77,16 +117,15 @@ function Set-ProtectedAcl($path, $forDirectory, $writable) {
$acl.SetOwner($admins)
$none = [System.Security.AccessControl.InheritanceFlags]::None
$children = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
$noPropagate = [System.Security.AccessControl.PropagationFlags]::None
$inheritOnly = [System.Security.AccessControl.PropagationFlags]::InheritOnly
$full = [System.Security.AccessControl.FileSystemRights]::FullControl
$some = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute -bor [System.Security.AccessControl.FileSystemRights]::Write
$read = [System.Security.AccessControl.FileSystemRights]::ReadAndExecute
$inherit = if ($forDirectory) { $children } else { $none }
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $system, $full, $inherit, $noPropagate, 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $admins, $full, $inherit, $noPropagate, 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $system, $full, $inherit, 'None', 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $admins, $full, $inherit, 'None', 'Allow'))
$userRights = if ($writable) { $some } else { $read }
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $userRights, $none, $noPropagate, 'Allow'))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $userRights, $none, 'None', 'Allow'))
if ($forDirectory) {
# the files in it are the user's own, they may delete them
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $full, $children, $inheritOnly, 'Allow'))
@ -101,8 +140,46 @@ function Set-ProtectedAcl($path, $forDirectory, $writable) {
# gets MSYS2's default files from /etc/skel, which MSYS2 itself only copies into a home that does not exist yet.
function New-Home($path) {
New-Item -ItemType Directory -Path $path | Out-Null
Set-ProtectedAcl $path $true $true
Get-ChildItem -LiteralPath 'C:\msys2\etc\skel' -Force | Copy-Item -Destination $path -Recurse -Force
Set-HomeAcl $path $true $true
Get-ChildItem -LiteralPath "$msys\etc\skel" -Force | Copy-Item -Destination $path -Recurse -Force
}
try {
# The protected login path: the copy the sshd and the login shell run from. The user cannot write it, so even
# after they replace or remove their own copies in /usr/bin the server and the journey keep working. The trusted
# originals are in bin\; anything missing from opt/kik is put back.
if (Test-Path -LiteralPath $binDir) {
if (-not (Test-Path -LiteralPath $kik)) { New-Item -ItemType Directory -Path $kik | Out-Null }
foreach ($file in Get-ChildItem -LiteralPath $binDir -File -Force) {
$target = Join-Path $kik $file.Name
if (-not (Test-Path -LiteralPath $target) -or (Get-Item -LiteralPath $target -Force).Length -ne $file.Length) {
Copy-Item -LiteralPath $file.FullName -Destination $target -Force
Write-Log "restored opt/kik/$($file.Name)"
}
}
New-Item -ItemType Directory -Force -Path "$kik\dev\shm", "$kik\dev\mqueue" | Out-Null
Set-LockedAcl $kik $true 'ReadAndExecute'
# /dev is scratch: the user may write it so POSIX shared memory works, the rest of opt/kik stays read-only
if ($sid) { icacls "$kik\dev" /grant "*${sid}:(OI)(CI)M" | Out-Null }
}
# The account database and the ssh keys and config the SYSTEM server reads: the user may read passwd (programs
# map ids through it) but change none of it, and has no business in /etc/ssh at all.
$rootShell = '/opt/kik/zsh.exe'
$pw = "$msys\etc\passwd"
$lines = Get-Content $pw
if ($lines | Where-Object { $_ -match '^root:' -and $_ -notmatch ':/opt/kik/zsh\.exe$' }) {
($lines | ForEach-Object { if ($_ -match '^root:') { $_ -replace ':[^:]*$', ":$rootShell" } else { $_ } }) | Set-Content $pw -Encoding ascii
Write-Log 'set the root login shell to the protected zsh'
}
Set-LockedAcl $pw $false 'Read'
if (Test-Path "$msys\etc\ssh") { Set-LockedAcl "$msys\etc\ssh" $true 'None' }
# The whole installation otherwise belongs to the user, so pacman can write it. The home directory keeps its own
# protected permissions because they are set without inheritance.
if ($sid) { icacls $msys /grant "*${sid}:(OI)(CI)F" | Out-Null }
} catch {
Write-Log "the protected login path is not fully in place: $_"
}
if (-not (Test-Path -LiteralPath $HomeDir)) {
@ -113,7 +190,7 @@ if (-not (Test-Path -LiteralPath $HomeDir)) {
New-Home $HomeDir
Write-Log "replaced the link $HomeDir"
} else {
Set-ProtectedAcl $HomeDir $true $true
Set-HomeAcl $HomeDir $true $true
}
foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
@ -124,7 +201,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
Remove-Anything $dir
}
if (-not (Test-Path -LiteralPath $dir)) { New-Item -ItemType Directory -Path $dir | Out-Null }
Set-ProtectedAcl $dir $true $true
Set-HomeAcl $dir $true $true
if (Test-Path -LiteralPath $target) {
$item = Get-Item -LiteralPath $target -Force
if ($item.PSIsContainer -or $item.Attributes -band [IO.FileAttributes]::ReparsePoint) {
@ -140,7 +217,7 @@ foreach ($copy in Get-ChildItem -LiteralPath $copies -Recurse -File -Force) {
} else {
Copy-Item -LiteralPath $copy.FullName -Destination $target -Force
}
Set-ProtectedAcl $target $false ($readOnly -notcontains $copy.Name)
Set-HomeAcl $target $false ($readOnly -notcontains $copy.Name)
# the user may have made the file read-only for themselves, the four texts are theirs to change
(Get-Item -LiteralPath $target -Force).IsReadOnly = $false
Write-Log "restored $($copy.Name)"
@ -154,39 +231,90 @@ if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdent
throw 'Run this in an elevated PowerShell.'
}
$system = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-18')
$admins = New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-544')
$passwd = Get-Content "$msys\etc\passwd" -ErrorAction SilentlyContinue | Where-Object { $_ -like 'root:*' }
if (-not $passwd) { throw "No root user in $msys\etc\passwd." }
$sid = ($passwd -split ':')[4].Split(',')[-1]
# A previous install may have locked opt/kik, the account database and the ssh keys. The administrators keep
# full control over them, so reset the permissions to inherited now and let this run write freely; the restore
# task locks everything again at the end.
$prevEA = $ErrorActionPreference; $ErrorActionPreference = 'SilentlyContinue'
foreach ($p in "$msys\opt\kik", "$msys\etc\ssh", "$msys\etc\passwd") {
if (Test-Path -LiteralPath $p) { icacls $p /reset /t /q | Out-Null }
}
Get-ChildItem "$msys\etc\ssh", "$msys\etc\passwd" -Force | ForEach-Object { try { $_.IsReadOnly = $false } catch {} }
$ErrorActionPreference = $prevEA
function Set-LockedAcl($path, $forDirectory, $userRights) {
$acl = Get-Acl -LiteralPath $path
$acl.SetAccessRuleProtection($true, $false)
foreach ($rule in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($rule) }
$acl.SetOwner($admins)
$inherit = if ($forDirectory) { 'ContainerInherit,ObjectInherit' } else { 'None' }
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($system, 'FullControl', $inherit, 'None', 'Allow')))
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($admins, 'FullControl', $inherit, 'None', 'Allow')))
if ($userRights -ne 'None') {
$user = New-Object System.Security.Principal.SecurityIdentifier($sid)
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($user, $userRights, $inherit, 'None', 'Allow')))
}
Set-Acl -LiteralPath $path -AclObject $acl
}
# The DLLs an MSYS2 program loads, as Windows paths under C:\msys2.
function Get-Libraries($program) {
$paths = @()
$posix = ($program -replace '^C:\\msys2', '') -replace '\\', '/'
foreach ($line in & "$msys\usr\bin\ldd.exe" $posix 2>$null) {
foreach ($m in [regex]::Matches($line, '/usr/[^\s]+\.dll')) { $paths += $m.Value }
}
$paths | Select-Object -Unique | ForEach-Object { $msys + ($_ -replace '/', '\') }
}
if ($Uninstall) {
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
foreach ($t in $restoreTask, $sshdTask) { Unregister-ScheduledTask -TaskName $t -Confirm:$false -ErrorAction SilentlyContinue }
if (Test-Path -LiteralPath $installDir) { Remove-Item -LiteralPath $installDir -Recurse -Force }
Write-Host "Removed the task and $installDir."
if (Test-Path -LiteralPath $kik) {
icacls $kik /reset /t /q 2>$null | Out-Null
Remove-Item -LiteralPath $kik -Recurse -Force -ErrorAction SilentlyContinue
}
# the installation's own cygrunsrv sshd takes over again
$pw = "$msys\etc\passwd"
(Get-Content $pw | ForEach-Object { if ($_ -match '^root:') { $_ -replace ':[^:]*$', ':/usr/bin/zsh' } else { $_ } }) | Set-Content $pw -Encoding ascii
icacls $pw /reset | Out-Null
if (Test-Path "$msys\etc\ssh") { icacls "$msys\etc\ssh" /reset /t /q 2>$null | Out-Null }
Set-Service $service -StartupType Automatic -ErrorAction SilentlyContinue
Start-Service $service -ErrorAction SilentlyContinue
Write-Host "Removed the tasks and $installDir; the $service service serves ssh again."
return
}
$resources = Join-Path $PSScriptRoot '..\resources'
if (-not (Get-ChildItem -LiteralPath $resources -Recurse -File -Force)) { throw "No files in $resources." }
# The root user may have prepared the directory or the file names, so nothing that looks like a link is used.
$prepared = Get-Item -LiteralPath $installDir -Force -ErrorAction SilentlyContinue
if ($prepared -and $prepared.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "$installDir is a link, refusing to install." }
New-Item -ItemType Directory -Force -Path $copiesDir | Out-Null
New-Item -ItemType Directory -Force -Path $copiesDir, $binDir | Out-Null
# Only SYSTEM and the administrators may read or write the copies and the script.
# Only SYSTEM and the administrators may read or write the copies, the trusted login programs and the scripts.
$acl = New-Object System.Security.AccessControl.DirectorySecurity
$acl.SetAccessRuleProtection($true, $false)
foreach ($sid in 'S-1-5-18', 'S-1-5-32-544') {
$identity = New-Object System.Security.Principal.SecurityIdentifier $sid
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $identity, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))
foreach ($owner in $system, $admins) {
$acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule($owner, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')))
}
$acl.SetOwner((New-Object System.Security.Principal.SecurityIdentifier 'S-1-5-32-544'))
$acl.SetOwner($admins)
Set-Acl -LiteralPath $installDir -AclObject $acl
# Files the root user prepared are removed, the copies are written fresh and inherit the restricted permissions.
foreach ($item in Get-ChildItem -LiteralPath $copiesDir -Recurse -Force) {
foreach ($item in Get-ChildItem -LiteralPath $installDir -Recurse -Force) {
if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "$($item.FullName) is a link, refusing to install." }
}
Remove-Item -LiteralPath $copiesDir -Recurse -Force
New-Item -ItemType Directory -Force -Path $copiesDir | Out-Null
Copy-Item -Path (Join-Path $resources '*') -Destination $copiesDir -Recurse -Force
# The Administrator logs in with the key of the people who deploy, which is in the resources. The keys that are
# already there stay.
$adminKeys = 'C:\Users\Administrator\.ssh\authorized_keys'
@ -199,23 +327,65 @@ if (-not ($keys | Where-Object { ($_ -split '\s+')[1] -eq ($required[0] -split '
}
[IO.File]::WriteAllText((Join-Path $installDir 'restore.ps1'), $restoreScript)
# The shell configuration used to be in the home directory, the shell reads it from ~/.zsh now.
if (Test-Path -LiteralPath (Join-Path $HomeDir '.zsh\.zshrc')) {
Remove-Item -LiteralPath (Join-Path $HomeDir '.zshrc') -Force -ErrorAction SilentlyContinue
# The trusted login programs: the sshd, its helpers, the login shell and cat, with every DLL they load. The restore
# task puts these into opt/kik, which the user cannot write.
Remove-Item "$binDir\*" -Force -ErrorAction SilentlyContinue
$files = @{}
foreach ($b in $loginBinaries) {
if (-not (Test-Path -LiteralPath $b)) { throw "$b does not exist." }
$files[$b] = $true
foreach ($lib in Get-Libraries $b) { $files[$lib] = $true }
}
foreach ($f in $files.Keys) { Copy-Item -LiteralPath $f -Destination $binDir -Force }
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`""
$triggers = @(
# The sshd reads its configuration and finds its helpers in opt/kik; nothing here depends on the user's /usr.
$sshdConfig = "$msys\etc\ssh\sshd_config"
Set-Content -LiteralPath $sshdConfig -Encoding ascii -Value @(
'# The website logs in as root, the deployers as Administrator. Both come in over the websocket bridge.',
'Port 22',
'AllowUsers root Administrator',
'# Files are protected by Windows ACLs; under MSYS2''s noacl mounts the POSIX modes sshd checks are fake.',
'StrictModes no',
'Subsystem sftp internal-sftp',
'# The server and its per-connection helpers are the protected copies, not the ones the user can replace.',
'SshdSessionPath /opt/kik/sshd-session.exe',
'SshdAuthPath /opt/kik/sshd-auth.exe'
)
# The sshd runs as SYSTEM from the protected copy, with opt/kik first on the path so its own DLLs and the session's
# programs (zsh, cat) are the protected ones. The log stays in the install directory, out of the user's reach.
Set-Content -LiteralPath $sshdBat -Encoding ascii -Value @(
'@echo off',
'set PATH=C:\msys2\opt\kik;C:\msys2\usr\bin;%PATH%',
'C:\msys2\opt\kik\sshd.exe -D -e > C:\ProgramData\kik-restore\sshd.log 2>&1'
)
# The installation's own cygrunsrv sshd service would fight for port 22; the scheduled task owns the sshd instead.
Set-Service $service -StartupType Disabled -ErrorAction SilentlyContinue
Stop-Service $service -Force -ErrorAction SilentlyContinue
$atSystem = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
$sshdAction = New-ScheduledTaskAction -Execute $sshdBat
$sshdSettings = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Seconds 0) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName $sshdTask -Action $sshdAction -Trigger (New-ScheduledTaskTrigger -AtStartup) -Principal $atSystem -Settings $sshdSettings -Force | Out-Null
$restoreAction = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -HomeDir `"$HomeDir`""
$restoreTriggers = @(
New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 30) -RepetitionDuration (New-TimeSpan -Days 3650)
New-ScheduledTaskTrigger -AtStartup
)
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $triggers -Principal $principal -Settings $settings -Force | Out-Null
$restoreSettings = New-ScheduledTaskSettingsSet -StartWhenAvailable -MultipleInstances IgnoreNew -ExecutionTimeLimit (New-TimeSpan -Minutes 10) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 5) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName $restoreTask -Action $restoreAction -Trigger $restoreTriggers -Principal $atSystem -Settings $restoreSettings -Force | Out-Null
Start-ScheduledTask -TaskName $taskName
Start-Sleep -Seconds 5
Write-Host "Task '$taskName' runs as SYSTEM every 30 minutes and at startup."
Get-ScheduledTaskInfo -TaskName $taskName | Format-List LastRunTime, LastTaskResult, NextRunTime
Get-Content (Join-Path $installDir 'restore.log') -Tail 8 -ErrorAction SilentlyContinue
icacls $installDir
# The restore task builds opt/kik and sets every permission; run it before starting the sshd that depends on it.
Start-ScheduledTask -TaskName $restoreTask
Start-Sleep -Seconds 6
$waited = 0
while ((Get-ScheduledTask -TaskName $restoreTask).State -eq 'Running' -and $waited -lt 60) { Start-Sleep 2; $waited += 2 }
Start-ScheduledTask -TaskName $sshdTask
Start-Sleep -Seconds 4
Write-Host "The sshd runs as SYSTEM from C:\msys2\opt\kik (task '$sshdTask'); '$restoreTask' keeps it and the home directory in shape."
Get-Content (Join-Path $installDir 'restore.log') -Tail 10 -ErrorAction SilentlyContinue
Get-CimInstance Win32_Process -Filter "Name='sshd.exe'" | ForEach-Object { $_.CommandLine }