diff --git a/README.md b/README.md index f713b40..7fa82a7 100644 --- a/README.md +++ b/README.md @@ -101,9 +101,7 @@ The server serves the SPA from `frontend/dist` in production. All non-`/api/*`, non-`/assets/*` requests fall through to `index.html` so client-side routing still works. -## Deployment - -### Container (podman) +## Container (podman) The provided `Containerfile` builds a single image that serves API + frontend and persists the SQLite database in `/app/data` (one volume). @@ -126,107 +124,7 @@ podman run --replace --name vinterliste \ ``` The container exposes `/api/health` for healthchecks and bakes the build date / -git revision into both OCI labels and `/etc/build-info`. Use `podman run ---replace ...` for redeploys — it's atomic and avoids the "container exists" -race. - -### Environment variables - -| Variable | Default | Notes | -|--------------------|------------------------|---------------------------------------------------------------| -| `PORT` | `3000` | TCP port the server listens on. | -| `NODE_ENV` | (unset) | Set to `production` to serve `frontend/dist` from the API. | -| `VINTERLISTE_DB` | `data/vinterliste.db` | Path to the SQLite file. Override for an external volume. | -| `PUBLIC_BASE_URL` | (derived from request) | Override the absolute URL used in OpenGraph `og:url` tags. | - -There are no secrets to set. Auth verifiers and DEK wraps live in the SQLite -file; session tokens are generated per process and stored server-side, not -signed. - -### TLS termination - -The app speaks plain HTTP — terminate TLS at a reverse proxy (Caddy, nginx, -Traefik). The session cookie is marked `Secure` when the request was HTTPS -(`X-Forwarded-Proto: https`), so make sure the proxy sets that header. - -Sample Caddyfile: - -```caddyfile -vinterliste.example.org { - encode zstd gzip - reverse_proxy localhost:3000 -} -``` - -Caddy auto-provisions a Let's Encrypt cert. Other proxies need the cert -configured manually. - -### Backup and restore - -The SQLite database is the entire app state — user accounts, DEK wraps, -activity ciphertexts, sessions, the lot. Backing it up while the server is -running is safe because of WAL mode: - -```bash -# Atomic backup using SQLite's built-in copy -sqlite3 data/vinterliste.db ".backup '/path/to/backup/vinterliste-$(date +%F).db'" - -# Or via the container's volume -podman exec vinterliste sqlite3 /app/data/vinterliste.db \ - ".backup '/app/data/backup-$(date +%F).db'" -``` - -Plain file copy of the `.db` works too if the server is stopped first. With WAL -files (`.db-wal`, `.db-shm`) present, copy all three or use `.backup`. - -To restore: replace the file on disk and restart the server. There are no -out-of-band caches. - -### Healthcheck - -`GET /api/health` returns `{ ok: true, build: { revision, built_at } }` with -HTTP 200. Hook your monitoring or `HEALTHCHECK` directive at this endpoint. - -### Upgrading - -1. Build a new image with current `BUILD_DATE` and `GIT_REVISION` args. -2. `podman run --replace` — schema migrations are idempotent - (`CREATE TABLE IF NOT EXISTS …` and `ensureColumn(...)` add new columns - without touching existing data). -3. Verify `/api/health` returns the new `revision`. -4. The `activities` table's CHECK constraint includes all visibility values; - the `friends` visibility added later is migrated in via - `ensureActivitiesCheckIncludesFriends()` (table copy-drop-rename) on - first boot if needed. Take a backup beforehand the first time you upgrade - past a CHECK-constraint change. - -### Emergency password reset (CLI) - -If an admin has lost access (forgotten password, lost recovery code, etc.) and -can't recover via the UI, the server box has a CLI tool: - -```bash -# Inside the container: -podman exec -it vinterliste bun run reset-password admin@example.org - -# Or on the host if you're running the server directly: -bun run reset-password admin@example.org -``` - -It asks one question first: **do you still have this user's recovery code?** - -- **Yes → recovery mode.** Behaves exactly like the in-app recovery flow: - unwraps the existing DEK with the recovery code, re-wraps it with the new - password. No data is lost. The recovery code stays valid afterwards. -- **No → nuke mode.** Generates a brand-new DEK + new recovery code and - prints the new code to stdout (write it down — it's shown once). The - user's **private activities are deleted** because their ciphertext was - encrypted with the now-unrecoverable old DEK. Public, semi, friends-only - activities, plus hearts / bookmarks / "gjort" marks, are kept. - -Both modes invalidate every existing session for the user, matching the -hygiene of the in-app `/auth/recovery-complete` endpoint. The CLI requires -direct DB access — there is no network exposure of this code path. +git revision into both OCI labels and `/etc/build-info`. ## Registration: open, invite-only, or both diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte index 324a90b..3534856 100644 --- a/frontend/src/App.svelte +++ b/frontend/src/App.svelte @@ -3,7 +3,6 @@ import { ready } from './lib/crypto'; import { api, ApiError } from './lib/api'; import { session, setSessionUserOnly } from './lib/session.svelte'; - import { goBack } from './lib/navigate'; import { logout } from './lib/auth'; import Login from './components/Login.svelte'; import Signup from './components/Signup.svelte'; @@ -139,18 +138,7 @@ // No session — fine. } - // Cold-load redirect: a logged-in user landing on the public landing - // probably wants their own dashboard, not the marketing-y "what is this" - // page. We only redirect on this initial mount — not on every - // applyRoute call — so browser-back from /hjem to / still lets the - // explicit navigation through (no loop, and the wordmark intentionally - // sends logged-in users to /hjem instead of / anyway). - if (route.view === 'public-home' && session.user) { - pushUrl('/hjem'); - view = 'home'; - } else { - applyRoute(route); - } + applyRoute(route); }); function applyRoute(route: Route) { @@ -176,20 +164,22 @@ } } + function leaveTag() { + // Same logic as leavePersonvern — back to wherever they were. + if (session.user) goHome(); + else goPublicHome(); + } + function goPersonvern() { pushUrl('/personvern'); view = 'personvern'; } - /** - * Back-button handler for sub-views (permalink, tag page, personvern, - * public list). Uses real browser history so the user returns to - * wherever they came from in the SPA — /hjem, /etiketter/foo, - * /aktivitet/bar, anywhere. Falls back to /hjem (or / when anonymous) - * on cold-loads where there's no prior history entry. - */ - function backToCallerOrHome() { - goBack(session.user ? '/hjem' : '/'); + function leavePersonvern() { + // Send the visitor wherever they "would have been" — landing if logged out, + // dashboard if logged in. Either is more useful than staying on the doc page. + if (session.user) goHome(); + else goPublicHome(); } function onAuthed() { @@ -217,8 +207,7 @@