kik-website/server/scripts/deploy.sh
Zebreus b142a1c5d2 Rebuild the shell server from a pristine installation it cannot break
On 6 Nov a visitor deleted the whole MSYS2 installation, the locked
/etc/passwd included, and the site was down for a week: the cleanup job
repaired the runtime with pacman, which was gone too, and never tried to
start the server again because a port-22 query without results counted as
a failed query.

The installer now builds a pristine installation from the official base
archive (hash pinned) and the site's packages in the administrators'
folder. Everything the SYSTEM server trusts comes from there and is kept
in place and locked by the cleanup job: opt/kik, the runtime, passwd,
group, nsswitch.conf, fstab, fstab.d and etc/ssh. The user gets modify
rather than full control, so locked files can no longer be deleted, and
the folders leading to them cannot be renamed. When pacman is gone the
job copies the missing files back from the pristine installation.

Nothing that runs as SYSTEM or Administrator runs a file the user can
write any more: the job ran /usr/bin/pacman.exe as SYSTEM, the installer
ran ldd and pacman from the installation, the Administrator's shell was
a cmdshell.exe in /usr/local/bin, and the Administrator's sessions
searched /usr/bin for programs. The Administrator's shell is PowerShell
now, and deploy.sh speaks it.

server/scripts/rdp.sh opens a headless remote desktop session for when
SSH is down. The shell finds the CLANG64 programs and runs helix through
winpty.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-11-14 02:19:41 +01:00

66 lines
3 KiB
Shell
Executable file

#!/usr/bin/env bash
# Deploys the cleanup job to the shell server and installs it there.
#
# server/scripts/deploy.sh [-i KEYFILE] [-h HOST] [--uninstall]
#
# The job runs on the box as SYSTEM, never as the root user. The Administrator account sets it up; its shell is
# PowerShell, so the commands we run there are PowerShell commands.
#
# The Administrator logs in with the personal key of the deployer, from the agent or from -i. The key of the website
# never gets administrator rights: it only logs in as the root user.
set -euo pipefail
HOST=nowhere.wirs.ing
USER=Administrator
KEYFILE=''
UNINSTALL=0
while [ $# -gt 0 ]; do
case $1 in
-i) KEYFILE=$2; shift 2 ;;
-h) HOST=$2; shift 2 ;;
--uninstall) UNINSTALL=1; shift ;;
*) echo "usage: $(basename "$0") [-i KEYFILE] [-h HOST] [--uninstall]" >&2; exit 2 ;;
esac
done
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
server="$repo/server"
remote='C:\Users\Administrator\kik' # where the installer is put, in the home of the Administrator
remotePath='/c/Users\Administrator\kik' # the same place, as the file transfer sees it
temporary=()
# The host key is the one the website pins, so that the deployment cannot be intercepted.
expected=$(sed -n "s/.*const HOST_KEY = '\([^']*\)'.*/\1/p" "$repo/src/scripts/terminal.ts")
known=$(mktemp)
temporary+=("$known")
trap 'rm -f "${temporary[@]}"' EXIT
ssh-keyscan -t ed25519 "$HOST" > "$known" 2>/dev/null
actual=$(ssh-keygen -lf "$known" | awk '{print $2}')
if [ "$actual" != "$expected" ]; then
echo "the host key of $HOST is $actual, but $expected is expected" >&2
exit 1
fi
# The site logs in with the first key in the authorized_keys of the user on the box, so it has to be the right one.
sitekey=$(ssh-keygen -lf "$server/resources/home/.ssh/authorized_keys" | awk 'NR==1 {print $2}')
if [ "$sitekey" != 'SHA256:KUEv4d4+75h41l0OGTWCA2FbtEOQZ047ujkeh+kkFW0' ]; then
echo "the key of the website is not the first one in server/resources/home/.ssh/authorized_keys" >&2
exit 1
fi
options=(-o "UserKnownHostsFile=$known" -o StrictHostKeyChecking=yes)
if [ -n "$KEYFILE" ]; then options+=(-i "$KEYFILE"); fi
ssh=(ssh "${options[@]}" "$USER@$HOST")
scp=(scp "${options[@]}")
if [ "$UNINSTALL" = 1 ]; then
"${ssh[@]}" "if (Test-Path $remote\\scripts\\install_restore_task.ps1) { powershell -NoProfile -ExecutionPolicy Bypass -File $remote\\scripts\\install_restore_task.ps1 -Uninstall } else { 'Nothing is installed.' }"
exit
fi
# The installer looks for the resources next to itself, so both directories go to the server. What was there
# before is removed, so that files that are gone from the repository do not stay on the box.
"${ssh[@]}" "Remove-Item -Recurse -Force -ErrorAction SilentlyContinue $remote; New-Item -ItemType Directory $remote | Out-Null"
"${scp[@]}" -r "$server/scripts" "$server/resources" "$USER@$HOST:$remotePath/"
"${ssh[@]}" "powershell -NoProfile -ExecutionPolicy Bypass -File $remote\\scripts\\install_restore_task.ps1"