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
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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
4d28a82937
commit
e36b70d8d4
@@ -171,6 +171,16 @@ whole-batch DDL rollback, Drizzle breakpoint execution, real advisory-lock concu
|
||||
timeouts, lock release after failure, exact destructive approvals, and public-error sanitization.
|
||||
It does not make migrations automatic during Core startup.
|
||||
|
||||
It also proves ADR 0048's retained history for an uninstalled module end to end, with real
|
||||
discovery: Core's committed migrations (the newest one withheld at first) and the module template
|
||||
installed in a modules directory outside the repository are applied, the module writes a row, and
|
||||
its directory is removed. Planning then reports the module's history as retained, and the withheld
|
||||
Core migration applies with the module absent. The module's table and row survive. Reinstalled, the
|
||||
module's history validates strictly again with nothing pending, its row is intact, and a reinstalled
|
||||
package whose applied migration changed is refused. A history row for an absent module is retained
|
||||
and reported, never fatal, while an unknown row belonging to an installed module is still rejected
|
||||
without repair.
|
||||
|
||||
A dedicated set of scenarios exercises the committed Core identity migration against real
|
||||
PostgreSQL: read-only planning on a fresh database, applying it and confirming exactly the four
|
||||
expected `ekklesia_core_*` tables and their indexes exist, a genuine no-op on a second apply, Core
|
||||
@@ -554,6 +564,33 @@ the following pull-request status context to `main` branch protection:
|
||||
CI / migration-smoke (pull_request)
|
||||
```
|
||||
|
||||
## The generic platform image and the reference deployment
|
||||
|
||||
These need Docker and build images locally; nothing is pushed or published.
|
||||
|
||||
- `bash scripts/check-image.sh` builds the production image and proves it is the one generic
|
||||
platform artifact ADR 0048 requires. It checks that the image has:
|
||||
- no `/opt/ekklesia/modules`;
|
||||
- no module template;
|
||||
- no `ekklesia.module.json` outside installed dependencies;
|
||||
- an empty, root-owned `/var/lib/ekklesia/modules`;
|
||||
- `EKKLESIA_MODULES_DIR` set, in the image configuration and in a running process.
|
||||
|
||||
It then builds again from the same checkout with a populated `modules/` directory, and requires
|
||||
the same checks and an identical runtime file system. CI's `image-build` job runs it.
|
||||
- `pnpm deploy:smoke` (`scripts/smoke-deploy.sh`) runs the reference deployment end to end on the
|
||||
generic image with empty module storage. With `EKKLESIA_SMOKE_TOPOLOGY=split`, the same image also
|
||||
runs the installed-module lifecycle:
|
||||
- the module template, packaged as a self-contained test module
|
||||
(`scripts/lib/test-module-package.Dockerfile`, test-only), is installed into module storage;
|
||||
- its migration is planned and explicitly applied, and the API restarted;
|
||||
- a real browser mounts it;
|
||||
- it is removed and the API restarted, and Core still runs, migrations still plan and apply, and
|
||||
its data survives;
|
||||
- it is restored, with its data.
|
||||
|
||||
See [deploy/README.md](../../deploy/README.md#testing-this-deployment).
|
||||
|
||||
## Safe troubleshooting
|
||||
|
||||
Confirm the prerequisites without printing configuration:
|
||||
|
||||
Reference in New Issue
Block a user