use tools/nickel/process.nu [ncl-eval-soft] use primitives/io/interface.nu [_print] use platform/settings/loader.nu [find_get_settings] use utils.nu [read_infra_taskserv_states] def _prov-root []: nothing -> string { $env.PROVISIONING? | default "/usr/local/provisioning" } def _infra-dir [settings: record]: nothing -> string { let infra_path = ($settings | get -o infra_path | default "") let infra_name = ($settings | get -o infra | default "") if ($infra_name | is-not-empty) { $infra_path | path join $infra_name } else { $infra_path } } def _icon [state: string]: nothing -> string { match $state { "completed" => "βœ…" "failed" => "❌" "running" => "πŸ”„" "partial" => "🟑" _ => "⏳" } } def _load-formula [infra_dir: string, hostname: string]: nothing -> any { let dag_path = ($infra_dir | path join "dag.ncl") if not ($dag_path | path exists) { return null } let dag = (ncl-eval-soft $dag_path [(_prov-root)] null) if $dag == null { return null } let formulas = ($dag | get -o formulas | default []) $formulas | where { |f| ($f | get -o server | default "") == $hostname } | get 0? | default null } def _load-components [infra_dir: string]: nothing -> record { let settings_path = ($infra_dir | path join "settings.ncl") if not ($settings_path | path exists) { return {} } let data = (ncl-eval-soft $settings_path [(_prov-root)] {}) $data | get -o components | default {} } # Resolve component record for a formula node, trying profile-derived key variants. def _comp-for-node [components: record, ts_name: string, profile: string]: nothing -> record { let keys_to_try = if ($profile | is-not-empty) { let prof_key = ($profile | str replace "-" "_") [$"($ts_name)_($prof_key)", $ts_name] } else { [$ts_name] } for $k in $keys_to_try { if $k in ($components | columns) { return ($components | get $k) } } {} } # Return taskserv-mode components that explicitly target the given server (e.g. cilium, hetzner_csi). def _targeted-taskservs-for [components: record, hostname: string]: nothing -> list { $components | items {|name comp| if ($comp | get -o mode | default "cluster") == "taskserv" and ($comp | get -o target | default "") == $hostname { [{ name: $name, version: ($comp | get -o version | default ""), comp: $comp }] } else { [] } } | flatten } # Return all cluster-mode components (K8s workloads deployed via just deploy from the CP). def _k8s-workload-comps [components: record]: nothing -> list { $components | items {|name comp| if ($comp | get -o mode | default "cluster") == "cluster" { [{ name: $name, version: ($comp | get -o version | default ""), comp: $comp }] } else { [] } } | flatten } # True when the formula contains a kubernetes control-plane node β€” used to identify CP formulas. def _is-cp-formula [formula: record]: nothing -> bool { $formula.nodes | any { |n| let ts = ($n | get -o taskserv | default {}) (($ts | get -o name | default "") == "kubernetes") and (($ts | get -o profile | default "") == "control-plane") } } # Show the formula node chain, component settings, and available commands for a server. export def on_server_formula [ hostname?: string --infra (-i): string = "" --settings (-s): string = "" ]: nothing -> nothing { let resolved = (find_get_settings --infra $infra --settings $settings false true) let infra_dir = (_infra-dir $resolved) let infra_name = ($resolved | get -o infra | default "") let all_servers = ($resolved.data.servers? | default []) let hostname = if ($hostname | is-not-empty) { $hostname } else { let choices = ($all_servers | where { |s| not ($s | get -o not_use | default false) } | each {|s| $s.hostname}) if ($choices | is-empty) { print "No servers configured"; return } let sel = ($choices | input list "Select server:") if ($sel | is-empty) { return } $sel } let formula = (_load-formula $infra_dir $hostname) if $formula == null { print $"❌ No formula found for '($hostname)' in ($infra_dir)/dag.ncl" return } let components = (_load-components $infra_dir) let dag_states = (read_infra_taskserv_states $infra_dir) let node_states = if $hostname in ($dag_states | columns) { $dag_states | get $hostname } else { [] } print $"== ($formula.id) ==" print $"server: ($hostname)" let desc = ($formula | get -o description | default "") if ($desc | is-not-empty) { print $"chain: ($desc)" } print "" # ── Node chain ─────────────────────────────────────────────────────────── let total = ($formula.nodes | length) let completed = ($node_states | where { $in.state == "completed" } | length) print $"Nodes β€” ($completed)/($total) completed:" print "" for node in $formula.nodes { let ts_name = ($node | get -o taskserv | default {} | get -o name | default $node.id) let profile = ($node | get -o taskserv | default {} | get -o profile | default "") let deps = ($node | get -o depends_on | default [] | each {|d| $d.node_id } | str join ", ") let on_err = ($node | get -o on_error | default "Stop" | into string) let node_key = ($ts_name | str replace --all "_" "-") let st_entry = ($node_states | where { |e| $e.name == $node.id or $e.name == $node_key } | get 0?) let state = ($st_entry | get -o state | default "pending") let icon = (_icon $state) let comp = (_comp-for-node $components $ts_name $profile) let comp_ver = ($comp | get -o version | default "") let profile_tag = if ($profile | is-not-empty) { $" profile:($profile)" } else { "" } let ver_tag = if ($comp_ver | is-not-empty) { $" v($comp_ver)" } else { "" } let dep_tag = if ($deps | is-not-empty) { $" after:($deps)" } else { "" } let err_tag = if $on_err == "Continue" { " on_error:Continue" } else { "" } print $" ($icon) ($node.id | fill -a l -w 22) [($ts_name)]($profile_tag)($ver_tag)($dep_tag)($err_tag)" # Show key configurable settings β€” skip structural/meta fields, cap line length if ($comp | columns | is-not-empty) { let meta = ["mode" "version" "target" "operations" "requires" "provides" "live_check" "pod_selector" "namespace" "profile"] let pairs = ($comp | items {|k v| {k: $k, v: $v}} | where { |p| not ($p.k in $meta) } | each { |p| let s = if ($p.v | describe) in ["bool" "int" "float" "string"] { $p.v | into string } else { $p.v | to nuon } if ($s | str length) <= 72 { $"($p.k)=($s)" } else { "" } } | where { is-not-empty }) if ($pairs | is-not-empty) { print $" settings: ($pairs | str join ' ')" } } } let is_cp = (_is-cp-formula $formula) let targeted_ts = (_targeted-taskservs-for $components $hostname) let k8s_workloads = if $is_cp { (_k8s-workload-comps $components) } else { [] } # ── Targeted taskservs on this server (cilium, hetzner_csi, longhorn…) ── if ($targeted_ts | is-not-empty) { print "" print "Targeted taskservs on this server:" for c in $targeted_ts { let ver = if ($c.version | is-not-empty) { $" v($c.version)" } else { "" } print $" βš™οΈ ($c.name)($ver)" } } # ── K8s workload components (cluster-mode, CP only) ────────────────────── if ($k8s_workloads | is-not-empty) { print "" print "K8s workload components (cluster-mode β€” deployed from this CP via just deploy):" for c in $k8s_workloads { let ver = if ($c.version | is-not-empty) { $" v($c.version)" } else { "" } print $" 🎯 ($c.name)($ver)" } } # ── Commands ───────────────────────────────────────────────────────────── print "" print "── Commands ──────────────────────────────────────────────────────────" print "" print "Formula nodes (taskserv-mode β€” executed by DAG, no op governance):" print $" install node: prvng component install --infra ($infra_name) --server ($hostname)" print $" update node: prvng component update --infra ($infra_name) --server ($hostname)" print $" delete node: prvng component delete --infra ($infra_name) --server ($hostname)" print $" full formula: prvng server create ($hostname) --infra ($infra_name) --wait" if ($targeted_ts | is-not-empty) { print "" print "Targeted taskservs (taskserv-mode, run from CP, no op governance):" for c in $targeted_ts { print $" prvng component install ($c.name) --infra ($infra_name)" print $" prvng component update ($c.name) --infra ($infra_name)" print $" prvng component delete ($c.name) --infra ($infra_name)" } } if ($k8s_workloads | is-not-empty) { print "" print "K8s workload components (cluster-mode β€” two execution paths):" print "" print " With governance ← USE THIS in production" print " jj captures a snapshot before + after, op record enables rollback and audit." for c in $k8s_workloads { print $" just deploy ($c.name) install \"\" # first deploy" print $" just deploy ($c.name) update \"\" # apply config changes + rolling restart" print $" just deploy ($c.name) restart \"\" # pod cycle only" print $" just deploy ($c.name) delete \"\" # teardown workload, keep data (pvc/ns)" print $" just rollback # revert to pre-op snapshot" print $" just log ($c.name) # full op history" print $" just render ($c.name) [update] [bundle] # inspect manifests locally, no SSH" } print "" print " Direct ← dev/debug only, no audit trail" for c in $k8s_workloads { print $" prvng component install ($c.name) --infra ($infra_name)" print $" prvng component update ($c.name) --infra ($infra_name)" print $" prvng component delete ($c.name) --infra ($infra_name)" } print "" print " ⚑ Why just deploy? Preflight (--check) runs first β€” fails fast before any state" print " changes. Then jj opens a change (snapshot of infra state), the op is recorded" print " in infra/libre-wuji/ops/ with intent + timestamps. If something goes wrong," print " just rollback restores the pre-op jj snapshot. prvng component runs" print " the deploy directly with none of that β€” fine for testing, risky in prod." } }