provisioning/scripts/nixos/hetzner-nixos-anywhere.nu

116 lines
3.7 KiB
Text

#!/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
}
}
}
}