Saved prompts: W2, OLLAMA-1, BOOT-1, S1, AS0, AS1, JH-1, V0, V1, VS-1. DECISIONS.md 2026-09-27 entry (sudo-bridge retired, Jenkins deploys via agent-sudo deploy_service, Chatterbox-Turbo, vault-sandbox auto-unseal). Voice A1/A2 superseded. Redacted two plaintext secrets in agent-builder context (still in history; rotation tracked under #192). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6.6 KiB
W2 BUILD-PREP — cloudflare-ddns + wg-easy IPv4-only fix (#274) — write files, deploy NOTHING
Tracks: personal_projects #274. Design: /opt/appdata/docker/research/diy_wireguard_design.md (LOCKED 2026-09-25).
Written 2026-09-27 (infrastructure general questions conversation) for the 2026-09-28 parallel-agent day.
Safe to run in parallel with S1, AS0, JH-1, BOOT-1, OLLAMA-1 (no shared files).
You are a bounded background BUILD-PREP agent for personal_projects #274 (DIY WireGuard home-access tunnel), PHASE W2-PREP. Budget: --max-turns 30. If you issue the same tool call twice with identical arguments, STOP and output the wrap-up block with status=partially_succeeded.
HARD RULES
- WRITE FILES ONLY. Do NOT start, create, pull or restart any container; do NOT run
docker compose up; do NOT install, enable or start any systemd unit; no sudo; no router/DNS/Cloudflare API writes; no git add/commit/push; no Postgres writes; no ntfy sends. The owner deploys after review. - Read-only on the host otherwise (docker ps/inspect image/ports/networks/labels only — NEVER print
.Config.Env; never print a secret value). NO Vault writes this phase — both Cloudflare tokens are already stored. - If a security hook or permission blocks something, stop with a partial wrap-up (do not work around it). Issue each file write as its own call.
CURRENT STATE (verified 2026-09-27 14:00 by the main session)
- wg-easy
ghcr.io/wg-easy/wg-easy:15.4.0(digest-pinned in the compose) is Up (healthy) on primary 192.168.1.88, project dir/opt/appdata/docker/docker-compose/wireguard/, published0.0.0.0:45791->45791/udpAND[::]:45791->45791/udp, UI127.0.0.1:51821. Laptop client works on LAN. Router forwards UDP 45791 → 192.168.1.88 (done W0). - Secrets pattern already in use for wireguard:
secretspec.toml+deploy/resolver.sh+wireguard-secretspec-resolver.{service,timer}(installed + enabled). Read all of them first — W2 extends this, it does not invent a new pattern. - Vault (already stored, verified):
secret/cloudflare/ddnsfieldtoken(Zone:DNS:Edit on reverseproxyserver.net).secret/cloudflare/traefik-dns01is for W3 — do NOT wire it now. - Public IP is dynamic (66.164.12.179 on 09-25, 66.164.11.87 on 09-17). Not CGNAT.
LOCKED DECISIONS — do not revisit
- DDNS client = favonia/cloudflare-ddns, pinned to an exact current release tag + digest (look it up on Docker Hub / its GitHub releases; record both). Run it as a second service in the SAME wireguard compose project (it is part of the tunnel), on the
wgbridge network (NOT host mode — IPv4 only, so bridge is fine). - Record =
fuppzd3f9v.reverseproxyserver.net, DNS-only (PROXIED=false), IPv4 only (IP6_PROVIDER=none), TTL 60, update every 2 minutes (favonia'sUPDATE_CRON=@every 2mor whatever its README documents). The DDNS client must manage ONLY that one record (never the zone apex or other hosts; if favonia offers a "delete on stop" option, set it OFF so a stopped container never deletes the record). - Token via SecretSpec: add
CLOUDFLARE_API_TOKEN(or the_FILEvariant if favonia recommends it and the resolver pattern supports it — prefer whatever keeps the value out ofdocker inspect; explain your choice) sourced from Vaultsecret/cloudflare/ddnsfieldtoken, exactly the way INIT_PASSWORD is mapped today. - Hardening per favonia's README:
read_only: true,cap_drop: [ALL],security_opt: [no-new-privileges:true], non-rootuser:as documented, restart policy per template, healthcheck only if the image supports one (do not invent one that can't run in the image — check the image for a shell first viadocker manifest inspect/docs; if none, omit and say so). - wg-easy IPv4-only publish fix: change the port mapping to
"0.0.0.0:45791:45791/udp"so Docker stops publishing on[::]. Edit only that line (+ a one-line comment). This takes effect at the owner's next recreate. - Compose MUST keep following the template
/opt/appdata/docker/non-docker-python-scripts/Docker Template/docker-compose.yml(read it first). Remove the W2 placeholder comment the W1 agent left for ddns (keep the W3 dnsmasq placeholder).
VERIFY FAVONIA FACTS FROM THE SOURCE — WebFetch https://github.com/favonia/cloudflare-ddns (README) once and use its exact env var names (e.g. CLOUDFLARE_API_TOKEN / _FILE, DOMAINS, PROXIED, IP6_PROVIDER, TTL, UPDATE_CRON, DELETE_ON_STOP). Do not guess names from memory.
DELIVERABLES
- Edited
/opt/appdata/docker/docker-compose/wireguard/docker-compose.yml(ddns service + IPv4-only fix). - Edited
secretspec.toml(+deploy/resolver.shonly if the new var needs a resolver change — keep it minimal). README.md— add a "W2 — owner steps" section: (a) how to apply: run the resolver service once (sudo systemctl start wireguard-secretspec-resolver.service), confirm both containers healthy/running,docker logsof the ddns container shows the record set to the current public IP (comparecurl -4 -s https://ifconfig.me),dig +short fuppzd3f9v.reverseproxyserver.net @1.1.1.1;ss -lnup | grep 45791shows IPv4 only; (b) phone (Android 9) + tablet: create clients in the wg-easy UI, scan QR, AllowedIPs split-tunnel per design; (c) the W2 tests from design §5: off-LAN handshake over cellular, the silence test (from off-LAN,nmap -sU -p 45791 <public ip>shows open|filtered and wg-easy logs nothing for a non-peer), and the IP-change drill (how to force a DDNS re-check); (d) rollback (remove the ddns service,docker compose up -dre-applies; the DNS record stays pointing at the last IP — harmless).- Validate without deploying:
docker compose -f <file> config -q(unset secretspec vars warnings expected — note them),bash -n deploy/resolver.shif touched.
SCOPE ALLOWLIST: /opt/appdata/docker/docker-compose/wireguard/ (docker-compose.yml, secretspec.toml, deploy/resolver.sh, README.md, .claude/context.md). Nothing else.
PERSIST BEFORE YOU FINISH
- Append (one
cat >>heredoc; append only) "## #274 W2-prep — 2026-09-28 (background agent)" to/opt/appdata/docker/docker-compose/wireguard/.claude/context.md: What was done / Current state / Owner steps (short) / Next step (W3 dnsmasq + Traefik DNS-01 prep). - Run: python3 /opt/appdata/docker/.claude/scripts/embed_memory_dir.py --only-recent 5
- FINAL message = wrap-up JSON only, always: {"status":"succeeded|partially_succeeded|failed","project":"diy-wireguard #274","phase":"W2-prep","actions_taken":[],"actions_failed":[],"files_touched":[],"containers_restarted":[],"unverified_claims":[],"ddns_image_tag":"","ddns_image_digest":"","token_mapping":"env|file — why","validation":{"compose_config":"","resolver_syntax":""},"owner_steps":[],"next_step":"","notes":""}