|
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m45s
CD / Build and push images (push) Successful in 3m49s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 11s
CI / Auth e2e pack (push) Successful in 5m35s
CI / Import/export fidelity gate (push) Successful in 47s
Off-host backups for every self-hoster, configured entirely in the admin UI — supersedes the host-specific mirror plan behind #84. shared: - webdav.ts (new package entry like token-crypto): minimal WebDAV client with basic auth — PROPFIND (tolerant multistatus parser), MKCOL, PUT (streamed), GET, DELETE; Nextcloud DAV path derived from the plain server URL, explicit DAV bases pass through - backup-status.ts: additive remote-upload status in status.json, the restore-status.json contract (running/succeeded/failed + staleness bound), the backup_command/backup_maintenance NOTIFY channels, and the one-bundle-per-set naming (dorfteich-backup-<id>.tar.gz) - backup-set.ts moved here from apps/backup (api lists local sets) backup sidecar: - reads the backup.* instance settings directly from the database (admin changes apply next run; local retention row overrides the env) and the app password from the secret store - after each successful set: bundle dump + files archive + manifest into ONE self-contained tar.gz, upload via WebDAV per schedule (off/daily/weekly; manual runs always upload), prune remote bundles — never the newest — and record the outcome in status.json; upload failures alert via a new backupUploadFailed mail (de+en) - command listener on backup_command (run / restore) with a serial queue against the nightly timer - restore orchestrator: restore-status.json → maintenance NOTIFY → grace → (remote: download + manifest-verify bundle) → terminate other DB connections → shared perform-restore path (same code as restore.sh) → final status + maintenance exit api: - MaintenanceGuard (global, registered before the setup gate): 503 maintenance_mode while restore-status says running; health endpoints and the new public GET /backup/restore-status stay exempt; a stale running state (crashed sidecar) unblocks after 30 min - MaintenanceStateService watches the file and restarts the api after a successful restore (fresh caches, migrate-on-start for older dumps); main.ts refuses to touch the database while a restore runs — a container restarting mid-restore must not race pg_restore with migrate deploy - worker sweeps (conversion, mail outbox, scheduler) catch transient database failures instead of dying on an unhandled rejection — the restore's connection termination crashed the api in verification - backup admin endpoints under /admin/system/backup: settings (live connection test before save, password write-only into the secret store), nextcloud/test, sets (local via the ro backups mount + remote via WebDAV), run + restore (type-to-confirm backstop, source validation) — commands travel as NOTIFY payloads; audit actions backup.settings_changed/run_triggered/restore_requested - readyz: new warning-level backup_remote check while a target is configured (26 h daily / 170 h weekly bound) collab: - maintenance listener: on enter, persist + close every live session and refuse new connections until exit (failsafe timeout 30 min) — no in-memory document may write pre-restore content back afterwards web: - Admin → System backup section: status card with remote facts and a "Back up now" button, the Nextcloud settings form with test button, and the restore picker (local + remote sets, type-to-confirm) - global maintenance screen: any 503 maintenance_mode flips the SPA to a status page polling the exempt endpoint, reloading when the instance returns Verified end-to-end against a live stack (fresh DB, native api + sidecar, fake WebDAV server): configure → test → manual backup → bundle upload → readyz/sets/status surfaces → remote restore with maintenance gate, marker rollback and api restart; suites: shared 21, backup 9, collab 11, api 58 files green, lint + i18n:check + typecheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
||
|---|---|---|
| .. | ||
| legal-template.md | ||
| README.md | ||
Self-hosting Dorfteich
Everything you need to install, run, update, and back up your own Dorfteich with Docker — this guide is the complete contract: if a step here does not work, that is a bug (issue #88).
Docs are English-only by decision (issue #88): the product UI is fully localized (de/en), operator documentation is not — one authoritative text beats two drifting ones.
Requirements
- Docker Engine with the Compose plugin (
docker compose version≥ 2.20). - 2 GB RAM, ~2 GB disk for images plus room for your content and backups.
- A domain pointing at the host — TLS via your own reverse proxy or the
bundled
caddyprofile (below). - Outbound SMTP relay (optional at install time: the setup wizard can configure it later, or you skip mail entirely at first).
Install
-
Create a directory and fetch the two reference files from the repository (
deploy/compose/):docker-compose.yml,.env.example— plusCaddyfileif you want thecaddyprofile.mkdir dorfteich && cd dorfteich # copy docker-compose.yml, .env.example (and Caddyfile) here cp .env.example .env && chmod 600 .env -
Edit
.env— the minimum:POSTGRES_PASSWORD,COLLAB_TOKEN_SECRET: long random strings (openssl rand -base64 32).IMAGE_PREFIX=gitea.101010.cloud/stwaidele/dorfteichandTAG: releases are semver tags (v1.2.3); until the first public release,testtracks the latest verified build.APP_BASE_URL=https://wiki.example.com— must be exactly what browsers will use; e-mail links and the CSRF origin check derive from it.- Every other variable is documented inline in
.env.examplewith its default and effect; nothing outside that file configures the stack.
-
Start:
docker compose up -d # behind your own reverse proxy docker compose --profile caddy up -d # or with the bundled TLS ingress- Own proxy: route
/*→127.0.0.1:$WEB_PORT,/api/*→$API_PORT,/collab*→$COLLAB_PORT(WebSocket upgrade required on/collab). caddyprofile: setDOMAIN=wiki.example.comin.env; Caddy publishes 80/443 and obtains Let's Encrypt certificates automatically (both ports must be reachable from the internet).DOMAIN=localhostissues an internal-CA certificate — for smoke tests only.
- Own proxy: route
-
Open
https://your-domain/— a fresh instance shows the first-run setup wizard.
First-run wizard
Six steps, all in the browser (issue #80/#81): language → the Site-Admin
account (created verified, you are signed in immediately) → instance name
and default language → SMTP relay (Save runs a live test and sends a
test mail to you; Skip leaves mail unconfigured — sign-up verification
will not work until an admin adds a relay) → registration mode
(open/closed) → summary + finish. The wizard locks itself permanently on
completion. Until it completes, the api answers everything but the wizard
and health endpoints with 503 setup_required — that is not an error.
Unattended installs skip the wizard by pre-seeding: set the
SETUP_ADMIN_* variables in .env before the first start (see
.env.example).
Updating
# edit .env: TAG=v1.3.0
docker compose pull && docker compose up -d
Database migrations run automatically at api start. Release notes flag
releases with a migration label and any manual steps. Downgrade
window: one minor release — TAG back + pull + up -d is supported
one step back; further back, restore the backup taken before the update
instead (the nightly sidecar gives you one at most 24 h old).
Backups & restore
Enabled by default (ADR 0015): the backup sidecar dumps the database and
archives the uploads/plugins volumes nightly at BACKUP_TIME onto the
backups volume, prunes by BACKUP_RETENTION_DAYS, writes status.json,
and — with BACKUP_MAIL_TO set — mails you on failure.
- On-demand backup:
docker compose run --rm -e BACKUP_RUN_ONCE=1 backup, or the Back up now button under Admin → System. - List sets:
docker compose exec backup ls /backups - Restore:
./restore.sh <backup-id>(fetchdeploy/backup/restore.shnext to your compose file) — details indocs/operations/restore-runbook.md.
Off-host copies to a Nextcloud
Get the backups off the host — a backup on the same disk protects against mistakes, not against losing the host. Any Nextcloud you can reach works as the target; configure it entirely in the admin UI (Admin → System → Backups):
- In Nextcloud, create an app password for the account that should hold the backups (Settings → Security → Devices & sessions).
- In Dorfteich, enable Upload backups to Nextcloud, enter the plain
Nextcloud address (e.g.
https://cloud.example.com), the username, the app password and a folder, and use Test connection — it verifies the credentials and creates the folder. The password is kept in the secret store on thesecretsvolume, never in the database. - Pick the upload schedule (after every nightly backup, weekly, or manual only) and the retention for both sides. After each successful upload, old remote bundles beyond the retention are pruned — never the newest one.
Each upload is ONE self-contained archive
(dorfteich-backup-<id>.tar.gz = database dump + files archive +
manifest) — everything needed to rebuild the instance after total loss.
readyz warns (backup_remote check) when the off-host copy grows stale,
and upload failures alert through the backup failure mail.
Restore from the admin UI: Admin → System → Backups → Restore lists
local and Nextcloud sets. Restoring asks you to re-type the backup id,
then the instance enters maintenance mode (everything answers 503 plus a
status page), restores itself through the backup sidecar, and restarts.
If the app itself is gone, use the operator path in
docs/operations/restore-runbook.md instead — it documents fetching a
bundle from Nextcloud by hand.
Health & troubleshooting
GET /api/v1/readyzis the instance's own diagnosis. HTTP 503 = database/migrations broken (the instance cannot serve). HTTP 200 with"status":"degraded"= a warning-level check:converter/rendererdown (import/export/PDF degrade, everything else works) orbackupstale (last success older than 26 h). Each check carries adetail. Monitor set:deploy/monitoring.md.- Logs:
docker compose logs api(orweb,collab,backup,db) — structured JSON, rotated by Docker. - Proxy pitfalls: editor never connects / "offline" although the page
loads → the proxy does not upgrade WebSockets on
/collab. All mutations fail with 403csrf_origin_mismatch→APP_BASE_URLdoes not match the URL in the browser (scheme and host must be identical). E-mail links point at the wrong host → same variable. - Wizard reappears after a restart → the database volume was not
persisted; never run without the
db-datavolume. docker compose psshowsunhealthy→ that container's liveness check fails; a degraded readyz alone never marks containers unhealthy and never restarts anything.
Building from source instead
Clone the repository and build the images locally — the reference compose carries the build contexts already:
docker compose build && docker compose up -d
Same layout, same volumes; you trade the registry pull for a local toolchain (Node 22 build stages run inside Docker, nothing else needed).
Verified install
The guide is verified by a scripted clean-machine run
(deploy/self-hosting-verify.sh): fresh directory, reference compose +
.env.example only, --profile caddy with an internal-CA certificate,
asserting that the wizard answers over TLS. Run it yourself on any Docker
host — it uses its own compose project name and high ports, then removes
everything.