70854d234c1428bacc6da1e2138c98d92031ed17
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e36b70d8d4 |
feat: one generic image, with installed modules in installation-owned storage (ADR 0048)
CI / verify (pull_request) Failing after 47s
CI / infrastructure-smoke (pull_request) Skipped
CI / migration-smoke (pull_request) Skipped
CI / image-build (pull_request) Skipped
CI / deploy-smoke (same-origin) (pull_request) Skipped
CI / deploy-smoke (split) (pull_request) Skipped
The production image now contains zero feature modules and is the same for every installation. Installed modules live in module storage that one Core resolver selects from EKKLESIA_MODULES_DIR: the checkout's modules/ when unset, an absolute path that must exist when set (/var/lib/ekklesia/modules in the image). A relative, blank, missing, or unreadable configured directory fails closed with a sanitized error that never contains the value. - Host configuration reads EKKLESIA_MODULES_DIR. The API, migration plan, apply and validate, module validation, module enablement, and Doctor all resolve it the same way; the test-only API override still takes precedence. - Doctor receives Core's migrations directory explicitly instead of deriving it from the modules directory's parent. - The migration runner retains and reports the applied history of a module that is not installed, and plans and applies Core's and present modules' migrations around it. Core's history and every installed module's history stay strict: a missing or changed applied migration, or a downgrade, still fails. No down migrations, no data deletion. - deploy/Dockerfile copies no module and installs nothing for the module template; .dockerignore excludes modules/ and the build refuses a context that contains it; the runtime has an empty root-owned /var/lib/ekklesia/modules and sets EKKLESIA_MODULES_DIR. - deploy/compose.yaml bind-mounts EKKLESIA_MODULES_HOST_DIR read-only into api and migrate, with create_host_path disabled. - scripts/check-image.sh proves the image holds no modules and that a populated modules/ directory leaves its runtime file system identical; CI runs it. - The split deploy smoke now uses the same generic image: it boots with no modules, installs a self-contained test module packaged from the template (test-only), migrates and restarts, removes it (Core runs, planning reports the retained history, data survives), and restores it with its data. Docs: deploy README (module storage, install/update/remove/restore, the runtime modules target), .env.example, API configuration, module development guide, testing, workflow, security, and the architecture overview. Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
54ac6ab83f |
test(deploy): Doctor must check the client-address configuration the API runs
Behind the reference proxy the API runs in trusted-proxy mode, but compose.proxy.yaml gives those variables to `api` only. The documented Doctor command runs as `migrate`, so it validated an empty configuration and reported "Direct client-address mode" for an installation that trusts its proxy. That is what the first production installation saw. Fails today: - deploy.test.ts: the proxy overlay gives `migrate` no client-address environment; - doctor.test.ts: the check does not say whose configuration it read; - smoke-deploy.sh: Doctor through the proxy overlay does not report trusted-proxy mode (Docker). Co-Authored-By: Claude Opus 5.5 <[email protected]> |
||
|
|
167b4c4f14 |
feat(deploy): add the reference deployment Compose project
CI / verify (pull_request) Successful in 54s
CI / infrastructure-smoke (pull_request) Successful in 21s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 27s
CI / deploy-smoke (pull_request) Successful in 42s
Implement ADR 0044's reference deployment as a base Compose file plus overlays, so an installation that already runs a proxy can use the base alone. - compose.yaml: api, postgres, object-storage, and the operator-only migrate (profile ops, plan by default). No published port. api and migrate run init, read-only, tmpfs /tmp, cap_drop ALL, no-new-privileges. postgres is only on the internal data network. - TLS everywhere from one installation-private CA: PostgreSQL TLS 1.3 only, with pg_hba rejecting every non-TLS connection, and the API on verify-full via EKKLESIA_DATABASE_CA (PEM text). SeaweedFS serves S3 over HTTPS (ADR 0030 requires https in production), trusted by the API through NODE_EXTRA_CA_CERTS. No Core change. - compose.proxy.yaml + Caddyfile.example: Caddy pinned by digest, ACME HTTP-01 (DNS-01 documented), routes /, /api/*, /display/*, /assets/* to api and refuses everything else; the storage hostname preserves Host. The API trusts exactly the proxy's fixed /32 on an edge network whose dynamic range excludes the fixed addresses; Caddy overwrites X-Forwarded-For and strips caller-supplied forwarding headers. - compose.tunnel.yaml: cloudflared pinned by digest, token from its own env file; Caddy honors CF-Connecting-IP only from its fixed address. - Four per-service root-owned env files, examples with no values; scripts/init-tls.sh (idempotent, never overwrites a key) and scripts/gen-secrets.sh (prints only). - CI: pnpm deploy:config in ci:verify for all three combinations, and a deploy-smoke job (scripts/smoke-deploy.sh) proving readiness, the Dashboard, signed storage delivery, the client-address chain, initial owner sign-in/out, verify-full database TLS, and Doctor through the proxy, against disposable services. tests/deploy.test.ts pins the structural guarantees. - ADR 0044 (still Proposed) amended to match: API-to-storage HTTPS, DNS-01 needs a custom Caddy build and tunnel-only needs DNS-01, the tunnel's origin TLS settings live in the Cloudflare dashboard, port 80 serves HTTP-01, and secrets are one env file per service. Co-Authored-By: Claude Opus 5.5 <[email protected]> |