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.
Phase 1 task 1.1 lands. Directus 11.17.4 boots locally end-to-end
against a TimescaleDB+PostGIS container; admin UI serves at :8055,
admin bootstrap from env vars works, named volumes preserve data
across down/up cycles.
Scaffold:
- Dockerfile — FROM directus/directus:11.17.4. Pre-installs
postgresql16-client (ahead of task 1.2's db-init runner needing psql).
Bakes in /directus/snapshots, /directus/db-init, /directus/scripts,
/directus/extensions, /directus/entrypoint.sh.
- compose.dev.yaml — db (timescale/timescaledb-ha:pg16.6-ts2.17.2-all)
+ directus (local build), healthchecks, named volumes
directus-pg-data + directus-uploads.
- entrypoint.sh — placeholder using upstream's actual flow
(node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs);
the real db-init -> schema apply -> start wrapper lands in task 1.7.
- package.json — scripts-only (dev, dev:down, dev:reset,
schema:snapshot, schema:apply, db:init), no runtime deps.
- .env.example — sectioned, fully documented, KEY/SECRET marked
required with generation hints.
- .gitignore, .dockerignore — match the processor service conventions.
- snapshots/, db-init/, scripts/, extensions/ — empty with .gitkeep,
filled by later Phase 1 tasks (1.3, 1.6) and Phase 5.
Lessons locked in (against the empirical pnpm dev boot):
- timescale/timescaledb-ha:pg16-latest does NOT exist on Docker Hub.
Pin a concrete version (we used pg16.6-ts2.17.2-all).
- This image's data directory is /home/postgres/pgdata/data, not
/pgdata or /var/lib/postgresql/data. PGDATA env var and the volume
mount must both target it.
- The -all variant bundles PostGIS binaries but the extension is not
auto-created on the directus database; CREATE EXTENSION lands in
Phase 2 alongside the geofences/SLZs/waypoints collections.
- The upstream image's CMD is bootstrap + pm2-runtime, not a simple
cli.js start. Bypassing pm2 would lose crash recovery.
These corrections folded into 01-project-scaffold.md (deliverable line
+ Done section), 08-gitea-ci-dryrun.md (CI service tag), and the
inline comments in compose.dev.yaml so future implementers don't
re-discover them.
Status: ROADMAP marks 1.1 done, Phase 1 in progress, 1.2 next.