Correct AdventureLog Caddy routing to official design (all /api via frontend); document 2026-07-02 geocoding fix
This commit is contained in:
@@ -20,74 +20,90 @@ Live at: **https://go.jgitta.com**
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Caddy Routing (Critical — Non-Obvious)
|
## Caddy Routing (Corrected 2026-07-02 — now matches official docs)
|
||||||
|
|
||||||
AdventureLog has a SvelteKit API proxy that deletes the `csrftoken` cookie from the browser on every request, making direct browser→Django API calls impossible. The routing is split:
|
AdventureLog authenticates through the SvelteKit frontend: on login the frontend keeps the
|
||||||
|
Django session **server-side** and does NOT hand the browser a Django `sessionid` cookie (it even
|
||||||
|
clears `csrftoken` in the browser). Therefore **all `/api/*` and `/auth/*` traffic must go through
|
||||||
|
the frontend**, which proxies to Django server-to-server with the correct session + CSRF token.
|
||||||
|
Only static-type paths go straight to Django.
|
||||||
|
|
||||||
|
This is the official AdventureLog Caddy design (https://adventurelog.app/docs/install/caddy.html),
|
||||||
|
adapted for our remote Caddy VM (proxying to LAN ports 8015/8016) plus our standard security headers:
|
||||||
|
|
||||||
```caddy
|
```caddy
|
||||||
go.jgitta.com {
|
go.jgitta.com {
|
||||||
# GET/HEAD/OPTIONS /api/* → Django directly (port 8016)
|
import headers_base
|
||||||
# Browser sends session cookie; no CSRF needed for reads.
|
encode zstd gzip
|
||||||
# Accept: application/json header forces JSON instead of DRF HTML.
|
header {
|
||||||
@api_read { method GET HEAD OPTIONS; path /api/* }
|
X-Frame-Options "SAMEORIGIN"
|
||||||
handle @api_read {
|
Permissions-Policy "geolocation=(self), microphone=(), camera=()"
|
||||||
reverse_proxy http://192.168.88.27:8016 {
|
|
||||||
header_up Accept "application/json"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# All other requests (pages + POST/PUT/DELETE /api/*) → SvelteKit (port 8015)
|
# Backend (Django): media, admin, static files, legacy account pages
|
||||||
# SvelteKit proxy fetches fresh CSRF token server-to-server and handles auth.
|
@backend path /media* /admin* /static* /accounts*
|
||||||
@frontend { not path /media* /admin* /static* /accounts* }
|
handle @backend {
|
||||||
handle @frontend { reverse_proxy http://192.168.88.27:8015 }
|
reverse_proxy http://192.168.88.27:8016 { import proxy_timeouts }
|
||||||
|
}
|
||||||
|
|
||||||
# Django: media, admin, static, auth endpoints
|
# Everything else (pages, /api/*, /auth/*) -> SvelteKit frontend,
|
||||||
handle { reverse_proxy http://192.168.88.27:8016 }
|
# which proxies API/auth to Django server-side with session + CSRF.
|
||||||
|
handle {
|
||||||
|
reverse_proxy http://192.168.88.27:8015 { import proxy_timeouts }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Why this matters:** POST/PUT/DELETE must go through SvelteKit (port 8015 → gunicorn :8000). These requests will NOT appear in the Django nginx access log (`/var/log/nginx/access.log` inside the backend container). Only GET requests via Caddy appear there.
|
**`geolocation=(self)`** (inline, not the `web_secure` snippet) is required so the "Use My Location"
|
||||||
|
button works.
|
||||||
|
|
||||||
|
### History — why the old split routing was removed
|
||||||
|
Earlier the config split `/api/*` by HTTP method: GET/HEAD/OPTIONS went **directly to Django** and
|
||||||
|
only writes went through the frontend. That was a workaround for a geocode 401 seen during initial
|
||||||
|
setup. After a frontend container auto-update (`:latest`) on 2026-07-02, the frontend stopped
|
||||||
|
exposing a Django session to the browser, so the "GET direct to Django" path could no longer
|
||||||
|
authenticate — every authenticated read (including **geocoding / map search**) returned
|
||||||
|
`401 "Authentication credentials were not provided."` Reverting to the official all-`/api`-through-
|
||||||
|
frontend routing fixed it. Lesson: don't hand-split `/api`; let the frontend proxy it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Portainer Environment Variables
|
## Portainer Environment Variables
|
||||||
|
|
||||||
All 17 env vars are stored in Portainer's environment section (not hardcoded in compose). Key ones:
|
All env vars are stored in Portainer's environment section (not hardcoded in compose). Key ones:
|
||||||
|
|
||||||
| Variable | Value |
|
| Variable | Value |
|
||||||
|----------|-------|
|
|----------|-------|
|
||||||
|
| PUBLIC_SERVER_URL | http://server:8000 |
|
||||||
| ORIGIN | https://go.jgitta.com |
|
| ORIGIN | https://go.jgitta.com |
|
||||||
| PUBLIC_URL | https://go.jgitta.com |
|
| PUBLIC_URL | https://go.jgitta.com |
|
||||||
| CSRF_TRUSTED_ORIGINS | https://go.jgitta.com |
|
| CSRF_TRUSTED_ORIGINS | https://go.jgitta.com |
|
||||||
| FRONTEND_URL | https://go.jgitta.com |
|
| FRONTEND_URL | https://go.jgitta.com |
|
||||||
| PUBLIC_SERVER_URL | http://server:8000 |
|
|
||||||
| FRONTEND_PORT | 8015 |
|
| FRONTEND_PORT | 8015 |
|
||||||
| BACKEND_PORT | 8016 |
|
| BACKEND_PORT | 8016 |
|
||||||
|
| BODY_SIZE_LIMIT | Infinity |
|
||||||
| POSTGRES_DB | adventurelog |
|
| POSTGRES_DB | adventurelog |
|
||||||
| POSTGRES_USER | adventure |
|
| POSTGRES_USER | adventure |
|
||||||
| DEBUG | False |
|
| DEBUG | False |
|
||||||
| DISABLE_REGISTRATION | False |
|
| DISABLE_REGISTRATION | False |
|
||||||
|
|
||||||
Secrets (password, secret key, admin credentials) are in Portainer only — not in this file.
|
Secrets (DB password, SECRET_KEY, admin credentials) are in Portainer only — not in this file.
|
||||||
|
Note: `DJANGO_ADMIN_PASSWORD` is only the *bootstrap* password used at first container start; the
|
||||||
|
live admin password has since been changed in-app.
|
||||||
|
|
||||||
|
Optional/recommended (not currently set): `ENABLE_RATE_LIMITS=True` (docs recommend for production).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Bugs Fixed During Setup
|
## Bugs Fixed During Setup
|
||||||
|
|
||||||
### 1. CSRF errors on login
|
1. **CSRF errors on login** — `CSRF_TRUSTED_ORIGINS` must match the public URL exactly. Set in env.
|
||||||
Django's `CSRF_TRUSTED_ORIGINS` must match the public URL exactly. Fixed by setting it in env.
|
2. **GET /api/* returning DRF HTML instead of JSON** — old Caddy did a broken URI rewrite; removed.
|
||||||
|
3. **Geocode search 401** — see "Caddy Routing" history above. Final fix: route all `/api/*`
|
||||||
### 2. GET /api/* returning DRF HTML instead of JSON
|
through the SvelteKit frontend (official design), not directly to Django.
|
||||||
Caddy was doing a broken URI rewrite (`{1}` not expanding). Fixed by removing the rewrite and adding `header_up Accept "application/json"`.
|
4. **Category creation 500** — AdventureLog returns 500 (not 400) on duplicate category slug
|
||||||
|
(unhandled IntegrityError). Workaround: delete the duplicate via Django shell.
|
||||||
### 3. Geocode search returning 401
|
5. **"Use My Location" blocked** — set `Permissions-Policy: geolocation=(self)` on the site block.
|
||||||
GET requests were going through SvelteKit's proxy, which strips the session cookie on redirect. Fixed by routing GET /api/* directly to Django (see Caddy config above).
|
|
||||||
|
|
||||||
### 4. Category creation returning 500
|
|
||||||
AdventureLog has an unhandled `IntegrityError` when a duplicate category slug is created — it returns 500 instead of 400. Workaround: delete duplicate categories via Django shell if this occurs.
|
|
||||||
|
|
||||||
### 5. "Use My Location" button blocked
|
|
||||||
Caddy's `web_secure` snippet sets `Permissions-Policy: geolocation=()`. Changed the `go.jgitta.com` block to use inline headers with `geolocation=(self)` instead.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -99,6 +115,8 @@ Caddy's `web_secure` snippet sets `Permissions-Policy: geolocation=()`. Changed
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Known Limitations / Not Configured
|
## Known Limitations / Not Configured
|
||||||
- No Google Maps API key — geocoding uses OpenStreetMap/Nominatim (works fine)
|
- Images run on `:latest` with Watchtower auto-update — an update on 2026-07-02 changed frontend
|
||||||
- No Strava integration
|
auth behavior and broke the old routing. Consider pinning image versions.
|
||||||
- B2/Kopia backup not configured for this stack
|
- No Google Maps API key — geocoding uses OpenStreetMap/Nominatim (works fine).
|
||||||
|
- No Strava integration.
|
||||||
|
- B2/Kopia backup not configured for this stack.
|
||||||
|
|||||||
Reference in New Issue
Block a user