116 lines
3.7 KiB
Text
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
|
|
}
|
|
}
|
|
}
|
|
}
|