From 0f2a87b08a5cf3f911846cc6e9a3671fca68b746 Mon Sep 17 00:00:00 2001 From: Antigravity Agent Date: Sat, 3 Oct 2026 00:01:09 -0400 Subject: [PATCH] NetVM: per-node Warp egress design + lifecycle scripts --- NODES.md | 8 ++++ README.md | 95 ++++++++++++++++++++++++++++++++++++++++++ bin/netvm-node-down.sh | 8 ++++ bin/netvm-node-up.sh | 21 ++++++++++ bin/netvm-topology.sh | 8 ++++ bin/netvm-verify.sh | 7 ++++ 6 files changed, 147 insertions(+) create mode 100644 NODES.md create mode 100644 README.md create mode 100755 bin/netvm-node-down.sh create mode 100755 bin/netvm-node-up.sh create mode 100755 bin/netvm-topology.sh create mode 100755 bin/netvm-verify.sh diff --git a/NODES.md b/NODES.md new file mode 100644 index 0000000..5884f34 --- /dev/null +++ b/NODES.md @@ -0,0 +1,8 @@ +# NetVM nodes + +Registry: which node egresses where. Sharing an egress identity is a +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 | diff --git a/README.md b/README.md new file mode 100644 index 0000000..da3ecd7 --- /dev/null +++ b/README.md @@ -0,0 +1,95 @@ +# NetVM + +Fleet networking layer. Every node gets a stable network identity; every +byte of automation traffic is attributable, consistent, and boring — the +way good citizens look to the rest of the internet. + +## Scope + +1. **Per-node egress identity** — one Warp-backed egress per chrome-box / + node, in its own network namespace. Stable egress IP per node, explicit + mapping, isolated blast radius. +2. **Tailscale fabric** — node addressing, operator SSH, human reachability. +3. **Human-handoff links** — the reachable path a human uses to complete + CAPTCHA/2FA on a live browser session (original scope, kept). + +NetVM owns networking. chrome-box owns browsers and consumes network from +NetVM. email-alert owns notifications. The Account(s) hub owns identity +records. Secrets and identities are the human's — agents manage structure +and lifecycle only, never credentials. + +## Why per-node egress + +- **Anti-fraud, not evasion.** Providers flag IP-hopping as suspicious. A + node that always egresses from the same place looks like what it is: a + legitimate machine. Standardizing on Warp is the good-citizen move. +- **Blast-radius isolation.** One IP reputation problem affects one node, + never the fleet. Nodes deliberately do not all share one egress. +- **DevOps standardization.** Every node is provisioned the same way: + netns + WireGuard-via-Warp + topology entry. No snowflakes. + +Nodes may share an egress identity deliberately (documented in NODES.md); +sharing is a designed topology choice, never an accident. + +## Architecture + + chrome-box profile (client X) + -> launched inside netns warp-clientX (NetVM provides the launcher) + -> wg interface (Warp WireGuard params, human-generated config) + -> Cloudflare edge, stable colo (egress IP in NODES.md) + -> internet + + operator / human + -> Tailscale tailnet (100.x) (management + Waypipe handoff) + -> node tail IP + +Warp runs at the network layer, so the browser needs no proxy config — +the whole namespace egresses through Warp. + +### Pattern decision + +Two ways to get per-node Warp egress were considered: + +- **warp-cli proxy mode** (mode proxy + SOCKS5 per node): simpler, but + warp-cli talks to a single system daemon (warp-svc) — running N daemons + on one host is unverified and fights the service model. +- **WireGuard in netns (chosen)**: Warp is WireGuard under the hood. One + wg interface per node namespace, config generated once by the human + (wgcf or equivalent), no daemon, no D-Bus, fully scriptable. One + interface per chrome-box, each independently up/down-able. + +Upgrade path if pool IPs prove too fluid: Cloudflare Zero Trust dedicated +egress (true static IPs, paid). + +## Provisioning a node + +Identity creation is the human's job; lifecycle is scriptable: + +1. Human: generate the node's WireGuard config once (wgcf register + + wgcf generate, or warp-cli equivalent) and place it at + /etc/netvm/.conf (root-owned, 0600). This file is a credential — + agents never create, read, or copy it. +2. sudo bin/netvm-node-up.sh — creates netns warp-, raises + the wg interface inside it, verifies egress, prints the result. +3. chrome-box launches the client's Chromium inside that netns. + +Tear down: sudo bin/netvm-node-down.sh . + +## Files + +- bin/netvm-verify.sh — prerequisite checks (ip, wg, netns, tailscale…). +- bin/netvm-node-up.sh — bring up a node's egress. +- bin/netvm-node-down.sh — tear a node's egress down. +- bin/netvm-topology.sh — print the live topology table. +- NODES.md — the registry: node -> netns -> Warp identity -> egress IP. + +## Verification checklist + +- [ ] netvm-verify.sh passes on the target host. +- [ ] Per-node wg interface comes up inside its netns; egress IP differs + per node (or matches the designed sharing in NODES.md). +- [ ] Egress IP per node stable over 7 days (same colo pool). +- [ ] Chromium launched in the netns reaches the internet via the node's + egress (ip netns exec warp- curl ifconfig.me). +- [ ] Waypipe handoff to a node over the tailnet shows a live browser. +- [ ] One node's egress killed -> other nodes unaffected (blast radius). diff --git a/bin/netvm-node-down.sh b/bin/netvm-node-down.sh new file mode 100755 index 0000000..cee0784 --- /dev/null +++ b/bin/netvm-node-down.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Tear down a node's Warp egress. Run as root. +set -euo pipefail +NODE="${1:?usage: netvm-node-down.sh }" +NETNS="warp-${NODE}" +ip netns exec "$NETNS" ip link set "wg-${NODE}" down 2>/dev/null || true +ip netns del "$NETNS" 2>/dev/null || true +echo "node=$NODE down" diff --git a/bin/netvm-node-up.sh b/bin/netvm-node-up.sh new file mode 100755 index 0000000..b169b81 --- /dev/null +++ b/bin/netvm-node-up.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# Bring up a node's Warp egress. Run as root. +# The WireGuard config at /etc/netvm/.conf is human-generated +# (a credential). This script manages lifecycle only — it never creates +# or copies identities. +set -euo pipefail +NODE="${1:?usage: netvm-node-up.sh }" +NETNS="warp-${NODE}" +CONF="/etc/netvm/${NODE}.conf" +[ -f "$CONF" ] || { echo "missing $CONF — human generates it once (wgcf), root-owned 0600"; exit 1; } +chmod 600 "$CONF" +ip netns add "$NETNS" 2>/dev/null || true +ip link add "wg-${NODE}" type wireguard 2>/dev/null || true +ip link set "wg-${NODE}" netns "$NETNS" +ip netns exec "$NETNS" wg setconf "wg-${NODE}" < "$CONF" +ip netns exec "$NETNS" ip link set lo up +ip netns exec "$NETNS" ip link set "wg-${NODE}" up +# NOTE: addresses/routes come from the generated config (wgcf carries them). +EGRESS=$(ip netns exec "$NETNS" curl -s --max-time 15 ifconfig.me || true) +echo "node=$NODE netns=$NETNS egress=${EGRESS:-UNREACHABLE}" +[ -n "$EGRESS" ] || { echo "egress check failed"; exit 1; } diff --git a/bin/netvm-topology.sh b/bin/netvm-topology.sh new file mode 100755 index 0000000..1e0123e --- /dev/null +++ b/bin/netvm-topology.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +# Live topology: netns -> egress IP. Run as root for netns exec. +set -u +for ns in $(ip netns list 2>/dev/null | awk '{print $1}' | grep '^warp-'); do + node="${ns#warp-}" + egress=$(ip netns exec "$ns" curl -s --max-time 10 ifconfig.me 2>/dev/null || echo UNREACHABLE) + printf '%-20s %-16s %s\n' "$node" "$ns" "$egress" +done diff --git a/bin/netvm-verify.sh b/bin/netvm-verify.sh new file mode 100755 index 0000000..2b9d8e5 --- /dev/null +++ b/bin/netvm-verify.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +# Prerequisite checks for a NetVM host. Reports only; safe for any user. +set -u +ok=1 +need() { command -v "$1" >/dev/null 2>&1 && echo "ok: $1" || { echo "MISSING: $1"; ok=0; }; } +need ip; need wg; need tailscale; need warp-cli; need waypipe; need chromium; need curl +[ "$ok" = 1 ] && echo "ALL CHECKS PASSED" || { echo "CHECKS FAILED"; exit 1; }