From 54607982cfbd63ae15a5daa97799bc8008ab91cf Mon Sep 17 00:00:00 2001 From: Zebreus Date: Sun, 25 Oct 2026 12:47:26 +0100 Subject: [PATCH] Sort the server directory and add the restore task installer The README and Code of Conduct files move to server/resources, the scripts that build and deploy them to server/scripts. The new install_restore_task.ps1 installs a scheduled task as SYSTEM that puts the files back every 30 minutes, so the shell server's root user can neither stop it nor change the copies it restores from. Co-Authored-By: Claude Code --- server/README.md | 23 +++++ server/{ => resources}/CODE_OF_CONDUCT | 0 server/{ => resources}/CODE_OF_CONDUCT.en | 0 server/{ => resources}/README | 0 server/{ => resources}/README.en | 0 server/scripts/install_restore_task.ps1 | 101 ++++++++++++++++++++++ server/{ => scripts}/make_files.py | 7 +- 7 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 server/README.md rename server/{ => resources}/CODE_OF_CONDUCT (100%) rename server/{ => resources}/CODE_OF_CONDUCT.en (100%) rename server/{ => resources}/README (100%) rename server/{ => resources}/README.en (100%) create mode 100644 server/scripts/install_restore_task.ps1 rename server/{ => scripts}/make_files.py (99%) diff --git a/server/README.md b/server/README.md new file mode 100644 index 0000000..dc3cc87 --- /dev/null +++ b/server/README.md @@ -0,0 +1,23 @@ +# The shell server + +The website's terminal connects to a shell server, whose root user logs in at `wss://web.nowhere.wirs.ing`. + +## Layout + +- `resources/` — the files in the root user's home directory: `README`, `README.en`, `CODE_OF_CONDUCT` and + `CODE_OF_CONDUCT.en`. +- `scripts/make_files.py` — builds the resources, the website's pages as terminal text. +- `scripts/install_restore_task.ps1` — installs the scheduled task that puts the resources back every 30 minutes. + +## Rebuilding the resources + + python3 server/scripts/make_files.py + +## Installing the restore task on the server + +Copy `server/scripts` and `server/resources` to the server and run this in an elevated PowerShell: + + powershell -ExecutionPolicy Bypass -File .\scripts\install_restore_task.ps1 + +The task runs as SYSTEM, so the root user can neither stop it nor change the copies it restores from. It also +runs at startup. Remove it again with `-Uninstall`. diff --git a/server/CODE_OF_CONDUCT b/server/resources/CODE_OF_CONDUCT similarity index 100% rename from server/CODE_OF_CONDUCT rename to server/resources/CODE_OF_CONDUCT diff --git a/server/CODE_OF_CONDUCT.en b/server/resources/CODE_OF_CONDUCT.en similarity index 100% rename from server/CODE_OF_CONDUCT.en rename to server/resources/CODE_OF_CONDUCT.en diff --git a/server/README b/server/resources/README similarity index 100% rename from server/README rename to server/resources/README diff --git a/server/README.en b/server/resources/README.en similarity index 100% rename from server/README.en rename to server/resources/README.en diff --git a/server/scripts/install_restore_task.ps1 b/server/scripts/install_restore_task.ps1 new file mode 100644 index 0000000..9c4469d --- /dev/null +++ b/server/scripts/install_restore_task.ps1 @@ -0,0 +1,101 @@ +# Installs the scheduled task that puts the shell server's README and Code of Conduct files back every 30 +# minutes. The task runs as SYSTEM, so the shell server's root user can neither stop it nor change the copies it +# restores from. +# +# 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: +# powershell -ExecutionPolicy Bypass -File .\install_restore_task.ps1 -Uninstall +[CmdletBinding()] +param( + [string]$Target = 'C:\msys2\home\root', + [switch]$Uninstall +) + +$ErrorActionPreference = 'Stop' +$installDir = 'C:\ProgramData\kik-restore' +$taskName = 'KiK restore shell files' +$files = 'README', 'README.en', 'CODE_OF_CONDUCT', 'CODE_OF_CONDUCT.en' + +# 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 README and Code of Conduct files back from the copies next to this script. +# Runs as SYSTEM every 30 minutes; the shell server's root user can neither stop it nor change the copies. +param([string]$Target = 'C:\msys2\home\root') +$log = Join-Path $PSScriptRoot 'restore.log' + +foreach ($name in @('README', 'README.en', 'CODE_OF_CONDUCT', 'CODE_OF_CONDUCT.en')) { + $path = Join-Path $Target $name + try { + if (Test-Path -LiteralPath $path) { + # the root user may have replaced the file or locked everyone else out + takeown /f $path /a | Out-Null + icacls $path /reset | Out-Null + if ((Get-Item -LiteralPath $path -Force).PSIsContainer) { Remove-Item -LiteralPath $path -Recurse -Force } + } + Copy-Item -LiteralPath (Join-Path $PSScriptRoot $name) -Destination $path -Force + "$(Get-Date -Format s) restored $name" | Out-File -FilePath $log -Append + } catch { + "$(Get-Date -Format s) failed $name : $_" | Out-File -FilePath $log -Append + } +} +'@ + +if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { + throw 'Run this in an elevated PowerShell.' +} + +if ($Uninstall) { + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue + if (Test-Path -LiteralPath $installDir) { Remove-Item -LiteralPath $installDir -Recurse -Force } + Write-Host "Removed the task and $installDir." + return +} + +$resources = Join-Path $PSScriptRoot '..\resources' +$missing = $files | Where-Object { -not (Test-Path -LiteralPath (Join-Path $resources $_)) } +if ($missing) { throw "Missing files in $resources : $($missing -join ', ')" } + +# 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 $installDir | Out-Null + +# Only SYSTEM and the administrators may read or write the copies and the script. +$acl = New-Object System.Security.AccessControl.DirectorySecurity +$acl.SetAccessRuleProtection($true, $false) +foreach ($sid in 'S-1-5-18', 'S-1-5-32-544') { + $acl.AddAccessRule((New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $sid, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')) +} +$acl.SetOwner((New-Object System.Security.Principal.SecurityIdentifier 'S-1-5-32-544')) +Set-Acl -LiteralPath $installDir -AclObject $acl + +# Files the root user prepared are removed, the copies are written fresh and inherit the restricted permissions. +foreach ($name in $files + 'restore.ps1') { + $path = Join-Path $installDir $name + if (Test-Path -LiteralPath $path) { + $item = Get-Item -LiteralPath $path -Force + if ($item.Attributes -band [IO.FileAttributes]::ReparsePoint) { throw "$path is a link, refusing to install." } + Remove-Item -LiteralPath $path -Recurse -Force + } +} +Copy-Item -Path (Join-Path $resources '*') -Destination $installDir -Force +[IO.File]::WriteAllText((Join-Path $installDir 'restore.ps1'), $restoreScript) + +$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -NonInteractive -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$installDir\restore.ps1`" -Target `"$Target`"" +$triggers = @( + 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 + +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 4 -ErrorAction SilentlyContinue +icacls $installDir diff --git a/server/make_files.py b/server/scripts/make_files.py similarity index 99% rename from server/make_files.py rename to server/scripts/make_files.py index 6822a7a..db3209d 100644 --- a/server/make_files.py +++ b/server/scripts/make_files.py @@ -1,6 +1,6 @@ -# Builds README, README.en, CODE_OF_CONDUCT and CODE_OF_CONDUCT.en next to this script: the website's pages as +# Builds README, README.en, CODE_OF_CONDUCT and CODE_OF_CONDUCT.en in ../resources: the website's pages as # terminal text with escape sequences, for the home directory of the shell server's root user. -# Usage: python3 server/make_files.py +# Usage: python3 server/scripts/make_files.py import pathlib import textwrap @@ -329,7 +329,8 @@ COC_EN = { 'footer': [('Home', f'{SITE}en.html'), ('Legal notice', IMPRESSUM)], } -out = pathlib.Path(__file__).parent +out = pathlib.Path(__file__).parent.parent / 'resources' +out.mkdir(exist_ok=True) for name, text in [('README', readme(DE)), ('README.en', readme(EN)), ('CODE_OF_CONDUCT', code_of_conduct(COC_DE)), ('CODE_OF_CONDUCT.en', code_of_conduct(COC_EN))]: (out / name).write_text(text, encoding='utf-8')