70 lines
2.6 KiB
Bash
Executable File
70 lines
2.6 KiB
Bash
Executable File
#!/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>|exec <node> -- <cmd>|cdp <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}"
|
|
;;
|
|
exec)
|
|
node="${2:?usage: netvm-fleet.sh exec <node> -- <cmd> [args...]}"
|
|
shift 2
|
|
[ "${1:-}" = "--" ] && shift
|
|
run_on "$node" "sudo -n \$HOME/Projects/NetVM/bin/netvm-exec.sh ${node} -- $*"
|
|
;;
|
|
cdp)
|
|
node="${2:?usage: netvm-fleet.sh cdp <node>}"
|
|
run_on "$node" "\$HOME/Projects/NetVM/bin/netvm-cdp.sh ${node}"
|
|
;;
|
|
*) echo "unknown command: $cmd"; exit 1 ;;
|
|
esac
|