From f2a9ab06552e5a4f1d0aa40df3ec45687eaa5ba3 Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Thu, 20 Mar 2025 14:44:12 -0500 Subject: [PATCH] feat: add upgrader --- flake.nix | 3 - lib/colmena/default.nix | 70 ---------------------- packages/default.nix | 1 + packages/upgrader/default.nix | 106 ++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 73 deletions(-) delete mode 100644 lib/colmena/default.nix create mode 100644 packages/upgrader/default.nix diff --git a/flake.nix b/flake.nix index 3e13211..4ad90d4 100644 --- a/flake.nix +++ b/flake.nix @@ -25,9 +25,6 @@ formatter = uGenPkgs (p: p.nixfmt-rfc-style); - colmena = import ./lib/colmena inputs; - colmenaHive = inputs.colmena.lib.makeHive inputs.self.outputs.colmena; - /* TODO: nix-on-droid for phone terminal usage? mobile-nixos? TODO: nix-darwin for work? diff --git a/lib/colmena/default.nix b/lib/colmena/default.nix deleted file mode 100644 index f80ca61..0000000 --- a/lib/colmena/default.nix +++ /dev/null @@ -1,70 +0,0 @@ -{ - home-manager, - nixpkgs-unstable, - self, - ... -}@inputs: -{ - meta = - let - nixpkgsSet = - nixpkgs: - (import nixpkgs { - system = "x86_64-linux"; - overlays = [ self.outputs.flakeLib.forSelfOverlay ]; - }); - nixpkgs = nixpkgsSet nixpkgs-unstable; - stable = nixpkgsSet nixpkgs; - in - { - inherit nixpkgs; - nodeNixpkgs = { - # router = stable; - beefcake = stable; - }; - specialArgs = { - inherit home-manager; - hardware = inputs.hardware.outputs.nixosModules; - diskoConfigurations = inputs.self.outputs.diskoConfigurations; - }; - }; - - # TODO: setup builders? - foxtrot = - { - # name, - # nodes, - # pkgs, - ... - }: - { - deployment = { - # Allow local deployment with `colmena apply-local` - allowLocalDeployment = true; - - # Disable SSH deployment. This node will be skipped in a - # normal`colmena apply`. - targetHost = null; - }; - - imports = [ - inputs.self.outputs.nixosModules.default - (import ./../../packages/hosts/foxtrot.nix) - ]; - - # boot.isContainer = true; - # time.timeZone = nodes.host-b.config.time.timeZone; - }; - beefcake = - { ... }: - { - deployment = { - buildOnTarget = true; - }; - - imports = [ - inputs.self.outputs.nixosModules.default - (import ./../../packages/hosts/beefcake.nix) - ]; - }; -} diff --git a/packages/default.nix b/packages/default.nix index 8ec7afe..01fa1da 100644 --- a/packages/default.nix +++ b/packages/default.nix @@ -7,6 +7,7 @@ in iosevka = pkgs.callPackage ./iosevka.nix { }; iosevkaLyteTermSubset = pkgs.callPackage ./iosevkaLyteTermSubset.nix { }; installer = pkgs.callPackage ./installer.nix { }; + upgrader = pkgs.callPackage ./upgrader { }; ghostty-terminfo = pkgs.callPackage ./ghostty-terminfo.nix { }; forgejo-actions-container = pkgs.callPackage ./forgejo-actions-container.nix { }; } diff --git a/packages/upgrader/default.nix b/packages/upgrader/default.nix new file mode 100644 index 0000000..cb5aa7b --- /dev/null +++ b/packages/upgrader/default.nix @@ -0,0 +1,106 @@ +{ + pkgs, + ... +}: +pkgs.writeShellApplication { + name = "upgrader"; + runtimeInputs = with pkgs; [ + ripgrep + fzf + jq + gawk + ]; + text = '' + CHECK_TIMEOUT=120 + + usage() { + exit_code="''${1:-0}" + echo "upgrader TARGET_HOST" + echo " -h,--help Show help" + echo " --wait-and-rollback GEN Wait CHECK_TIMEOUT seconds and rollback to the boot generation [REMOTE]" + echo " --ignore-lock Ignore locks" + exit "$exit_code" + } + + ilog() { + echo "$1" >> .upgrader.log + } + + wait_and_rollback() { + [[ $# -lt 1 ]] && { printf "error: no GEN argument provided for --wait-and-rollback\n" >&2; usage 1; } + gen="$1" + shift + while ! pgrep nixos-rebuild &>/dev/null; do + sleep 1; + done + while ! pgrep nixos-rebuild &>/dev/null; do + sleep 1 + done + for i in $(seq 0 '"$CHECK_TIMEOUT"'; do + sleep 1 + done + sudo nix-env --switch-generation -p /nix/var/nix/profiles/system + sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch + exit + } + + [[ $# -lt 1 ]] && { printf "error: no TARGET_HOST argument provided\n" >&2; usage 1; } + target_host="$1" + shift + + ignore_locks=0 + + while [ "$#" -gt 0 ]; do + case "$1" in + --wait-and-rollback) + wait_and_rollback + --ignore-locks) + ignore_locks=1 + break + ;; + -h | --help | help) + usage 0 + ;; + *) + echo "error: unexpected argument '$1' (if you meant to pass this to the underlying command(s), use '-- $1')" + usage 1 + esac + shift + done + + ssh "$target_host" true + echo "SSH access to '$target_host' confirmed!" + + # TODO: install upgrader on remote host and know how to call it + + log() { + echo "$1" + ssh "$target_host" 'echo "$(date) $1" >> .upgrader.log' + } + + remote_bg_job() { + ssh "$target_host" 'nohup sh -c "( ( '"$1"' ) & )"' + } + + if [[ $ignore_locks == 0 ]] && ssh "$target_host" "stat .upgrader.lck &>/dev/null" &>/dev/null; then + log ".upgrader.lck exists on '$target_host', refusing to upgrade" + exit 1 + fi + + # TODO: handle boot-level failures? + + boot_gen="$(ssh "$target_host" "cat /boot/loader/loader.conf | rg 'default\s*nixos-generation-(\d+)\.conf' -r '\$1'")" + log "Current Boot Generation for '$target_host': $boot_gen" + + # TODO: start some kind of "waiter" + remote_bg_job 'remote_upgrader --wait-and-rollback' + + log "Upgrading '$target_host' (may require sudo prompt)..." + ssh -t "$target_host" "touch .upgrader.lck; sudo nixos-rebuild test --flake git+https://git.lyte.dev/lytedev/nix" + + ssh -t "$target_host" 'remote_upgrader --kill-waiter' + + log 'Promoting current configuration to boot generation (may require sudo prompt)...' + ssh -t "$target_host" 'sudo /run/current-system/bin/switch-to-configuration switch' + ''; +}