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 index f80ca61..a6f91ee 100644 --- a/lib/colmena/default.nix +++ b/lib/colmena/default.nix @@ -1,6 +1,5 @@ { home-manager, - nixpkgs-unstable, self, ... }@inputs: @@ -13,15 +12,15 @@ system = "x86_64-linux"; overlays = [ self.outputs.flakeLib.forSelfOverlay ]; }); - nixpkgs = nixpkgsSet nixpkgs-unstable; - stable = nixpkgsSet nixpkgs; + nixpkgs = nixpkgsSet inputs.nixpkgs-unstable; + stable = nixpkgsSet inputs.nixpkgs; in { inherit nixpkgs; - nodeNixpkgs = { - # router = stable; - beefcake = stable; - }; + # nodeNixpkgs = { + # # router = stable; + # beefcake = stable; + # }; specialArgs = { inherit home-manager; hardware = inputs.hardware.outputs.nixosModules; @@ -47,24 +46,16 @@ targetHost = null; }; - imports = [ - inputs.self.outputs.nixosModules.default - (import ./../../packages/hosts/foxtrot.nix) - ]; - # boot.isContainer = true; # time.timeZone = nodes.host-b.config.time.timeZone; - }; + } + // self.outputs.nixosConfigurations.foxtrot.config; beefcake = { ... }: { deployment = { buildOnTarget = true; }; - - imports = [ - inputs.self.outputs.nixosModules.default - (import ./../../packages/hosts/beefcake.nix) - ]; - }; + } + // self.outputs.nixosConfigurations.beefcake.config; } 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/updater.nix b/packages/updater.nix new file mode 100644 index 0000000..9f68903 --- /dev/null +++ b/packages/updater.nix @@ -0,0 +1,58 @@ +{ + pkgs, + ... +}: +pkgs.writeShellApplication { + name = "installer"; + runtimeInputs = with pkgs; [ + fzf + jq + gawk + ]; + text = '' + repo='https://git.lyte.dev/lytedev/nix' + if ! [[ -f flake.nix ]]; then + dir="$(mktemp -d)" + echo "No flake detected. Cloning '$repo' to '$dir/nix'" + cd "$dir" + git clone "$repo" + cd nix + fi + + read -r -p 'Swap Size:' swap_size + echo + read -s -r -p 'Disk Encryption Password:' pass1 + echo + read -s -r -p 'Disk Encryption Password (Again):' pass2 + echo + if ! [[ $pass1 = "$pass2" ]]; then + echo "error: disk encryption passwords did not match!" + exit 1 + fi + nixos_host="$(nix --extra-experimental-features 'nix-command flakes' eval --accept-flake-config --json .#nixosConfigurations --apply 'builtins.attrNames' | jq -r .[] | fzf --prompt 'Select NixOS configuration')" + partition_scheme="$(nix --extra-experimental-features 'nix-command flakes' eval --accept-flake-config --json .#diskoConfigurations --apply 'builtins.attrNames' | jq -r .[] | fzf --prompt 'Select disk partition scheme (must match NixOS configuration!)')" + disk_path="/dev/$(lsblk -d --raw | tail -n +2 | fzf --prompt 'Select local disk device' | awk '{print $1}')" + + echo "$pass1" | tr -d "\n" > /tmp/secret.key + + echo "This will install the host configuration for '$nixos_host' to '$disk_path' using partition scheme '$partition_scheme'. All data on the disk will be lost." + echo "Press enter to proceed. Press Ctrl-C to cancel." + read -r + echo "Starting..." + + nix-shell --packages git --run "sudo nix run \ + --extra-experimental-features nix-command \ + --extra-experimental-features flakes \ + github:nix-community/disko -- \ + --flake '.#$partition_scheme' \ + --mode disko \ + --arg disk '\"$disk_path\"' \ + --arg swapSize '\"$swap_size\"'" + + nix-shell --packages git \ + --run "sudo nixos-install \ + --no-write-lock-file \ + --flake '.#$nixos_host' \ + --option trusted-substituters 'https://cache.nixos.org https://nix.h.lyte.dev' \ + --option trusted-public-keys 'cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= h.lyte.dev-2:te9xK/GcWPA/5aXav8+e5RHImKYMug8hIIbhHsKPN0M='" ''; +} 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' + ''; +}