#!/usr/bin/env bash # Updates the server (granstrm, from the srv repo) the way nixos-update updates this # desktop, from a throwaway worktree so the checkout you edit in is left alone. # Started from the "Server" half of the control centre's update tile in a kitty # window (installed as `server-update`, modules/home/packages.nix). # # 1. a temporary worktree of main # 2. nix flake update, committed there # 3. builds the server's system and the repo's VM tests (router, web, media), then # nvd's list of what changed against the system the server runs # 4. asks before going on; then pushes the commit, switches the server to it over # SSH (nixos-rebuild switch --target-host) and brings your checkout's main up # to date # 5. if the kernel changed, asks whether to reboot the server now. It is the # house router, so the internet drops while it reboots. # # If anything fails, the window stays open until you press Enter, and nothing on the # server is changed. set -E repo=${SRV_REPO:-$HOME/git/srv} host=${SRV_HOST:-granstrm} tmp="" step() { printf '\n\e[1;34m==> %s\e[0m\n' "$*"; } note() { printf '\e[1;33m%s\e[0m\n' "$*"; } wait_close() { read -rp "Press Enter to close. " _ || true; } die() { trap - ERR printf '\n\e[1;31m%s\e[0m\n' "$*" wait_close exit 1 } cleanup() { if [ -n "$tmp" ]; then git -C "$repo" worktree remove --force "$tmp" 2>/dev/null || true rm -rf "$tmp" tmp="" fi } trap 'die "The update failed (see above). The server was not changed."' ERR trap cleanup EXIT step "Fetching $repo" git -C "$repo" worktree prune git -C "$repo" fetch origin main git -C "$repo" merge-base --is-ancestor origin/main main || die "main in $repo is behind origin/main (or has diverged); pull first." base=$(git -C "$repo" rev-parse main) ahead=$(git -C "$repo" rev-list --count origin/main..main) dirty=$(git -C "$repo" status --porcelain) tmp=$(mktemp -d -t srv-update.XXXXXX) step "Creating a worktree of main in $tmp" git -C "$repo" worktree add --detach "$tmp" main cd "$tmp" || die "Could not enter $tmp." step "Updating flake inputs" nix flake update --commit-lock-file if [ "$(git rev-parse HEAD)" = "$base" ]; then printf '\nEverything is already up to date.\n' wait_close exit 0 fi step "Building the server's system" nixos-rebuild build --flake "$tmp#$host" checks=$(nix eval --raw .#checks.x86_64-linux --apply 'c: builtins.concatStringsSep " " (builtins.attrNames c)') step "Running the VM tests ($checks)" for c in $checks; do nix build --no-link ".#checks.x86_64-linux.$c" done step "What changes" git --no-pager log -1 --format=%b running=$(ssh "$host" readlink -f /run/current-system) # Usually still here from the last deploy; fetched from the server if not. nix path-info "$running" >/dev/null 2>&1 || nix copy --from "ssh-ng://$host" "$running" nvd diff "$running" "$tmp/result" echo [ -z "$dirty" ] || note "Uncommitted changes in $repo are not part of this update." [ "$ahead" -eq 0 ] || note "main has $ahead commit(s) not on GitHub yet; they are pushed and deployed too." read -rp "Commit, push and switch the server? [y/N] " answer || answer="" case $answer in [yY]*) ;; *) echo "Nothing was changed." sleep 1 exit 0 ;; esac step "Pushing" git push origin HEAD:main step "Switching the server" nixos-rebuild switch --flake "$tmp#$host" --target-host "$host" step "Updating main in $repo" commit=$(git rev-parse HEAD) if [ "$(git -C "$repo" symbolic-ref --quiet --short HEAD)" = main ]; then git -C "$repo" merge --ff-only "$commit" else git -C "$repo" update-ref refs/heads/main "$commit" "$base" fi cd "$repo" cleanup # The same test NixOS's own auto-upgrade uses: a new kernel or initrd only takes # effect after a reboot. # shellcheck disable=SC2016 # expanded on the server if ssh "$host" '[ "$(readlink /run/booted-system/{initrd,kernel,kernel-modules})" = "$(readlink /run/current-system/{initrd,kernel,kernel-modules})" ]'; then printf '\nDone. No reboot needed.\n' wait_close exit 0 fi echo note "The kernel changed; it is used after the server's next reboot." read -rp "Reboot the server now? The internet drops for a few minutes. [y/N] " answer || answer="" case $answer in [yY]*) step "Rebooting the server" ssh "$host" systemctl reboot || true ;; *) echo "Not rebooted. The new kernel is used after the next reboot." ;; esac wait_close