- 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>
98 lines
3.8 KiB
Markdown
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.
|