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:
@@ -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": ""
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user