|
Some checks failed
CI / Lint, typecheck, test (push) Failing after 1m39s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m51s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 11s
Token-authenticated machine access at /api/public/v1 — the foundation for the built-in MCP endpoint (#105). Personal access tokens: - api_tokens table (SHA-256 hash, scope read|write, optional pond restriction, expiry, revocation, throttled last-used) + migration; secrets are dt_pat_<random>, shown exactly once - lifecycle endpoints under /users/me/api-tokens (session-only — a leaked token can never mint more tokens) with audit entries api.token_created/api.token_revoked - settings UI section (create with scope/expiry/pond restriction, one-time reveal with copy, list with status + revoke), de+en Activation (404 semantics per #60 on both levels): - instance setting api.enabled (default off, admin settings switch) - pond setting apiEnabled (default off, pond settings toggle; the PondsService settings-merge learned the key — the #92 lesson) Surface (/api/public/v1, excluded from the SPA's global prefix): - me, ponds, pages (list/read as Markdown+HTML, create from Markdown via the shared pipeline, PATCH title/content, DELETE to trash), search (permission-filtered + narrowed to exposed ponds, highlights as **…**), markdown ZIP export, labels (tree, create/rename/recolour/move/delete, assign/unassign), comments (threads, create, resolve/reopen) - content replacement travels the collab-owned document path: the new state lands as a MANUAL version "API update", then the established restore NOTIFY applies it — open editors converge, history stays append-only, no second lineage (VersionsService.replaceContent) - hand-maintained OpenAPI 3.1 document at /openapi.json, pinned to the controller by a route-coverage test in both directions Enforcement: - PublicApiGuard: instance switch → bearer PAT auth (request.user is the token's user) → per-token rate limit (429 + Retry-After) → scope (403 scope_required) → pond opt-in + token restriction - the shared PermissionGuard then applies the unchanged permission model; PageParamSource gained pondSlugParam for the slug+slug routes - no cookies anywhere → no CSRF surface (pinned by a hostile-Origin test) - every write audit-logged as api.write with the token attributed Tests/verification: - 12-test e2e pack: lifecycle, switches, permission matrix (reader/editor/outsider × scopes), restriction, page roundtrip incl. restore-NOTIFY assertion, labels, comments incl. policy, search narrowing, ZIP export, rate limit; full api suite 60/60 green (quota fixture via per-user override — never the instance default) - new collab-pack test proves an open editor converges onto an API content replacement (green against a local seeded stack) - UI smoke against the built SPA: token create/reveal/revoke, pond opt-in persists, admin switch persists (10/10) - docs/self-hosting/public-api.md + README link Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
||
|---|---|---|
| .. | ||
| legal-template.md | ||
| public-api.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.
Public REST API
Scripts and integrations can talk to the instance through a
token-authenticated API at /api/public/v1 — off by default, enabled per
instance and per pond. Details, token walkthrough, and the OpenAPI
document: public-api.md.
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.