Files
Mangalord/docs/backup.md
MechaCat02 57364fae32 chore: release-prep docs, env vars, compose, and e2e port hygiene
- README rewritten end-to-end: stack, quick start, dev workflow, full
  /api/v1 endpoint table, error and pagination envelopes, auth
  quick-start (browser + bot bearer), configuration table, deployment
  notes, backup/restore pointer. Stale "next features" section dropped
  now that all eight feat branches are in.
- .env.example now lists every env var the backend reads, with
  inline explanations:
  - COOKIE_SECURE / COOKIE_DOMAIN / SESSION_TTL_DAYS (auth)
  - CORS_ALLOWED_ORIGINS (same-origin by default)
  - MAX_REQUEST_BYTES / MAX_FILE_BYTES (upload caps)
  - Postgres + storage + log vars carried over.
- docker-compose.yml forwards all of the above into the backend
  service with `${VAR:-default}` so an unset value falls back to the
  same default the code uses, and any `.env` override flows through
  without a compose edit.
- docs/backup.md: step-by-step backup, restore, and smoke-test drill
  for both stateful volumes (postgres-data + storage-data), plus a
  list of what's deliberately *not* in the backup (e.g., .env).
- playwright.config.ts: pins the e2e dev server to port 5174 with
  `--strictPort` so it neither reuses nor silently bumps off
  collision with another vite instance on 5173. Drops the flaky
  manual-start workflow the earlier branches needed.
- docker-compose syntax (both prod and dev) validates cleanly against
  .env.example with no undefined-variable warnings.

No version bump — this is documentation, config, and tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 22:58:49 +02:00

98 lines
3.8 KiB
Markdown

# Backup and restore
Mangalord persists state in two places:
1. **Postgres** (`postgres-data` volume) — users, mangas, chapters, pages, bookmarks, sessions, tokens.
2. **Storage** (`storage-data` volume) — cover and page image bytes. Keys in the DB reference paths inside this volume; a backup that has the DB without the storage (or vice versa) is incomplete.
Always back both up together, and restore them together.
## Backing up
Run while the stack is up. The compose service name is `postgres`; if you renamed it, substitute below.
```bash
# 1. Dump Postgres. `--format=custom` produces a single compressed file that
# pg_restore can stream.
docker compose exec -T postgres pg_dump \
-U "${POSTGRES_USER:-mangalord}" \
-d "${POSTGRES_DB:-mangalord}" \
--format=custom \
> mangalord-$(date -u +%Y%m%dT%H%M%SZ).dump
# 2. Tar the storage volume. The simplest way is to mount the named volume
# into an ephemeral container and tar from there.
docker run --rm \
-v mangalord_storage-data:/source:ro \
-v "$PWD":/backup \
alpine \
tar czf "/backup/mangalord-storage-$(date -u +%Y%m%dT%H%M%SZ).tar.gz" -C /source .
```
Both files are timestamped so consecutive runs don't overwrite. Copy them off-host (rsync, rclone, your object store of choice) — keeping them on the same machine doesn't help if the disk fails.
> The volume's prefix (`mangalord_`) comes from the compose project name, which defaults to the directory name. Confirm with `docker volume ls | grep storage-data`.
## Restoring
The procedure is destructive; do it on a fresh stack or after confirming you really want to overwrite the current state.
```bash
# 0. Stop the stack so nothing writes during the restore.
docker compose down
# 1. Wipe the existing volumes and recreate them. (Skip this if restoring
# onto a host that doesn't have prior state.)
docker volume rm mangalord_postgres-data mangalord_storage-data || true
# 2. Bring Postgres up alone so we can restore into it before the backend
# starts and runs migrations against the wrong schema.
docker compose up -d postgres
# Wait for it to become healthy.
until docker compose exec -T postgres pg_isready -U "${POSTGRES_USER:-mangalord}" >/dev/null; do
sleep 1
done
# 3. Restore the dump.
docker compose exec -T postgres pg_restore \
-U "${POSTGRES_USER:-mangalord}" \
-d "${POSTGRES_DB:-mangalord}" \
--clean --if-exists \
< mangalord-YYYYMMDDTHHMMSSZ.dump
# 4. Restore the storage tarball.
docker run --rm \
-v mangalord_storage-data:/target \
-v "$PWD":/backup \
alpine \
sh -c "cd /target && tar xzf /backup/mangalord-storage-YYYYMMDDTHHMMSSZ.tar.gz"
# 5. Start the rest of the stack.
docker compose up -d backend frontend
```
The backend re-runs migrations on startup. They're idempotent (additive), so restoring a dump from an older schema and then starting a newer backend will forward-migrate the data; the reverse is not supported.
## Restore drill
It's worth running this end-to-end at least once before you need it for real. A minimal smoke check after restore:
```bash
# Health endpoint should respond.
curl -sf http://localhost:8080/api/v1/health
# An expected manga should be visible.
curl -s 'http://localhost:8080/api/v1/mangas?limit=1' | jq '.items[0].title, .page.total'
# A known cover or page should stream back with the right Content-Type.
curl -sI http://localhost:8080/api/v1/files/<known-key>
```
If `.page.total` matches your expectation and a known file is reachable, the restore is complete.
## What's *not* in the backup
- The `.env` file — keep this in your password manager / secrets store; the backup deliberately doesn't include it so a leaked archive doesn't leak credentials.
- Application logs — stream them to your usual log sink (journald, Loki, whatever) rather than relying on container logs.
- Anything outside the two named volumes.