Standalone plan for the TaPPaaS operator's Claude Code: WireGuard client (peer 10.13.0.9, split-tunnel), Caddy plain-HTTP backend on 10.13.0.9:80, firewall lock to 10.13.0.1, internal split-horizon DNS. Bakes in the verified VPS-side contract (hub endpoint/pubkey, preserved Host, *.tappaas wildcard, public DNS) and the key-exchange handshake. Flags the internal-TLS decision (internal CA vs Gandi DNS-01 vs no internal TLS). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
TaPPaaS-side Publishing — Implementation Plan (for the TaPPaaS operator)
For agentic workers (Claude Code on the TaPPaaS side): this plan is self-contained. The VPS edge is already built, deployed, and verified — your job is the TaPPaaS/FLOSSFirewall end so the two meet. Every fact about the VPS side below is live and confirmed (2026-06-28). Work in the TaPPaaS infra/config repo (whatever holds the FLOSSFirewall WireGuard, Caddy, firewall and DNS config). Ask the operator when a value is environment-specific (upstream addresses, interface names, the local Caddy IP).
Goal: Make https://<svc>.tappaas.makerfloss.eu serve real TaPPaaS services
from anywhere, and give internal clients a local (no-VPS-round-trip) path to the
same names (split-horizon DNS).
How it works (the pattern): TLS for external clients terminates on the
makerfloss VPS. The VPS forwards every *.tappaas.makerfloss.eu request as
plain HTTP, original Host preserved, over a WireGuard tunnel (wg1) to
the FLOSSFirewall at 10.13.0.9:80, where Caddy routes by Host to the
service. (This mirrors the already-running mf01 setup.)
The contract — what the VPS already provides (live & verified)
You are building the other end of these. Do not change them; rely on them.
| Fact | Value |
|---|---|
| Public DNS (live) | tappaas.makerfloss.eu and *.tappaas.makerfloss.eu → A 88.99.32.236 |
| External TLS | Terminates at the VPS. Wildcard cert *.tappaas.makerfloss.eu already issued (Gandi DNS-01). You do NOT need a public cert for the external path. |
| What the VPS sends you | Plain HTTP to 10.13.0.9:80, over wg1, with the original Host header preserved (so Caddy sees whoami.tappaas.makerfloss.eu, etc.) |
| Current state of that route | Returns HTTP 502 today, because 10.13.0.9:80 isn't answering yet. Your work makes it answer → 200. |
| WireGuard hub (the VPS) | Endpoint makerfloss.eu:51820 (UDP) · hub overlay IP 10.13.0.1 · overlay subnet 10.13.0.0/24 · hub public key mtx4bxTq5KvQmFPRubRBrVoWL6WDUrll9LWLhOMSlCQ= |
| Your assigned peer overlay IP | 10.13.0.9/32 |
A placeholder peer public key is currently registered on the VPS for
10.13.0.9. It will be replaced by yours in the key-exchange step below — the tunnel won't complete a handshake until that swap happens.
What you will build (4 parts + 1 handshake)
- Key exchange (do first — it gates everything).
- WireGuard client on the FLOSSFirewall (peer
10.13.0.9, split-tunnel). - Caddy plain-HTTP backend on
10.13.0.9:80(routes by Host, redirect OFF). - Firewall allowing
tcp/80to the wg interface only from10.13.0.1. - Internal split-horizon DNS for
*.tappaas.makerfloss.eu.
Task 0 — WireGuard key exchange (do this first)
WireGuard needs a public-key swap; this is the one round-trip with the VPS owner (sjat). Private keys never leave the FLOSSFirewall.
Steps (on the FLOSSFirewall, or in your WG GUI):
umask 077
wg genkey | tee /etc/wireguard/wg1.key | wg pubkey | tee /etc/wireguard/wg1.pub
# Keep wg1.key secret (it goes only into your WG config, never committed).
cat /etc/wireguard/wg1.pub # <-- send THIS public key to sjat
Send sjat: the contents of wg1.pub (a 44-char base64 string ending =) and
confirm your peer IP is 10.13.0.9. sjat then replaces the peer public key on
the VPS (vault_wireguard_makerfloss_peers.flossfw.public_key) and re-runs the
WireGuard server play. After that the handshake can complete.
If your FLOSSFirewall is OPNsense/pfSense, generate the keypair in the WireGuard GUI instead and copy out the public key the same way.
Task 1 — WireGuard client (peer 10.13.0.9, split-tunnel)
Requirement: an interface that holds 10.13.0.9/32, peers with the VPS hub,
and routes only the overlay subnet — never default traffic (isolation:
nothing from TaPPaaS should egress via the VPS).
Example wg-quick config — /etc/wireguard/wg1.conf (adapt to your stack):
[Interface]
Address = 10.13.0.9/32
PrivateKey = <contents of /etc/wireguard/wg1.key from Task 0>
# No DNS=, no default route — split tunnel.
[Peer]
PublicKey = mtx4bxTq5KvQmFPRubRBrVoWL6WDUrll9LWLhOMSlCQ=
Endpoint = makerfloss.eu:51820
AllowedIPs = 10.13.0.0/24
PersistentKeepalive = 25
Bring up and enable on boot:
wg-quick up wg1
systemctl enable wg-quick@wg1 # systemd hosts
OPNsense/pfSense equivalent: local tunnel address 10.13.0.9/32; peer
endpoint makerfloss.eu:51820; peer public key
mtx4bxTq5KvQmFPRubRBrVoWL6WDUrll9LWLhOMSlCQ=; allowed IPs 10.13.0.0/24;
keepalive 25. Do not set it as a gateway / default route.
Verify (after sjat has swapped your key):
wg show wg1 # latest handshake is recent; transfer > 0
ping -c3 10.13.0.1 # the VPS hub answers over the overlay
If there is no handshake: confirm sjat swapped your public key; confirm UDP
51820 egress to makerfloss.eu isn't blocked by an upstream firewall.
Task 2 — Caddy plain-HTTP backend on 10.13.0.9:80
Requirement: Caddy must serve plain HTTP on 10.13.0.9:80, route by the
Host the VPS preserved, and NOT redirect to HTTPS on this listener (the VPS
already did TLS; a redirect here causes a loop — this is the one gotcha the mf01
setup hit). Your existing internal :443 serving stays unchanged; you are only
adding the wg-bound HTTP backend.
Why http:// in the site address: in Caddy v2 an http:// site address
disables Automatic HTTPS for that site and serves on port 80 — exactly the
no-cert, no-redirect behavior needed. bind 10.13.0.9 pins it to the wg
interface.
Example Caddyfile — one block per published service (adapt upstreams):
# --- VPS edge backend: plain HTTP on the wg interface only ---
# Repeat one block per service you want published publicly.
http://whoami.tappaas.makerfloss.eu {
bind 10.13.0.9
reverse_proxy <whoami-upstream-host>:<port>
}
http://forgejo.tappaas.makerfloss.eu {
bind 10.13.0.9
reverse_proxy <forgejo-upstream-host>:<port>
}
Notes:
- Exposure is opt-in here. Only hostnames you give a block become reachable publicly; the VPS forwards the whole wildcard, but unmatched hosts just get a Caddy 404. Add a block to publish a service; remove it to unpublish.
bind 10.13.0.9requires thewg1interface to exist before Caddy starts. Order Caddy afterwg-quick@wg1, or bind to0.0.0.0on:80and rely on the Task 3 firewall to restrict it to10.13.0.1(simpler startup, firewall does the isolation).- Point
reverse_proxyat the same upstreams your internal:443blocks already use for these services.
Verify locally (on the FLOSSFirewall):
curl -s -o /dev/null -w '%{http_code}\n' \
-H 'Host: whoami.tappaas.makerfloss.eu' http://10.13.0.9:80/
# Expect 200 (or whatever the service returns) — NOT a 3xx redirect.
Task 3 — Firewall: lock :80 to the VPS
Requirement: on the wg interface, allow tcp/80 only from 10.13.0.1
(the VPS hub); block :80 from anywhere else; ensure :80 is not exposed on
other interfaces. This is the isolation guarantee — the plain-HTTP backend is
reachable solely by the VPS.
Example (nftables/iptables host firewall):
iptables -A INPUT -i wg1 -p tcp --dport 80 -s 10.13.0.1 -j ACCEPT
iptables -A INPUT -i wg1 -p tcp --dport 80 -j DROP
OPNsense/pfSense: on the WireGuard interface, a pass rule — proto TCP, source
10.13.0.1, destination (this firewall) port 80; below it a block rule for
TCP to port 80 on that interface. Confirm no other interface rule exposes :80
to untrusted networks.
Verify: the local curl in Task 2 (sourced from the host itself) still works;
once end-to-end is up (Task 5), the request arrives from 10.13.0.1 and is
allowed.
Task 4 — Internal split-horizon DNS
Requirement: internal clients (the TaPPaaS cluster now; the makerspace LAN
later) resolving <svc>.tappaas.makerfloss.eu should get Caddy's local IP, so
they reach Caddy directly and skip the VPS round-trip. External clients keep
getting the VPS (public DNS already does that). Configure this on the
FLOSSFirewall's resolver.
Example — unbound (wildcard for the whole subdomain via redirect):
local-zone: "tappaas.makerfloss.eu." redirect
local-data: "tappaas.makerfloss.eu. 300 IN A <caddy-local-ip>"
Example — dnsmasq:
address=/tappaas.makerfloss.eu/<caddy-local-ip>
Serve this view to the cluster now. (Makerspace-LAN coverage is a later phase and depends on the OrangeMakers router — out of scope here.)
Decision point — internal TLS. Internal clients hitting Caddy directly will get Caddy's certificate for
<svc>.tappaas.makerfloss.eu, not the VPS's. While the cluster is isolated, Caddy can obtain a publicly-trusted cert for these names only via DNS-01 (which means putting a Gandi DNS-write credential on the FLOSSFirewall — the very thing the external-path design kept off the makerspace). Choose one, with sjat:
- Internal CA /
mkcert— issue an internal cert for*.tappaasand have cluster clients trust that CA. Keeps Gandi creds off-site. (Recommended.)- Gandi DNS-01 on the FLOSSFirewall — Caddy serves the real public wildcard internally too; reintroduces the credential in the makerspace.
- No internal TLS termination — point internal DNS at the VPS as well (drop split-horizon); simplest, but every internal request round-trips the VPS.
Tasks 1-3 do not depend on this; pick it before relying on the internal view.
Task 5 — End-to-end verification
Run after Tasks 0-3 are in place (Task 4 is for the internal view).
# 1) Tunnel handshake (on the FLOSSFirewall)
wg show wg1 # recent handshake, transfer > 0
ping -c3 10.13.0.1 # hub reachable
# 2) External end-to-end (from anywhere on the internet)
curl -s -o /dev/null -w '%{http_code}\n' https://whoami.tappaas.makerfloss.eu/
# Expect 200 (was 502 before your backend existed). TLS is the VPS's
# *.tappaas wildcard and validates with no warning.
# 3) Internal split-horizon (from a TaPPaaS cluster node, after Task 4)
dig +short whoami.tappaas.makerfloss.eu # → Caddy's LOCAL IP, not 88.99.32.236
curl -sI https://whoami.tappaas.makerfloss.eu/ # served by Caddy directly
Report back to sjat once (2) returns 200 — that confirms the full path
(VPS TLS → wg1 → Caddy → service) is live.
Isolation requirements (must hold)
- WireGuard is split-tunnel (
AllowedIPs = 10.13.0.0/24, no default route) — TaPPaaS never egresses through the VPS, and nothing onwg1reaches the wider makerspace or the VPS owner's homelab. - The plain-HTTP backend is reachable only from
10.13.0.1(Task 3). - The backend hop is plain HTTP but travels inside the WireGuard tunnel, so it is encrypted on the wire.
- No public-DNS-write credential is required on the FLOSSFirewall for the external path (the VPS owns that). Only the internal-TLS option 2 above would reintroduce one — avoid it unless deliberately chosen.
Reference
- Already-running sibling pattern (the template this mirrors):
MakerFLOSS_Troubleshooting/runbooks/publishing-services-mf01.md. - VPS-side design + the changes that produced the contract above:
MakerFLOSS_Troubleshooting/docs/superpowers/specs/2026-06-28-tappaas-vps-publishing-design.mdand.../plans/2026-06-28-tappaas-vps-publishing.md. - Questions about the VPS side / to do the key swap: contact sjat.