provisioning-core/nulib/platform/clients/ssh.nu
Jesús Pérez c22722a418
core: push cluster foundation out of cli/components.nu to proper layers (ADR-026 slice 2a)
Untangle the shared foundation that the cluster subsystem depends on, moving each
helper to the layer its dependency topology dictates, de-cli-ifying on the way
(cli-err → error make). One-directional imports only; verified no cycles.

  comp-ncl-export                        → tools/nickel/process.nu
  ws-resolution (8 fns + root finders)   → domain/workspace/resolve.nu  (new)
  comp-ssh-run, comp-server-ssh-info     → platform/clients/ssh.nu       (new)
  comp-kubectl-access, comp-kubectl-ssh  → domain/cluster/access.nu      (new)

Consumers (components.nu, components_cluster.nu, component.nu) repointed to the
new locations. components.nu 2664 → 2408.

Verified: NU_LIB_DIRS module-load of all 8 touched modules (no cycle, no missing
command, no export error); ADR-026 hygiene (no upward imports in the new
platform/domain/tools modules); `provisioning component list` runs; component/cluster
--help exit 0. Prereq for slice 2b (cluster helpers → domain/cluster/introspect.nu).
2026-07-10 07:16:14 +01:00

52 lines
2.5 KiB
Text
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# SSH client primitives — extracted from cli/components.nu (ADR-026 slice 2a-iii).
# Get SSH connection info for a server (user, key path, host) from servers.ncl.
export def comp-server-ssh-info [ws_root: string, infra: string, server: string]: nothing -> record {
let servers_path = ($ws_root | path join "infra" $infra "servers.ncl")
if not ($servers_path | path exists) {
return { user: "root", key: "", host: $server }
}
let raw = (ncl-eval-soft $servers_path (default-ncl-paths $ws_root) null)
if ($raw == null) {
return { user: "root", key: "", host: $server }
}
let srv = ($raw | get -o servers | default [] | where {|s| ($s.hostname? | default "") == $server } | get 0?)
let user = if ($srv | is-not-empty) { ($srv | get -o installer_user | default "root") } else { "root" }
let key_raw = if ($srv | is-not-empty) { ($srv | get -o ssh_key_path | default "") } else { "" }
let key = if ($key_raw | is-not-empty) { $key_raw | path expand } else { "" }
let state_path = ($ws_root | path join "infra" $infra ".servers-state.json")
let srv_state = if ($state_path | path exists) {
$state_path | open | get -o $server | default {}
} else { {} }
let host = if ($srv | is-not-empty) {
let np = ($srv | get -o networking.public_ip | default "")
if ($np | is-not-empty) {
$np
} else {
# floating_ip is a Hetzner API assignment name, not an SSH target.
# Always use public_ip from state for SSH — the server's primary reachable address.
let state_ip = ($srv_state | get -o public_ip | default "")
if ($state_ip | is-not-empty) { $state_ip } else { $server }
}
} else { $server }
{ user: $user, key: $key, host: $host }
}
# Run a command on a remote host via SSH (with or without explicit identity file).
# ConnectTimeout bounds the connect phase; ServerAlive (5s × 3) tears down a
# session that goes silent after connect so a stalled remote command can never
# hang the caller indefinitely. Callers that run kubectl wrap it in `timeout`.
export def comp-ssh-run [user: string, key: string, host: string, cmd: string]: nothing -> record {
let opts = [
-o ConnectTimeout=10
-o ServerAliveInterval=5
-o ServerAliveCountMax=3
-o BatchMode=yes
]
if ($key | is-not-empty) {
do { ^ssh -i $key ...$opts $"($user)@($host)" $cmd } | complete
} else {
do { ^ssh ...$opts $"($user)@($host)" $cmd } | complete
}
}