diff --git a/docs/caddy-reverse-proxy.md b/docs/caddy-reverse-proxy.md index 2beb4f9..712ac1f 100644 --- a/docs/caddy-reverse-proxy.md +++ b/docs/caddy-reverse-proxy.md @@ -1,27 +1,42 @@ # Caddy Reverse Proxy Setup -Caddy sits in front of the SvelteKit app and handles all access methods: -- Internal network (LAN/Tailscale) -- Tor hidden service -- Yggdrasil mesh network -- Public domain with automatic HTTPS +The LXC runs a local Caddy that accepts traffic from all networks and proxies +to the SvelteKit app. TLS termination for the public domain happens on a +separate upstream Caddy — this instance only handles plain HTTP. -## 1. Install Caddy +## Architecture + +``` + ┌─────────────────────────────────┐ +Internet ──► Upstream Caddy (TLS) ──────┤ │ + collection.newedge.house │ │ + │ LXC: Caddy (:80) ──► :3000 │ +NetBird ──► nb-ip:80 ──────────────────┤ (SvelteKit app) │ + │ │ +Yggdrasil──► [200:...]:80 ──────────────┤ │ + │ │ +Tor ──► .onion ──► tor ──► :8880 ──┤ │ + └─────────────────────────────────┘ +``` + +All routes inject `Host: collection.newedge.house` so SvelteKit CSRF passes. + +## 1. Install Caddy and Tor ```bash +# Caddy apt install -y debian-keyring debian-archive-keyring apt-transport-https curl curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list apt update apt install caddy -``` -## 2. Install Tor - -```bash +# Tor apt install tor ``` +## 2. Configure Tor hidden service + Edit `/etc/tor/torrc`: ``` @@ -29,19 +44,34 @@ HiddenServiceDir /var/lib/tor/bflr/ HiddenServicePort 80 127.0.0.1:8880 ``` -Restart and get your .onion address: - ```bash systemctl restart tor cat /var/lib/tor/bflr/hostname +# → your .onion address ``` -## 3. Configure Caddy +## 3. Get your interface addresses + +```bash +# Yggdrasil IPv6 +yggdrasilctl getSelf +# → 200:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx + +# NetBird IP +ip addr show wt0 +# → 100.x.x.x (or whatever your NetBird interface is called) + +# LAN IP +ip addr show eth0 +# → 192.168.x.x +``` + +## 4. Caddyfile Edit `/etc/caddy/Caddyfile`: ```caddy -# ─── Shared config ─────────────────────────────────────────────────── +# ─── Shared snippets ──────────────────────────────────────────────── (proxy) { reverse_proxy 127.0.0.1:3000 { @@ -59,25 +89,14 @@ Edit `/etc/caddy/Caddyfile`: } } -# ─── Public domain (automatic HTTPS via Let's Encrypt) ─────────────── - -collection.newedge.house { - import common - reverse_proxy 127.0.0.1:3000 { - header_up X-Real-IP {remote_host} - header_up X-Forwarded-For {remote_host} - header_up X-Forwarded-Proto https - } -} - -# ─── Internal / LAN access (HTTP on port 80) ───────────────────────── +# ─── Public traffic (HTTP from upstream Caddy, TLS already terminated) ─ :80 { import common import proxy } -# ─── Tor hidden service (HTTP on port 8880, Tor connects here) ─────── +# ─── Tor hidden service (Tor connects to localhost:8880) ───────────── :8880 { import common @@ -85,56 +104,75 @@ collection.newedge.house { bind 127.0.0.1 } -# ─── Yggdrasil (HTTP on port 80, bind to Yggdrasil IPv6) ───────────── -# Replace with your actual Yggdrasil address from: yggdrasilctl getSelf +# ─── Yggdrasil (bind to Yggdrasil IPv6 only) ──────────────────────── +# Replace with your address from: yggdrasilctl getSelf http://[200:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]:80 { import common import proxy } + +# ─── NetBird (bind to NetBird interface IP only) ───────────────────── +# Replace with your address from: ip addr show wt0 + +http://100.x.x.x:80 { + import common + import proxy +} ``` -**Important:** Replace `200:xxxx:...` with your actual Yggdrasil IPv6 address. +**Replace:** +- `200:xxxx:...` with your Yggdrasil address +- `100.x.x.x` with your NetBird IP -The `(proxy)` snippet sets `Host: collection.newedge.house` on all non-public routes so SvelteKit's CSRF check passes regardless of how you access the app. +**Note:** The `:80` block catches all traffic including from the upstream Caddy. +The Yggdrasil and NetBird blocks are explicit bindings so Caddy listens on +those interfaces. If they conflict with `:80`, remove the `:80` block and +add explicit LAN binding instead: -## 4. Update the SvelteKit service +```caddy +# If you need separate LAN binding instead of catch-all :80 +http://192.168.x.x:80 { + import common + import proxy +} +``` -Change the app to only listen on localhost since Caddy handles external access: +## 5. Upstream Caddy (the TLS termination server) + +On your upstream Caddy that handles public TLS, add: + +```caddy +collection.newedge.house { + reverse_proxy LXC_IP:80 { + header_up X-Forwarded-Proto https + } +} +``` + +Replace `LXC_IP` with the LXC's LAN or Tailscale/NetBird IP as seen from +the upstream server. + +## 6. App config + +The app only listens on localhost: ```bash -nano /home/bflr/buildfor_life_repair/.env -``` - -``` +# /home/bflr/buildfor_life_repair/.env HOST=127.0.0.1 PORT=3000 +ORIGIN=https://collection.newedge.house +BASE_URL=https://collection.newedge.house ``` -Update systemd if HOST is set there too: - -```bash -nano /etc/systemd/system/bflr.service -``` +systemd service: ```ini Environment=HOST=127.0.0.1 +Environment=PORT=3000 ``` -## 5. Firewall - -Only Caddy needs external access: - -```bash -# Allow HTTP/HTTPS from anywhere -ufw allow 80/tcp -ufw allow 443/tcp - -# Block direct app access from outside -ufw deny 3000/tcp -``` - -## 6. Start everything +## 7. Start everything ```bash systemctl daemon-reload @@ -143,48 +181,51 @@ systemctl enable --now caddy systemctl restart tor ``` -## 7. Verify +## 8. Verify each path ```bash -# Public domain +# Via upstream (public domain) curl -s -o /dev/null -w "%{http_code}" https://collection.newedge.house/login -# Internal -curl -s -o /dev/null -w "%{http_code}" http://localhost/login +# Direct LAN +curl -s -o /dev/null -w "%{http_code}" http://LXC_LAN_IP/login + +# NetBird +curl -s -o /dev/null -w "%{http_code}" http://100.x.x.x/login + +# Yggdrasil (from a peer) +curl -s -o /dev/null -w "%{http_code}" http://[200:xxxx:...]/login # Tor (from Tor Browser) # http://your-onion-address.onion/login -# Yggdrasil (from a Yggdrasil peer) -# http://[200:xxxx:...]/login +# Local test +curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/login ``` -## How it works - -``` -Internet ──► collection.newedge.house:443 ──► Caddy ──► :3000 (app) -LAN ──► server-ip:80 ──► Caddy ──► :3000 (app) -Tor ──► .onion:80 ──► tor ──► :8880 ──► Caddy ──► :3000 (app) -Yggdrasil──► [200:...]:80 ──► Caddy ──► :3000 (app) -``` - -All routes set `Host: collection.newedge.house` so SvelteKit CSRF passes. -Caddy handles TLS for the public domain automatically via Let's Encrypt. - ## Troubleshooting ```bash -# Check Caddy status +# Caddy systemctl status caddy journalctl -u caddy -f +caddy validate --config /etc/caddy/Caddyfile -# Check Tor status +# Tor systemctl status tor cat /var/lib/tor/bflr/hostname -# Check Yggdrasil address +# Yggdrasil yggdrasilctl getSelf +yggdrasilctl getPeers -# Test reverse proxy +# NetBird +netbird status + +# App +systemctl status bflr +journalctl -u bflr -f + +# Test CSRF manually curl -H "Host: collection.newedge.house" http://127.0.0.1:3000/login ```