issue-222-read-trail
6 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 5cef359b8f |
Nextcloud backup target: admin-configured, manual + scheduled uploads, in-app restore (#103)
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 |
|||
| 0ef96147e0 |
Extend readyz with backup freshness and a degraded status level (#85)
All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m11s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m44s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 10s
CI / Auth e2e pack (push) Successful in 5m13s
CI / Import/export fidelity gate (push) Successful in 46s
readyz now enumerates database/migrations (hard failures, HTTP 503), converter/renderer, and a new backup check that reads the sidecar's status.json from the read-only backups mount and warns when the last successful backup is older than 26 h. Warning-level checks surface as overall status "degraded" while staying HTTP 200 — monitors alert on the body keyword, Docker healthchecks keep using the liveness endpoints so a degraded instance is never restart-looped. The status.json shape moved to @dorfteich/shared as the contract between the sidecar and its readers (#85/#86); deploy/monitoring.md defines the Uptime-Kuma monitor set per stage. The api image also pre-creates /data/backups node-owned so the shared backups volume stays writable for the sidecar regardless of which container initializes it, and the sidecar's scheduler survives runs that cannot even record their status. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| f0a82bad20 |
Add the first-run setup wizard API with env-backed secret store (#80)
Some checks failed
CD / Build and push images (push) Successful in 3m16s
CI / Lint, typecheck, test (push) Successful in 3m5s
CD / Deploy to Test (push) Successful in 13s
CD / Smoke tests against Test (push) Failing after 3m35s
CD / Promote to Int (push) Has been skipped
CI / Auth e2e pack (push) Successful in 5m6s
CI / Import/export fidelity gate (push) Successful in 43s
CI / Build container images (push) Has been skipped
When the api runs against a database without the setup.completedAt marker, a global SetupGuard answers every non-exempt route with 503 setup_required; only /setup/*, health probes, and the session routes stay reachable. The wizard steps (POST /setup/admin|instance|smtp| registration|complete) write straight to their production homes; the Site Admin step signs its creator in, later steps require that session. Completing sets the marker and locks every step permanently (410, also across restarts, and not reopenable via PATCH /admin/settings). SMTP entered in the wizard is verified with a live delivery test first (failure blocks the step with the transport error as detail) and then persisted to the new env-backed secret store: a mode-600 dotenv file on the new `secrets` volume (SECRETS_FILE). Explicit container env always wins over the store; empty compose-passed strings count as unset. The mail transport now resolves lazily through SmtpConfigService so wizard changes apply without a restart. SETUP_ADMIN_* env pre-seeds the whole wizard at boot for automated deploys; a backfill migration marks instances that already have a Site Admin as completed, and seed/vitest global-setup do the same for fixture databases. The setup e2e suite provisions its own fresh database (CREATE DATABASE + migrate deploy) per run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1 |
|||
| bed9fc9307 |
Add authentication: signup, verification, sessions, password reset
AuthModule implements the M1 core as one coherent unit: Signup (#13): POST signup/verify-email/resend-verification with shared Zod validation (field-level error details), double opt-in via hashed single-use tokens (24h, superseding reissue), registration_mode enforcement, and per-IP rate limits. Sessions (#14): opaque 32-byte cookie tokens stored as SHA-256 row ids, sliding 30-day expiry (refresh at most hourly), global AuthGuard with @Public() opt-out attaching the user to every request, CSRF origin check on mutating requests, per-account login backoff (5/15min, reset on success), generic 401 for wrong-vs-unknown credentials, logout with immediate invalidation, GET /auth/me. Reset (#15): forgot-password without account enumeration, one-hour single-use tokens, reset destroys all existing sessions. A 14-case supertest e2e suite drives every flow against the test database, reading verification/reset links from the mail outbox. Closes #13 Closes #14 Closes #15 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| ca0f7cf4b1 |
Add Prisma with PostgreSQL, automatic migrations, and /readyz
apps/api gains Prisma (instance_settings as the first model) with the initial migration applied automatically at startup via prisma migrate deploy, a lazy-connecting PrismaService, and GET /api/v1/readyz reporting named checks (database reachable, migrations applied) with 200/503. DATABASE_URL joins the validated environment schema; MIGRATE_ON_START=false skips deploys for tests and tooling. An idempotent seed script and a Compose dev overlay with PostgreSQL (host port 5434 — 5433 is taken locally) complete the loop. Closes #3 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
|||
| c12acbdb2c |
Add NestJS API skeleton with config, logging, and /healthz
apps/api boots a NestJS application with: Zod-validated environment configuration (schema in @dorfteich/shared, fails fast listing every invalid variable), structured pino request logging via nestjs-pino (pretty in development, JSON otherwise, auth headers redacted), a global exception filter producing the uniform ApiErrorBody shape, and GET /api/v1/healthz. Vitest runs Nest through SWC for decorator metadata; supertest covers healthz and the 404 error shape. Closes #2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |