nixos: describe asxpio-services as a NixOS system

asxpio-services is to move from Debian with Docker to NixOS with
Podman. This is the target system; nothing is switched over yet.

One flake holds what was spread over cloud-init, Ansible roles and
hand-made units: network and WireGuard, users and SSH restrictions,
firewall, the weekly image prune, and the four container stacks
(Paper and Fossils Minecraft, SillyTavern, the Forgejo runner), each
as a systemd unit under Podman. The runner keeps running as a
container and starts jobs through Podman's Docker-compatible socket.

Secrets (WireGuard keys, RCON and CurseForge keys) are encrypted with
sops for the admin's age key and the server's SSH host key, so the
repository holds no plaintext secret and CI needs none of them.

Containers that own data wait for /opt/.restored. On a freshly
installed disk a game server would otherwise generate a new world
exactly where the restore is about to land.

Tried on a throwaway server (.#rehearsal): install with nixos-anywhere
keeping a given host key, secret decryption, all stacks, a real Paper
server stopping with a full save, what CI jobs need from the engine
(short image names, the nix-store volume, service containers by name,
a kaniko build, the socket inside a job), an update over SSH and a
reboot.

migration/ has the scripts that move the data through object storage,
and a workflow builds the system on every push and switches the host
on main.

Assisted-by: Claude:fable-5-2
This commit is contained in:
Sergei Poljanski 2026-11-12 17:00:59 +04:00
commit 8d2125ae31
16 changed files with 1028 additions and 0 deletions

View file

@ -0,0 +1,57 @@
#!/usr/bin/env bash
# Runs on asxpio-services (Debian). Streams everything worth keeping to
# object storage as tar.zst archives, one per service, with checksums.
#
# backup-services.sh live copy while services run (rehearsal, timing)
# backup-services.sh final stop the game servers and the runner first
#
# Nothing is written to the local disk. Ownership is stored numerically so
# a restore on another OS keeps uid/gid.
set -euo pipefail
mode=${1:?usage: backup-services.sh live|final}
stamp=$(date -u +%Y%m%dT%H%M%SZ)
dest="asxpio-bucket-hel:asxpio-backups-main/asxpio-services/nixos-migration/$mode-$stamp"
# Hetzner needs the region set and rejects the bucket check rclone does
rc() { rclone --s3-region hel1 --s3-no-check-bucket --s3-chunk-size 64M "$@"; }
if [ "$mode" = final ]; then
echo "== stopping services"
docker exec minecraft-fossils rcon-cli save-all flush || true
docker stop -t 120 minecraft-fossils minecraft
docker stop -t 30 minecraft-fossils-wlsync sillytavern forgejo-runner
fi
sums=$(mktemp)
archive() { # name, then paths relative to /
local name=$1
shift
echo "== $name: $*"
# tar exits 1 when a file changed while being read. That is expected
# for a live copy of a running server and an error in a final one.
{ tar --numeric-owner --acls --xattrs -C / -cf - "$@" || { [ $? -eq 1 ] && [ "$mode" = live ]; }; } \
| zstd -T4 -3 \
| tee >(sha256sum | sed "s/-\$/$name.tar.zst/" >>"$sums") \
| rc rcat "$dest/$name.tar.zst"
}
archive minecraft opt/minecraft
archive minecraft-fossils opt/minecraft-fossils
archive sillytavern opt/sillytavern
archive forgejo-runner opt/forgejo-runner
# Not restored on NixOS; kept in case something in them is wanted later
archive legacy opt/immich opt/wordpress opt/traefik \
var/lib/docker/volumes/ppeu-wp_wordpress var/lib/docker/volumes/ppeu-wp_wordpress_db
archive system etc root home usr/local var/spool/cron
sleep 1
rc rcat "$dest/SHA256SUMS" <"$sums"
echo "== uploaded to $dest"
rc lsl "$dest"
echo "== verifying checksums against the uploaded objects"
while read -r sum name; do
got=$(rc cat "$dest/$name" | sha256sum | cut -d' ' -f1)
[ "$got" = "$sum" ] && echo "ok $name" || { echo "FAIL $name"; exit 1; }
done <"$sums"
rm -f "$sums"
echo "DEST=$dest"