From e895db32463b6aaa6b6e947e8745df1b3dcb2292 Mon Sep 17 00:00:00 2001 From: Jozsef Gtta Date: Thu, 2 Jul 2026 18:04:00 +0000 Subject: [PATCH] AdventureLog: correct to final split config + slash-rewrite; document Google Maps enablement --- AdventureLog-Setup-Summary.md | 128 ++++++++++++++++------------------ 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/AdventureLog-Setup-Summary.md b/AdventureLog-Setup-Summary.md index d613905..481d080 100644 --- a/AdventureLog-Setup-Summary.md +++ b/AdventureLog-Setup-Summary.md @@ -2,7 +2,7 @@ ## What It Is AdventureLog is a self-hosted travel logging app with a SvelteKit frontend and Django/PostGIS backend. -Live at: **https://go.jgitta.com** +Live at: **https://go.jgitta.com** — version **v0.12.1**. --- @@ -16,20 +16,21 @@ Live at: **https://go.jgitta.com** | Backend container | `adventurelog-backend` — port 8016 (Django/nginx → gunicorn :8000) | | Database container | `adventurelog-db` (PostGIS 16-3.5) | | Data volumes | `/srv/docker/adventurelog/postgres_data/` and `/srv/docker/adventurelog/media/` | -| Reverse proxy | Caddy VM (192.168.88.110) — `go.jgitta.com` block in `/etc/caddy/sites/infrastructure.caddy` | +| Reverse proxy | Caddy VM (192.168.88.110) — `go.jgitta.com` in `/etc/caddy/sites/infrastructure.caddy` | --- -## Caddy Routing (Corrected 2026-07-02 — now matches official docs) +## Caddy Routing (final, verified 2026-07-02) -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. +Use the **split** config below. The browser holds a valid Django session, so routing reads +directly to Django works. Routing reads through the SvelteKit frontend instead reintroduces a +trailing-slash redirect that breaks the category dropdown — do **not** switch to the official +docs' single-proxy config. -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: +The key fix: the app requests `/api/...` **without a trailing slash**; Django (DRF) then answers +`301` to add it, and that redirect breaks the browser fetch (empty search / empty categories). The +`@api_read` block rewrites the path to add the slash **internally**, so Django answers `200` +directly with no redirect. ```caddy go.jgitta.com { @@ -40,83 +41,74 @@ go.jgitta.com { Permissions-Policy "geolocation=(self), microphone=(), camera=()" } - # Backend (Django): media, admin, static files, legacy account pages - @backend path /media* /admin* /static* /accounts* - handle @backend { - reverse_proxy http://192.168.88.27:8016 { import proxy_timeouts } + # GET/HEAD/OPTIONS /api/* -> Django directly (browser sends session cookie) + @api_read { method GET HEAD OPTIONS; path /api/* } + handle @api_read { + # DRF needs a trailing slash; add it internally to avoid a 301 that breaks the fetch + @api_noslash path_regexp apinoslash ^(/api/(?:.*/)?[^/.]+)$ + rewrite @api_noslash {re.apinoslash.1}/ + reverse_proxy http://192.168.88.27:8016 { + import proxy_timeouts + header_up Accept "application/json" + } } - # Everything else (pages, /api/*, /auth/*) -> SvelteKit frontend, - # which proxies API/auth to Django server-side with session + CSRF. - handle { - reverse_proxy http://192.168.88.27:8015 { import proxy_timeouts } - } + # POST/PUT/DELETE /api/* + pages -> SvelteKit frontend + @frontend { not path /media* /admin* /static* /accounts* } + handle @frontend { reverse_proxy http://192.168.88.27:8015 { import proxy_timeouts } } + + # Django: media, admin, static, auth pages + handle { reverse_proxy http://192.168.88.27:8016 { import proxy_timeouts } } } ``` -**`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. +`geolocation=(self)` (inline) is required for the "Use My Location" button. +Backup of prior config: `/etc/caddy/sites/infrastructure.caddy.bak-20260702-041914`. --- -## Portainer Environment Variables +## Geocoding — Google Maps (enabled 2026-07-02) -All env vars are stored in Portainer's environment section (not hardcoded in compose). Key ones: +Switched from public OpenStreetMap/Nominatim to Google Maps. Nominatim was flaky: the app fires one +search per keystroke, and public Nominatim returned inconsistent/empty results for partial queries +and rate-limited the rapid autocomplete. + +- `GOOGLE_MAPS_API_KEY` is set in Portainer stack #47 (env + `server` service in compose). +- Google Cloud account: **jgitta@gmail.com**. Required APIs enabled: **Geocoding API** and + **Places API (New)**. +- Provider is automatic: key present → Google (`search_google`, `reverse_geocode_google`); + otherwise OSM. Google also enriches locations (photos, rating, website, phone, description). + +--- + +## Portainer Environment Variables (stack #47) | Variable | Value | |----------|-------| | PUBLIC_SERVER_URL | http://server:8000 | -| ORIGIN | https://go.jgitta.com | -| PUBLIC_URL | https://go.jgitta.com | -| CSRF_TRUSTED_ORIGINS | https://go.jgitta.com | -| FRONTEND_URL | https://go.jgitta.com | -| FRONTEND_PORT | 8015 | -| BACKEND_PORT | 8016 | +| ORIGIN / PUBLIC_URL / CSRF_TRUSTED_ORIGINS / FRONTEND_URL | https://go.jgitta.com | +| FRONTEND_PORT / BACKEND_PORT | 8015 / 8016 | | BODY_SIZE_LIMIT | Infinity | -| POSTGRES_DB | adventurelog | -| POSTGRES_USER | adventure | -| DEBUG | False | -| DISABLE_REGISTRATION | False | +| POSTGRES_DB / POSTGRES_USER | adventurelog / adventure | +| DEBUG / DISABLE_REGISTRATION | False / False | +| GOOGLE_MAPS_API_KEY | (set — Google Maps enabled) | -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). +Secrets (DB password, SECRET_KEY, admin creds, Google key) live in Portainer. `DJANGO_ADMIN_PASSWORD` +is only the bootstrap password; the live admin password was changed in-app. --- -## Bugs Fixed During Setup +## Known Bug (reported upstream) -1. **CSRF errors on login** — `CSRF_TRUSTED_ORIGINS` must match the public URL exactly. Set 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/*` - through the SvelteKit frontend (official design), not directly to Django. -4. **Category creation 500** — AdventureLog returns 500 (not 400) on duplicate category slug - (unhandled IntegrityError). Workaround: delete the duplicate via Django shell. -5. **"Use My Location" blocked** — set `Permissions-Policy: geolocation=(self)` on the site block. +Selecting a location **search result** overwrites the location **Name** with the reverse-geocoded +nearest-object name instead of keeping the search result's name. With OSM this produced "39" (a +campsite tagged near Elk Rock State Park's center). The app reverse-geocodes the selected result's +coordinates and lets that name win. Workaround: retype the Name after selecting. Report drafted in +`AdventureLog-BugReport-location-name-overwrite.md`. --- -## Admin Access -- **URL**: https://go.jgitta.com/admin -- **Username**: admin -- **Django shell**: `docker exec -it adventurelog-backend python manage.py shell` - ---- - -## Known Limitations / Not Configured -- Images run on `:latest` with Watchtower auto-update — an update on 2026-07-02 changed frontend - auth behavior and broke the old routing. Consider pinning image versions. -- No Google Maps API key — geocoding uses OpenStreetMap/Nominatim (works fine). -- No Strava integration. -- B2/Kopia backup not configured for this stack. +## Admin / Notes +- Admin: https://go.jgitta.com/admin (user `admin`). Django shell: `docker exec -it adventurelog-backend python manage.py shell`. +- Images run `:latest` with Watchtower auto-update — an update on 2026-07-02 changed frontend behavior mid-debug. Consider pinning versions. +- Not configured: B2/Kopia backup for this stack, Strava.