#!/usr/bin/env nu # Provision Hetzner server with NixOS using nixos-anywhere # Converts existing Debian/Ubuntu server to NixOS via nixos-anywhere use std # Configuration contracts def validate-inputs [server_ip: string, flake_path: string] { if ($server_ip | is-empty) { error make {msg: "server_ip is required"} } if ($flake_path | is-empty) { error make {msg: "flake_path is required"} } if not ($flake_path | path exists) { error make {msg: $"flake_path does not exist: ($flake_path)"} } if not ($flake_path | path join "flake.nix" | path exists) { error make {msg: $"flake.nix not found in ($flake_path)"} } } # Check if nixos-anywhere is installed def check-nixos-anywhere [] { if (which nixos-anywhere | is-empty) { error make {msg: "nixos-anywhere not found. Install with: nix-shell -p nixos-anywhere"} } } # Validate SSH connectivity def validate-ssh [target_host: string] { let ssh_check = (ssh -o ConnectTimeout=5 -o StrictHostKeyChecking=accept-new $target_host "echo OK" 2>&1 | complete) if $ssh_check.exit_code != 0 { error make {msg: $"SSH connection failed to ($target_host). Output: ($ssh_check.stderr)"} } } # Execute nixos-anywhere provisioning def provision-with-nixos-anywhere [target_host: string, flake_path: string, --no-reboot: bool] { let flake_uri = $"git+file://($flake_path)#nixosConfigurations.default" print $"Provisioning ($target_host) with NixOS from ($flake_path)" print $"Using flake URI: ($flake_uri)" let cmd = if $no_reboot { ["nixos-anywhere" "--no-reboot" "--flake" $flake_uri $target_host] } else { ["nixos-anywhere" "--flake" $flake_uri $target_host] } let result = (^$cmd[0] ...$cmd[1..] 2>&1 | complete) if $result.exit_code != 0 { error make {msg: $"nixos-anywhere failed with exit code ($result.exit_code). Output: ($result.stderr)"} } $result.stdout } # Validate NixOS boot (post-deployment) def validate-nixos-boot [target_host: string] { print $"Validating NixOS boot on ($target_host)..." let boot_check = (ssh -o ConnectTimeout=10 $target_host "uname -s" 2>&1 | complete) if $boot_check.exit_code != 0 { error make {msg: $"Failed to reach ($target_host) after provisioning"} } if not ($boot_check.stdout | str contains "Linux") { error make {msg: "System did not boot to Linux"} } } # Main provisioning function export def main [ server_ip: string # Hetzner server IP (e.g., 192.0.2.1) flake_path: string # Path to flake.nix directory --ssh-user: string = "root" # SSH user (default: root) --no-reboot: bool # Don't reboot after provisioning --skip-validation: bool # Skip pre-flight checks ] { if not $skip_validation { validate-inputs $server_ip $flake_path check-nixos-anywhere } let target_host = $"($ssh_user)@($server_ip)" if not $skip_validation { validate-ssh $target_host } # Execute provisioning let provision_output = (provision-with-nixos-anywhere $target_host $flake_path --no-reboot=$no_reboot) print $"Provisioning complete. Output:\n($provision_output)" # Wait for system to come back online (if not --no-reboot) if not $no_reboot { print "Waiting for system to reboot..." sleep 30sec let retry_count = 0 while $retry_count < 12 { try { validate-nixos-boot $target_host print "✅ NixOS boot validated successfully" break } catch {|e| print $"⏳ Boot check ($retry_count)/12 - $e" sleep 10sec mut $retry_count = $retry_count + 1 } } } }