NetVM: fleet control script (netvm-fleet.sh) for operators

This commit is contained in:
Antigravity Agent
2026-10-03 00:16:42 -04:00
parent 324d1c654f
commit 626918ff94
3 changed files with 61 additions and 1 deletions
+1 -1
View File
@@ -5,4 +5,4 @@ designed choice — record the reason.
| node | netns | warp identity | egress IP | tail IP | shared with / reason |
|------|-------|---------------|-----------|---------|----------------------|
| laptop (node zero) | — | — | — | — | orchestrator + first node, not yet provisioned |
| tp | warp-tp | — | — | — | laptop; orchestrator + first node; identity pending (netvm-new-identity.sh tp) |
+1
View File
@@ -83,6 +83,7 @@ Tear down: sudo bin/netvm-node-down.sh <node>.
- bin/netvm-node-down.sh <node> — tear a node's egress down.
- bin/netvm-topology.sh — print the live topology table.
- `bin/netvm-provision-edge.sh` — prepare an edge device (sudoers allowlist, deps, /etc/netvm); run on the node, once.
- `bin/netvm-fleet.sh` — operator fleet control over the tailnet (topology/up/down/ssh per node).
- NODES.md — the registry: node -> netns -> Warp identity -> egress IP.
## Verification checklist
+59
View File
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# netvm-fleet.sh — operator fleet control over the tailnet.
# Run on any tailnet-connected host (laptop, VM) as the operator user.
# netvm-fleet.sh topology # egress per node, fleet-wide
# netvm-fleet.sh up <node> # bring a node's egress up
# netvm-fleet.sh down <node> # bring it down
# netvm-fleet.sh ssh <node> # shell on the node
# Node names are tailnet hostnames (see NODES.md).
# Prereqs: targets provisioned via netvm-provision-edge.sh; this user's SSH
# key accepted on targets. Lifecycle runs through the sudoers allowlist
# (sudo -n), so it is audit-logged and never blanket root.
set -euo pipefail
REPO="${NETVM_REPO:-$HOME/Projects/NetVM}"
OPERATOR_USER="${OPERATOR_USER:-$(whoami)}"
nodes() {
awk -F'|' '/^\|/ && $2 !~ /node/ && $2 !~ /---/ { n=$2; gsub(/^ +| +$/, "", n); if (n != "") print n }' "$REPO/NODES.md"
}
tail_ip() {
NODE="$1" python3 -c "
import json, os, subprocess, sys
node = os.environ['NODE']
st = json.loads(subprocess.run(['tailscale','status','--json'], capture_output=True, text=True).stdout)
cands = list(st.get('Peer', {}).values()) + [st.get('Self', {})]
for p in cands:
names = {p.get('HostName',''), p.get('DNSName','').split('.')[0]}
if node in names and p.get('TailscaleIPs'):
print(p['TailscaleIPs'][0]); sys.exit(0)
sys.exit(1)
"
}
run_on() { # <node> <remote-command...>
local node="$1"; shift
local ip
ip=$(tail_ip "$node") || { echo "unknown tailnet node: $node"; exit 1; }
ssh -o BatchMode=yes -o ConnectTimeout=15 "${OPERATOR_USER}@${ip}" "$@"
}
cmd="${1:?usage: netvm-fleet.sh topology|up <node>|down <node>|ssh <node>}"
case "$cmd" in
topology)
for n in $(nodes); do
printf '== %s ==\n' "$n"
run_on "$n" 'sudo -n $HOME/Projects/NetVM/bin/netvm-topology.sh' 2>&1 || echo "unreachable"
done
;;
up|down)
node="${2:?usage: netvm-fleet.sh $cmd <node>}"
run_on "$node" "sudo -n \$HOME/Projects/NetVM/bin/netvm-node-${cmd}.sh ${node}"
;;
ssh)
node="${2:?usage: netvm-fleet.sh ssh <node>}"
ip=$(tail_ip "$node") || { echo "unknown tailnet node: $node"; exit 1; }
exec ssh "${OPERATOR_USER}@${ip}"
;;
*) echo "unknown command: $cmd"; exit 1 ;;
esac