# Pull-based NixOS updates for hosts that can't be pushed to reliably. # CI builds the system closure on muffin (which Harmonia serves), then # records the output store path at /deploy/. On boot this # service fetches that path, pulls the closure from the binary cache, # sets it as the boot profile, and reboots into it. { pkgs, hostname, lib, ... }: let deploy-url = "https://nix-cache.sigkill.computer/deploy/${hostname}"; pull-update = pkgs.writeShellApplication { name = "pull-update"; runtimeInputs = with pkgs; [ pkgs.curl pkgs.coreutils pkgs.nix pkgs.systemd pkgs.util-linux ]; text = '' set -uo pipefail # wait for actual connectivity, not just networkd "up" for i in $(seq 1 30); do if curl -sf --max-time 5 "${deploy-url}" >/dev/null; then break fi echo "Waiting for network... ($i/30)" sleep 2 done STORE_PATH=$(curl -sf --max-time 30 "${deploy-url}" || true) if [ -z "$STORE_PATH" ]; then echo "Server unreachable or no deployment available, skipping" exit 0 fi CURRENT=$(readlink -f /nix/var/nix/profiles/system) if [ "$CURRENT" = "$STORE_PATH" ]; then echo "Already on latest configuration" exit 0 fi echo "Update available: $CURRENT -> $STORE_PATH" nix-store -r "$STORE_PATH" || { echo "Failed to fetch closure"; exit 1; } nix-env -p /nix/var/nix/profiles/system --set "$STORE_PATH" || { echo "Failed to set profile"; exit 1; } "$STORE_PATH/bin/switch-to-configuration" boot || { echo "Failed to install boot entry"; exit 1; } wall "System update installed. Rebooting in 10 seconds..." sleep 10 systemctl reboot ''; }; in { systemd.services.pull-update = { description = "Pull latest NixOS configuration from binary cache"; after = [ "network-online.target" ]; wants = [ "network-online.target" ]; wantedBy = [ "multi-user.target" ]; restartIfChanged = false; serviceConfig = { Type = "oneshot"; ExecStart = lib.getExe pull-update; }; }; }