dec2d190ce
scripts/apply-db-init.sh implements the boot-time runner that walks db-init/*.sql in numeric-prefix order, applies each via psql, and records successful applications in a migrations_applied guard table so re-runs are no-ops. All 7 acceptance criteria pass live against the dev compose stack: empty dir, missing env var, apply, idempotent re-run, checksum mismatch, filename collision, broken SQL. Two retroactive Dockerfile corrections folded in (exposed by the first live-test attempt of 1.2's script): 1. apk add bash. The directus/directus:11.17.4 base is Alpine and ships ash via BusyBox, not bash. The script uses bash-specific features (associative arrays, [[ ]], mapfile, BASH_REMATCH) and fails at line 69 in sh. 2. .gitattributes added at repo root forcing LF on *.sh, *.sql, *.yaml, *.yml. Without it, Windows checkouts with core.autocrlf=true (the Git-for-Windows default) silently inject CRLF, causing "bad interpreter: /usr/bin/env bash^M" inside the Linux container. This failure mode only manifests in the container. Both corrections are documented in 01-project-scaffold.md's Done section; 02-db-init-runner.md's Done section captures the live-test results, the corrected docker compose run --entrypoint commands, and the gotcha about compose env defaults masking missing-env-var tests. ROADMAP marks 1.2 done; 1.3 next.
80 lines
7.9 KiB
Markdown
80 lines
7.9 KiB
Markdown
# Task 1.1 — Project scaffold
|
|
|
|
**Phase:** 1 — Slice 1 schema + deploy pipeline
|
|
**Status:** 🟩 Done
|
|
**Depends on:** None
|
|
**Wiki refs:** `docs/wiki/entities/directus.md`, `docs/wiki/synthesis/directus-schema-draft.md`
|
|
|
|
## Goal
|
|
|
|
Initialize the `directus/` service folder with the directory layout from the Phase 1 README, the config files needed for local Docker compose dev, and a minimal `compose.dev.yaml` that boots Directus + TimescaleDB so the next tasks have something to iterate against. **No Directus collections are created in this task** — that starts in 1.4.
|
|
|
|
## Deliverables
|
|
|
|
- `directus/Dockerfile` — `FROM directus/directus:11.x`, copies `snapshots/`, `db-init/`, `scripts/`, `entrypoint.sh`, `extensions/` into the image. Sets `ENTRYPOINT ["/directus/entrypoint.sh"]`. (Concrete entrypoint contents land in task 1.7; for now create a placeholder that just `exec`s the upstream entrypoint.)
|
|
- `directus/compose.dev.yaml` — two services:
|
|
- `db`: `timescale/timescaledb-ha:pg16.6-ts2.17.2-all` (the `-all` variant bundles PostGIS binaries; the `:pg16-latest` floating tag does NOT exist on Docker Hub — pin a concrete TS+PG version). Volume-mapped Postgres data dir, healthcheck.
|
|
- `directus`: built from local `Dockerfile`, depends on `db` healthy, env vars for DB connection + `KEY` + `SECRET` + admin bootstrap, port `8055` exposed.
|
|
- `directus/package.json` — minimal, only for npm scripts (no runtime deps). Scripts:
|
|
- `schema:snapshot` — `bash scripts/schema-snapshot.sh` (script body lands in 1.6)
|
|
- `schema:apply` — `bash scripts/schema-apply.sh`
|
|
- `db:init` — `bash scripts/apply-db-init.sh`
|
|
- `dev` — `docker compose -f compose.dev.yaml up --build`
|
|
- `dev:down` — `docker compose -f compose.dev.yaml down`
|
|
- `dev:reset` — `docker compose -f compose.dev.yaml down -v && docker compose -f compose.dev.yaml up --build`
|
|
- `directus/.env.example` — full list of env vars with descriptions and defaults. Required: `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USER`, `DB_PASSWORD`, `KEY`, `SECRET`, `ADMIN_EMAIL`, `ADMIN_PASSWORD`, `PUBLIC_URL`. Plus optional: `LOG_LEVEL`, `LOG_STYLE`, `CACHE_ENABLED`, `CORS_ENABLED`, `CORS_ORIGIN`, `WEBSOCKETS_ENABLED`.
|
|
- `directus/.gitignore` — `node_modules/`, `.env`, `.env.local`, `*.log`, `directus-data/` (the local Postgres volume mount, if used).
|
|
- `directus/.dockerignore` — `.git/`, `.planning/`, `node_modules/`, `.env*`, `*.md` except `README.md`, `compose.dev.yaml` (compose isn't part of the image), `directus-data/`.
|
|
- Empty placeholder directories with `.gitkeep`:
|
|
- `snapshots/` (1.6 fills it)
|
|
- `db-init/` (1.3 fills it)
|
|
- `scripts/` (1.2, 1.6 fill it)
|
|
- `extensions/` (Phase 5)
|
|
- `directus/entrypoint.sh` — placeholder that simply `exec /directus/cli.js start` (or whatever the upstream image's start command is). Real wrapper lands in 1.7.
|
|
- `directus/README.md` already exists from this scaffold pass — verify it's accurate.
|
|
|
|
## Specification
|
|
|
|
- **Postgres image choice.** Pin to a TimescaleDB image that includes PostgreSQL 16. PostGIS will be installed via `db-init/` in Phase 2; the base image must support `CREATE EXTENSION postgis` (most TimescaleDB-HA images do). Document the pinned tag in compose.dev.yaml.
|
|
- **Volume policy in compose.dev.yaml.** Use a named volume (`directus-pg-data`) so `dev:down` preserves data and `dev:reset` wipes it.
|
|
- **No secrets committed.** `.env` is gitignored. `.env.example` carries placeholder values only.
|
|
- **No bind mounts of `snapshots/` or `db-init/` in compose.dev.yaml.** The image bakes them in. (Implementer can override with a bind mount during local iteration but the committed file does not.)
|
|
- **Entrypoint is a placeholder in this task.** Real flow (db-init → schema apply → start) lands in 1.7. Keep the placeholder simple to unblock 1.4 testing.
|
|
|
|
## Acceptance criteria
|
|
|
|
- [ ] `pnpm install` succeeds (no runtime deps; lockfile generated).
|
|
- [ ] `docker compose -f compose.dev.yaml up --build` boots Directus successfully against a fresh TimescaleDB container.
|
|
- [ ] `http://localhost:8055` serves the Directus admin login.
|
|
- [ ] First-time bootstrap with `ADMIN_EMAIL` / `ADMIN_PASSWORD` from `.env` works.
|
|
- [ ] `pnpm dev:down` stops the stack, preserves the volume.
|
|
- [ ] `pnpm dev:reset` wipes the volume and reboots clean.
|
|
- [ ] No collection definitions exist yet — the Directus instance is empty by design.
|
|
|
|
## Risks / open questions
|
|
|
|
- **TimescaleDB-HA image PostGIS support.** Verify the chosen tag includes `postgis` extension binaries (or document the alternative — e.g. switching to `postgis/postgis:16-master` with manual TimescaleDB install). Capture the answer in this task's Done section.
|
|
- **Directus 11.x patch version.** Pin a specific tag (e.g. `11.5.1`) rather than `11.x` for reproducible builds. Update the pin via PR when bumping.
|
|
|
|
## Done
|
|
|
|
Pending commit by user. All deliverables created in the same working tree pass.
|
|
|
|
**Open question resolutions (corrected against live `pnpm dev` boot 2026-05-01):**
|
|
|
|
- **TimescaleDB-HA image tag.** `:pg16-latest` does **not** exist on Docker Hub (the agent's initial pin failed at pull time). The empirically-verified tag is `timescale/timescaledb-ha:pg16.6-ts2.17.2-all`. The `-all` suffix bundles PostGIS binaries.
|
|
- **PGDATA path.** Not `/pgdata` (the agent's first guess); the actual data directory baked into this image is `/home/postgres/pgdata/data`. `PGDATA: /pgdata` plus a volume mount to `/pgdata` produced "could not change permissions of directory" errors at initdb. Fixed by setting both `PGDATA` and the volume target to `/home/postgres/pgdata/data`.
|
|
- **PostGIS extension.** Binaries are bundled in the `-all` image but the extension is **not** auto-created on the `directus` database. Directus boot logs warn: `PostGIS isn't installed. Geometry type support will be limited.` Resolution: `CREATE EXTENSION IF NOT EXISTS postgis;` lands in db-init when geometry types are needed (Phase 2). Phase 1 has no geometry columns so the warning is benign.
|
|
- **Directus version pin**: `directus/directus:11.17.4` confirmed to exist on Docker Hub. Used as the image pin.
|
|
|
|
**Deviations from task spec:**
|
|
|
|
1. `entrypoint.sh` delegates to `node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs` (the upstream image's actual CMD) rather than `exec /directus/cli.js start`. The upstream image uses pm2-runtime to manage the process; bypassing it would skip crash recovery and signal handling that pm2 provides. The `bootstrap` step is idempotent (safe to run every boot) and handles admin user creation.
|
|
2. `compose.dev.yaml` sets `PGDATA: /home/postgres/pgdata/data` and mounts the named volume to the same path. Required by the `timescaledb-ha:*-all` image; mounting elsewhere fails initdb.
|
|
3. `Dockerfile` installs `bash` and `postgresql16-client` via apk. The upstream `directus/directus:11.17.4` is Alpine-based and ships `ash` (BusyBox) only — bash is required by `scripts/apply-db-init.sh` (task 1.2) which uses associative arrays, `[[ ]]`, `mapfile`, and `BASH_REMATCH`. `postgresql16-client` provides `psql` + `pg_isready` for the same script. Both pre-installed in 1.1 to avoid cache-busting Dockerfile diffs in later phases. *(Note: `bash` was added retroactively after 1.2's first live test exposed the dependency — 1.1's original commit shipped without it. Folded into 1.2's commit.)*
|
|
4. `README.md` updated: pinned `11.x` → `11.17.4`; CI section notes workflow file is pending (task 1.8).
|
|
|
|
**Live boot acceptance (2026-05-01):**
|
|
|
|
`pnpm dev` against fresh volumes succeeded: db became healthy, Directus ran 60+ system migrations, PM2 cluster started, server bound at `http://0.0.0.0:8055`, GraphQL Subscriptions and WebSocket Server started. Admin bootstrap (creating first admin role + admin user) completed. One benign warning ("PostGIS isn't installed" — see Open question resolutions above). One harmless migration warning about a non-existent constraint on `directus_comments_collection_foreign` (a Directus internal migration's expected idempotent guard, not caused by this task). All Phase 1 task 1.1 acceptance criteria met.
|