156 lines
7.3 KiB
Text
156 lines
7.3 KiB
Text
|
|
#!/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"
|
||
|
|
}
|