#!/usr/bin/env nu # Prepare script for fip taskserv. # Determines which FIPs apply to this server from floating_ips.ncl assignment policy, # resolves their IP addresses, and writes FLOATING_IPS + IFACE to env-fip. let vars_path = ($env.PROVISIONING_VARS? | default "") if ($vars_path | is-empty) or not ($vars_path | path exists) { print $"🛑 PROVISIONING_VARS not set or not found: ($vars_path)" exit 1 } let wk_data = (open $vars_path) let iface = ($wk_data | get -o taskserv.interface | default "eth0") let hostname = ($wk_data | get -o server.hostname | default "") if ($hostname | is-empty) { print "🛑 server.hostname not set in vars" exit 1 } # Resolve workspace root let ws_root = do { let explicit = ($env.PROVISIONING_WORKSPACE_PATH? | default "") if ($explicit | is-not-empty) { $explicit } else { let wss = ($env.PROVISIONING_WORKSPACES? | default "") if ($wss | is-not-empty) { $wss | path dirname } else { "" } } } # Load assignment policy from floating_ips.ncl def load-fip-policy [ws_root: string]: nothing -> record { let fips_ncl = ($ws_root | path join "infra/lib/floating_ips.ncl") if not ($fips_ncl | path exists) { return {} } let prov = ($env.PROVISIONING? | default ($ws_root | path join ".." ".." "provisioning" | path expand)) let import_args = ["--import-path" $prov, "--import-path" $ws_root] let r = (do { ^nickel export --format json $fips_ncl ...$import_args } | complete) if $r.exit_code != 0 { return {} } $r.stdout | from json } # Determine which FIPs apply to this hostname based on assignment policy def applicable-fips [fips: record, hostname: string, node_labels: record]: nothing -> list { $fips | transpose key val | get val | where {|fip| let mode = ($fip | get -o assignment.mode | default null) match $mode { "pinned" => { ($fip | get -o assignment.node | default "") == $hostname }, "floating" => { # Floating FIPs are owned end-to-end by fip-controller (follow-pod # DaemonSet): it manages both the Hetzner assignment and the eth0 alias # on the node currently running the workload. Statically pre-configuring # them here on every can_use_fip-labelled node created a second writer, # leaving stale aliases on non-home nodes (silent drift). The static # taskserv now handles pinned FIPs only; cross-check with `just fip-reconcile`. false }, _ => { false }, } } } # Load bootstrap state for IP resolution let bs_data = do { if ($ws_root | is-not-empty) { let bs_path = ($ws_root | path join ".provisioning-state.json") if ($bs_path | path exists) { open $bs_path } else { {} } } else { {} } } def resolve-ip [fip_name: string, hostname: string, bs_data: record, ws_root: string]: nothing -> string { # Bootstrap state is keyed by FIP name — always correct for multi-FIP scenarios. # servers-state.floating_ip_address is only the server's primary assigned FIP, so # checking it first gives the wrong IP when multiple FIPs apply to one server. let fip_key = ($fip_name | str replace --all "librecloud-fip-" "" | str replace --all "-" "_") let bs_rec = do -i { $bs_data.bootstrap.floating_ips | get $fip_key } | default null if $bs_rec != null { let addr = ($bs_rec | get -o ip | default "") if ($addr | is-not-empty) { print $"✓ ($fip_name) → ($addr) [bootstrap-state]" return $addr } } # Fallback: servers-state (single-FIP legacy, or bootstrap state missing) if ($ws_root | is-not-empty) and ($hostname | is-not-empty) { let infra_dir = ($ws_root | path join "infra") if ($infra_dir | path exists) { let candidates = (do -i { ls $infra_dir } | where type == "dir" | get name | each {|d| $d | path join ".servers-state.json"} | where {|p| $p | path exists}) for sc in $candidates { let rec = (open $sc | get -o $hostname | default null) if $rec != null { let addr = ($rec | get -o floating_ip_address | default "") if ($addr | is-not-empty) { print $"✓ ($fip_name) → ($addr) [servers-state]" return $addr } } } } } "" } let fips = if ($ws_root | is-not-empty) { load-fip-policy $ws_root } else { {} } let applicable = if ($fips | is-empty) { # Fallback: legacy floating_ips array on server config let arr = ($wk_data | get -o server.floating_ips | default []) if ($arr | is-not-empty) { $arr | each {|name| { name: $name }} } else { let singular = ($wk_data | get -o server.floating_ip | default "") if ($singular | is-not-empty) { [{ name: $singular }] } else { [] } } } else { let node_labels = ($wk_data | get -o server.node_labels | default {}) applicable-fips $fips $hostname $node_labels } if ($applicable | is-empty) { print $"🛑 No FIPs applicable to ($hostname) — check floating_ips.ncl assignment policy" exit 1 } let resolved = ($applicable | each {|fip| let addr = (resolve-ip $fip.name $hostname $bs_data $ws_root) if ($addr | is-empty) { print $"🛑 Cannot resolve IP for FIP '($fip.name)' on ($hostname) — run: provisioning bootstrap" exit 1 } $addr }) let floating_ips_str = ($resolved | str join " ") let out_dir = ($env.PROVISIONING_WK_ENV_PATH? | default ".") $"IFACE=($iface)\nFLOATING_IPS=\"($floating_ips_str)\"\n" | save -f ($out_dir | path join "env-fip") print $"✓ env-fip: IFACE=($iface) FLOATING_IPS=($floating_ips_str)"