provisioning-catalog/playbooks/bootstrap_initial/steps/setup_quian.nu

156 lines
7.3 KiB
Text
Raw Permalink Normal View History

#!/usr/bin/env nu
# Set up quian (NBG1) as ops launch node for libre-wuji (FSN1).
#
# Topology:
# M4 Mac → SSH → quian (46.225.25.227, pub; 10.0.9.2, wuwei NBG1 subnet)
# quian → SSH → libre-wuji-cp-0 (10.0.8.20, wuwei FSN1 subnet)
#
# Both are on the wuwei private network (10.0.8.0/22) but in different zones.
# Hetzner multi-location VPC routes 10.0.9.x ↔ 10.0.8.x automatically.
# M4 has NO direct access to 10.0.8.x — all control-plane ops go via quian.
#
# SSH TRUST SETUP (manual prerequisite):
# Run with --print-pubkey, add the printed key to libre-wuji-cp-0 authorized_keys,
# then re-run without that flag.
def main [
--quian-host: string = "46.225.25.227"
--wuji-cp-priv: string = "10.0.8.20"
--nats-node-port: int = 34222
--print-pubkey # print quian pubkey for manual trust setup, then exit
--skip-nats-patch # skip NATS NodePort patch (e.g. already done)
--dry-run
]: nothing -> nothing {
let quian = $quian_host
let wuji_cp = $wuji_cp_priv
if $dry_run {
print "[dry-run] would: install kubectl on quian, copy admin.conf quian→wuji-cp, patch NATS NodePort"
return
}
# ── Get/generate quian's SSH pubkey ────────────────────────────────────────
let gen_r = do {
^ssh $"root@($quian)" 'if [ ! -f ~/.ssh/id_ed25519 ]; then ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519 -q; fi'
} | complete
if $gen_r.exit_code != 0 {
error make { msg: $"failed to generate ssh key on quian: ($gen_r.stderr | str trim)" }
}
let pubkey_r = do { ^ssh $"root@($quian)" 'cat ~/.ssh/id_ed25519.pub' } | complete
if $pubkey_r.exit_code != 0 {
error make { msg: $"failed to read pubkey from quian: ($pubkey_r.stderr | str trim)" }
}
let quian_pubkey = $pubkey_r.stdout | str trim
if $print_pubkey {
print "── quian SSH public key ──────────────────────────────────────────"
print $quian_pubkey
print ""
print "Add this to libre-wuji-cp-0 /root/.ssh/authorized_keys:"
print $" echo '($quian_pubkey)' >> /root/.ssh/authorized_keys"
return
}
# ── Verify cross-zone routing (NBG1 → FSN1 via wuwei) ────────────────────
print $"── verify cross-zone routing quian → ($wuji_cp) ──"
let ping_r = do {
^ssh $"root@($quian)" $"ping -c 3 -W 2 ($wuji_cp)"
} | complete
if $ping_r.exit_code != 0 {
print $"FAIL: quian cannot reach ($wuji_cp) via private network"
print "Check: hcloud network describe wuwei — locations must include both nbg1 and fsn1"
error make { msg: "cross-zone routing not available — cannot proceed" }
}
print $"OK: quian (NBG1 10.0.9.2) → libre-wuji-cp-0 (FSN1 ($wuji_cp)) routed via wuwei"
# ── Install kubectl on quian ──────────────────────────────────────────────
print "── install kubectl (linux/arm64) on quian ──"
let kubectl_r = do {
^ssh $"root@($quian)" '
if command -v kubectl >/dev/null 2>&1; then
kubectl version --client 2>/dev/null || true
exit 0
fi
KVER=$(curl -Ls https://dl.k8s.io/release/stable.txt)
curl -Lo /usr/local/bin/kubectl "https://dl.k8s.io/release/${KVER}/bin/linux/arm64/kubectl"
chmod +x /usr/local/bin/kubectl
kubectl version --client
'
} | complete
if $kubectl_r.exit_code != 0 {
error make { msg: $"kubectl install failed on quian: ($kubectl_r.stderr | str trim)" }
}
print $kubectl_r.stdout
# ── Copy admin.conf from libre-wuji-cp-0 via quian SSH ───────────────────
print "── copy admin.conf from libre-wuji-cp-0 (via quian SSH trust) ──"
let mkdir_r = do { ^ssh $"root@($quian)" 'mkdir -p ~/.kube' } | complete
if $mkdir_r.exit_code != 0 {
error make { msg: "failed to create ~/.kube on quian" }
}
# Test SSH trust before attempting scp — give a clear error if missing
let trust_r = do {
^ssh $"root@($quian)" $"ssh -o BatchMode=yes -o ConnectTimeout=5 root@($wuji_cp) echo ok"
} | complete
if $trust_r.exit_code != 0 {
print "SSH trust from quian to libre-wuji-cp-0 is NOT established."
print ""
print $"Run: nu setup_quian.nu --quian-host ($quian) --print-pubkey"
print $"Then on libre-wuji-cp-0: echo '<pubkey>' >> /root/.ssh/authorized_keys"
print "Then re-run this script."
error make { msg: "SSH trust missing — quian cannot reach libre-wuji-cp-0" }
}
let scp_r = do {
^ssh $"root@($quian)" $"scp -o StrictHostKeyChecking=no root@($wuji_cp):/etc/kubernetes/admin.conf ~/.kube/config && chmod 600 ~/.kube/config"
} | complete
if $scp_r.exit_code != 0 {
error make { msg: $"admin.conf copy failed: ($scp_r.stderr | str trim)" }
}
print "admin.conf installed at quian ~/.kube/config"
# ── Verify kubectl works from quian ───────────────────────────────────────
print "── verify kubectl from quian ──"
let nodes_r = do { ^ssh $"root@($quian)" 'kubectl get nodes -o wide' } | complete
if $nodes_r.exit_code != 0 {
error make { msg: $"kubectl failed from quian: ($nodes_r.stderr | str trim)" }
}
print $nodes_r.stdout
# ── Patch NATS service to NodePort ────────────────────────────────────────
if not $skip_nats_patch {
print $"── patch NATS svc to NodePort ($nats_node_port) ──"
# Build patch as Nushell record → JSON, then pass to kubectl
let patch_json = {
spec: {
type: "NodePort"
ports: [
{ name: "nats", port: 4222, targetPort: 4222, nodePort: $nats_node_port }
{ name: "monitor", port: 8222, targetPort: 8222 }
]
}
} | to json --raw
let patch_r = do {
^ssh $"root@($quian)" $"kubectl patch svc nats -n core-service --type=merge -p '($patch_json)'"
} | complete
if $patch_r.exit_code != 0 {
print $"WARNING: NATS patch failed \(may already be NodePort\): ($patch_r.stderr | str trim)"
} else {
print $"NATS NodePort active: ($wuji_cp):($nats_node_port)"
}
}
print ""
print "── quian is ready as ops launch node ────────────────────────────"
print $" kubectl: operational from quian \(libre-wuji cluster\)"
print $" NATS NodePort: ($wuji_cp):($nats_node_port)"
print ""
print "Next: deploy zot into libre-wuji K8s"
print " scp extensions/components/zot/cluster/install-zot.sh root@46.225.25.227:/tmp/"
print $" ssh root@($quian)"
print " ZOT_NAMESPACE=registry ZOT_S3_ACCESS_KEY=... ZOT_S3_SECRET_KEY=... bash /tmp/install-zot.sh install"
}