# Selective imports replacing `use lib_provisioning *` (ADR-025 Phase 4). use primitives/infra/init.nu [get-provisioning-args get-provisioning-name provisioning_init] use primitives/io/interface.nu [_print end_run set-provisioning-no-terminal set-provisioning-out] use primitives/io/logging.nu [is-debug-enabled set-debug-enabled set-metadata-enabled] use platform/settings/loader.nu [find_get_settings] use primitives/io/undefined.nu [invalid_task] use utils.nu * use ssh.nu * # Detailed view of a single server: settings + cache + DAG component states + optional hcloud describe. export def "main show" [ name?: string --infra (-i): string = "" --settings (-s): string = "" --out: string = "" # Output format: json | yaml | text (default) --live --debug (-x) ]: nothing -> nothing { if $debug { set-debug-enabled true } let curr_settings = (find_get_settings --infra $infra --settings $settings) if (($curr_settings | describe) == "nothing") or ($curr_settings == null) { error make { msg: "Unable to load server settings" } } let servers = if ($curr_settings | get data? | is-not-empty) { $curr_settings.data | get servers? | default [] } else { $curr_settings | get servers? | default [] } if ($servers | is-empty) { print "No servers configured"; return } # Interactive selector requires terminal — skip when --out is set let hostname = if ($name | is-not-empty) { $name } else if ($out | is-not-empty) { error make { msg: "Pass a server name when using --out" } } else { let choices = ($servers | each {|s| $s.hostname}) if ($choices | length) == 1 { $choices | first } else { let sel = ($choices | input list "Select server:") if ($sel | is-empty) { return } $sel } } let srv_def = ($servers | where {|s| $s.hostname == $hostname} | get 0?) if $srv_def == null { print $"❌ Server '($hostname)' not found in settings" return } # Cache data let state_cache = (read_servers_state_cache $curr_settings) let cached = if $hostname in ($state_cache | columns) { $state_cache | get $hostname } else { null } # DAG data from .provisioning-state.ncl let infra_path_val = ($curr_settings | get -o infra_path | default "") let infra_name_val = ($curr_settings | get -o infra | default "") let infra_dir = if ($infra_name_val | is-not-empty) { $infra_path_val | path join $infra_name_val } else { $infra_path_val } let dag_all = (read_infra_taskserv_states $infra_dir) let server_dag = if $hostname in ($dag_all | columns) { $dag_all | get $hostname } else { [] } # Structured record for --out support let detail = { hostname: $hostname provider: ($srv_def | get -o provider | default "hetzner") server_type: ($srv_def | get -o server_type | default "") location: ($srv_def | get -o location | default "") status: (if $cached != null { $cached | get -o status | default "—" } else { "—" }) public_ip: (if $cached != null { $cached | get -o public_ip | default "" } else { "" }) private_ip: ($srv_def | get -o networking.private_ip | default "") floating_ip: (if $cached != null { $cached | get -o floating_ip_address | default "" } else { "" }) floating_ip_name: (if $cached != null { $cached | get -o floating_ip | default "" } else { "" }) protection: (if $cached != null { if ($cached | get -o protection_delete | default false) { "del" } else { "—" } } else { "—" }) cache_sync: (if $cached != null { $cached | get -o last_sync | default "" } else { "" }) dag: $server_dag } match $out { "json" => { let provider_data = (fetch-provider-record $hostname) let full = if $provider_data != null { $detail | insert hcloud $provider_data } else { $detail } print ($full | to json) } "yaml" => { let provider_data = (fetch-provider-record $hostname) let full = if $provider_data != null { $detail | insert hcloud $provider_data } else { $detail } print ($full | to yaml) } _ => { print $"== ($hostname) ==" print "" print $"provider: ($detail.provider) ($detail.server_type) ($detail.location)" if $cached != null { print $"status: ($detail.status)" print $"public ip: ($detail.public_ip)" } if ($detail.private_ip | is-not-empty) { print $"private ip: ($detail.private_ip)" } if $cached != null { if ($detail.floating_ip | is-not-empty) { let fip_suffix = if ($detail.floating_ip_name | is-not-empty) { $" \(($detail.floating_ip_name)\)" } else { "" } print $"floating: ($detail.floating_ip)($fip_suffix)" } print $"protection: ($detail.protection)" let ts = ($detail.cache_sync | str replace "T" " " | str replace "Z" "") if ($ts | is-not-empty) { print $"cache sync: ($ts)" } } if ($server_dag | is-not-empty) { print "" let total = ($server_dag | length) let completed = ($server_dag | where { $in.state == "completed" } | length) let failed = ($server_dag | where { $in.state == "failed" } | length) let fail_note = if $failed > 0 { $" [($failed) failed]" } else { "" } print $"DAG — ($completed)/($total) deployed($fail_note):" for entry in ($server_dag | sort-by name) { let icon = match $entry.state { "completed" => "✅" "failed" => "❌" "running" => "🔄" _ => "⏳" } let ended_at = ($entry | get -o ended_at | default "") let started_at = ($entry | get -o started_at | default "") let ts = if ($ended_at | is-not-empty) { $ended_at | str replace "T" " " | str substring 0..15 | str replace "Z" "" } else if ($started_at | is-not-empty) { let t = ($started_at | str substring 0..15) $"started ($t)" } else { "" } print $" ($icon) ($entry.name | fill -a l -w 20) ($entry.state | fill -a l -w 10) ($ts)" } } print "" hcloud-server-describe $hostname } } } # Fetch live provider data for a server as a structured record. Returns null if unavailable. def fetch-provider-record [hostname: string]: nothing -> any { let res = (do { ^hcloud server describe $hostname -o json } | complete) if $res.exit_code != 0 { return null } let s = ($res.stdout | from json) let fip_res = (do { ^hcloud floating-ip list -o json } | complete) let fips = if $fip_res.exit_code == 0 and ($fip_res.stdout | str trim | is-not-empty) { $fip_res.stdout | from json | where {|f| ($f.server? | default null) == $s.id} | each {|f| { name: $f.name, ip: $f.ip }} } else { [] } let vol_res = (do { ^hcloud volume list -o json } | complete) let volumes = if $vol_res.exit_code == 0 and ($s.volumes? | default [] | is-not-empty) { let vol_data = ($vol_res.stdout | from json) $s.volumes | each {|vid| $vol_data | where {|v| $v.id == $vid} | get 0? | if $in != null { { name: $in.name, size_gb: $in.size } } else { { name: $"id:($vid)", size_gb: 0 } } } } else { [] } { id: ($s.id | into string) name: $s.name status: $s.status server_type: ($s.server_type.name? | default "") cores: ($s.server_type.cores? | default 0) memory_gb: ($s.server_type.memory? | default 0) disk_gb: ($s.server_type.disk? | default 0) datacenter: ($s.datacenter.name? | default "") location: ($s.datacenter?.location?.name? | default "") public_ipv4: ($s.public_net?.ipv4?.ip? | default "") public_ipv6: ($s.public_net?.ipv6?.ip? | default "") private_ips: ($s.private_net? | default [] | each {|n| let net = ($n | get -o network | default null) let net_label = if $net == null { "" } else if ($net | describe) == "int" { $"id:($net)" } else { $net | get -o name | default $"id:($net | get -o id | default '?')" } { ip: $n.ip, network: $net_label } }) floating_ips: $fips volumes: $volumes labels: ($s.labels? | default {}) created: ($s.created? | default "") protection: { delete: ($s.protection?.delete? | default false), rebuild: ($s.protection?.rebuild? | default false) } } } # Full server describe via hcloud CLI: id, status, IPs, type, datacenter, volumes, labels. def hcloud-server-describe [hostname: string]: nothing -> nothing { let res = (do { ^hcloud server describe $hostname -o json } | complete) if $res.exit_code != 0 { print $"hcloud server describe ($hostname) failed (exit ($res.exit_code)):" print ($res.stderr | str trim) return } let s = ($res.stdout | from json) print $"server: ($s.name)" print $"id: ($s.id)" print $"status: ($s.status)" print $"type: ($s.server_type.name) [($s.server_type.cores) vCPU ($s.server_type.memory)GB RAM ($s.server_type.disk)GB disk]" print $"datacenter: ($s.datacenter.name) ($s.datacenter.location.description)" let pub_ip = ($s.public_net.ipv4.ip? | default "—") let pub_ip6 = ($s.public_net.ipv6.ip? | default "—") print $"public IPv4: ($pub_ip)" print $"public IPv6: ($pub_ip6)" let priv_ips = ($s.private_net? | default [] | each {|n| let net = ($n | get -o network | default null) let net_label = if $net == null { "?" } else if ($net | describe) == "int" { $"id:($net)" } else { $net | get -o name | default $"id:($net | get -o id | default '?')" } $"($n.ip) [($net_label)]" } | str join ", ") if ($priv_ips | is-not-empty) { print $"private IPs: ($priv_ips)" } let fip_res = (do { ^hcloud floating-ip list -o json } | complete) if $fip_res.exit_code == 0 and ($fip_res.stdout | str trim | is-not-empty) { let fips = ($fip_res.stdout | from json | where {|f| ($f.server? | default null) == $s.id}) if ($fips | is-not-empty) { let fip_str = ($fips | each {|f| $"($f.ip) [($f.name)]"} | str join ", ") print $"floating IPs: ($fip_str)" } } let vols = ($s.volumes? | default []) if ($vols | is-not-empty) { let vol_res = (do { ^hcloud volume list -o json } | complete) if $vol_res.exit_code == 0 { let vol_data = ($vol_res.stdout | from json) let vol_str = ($vols | each {|vid| let v = ($vol_data | where {|v| $v.id == $vid} | get 0?) if ($v | is-not-empty) { $"($v.name) ($v.size)GB" } else { $"id:($vid)" } } | str join ", ") print $"volumes: ($vol_str)" } } let labels = ($s.labels? | default {} | items {|k v| $"($k)=($v)"} | str join ", ") if ($labels | is-not-empty) { print $"labels: ($labels)" } print $"created: ($s.created)" let prot = (if ($s.protection?.delete? | default false) { "delete" } else { "none" }) print $"protection: ($prot)" } # > Servers status export def "main status" [ name?: string # Server hostname in settings ...args # Args for create command --infra (-i): string # Infra directory --settings (-s): string # Settings path --outfile (-o): string # Output file --serverpos (-p): int # Server position in settings --check (-c) # Only check mode no servers will be created --wait (-w) # Wait servers to be created --live # Call hcloud server describe for full provider-level detail --select: string # Select with task as option --debug (-x) # Use Debug mode --xm # Debug with PROVISIONING_METADATA --xc # Debuc for task and services locally PROVISIONING_DEBUG_CHECK --xr # Debug for remote servers PROVISIONING_DEBUG_REMOTE --xld # Log level with DEBUG PROVISIONING_LOG_LEVEL=debug --metadata # Error with metadata (-xm) --notitles # not tittles --helpinfo (-h) # For more details use options "help" (no dashes) --out: string # Print Output format: json, yaml, text (default) ] { if ($out | is-not-empty) { set-provisioning-out $out set-provisioning-no-terminal true } provisioning_init $helpinfo "servers status" $args if $debug { set-debug-enabled true } if $metadata { set-metadata-enabled true } # --live + named server: full hcloud describe if $live and ($name | is-not-empty) { hcloud-server-describe $name return } if $name != null and $name != "h" and $name != "help" { let curr_settings = (find_get_settings --infra $infra --settings $settings) if ($curr_settings.data.servers | find $name| length) == 0 { _print $"🛑 invalid name ($name)" exit 1 } } let task = if ($args | length) > 0 { ($args| get 0) } else { let str_task = (((get-provisioning-args) | str replace "create " " " )) let str_task = if $name != null { ($str_task | str replace $name "") } else { $str_task } ($str_task | str trim | split row " " | first | default "" | split row "-" | first | default "" | str trim) } let other = if ($args | length) > 0 { ($args| skip 1) } else { "" } let ops = $"((get-provisioning-args)) " | str replace $" ($task) " "" | str trim match $task { "" if $name == "h" => { ^$"(get-provisioning-name)" -mod server create help --notitles exit 0 }, "" if $name == "help" => { ^$"(get-provisioning-name)" -mod server create --help _print (provisioning_options "create") }, "" | "s" | "status" => { let curr_settings = (find_get_settings --infra $infra --settings $settings) if ($out | is-empty ) { _print (mw_servers_info $curr_settings | table -i false) } else { _print (mw_servers_info $curr_settings | to json) "json" "result" "table" } }, _ => { invalid_task "servers status" $task --end } } # "" | "create" if not $notitles and not (is-debug-enabled) { end_run "" } }