From bc15f00c4433760f48ce72ca9184cea1f486a36b Mon Sep 17 00:00:00 2001 From: lilleman Date: Tue, 16 Jun 2026 17:13:40 +0200 Subject: [PATCH] =?UTF-8?q?Add=20postgres=20service=20(todo=20=C2=A73);=20?= =?UTF-8?q?pin=20postgres:18.4-alpine3.23,=20one=20DB=20per=20Kratos/Keto/?= =?UTF-8?q?Hydra=20via=20init.sql?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + compose.yml | 22 ++++++++++++++++++++++ ory/postgres/init/init.sql | 6 ++++++ src/postgres.test.ts | 23 +++++++++++++++++++++++ todo.md | 2 +- 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 ory/postgres/init/init.sql create mode 100644 src/postgres.test.ts diff --git a/README.md b/README.md index 084bb28..3022d17 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,7 @@ src/menu-config.ts loadMenuConfig()/defineMenu(): read config/menu.ts (central views/ Core EJS templates (index = the app-shell People dashboard, 403/404/500, partials/ incl. app shell, nav tree, filter bar, data table, pagination, form field, auth card, menu/popover, theme switch, icon sprite) public/ Static assets under /public/ (css/styles.css + auth.css, favicon, robots.txt) config/menu.ts Central menu override + branding (optional; defaults apply if absent) +ory/ Ory service config + storage init (postgres/init/init.sql: one DB per Kratos/Keto/Hydra) plugins/ Drop-in plugin folders (scanned at /app/plugins; bind-mount or bake in) (planned) docs/ Reference docs (plugin-contract.md — the authoritative plugin API) e2e/ Playwright visual + functional E2E (Dockerfile.e2e + compose.e2e.yml run it) diff --git a/compose.yml b/compose.yml index 012a87c..294bb68 100644 --- a/compose.yml +++ b/compose.yml @@ -11,3 +11,25 @@ services: CACHE_TEMPLATES: "true" REQUIRE_SECURE_SECRETS: "true" restart: unless-stopped + + # Ory's storage only (Kratos/Keto/Hydra) — the web app never connects here. + # init/init.sql creates one database per service. Dev defaults below; supply + # POSTGRES_USER/PASSWORD via env in production. + postgres: + image: postgres:18.4-alpine3.23 + environment: + POSTGRES_USER: ${POSTGRES_USER:-ory} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-ory} + POSTGRES_DB: ory + volumes: + - ./ory/postgres/init:/docker-entrypoint-initdb.d:ro + - pgdata:/var/lib/postgresql # PG18+: mount the parent, not /data (version-subdir layout) + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-ory} -d ory"] + interval: 5s + timeout: 5s + retries: 10 + restart: unless-stopped + +volumes: + pgdata: diff --git a/ory/postgres/init/init.sql b/ory/postgres/init/init.sql new file mode 100644 index 0000000..e058258 --- /dev/null +++ b/ory/postgres/init/init.sql @@ -0,0 +1,6 @@ +-- Runs once on first boot (docker-entrypoint-initdb.d), as the POSTGRES_USER. +-- One database per Ory service: each owns its schema and runs its own migrations, +-- so they never collide. The web app never connects here (stateless — see README). +CREATE DATABASE kratos; +CREATE DATABASE keto; +CREATE DATABASE hydra; diff --git a/src/postgres.test.ts b/src/postgres.test.ts new file mode 100644 index 0000000..c9deffd --- /dev/null +++ b/src/postgres.test.ts @@ -0,0 +1,23 @@ +// Guards the Ory Postgres config (§3): image stays pinned to an exact version +// (AGENTS.md rule) and each Ory service keeps its own database. Real container +// behaviour is verified by booting postgres in CI/e2e; this catches edits. +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; + +const read = (p: string) => readFileSync(new URL(`../${p}`, import.meta.url), "utf8"); +const ORY_DATABASES = ["hydra", "keto", "kratos"]; // one DB per Ory service + +test("compose pins the postgres image to an exact version", () => { + const tag = read("compose.yml").match(/image:\s*postgres:(\S+)/)?.[1]; + assert.ok(tag, "compose.yml pins a postgres image"); + assert.match(tag, /^\d+\.\d+/, `${tag} pins major.minor`); + assert.doesNotMatch(tag, /latest|[\^~*]/, `${tag} is exact, not floating`); +}); + +test("init SQL gives each Ory service its own database", () => { + const sql = read("ory/postgres/init/init.sql"); + for (const db of ORY_DATABASES) { + assert.match(sql, new RegExp(`CREATE DATABASE ${db}\\b`, "i"), `creates ${db}`); + } +}); diff --git a/todo.md b/todo.md index f858486..06adfb3 100644 --- a/todo.md +++ b/todo.md @@ -57,7 +57,7 @@ everything via Docker. - [x] Go over all tests and combine/unify ones that cover the same stuff or are very related and could be combined in a good way. Remove tests that aren't helping, we only want tests that are actually helpful to us. → Reviewed all 24 test files. The suite already follows the deliberate per-module "matrix + edge" pattern from the §0/§1 merge (line 22), so most files carry no fat and force-merging distinct concerns would only hurt readability. Removed the genuine §2-era overlaps, all in `app.test.ts`: merged the two HTTP static tests into one (GET/HEAD + traversal/NUL→403), and dropped the standalone "renders the 403 error page" `ejs.renderFile` stopgap (its comment even said "403 has no first-party route yet") — the gated plugin route now exercises 403 over HTTP, so the template assertions (status + 403.ejs body + stylesheet link) moved there; also dropped the now-unused `ejs` import. Unified `view-resolver.test.ts`'s two `resolveViewPath` cases (resolve + reject) into one. 113 → 110 tests, zero coverage lost; typecheck + tests green. ## 3. Ory stack — compose + config -- [ ] `postgres` service (pinned tag); separate DB/schema per Kratos/Keto/Hydra. +- [x] `postgres` service (pinned tag); separate DB/schema per Kratos/Keto/Hydra. → `compose.yml` `postgres` service pinned to `postgres:18.4-alpine3.23` (verified latest stable PG + newest Alpine the official image ships); `ory/postgres/init/init.sql` (mounted at `docker-entrypoint-initdb.d`) creates one DB per service (`kratos`/`keto`/`hydra`) so each owns its schema + migrations. Dev defaults (`ory`/`ory`, env-overridable for prod), named `pgdata` volume mounted at `/var/lib/postgresql` (PG18+ version-subdir layout — not `/data`), `pg_isready` healthcheck. Web app never connects. Verified live: boots healthy, three DBs present, then torn down. `postgres.test.ts` guards the pin + DB-per-service. typecheck + 112 units green. - [ ] `kratos` service (pinned) + `migrate`; identity schema (traits: email, name). - [ ] Kratos self-service flows (login, registration, recovery, verification, settings) → return URLs at our themed pages. - [ ] Kratos OIDC/SSO providers (Google/Microsoft/SAML) config (secrets via env). **None enabled by default** — a clean clone runs password-only; a provider activates purely by supplying its env creds.