feat(netbird): stage N1 server-01 sandbox agent prompt (#258)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Backtalk6858
2026-09-17 23:05:26 -05:00
parent d9f8688937
commit 655a38e2b6
@@ -0,0 +1,128 @@
# N1 — NetBird server-01 Sandbox Validation (background agent)
> Executes Phase N1 of `playbook_netbird_phases.md`. Authoritative design =
> `research/netbird-design_2026-09-17.md` (§4 runbook). This prompt is self-contained; do NOT
> assume you can read those — every procedure-critical fact is inlined below.
> **This is a THROWAWAY sandbox. Nothing here is production. Tear it all down at the end.**
## How this is spawned
The main session spawns this via the Agent tool (general-purpose), max 15 turns, subject to the same
security hooks as the main session (no direct mutating sudo; privileged mutations route through
sudo-bridge with the announce-first pattern). Spawn ONLY after the main session confirms the N0 gate:
`secret/netbird/db`, `secret/netbird/oidc`, `secret/cloudflare/dns-api` all populated and Authelia
restarted with OIDC live. Do NOT self-check the gate by mutating anything.
---
## Loop detection (verbatim)
If you issue the same tool call twice with identical arguments, STOP and output the wrap-up block
with status=partially_succeeded.
## Blocked = stop
If a security hook or permission blocks an action, report a partial wrap-up. Never attempt creative
workarounds around a block.
## Objective (bounded)
On **server-01 (`ssh administrator@192.168.1.90`)** stand up a throwaway NetBird self-hosted stack and
validate the machine-testable parts of the design, then tear it down. You are NOT proving the
cross-NAT streaming case and you are NOT completing the interactive browser SSO login — those are
explicitly **owner-in-the-loop** and you only set them up + report readiness (see "Owner-verifies").
## Scope allowlist (touch NOTHING else)
- **server-01 only**, in a scratch dir `~/netbird-sandbox/` you create on server-01.
- Vault **reads** of the paths below (AppRole login->use->revoke-self).
- **No changes to the primary server. No prod DB reads/writes. No container restarts on primary.
No commits, no pushes.** server-01's only permanent residents are Ollama/GPU + Obsidian — do not
disturb them; deploy into your scratch dir and remove everything you add.
## Credential map (exact — never discover auth at runtime)
Vault AppRole (from THIS host, primary):
- `VAULT_IP=$(docker inspect vault-iwaulpoi5hwirdlogshmul40 --format '{{.NetworkSettings.Networks.coolify.IPAddress}}')`
- role-id file: `/opt/appdata/docker/docker-compose/vault/approle/role-id`
- secret-id file: `/opt/appdata/docker/docker-compose/vault/approle/secret-id`
- login: `POST http://$VAULT_IP:8200/v1/auth/approle/login` -> `.auth.client_token`; **revoke-self when done**.
Paths + fields:
- `secret/netbird/oidc` -> `client_id`, `client_secret`, `issuer` (=`https://auth.reverseproxyserver.net`), `token_endpoint_auth_method` (=`client_secret_post`)
- `secret/cloudflare/dns-api` -> `api_token` (scoped Zone:DNS:Edit for reverseproxyserver.net; for DNS-01)
- **Do NOT fetch `secret/netbird/db`** — the Postgres DSN dry-run is an N2 task on primary (server-01
need not route to the prod Postgres network). Skip it here.
Fetch each secret via a `/tmp` python script reading Vault over http (NOT `curl -sf | python3` — `-sf`
hides the body on error). **Mask every secret value in all output and in the wrap-up JSON.**
## Pre-decided design (do NOT re-decide — you execute these)
1. **Store = SQLite** (sandbox only). Official `netbirdio` compose + `setup.env`.
2. **Dashboard hostname = `netbird-sandbox.reverseproxyserver.net`** (already registered as an Authelia
redirect URI). Resolve it to server-01 on the LAN by adding a hosts entry on server-01 and telling
the owner to add one on their test machine (`192.168.1.90 netbird-sandbox.reverseproxyserver.net`).
3. **TLS = DNS-01 wildcard** for `*.reverseproxyserver.net` using the CF token — this simultaneously
validates DNS-01 (design §4 step 6). Use lego or the NetBird proxy's built-in DNS-01 if supported;
otherwise issue with `lego` in a container and mount the cert into the NetBird reverse proxy.
NEVER http-01 (server-01 has no public :80). If DNS-01 issuance fails, record it and continue with
a self-signed cert so the rest of the validation can proceed — but mark `dns01_works=false`.
4. **OIDC** = generic OIDC to Authelia (issuer/client from Vault). Configure the dashboard + management.
## Steps (issue mutating commands standalone; verify as a SEPARATE command)
1. SSH to server-01, create `~/netbird-sandbox/`, pull the official NetBird self-hosted compose +
`setup.env`. Fill `setup.env`: domain `netbird-sandbox.reverseproxyserver.net`, OIDC issuer/client
from Vault, SQLite store.
2. Issue DNS-01 wildcard cert with the CF token. Verify: cert file exists + CN/SAN covers the host.
3. Bring the stack up. Verify (separate command): `management`, `signal`, `dashboard`, `relay`
containers are Up/healthy (`docker ps` on server-01).
4. **OIDC wiring check (agent-testable, no browser):** fetch
`https://auth.reverseproxyserver.net/.well-known/openid-configuration` and confirm `issuer` +
endpoints resolve; fetch the dashboard's served `config.json`/env and confirm it carries the right
`authority`/`clientId`. Record both. (The actual browser login is Owner-verifies #1.)
5. **Relay transport check (the Q10 proof, agent-testable):** enroll two throwaway peers on server-01
using a setup key (device-flow is Owner-verifies #2). Confirm they connect. Then force relay
fallback (block the direct path) and confirm the relayed path uses the **UDP relay endpoint, NOT any
cloudflare hostname** — capture the relay endpoint host:port and the transport. Record
`relay_transport` = `udp-direct` or whatever it actually is. Record whether NetBird's built-in
`relay` sufficed or a separate `coturn` was needed (`coturn_needed`).
6. **Tear down:** stop + remove every container you created, delete `~/netbird-sandbox/` and any cert
material, remove the hosts entry you added on server-01. Verify server-01 is back to Ollama/GPU +
Obsidian only (`docker ps`). Leave NOTHING running.
## Owner-verifies (you SET UP + REPORT READINESS; you do NOT perform these — no browser, and cross-NAT
## needs the owner's off-LAN devices)
1. **Browser OIDC login** to the dashboard (auth-code + PKCE) -> owner confirms Authelia login lands in
the NetBird dashboard. Leave a one-line instruction in the wrap-up `notes`.
2. **Device-flow enrollment** (`netbird up` opening a browser / device code) vs setup-keys -> owner runs
`netbird up` on a real device against the sandbox and reports if device-flow works. State this.
3. **Cross-NAT DIRECT streaming proof** (the star Jellyfin proof) — deferred to N2 with real off-LAN
nodes; a single-host sandbox cannot represent it. State this explicitly; do NOT claim P2P streaming
is proven from the sandbox.
## Verify before reporting success
Do not set `status=succeeded` unless: stack came up healthy, OIDC discovery + dashboard config verified,
relay-transport captured as non-cloudflare, DNS-01 result recorded, AND teardown confirmed clean.
## Persistence (do these as final actions — your context is discarded on finish)
- Append a dated handoff to `research/netbird-design_2026-09-17.md` §7 (What was done / results for
steps 2,4,5 / device-flow + coturn verdicts / Next step) — this is a research doc, appending is in
scope; do NOT commit.
- Write per-agent run log `logs/agent_runs/N1.json` with the wrap-up JSON.
- Update the semantic index: `python3 /opt/appdata/docker/.claude/scripts/embed_memory_dir.py --only-recent 3`.
- Do NOT rely on the Stop hook's MEMORY_EMBED tag.
## Wrap-up block (emit REGARDLESS of success/failure; mask all secrets)
```json
{
"status": "succeeded | partially_succeeded | failed",
"project": "netbird #258 / N1 sandbox",
"actions_taken": [],
"actions_failed": [],
"files_touched": [],
"containers_restarted": [],
"stack_healthy": true,
"oidc_discovery_ok": true,
"dashboard_config_ok": true,
"relay_transport": "udp-direct | ... ",
"relay_is_cloudflare": false,
"coturn_needed": true,
"dns01_works": true,
"device_flow_status": "owner-to-verify",
"teardown_clean": true,
"next_step": "",
"notes": ""
}
```