From 387c3c4cfa85860fdb0a33857b7e19013b9023f8 Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Fri, 1 May 2026 21:29:00 +0200 Subject: [PATCH] =?UTF-8?q?Task=201.1=20=E2=80=94=20Project=20scaffold?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .dockerignore | 23 ++++ .env.example | 98 ++++++++++++++ .gitignore | 9 ++ .planning/ROADMAP.md | 4 +- .../01-project-scaffold.md | 24 +++- .../08-gitea-ci-dryrun.md | 2 +- Dockerfile | 45 +++++++ README.md | 6 +- compose.dev.yaml | 120 ++++++++++++++++++ db-init/.gitkeep | 6 + entrypoint.sh | 24 ++++ extensions/.gitkeep | 3 + package.json | 18 +++ pnpm-lock.yaml | 9 ++ scripts/.gitkeep | 4 + snapshots/.gitkeep | 2 + 16 files changed, 389 insertions(+), 8 deletions(-) create mode 100644 .dockerignore create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 compose.dev.yaml create mode 100644 db-init/.gitkeep create mode 100644 entrypoint.sh create mode 100644 extensions/.gitkeep create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 scripts/.gitkeep create mode 100644 snapshots/.gitkeep diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..bcc06de --- /dev/null +++ b/.dockerignore @@ -0,0 +1,23 @@ +# Files and directories excluded from the Docker build context. +# Keep this tight — a large context slows every `docker build`. + +.git/ +.planning/ +node_modules/ +directus-data/ + +# Secrets — never bake these into the image +.env +.env.local +.env.* + +# Compose files are runtime config, not part of the image +compose.dev.yaml + +# Markdown docs (README.md is excluded too — the image doesn't need it) +*.md + +# pnpm lockfile and package.json are not needed inside the Directus image +# (the upstream image manages its own Node environment). +package.json +pnpm-lock.yaml diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c08c30a --- /dev/null +++ b/.env.example @@ -0,0 +1,98 @@ +# Environment variables for the TRM directus service. +# Copy to .env and fill in values for local development. +# cp .env.example .env +# +# Required vars: DB_*, KEY, SECRET, ADMIN_EMAIL, ADMIN_PASSWORD, PUBLIC_URL. +# .env is gitignored — never commit real credentials. + +# --------------------------------------------------------------------------- +# Database connection — Postgres 16 + TimescaleDB + PostGIS +# --------------------------------------------------------------------------- + +# Directus DB driver. Always "pg" for this service. +DB_CLIENT=pg + +# Hostname of the Postgres container (matches the compose service name when +# running via compose.dev.yaml; change to your host/IP for external Postgres). +DB_HOST=db + +# Postgres port. +DB_PORT=5432 + +# Database name. +DB_DATABASE=directus + +# Postgres user. +DB_USER=directus + +# Postgres password. +DB_PASSWORD=directus + +# --------------------------------------------------------------------------- +# Instance security — REQUIRED; generate fresh values for each environment. +# +# KEY: uuidgen (or openssl rand -hex 32) +# SECRET: openssl rand -hex 64 +# +# IMPORTANT: two instances sharing the same KEY/SECRET will produce +# colliding JWT tokens. Use distinct values per environment. +# --------------------------------------------------------------------------- + +KEY=replace-with-a-random-uuid +SECRET=replace-with-a-long-random-string + +# --------------------------------------------------------------------------- +# Admin bootstrap +# +# Applied on first boot when the users table is empty. If the instance has +# already been initialised these values are ignored — change the password via +# the admin UI or Directus CLI instead. +# --------------------------------------------------------------------------- + +ADMIN_EMAIL=admin@example.com +ADMIN_PASSWORD=change-me-on-first-boot + +# --------------------------------------------------------------------------- +# Public URL +# +# Used in password-reset emails, OAuth redirect URIs, and the Directus admin +# UI's "share" links. Set to the externally reachable URL in staging/prod. +# --------------------------------------------------------------------------- + +PUBLIC_URL=http://localhost:8055 + +# --------------------------------------------------------------------------- +# Logging +# --------------------------------------------------------------------------- + +# Log level: fatal | error | warn | info | debug | trace +LOG_LEVEL=info + +# Log format: pretty (human-readable) | json (structured, for log aggregators) +LOG_STYLE=pretty + +# --------------------------------------------------------------------------- +# Cache (optional — disabled by default for local dev) +# --------------------------------------------------------------------------- + +# Set to true to enable Directus's built-in response cache. +# Requires a cache store (Redis or memory) when enabled. +CACHE_ENABLED=false + +# --------------------------------------------------------------------------- +# CORS (optional — disabled by default) +# --------------------------------------------------------------------------- + +# Set to true to enable CORS headers. +CORS_ENABLED=false + +# Allowed origin(s). Accepts a URL string, comma-separated list, or "true" +# to reflect any origin (development only — never use "true" in production). +# CORS_ORIGIN=http://localhost:3000 + +# --------------------------------------------------------------------------- +# WebSockets (enabled by default — required for the SPA live channel) +# --------------------------------------------------------------------------- + +# Set to false only when the SPA is not connected (e.g. API-only deployments). +WEBSOCKETS_ENABLED=true diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7e17756 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +.env +.env.local +*.log + +# Local Postgres data directory (only present if using a host bind mount +# instead of a named volume — the committed compose.dev.yaml uses named +# volumes, but a developer might override this locally). +directus-data/ diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index fa8b2d7..644a3fd 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -42,14 +42,14 @@ These rules govern every task. Any deviation must be discussed and documented as ### Phase 1 — Slice 1 schema + deploy pipeline -**Status:** ⬜ Not started +**Status:** 🟨 In progress (1.1 done; 1.2 next) **Outcome:** A Directus instance with the org-level catalog (orgs, users, organization_users, vehicles, devices and their org junctions) and event-participation collections (events, classes, entries, entry_crew, entry_devices) live and snapshot-tracked. `db-init/` covers the TimescaleDB extension, the `positions` hypertable, and the `faulty` column. Image builds via Gitea Actions with a CI dry-run that catches snapshot drift before deploy. Rally Albania 2026 is registered as the first event in admin UI to dogfood the registration workflow. **This is what Rally Albania 2026 needs.** [**See `phase-1-slice-1-schema/README.md`**](./phase-1-slice-1-schema/README.md) | # | Task | Status | Landed in | |---|------|--------|-----------| -| 1.1 | [Project scaffold](./phase-1-slice-1-schema/01-project-scaffold.md) | ⬜ | — | +| 1.1 | [Project scaffold](./phase-1-slice-1-schema/01-project-scaffold.md) | 🟩 | pending user commit | | 1.2 | [db-init runner script](./phase-1-slice-1-schema/02-db-init-runner.md) | ⬜ | — | | 1.3 | [Initial migrations (extensions, positions hypertable, faulty column)](./phase-1-slice-1-schema/03-initial-migrations.md) | ⬜ | — | | 1.4 | [Org-level catalog collections](./phase-1-slice-1-schema/04-org-catalog-collections.md) | ⬜ | — | diff --git a/.planning/phase-1-slice-1-schema/01-project-scaffold.md b/.planning/phase-1-slice-1-schema/01-project-scaffold.md index 36b0c7b..5bacfac 100644 --- a/.planning/phase-1-slice-1-schema/01-project-scaffold.md +++ b/.planning/phase-1-slice-1-schema/01-project-scaffold.md @@ -1,7 +1,7 @@ # Task 1.1 — Project scaffold **Phase:** 1 — Slice 1 schema + deploy pipeline -**Status:** ⬜ Not started +**Status:** 🟩 Done **Depends on:** None **Wiki refs:** `docs/wiki/entities/directus.md`, `docs/wiki/synthesis/directus-schema-draft.md` @@ -13,7 +13,7 @@ Initialize the `directus/` service folder with the directory layout from the Pha - `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-latest` (or equivalent), volume-mapped Postgres data dir, healthcheck. + - `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) @@ -58,4 +58,22 @@ Initialize the `directus/` service folder with the directory layout from the Pha ## Done -(Fill in commit SHA + one-line note when this lands.) +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 `postgresql16-client` via apk so that `scripts/apply-db-init.sh` (task 1.2) can invoke `psql` without adding that dependency later. +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. diff --git a/.planning/phase-1-slice-1-schema/08-gitea-ci-dryrun.md b/.planning/phase-1-slice-1-schema/08-gitea-ci-dryrun.md index 9748450..b9ca1fd 100644 --- a/.planning/phase-1-slice-1-schema/08-gitea-ci-dryrun.md +++ b/.planning/phase-1-slice-1-schema/08-gitea-ci-dryrun.md @@ -33,7 +33,7 @@ Build a Gitea Actions workflow that on push to `main` (when relevant paths chang runs-on: ubuntu-22.04 services: postgres: - image: timescale/timescaledb-ha:pg16-latest + image: timescale/timescaledb-ha:pg16.6-ts2.17.2-all # match compose.dev.yaml; :pg16-latest does NOT exist on Docker Hub env: POSTGRES_USER: directus POSTGRES_PASSWORD: directus diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2ca5cdb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,45 @@ +# syntax=docker/dockerfile:1.7 +# +# TRM directus service image. +# +# Single-stage build for Phase 1. A multi-stage build (with a Node builder for +# extensions) lands in Phase 5 when TypeScript extensions are introduced. +# +# Artifacts baked into the image at build time: +# /directus/snapshots/ — schema.yaml (generated; empty placeholder in Phase 1) +# /directus/db-init/ — numbered SQL migration files (Phase 1 task 1.3 fills these) +# /directus/scripts/ — shell helpers (Phase 1 tasks 1.2, 1.6 fill these) +# /directus/extensions/ — TypeScript extensions (Phase 5) +# /directus/entrypoint.sh — boot wrapper (real flow lands in Phase 1 task 1.7) +# +# No bind mounts of these directories in compose.dev.yaml — the image is the +# source of truth. Reproducible across local, CI, and production environments. + +FROM directus/directus:11.17.4 + +# Switch to root only for the setup steps; Directus's upstream image already +# drops to a non-root user — we preserve that for runtime. +USER root + +# Install postgresql-client so scripts/apply-db-init.sh can use psql. +# Phase 1 task 1.2 writes the runner; we pre-install the dependency now so +# the image build never needs internet access at runtime. +RUN apk add --no-cache postgresql16-client + +# ---- Copy baked-in artifacts ---- +# Each COPY is conditional on the directory existing at build time. +# .gitkeep files ensure the directories always exist so COPY never fails. +COPY snapshots/ /directus/snapshots/ +COPY db-init/ /directus/db-init/ +COPY scripts/ /directus/scripts/ +COPY extensions/ /directus/extensions/ +COPY entrypoint.sh /directus/entrypoint.sh + +# Ensure the entrypoint is executable inside the image regardless of the host +# filesystem's permission bits. +RUN chmod +x /directus/entrypoint.sh + +# Drop back to the non-root user the upstream image uses. +USER node + +ENTRYPOINT ["/directus/entrypoint.sh"] diff --git a/README.md b/README.md index 5b29b53..49ee676 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Apply order at boot: **db-init first, then `directus schema apply`, then `direct ## Quick start (local) -**Prerequisites:** Docker, the `directus/directus:11.x` image (pulled by compose), a running Postgres 16 + TimescaleDB + PostGIS instance. +**Prerequisites:** Docker, the `directus/directus:11.17.4` image (pulled automatically by compose), a running Postgres 16 + TimescaleDB + PostGIS instance (provided by `compose.dev.yaml`). ```bash git clone @@ -86,7 +86,9 @@ All other Directus envs (cache, logging, CORS, etc.) follow upstream defaults un ## CI behavior -Gitea Actions workflow is at `.gitea/workflows/build.yml`. +Gitea Actions workflow lands at `.gitea/workflows/build.yml` in Phase 1 task 1.8 — not yet present. + +When the workflow exists: - **Push to `main`** (only when `snapshots/`, `db-init/`, `extensions/`, `Dockerfile`, or the workflow file itself changes): builds the image, spins up a throwaway Postgres + TimescaleDB + PostGIS via `services:`, runs `apply-db-init.sh` and `directus schema apply --yes` against it as a **dry-run**, then publishes the image tagged `:main` if the dry-run exits 0. Auto-deploys to stage if a Portainer webhook is configured via `secrets.PORTAINER_WEBHOOK_URL`. - **Manual trigger** (`workflow_dispatch`): same flow, run on demand. diff --git a/compose.dev.yaml b/compose.dev.yaml new file mode 100644 index 0000000..07c2f7e --- /dev/null +++ b/compose.dev.yaml @@ -0,0 +1,120 @@ +# Local development compose — builds the Directus image from this repo's source +# tree and runs it alongside a TimescaleDB container. +# +# Use this file to verify Dockerfile changes, db-init migrations, and snapshot +# apply behaviour locally before pushing. For day-to-day admin UI work you can +# run `docker compose -f compose.dev.yaml up --build` and hit localhost:8055. +# +# For STAGE and PRODUCTION deployment, use the multi-service compose in the +# sibling `deploy/` repo (https://git.dev.microservices.al/trm/deploy), which +# references this service by its registry image tag instead of building locally. +# +# Usage: +# docker compose -f compose.dev.yaml up --build +# docker compose -f compose.dev.yaml down # preserves volumes +# docker compose -f compose.dev.yaml down -v # wipes volumes (clean slate) +# +# See also: pnpm dev | pnpm dev:down | pnpm dev:reset + +name: directus-dev + +services: + + # --------------------------------------------------------------------------- + # db — PostgreSQL 16 + TimescaleDB + PostGIS (via timescaledb-ha) + # + # The "-all" suffix variant bundles all extensions (TimescaleDB, PostGIS, + # postgis_raster, etc.). Note: the binaries are present in the image, but + # extensions still need `CREATE EXTENSION` on each database that uses them + # — that's what db-init/*.sql does (Phase 1 task 1.3 + Phase 2). + # + # Pin a specific TS+PG version (not :pg16-latest, which doesn't exist on + # Docker Hub). Bump the pin explicitly when upgrading. + # + # PGDATA: this image initialises Postgres at /home/postgres/pgdata/data, + # not the upstream-standard /var/lib/postgresql/data and not /pgdata. + # The volume mount must match exactly or initdb fails with permission errors. + # --------------------------------------------------------------------------- + db: + image: timescale/timescaledb-ha:pg16.6-ts2.17.2-all + environment: + POSTGRES_USER: ${DB_USER:-directus} + POSTGRES_PASSWORD: ${DB_PASSWORD:-directus} + POSTGRES_DB: ${DB_DATABASE:-directus} + PGDATA: /home/postgres/pgdata/data + volumes: + - directus-pg-data:/home/postgres/pgdata/data + expose: + - '5432' + restart: unless-stopped + healthcheck: + # pg_isready reads PGUSER / PGDATABASE from env; compose sets those from + # the environment block above, so no hard-coding is needed here. + test: ['CMD-SHELL', 'pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB'] + interval: 10s + timeout: 5s + start_period: 30s + retries: 5 + + # --------------------------------------------------------------------------- + # directus — built from local Dockerfile + # + # All env vars are read from .env (copy .env.example → .env to get started). + # The service waits for db to pass its healthcheck before starting. + # --------------------------------------------------------------------------- + directus: + build: + context: . + dockerfile: Dockerfile + depends_on: + db: + condition: service_healthy + ports: + - '8055:8055' + environment: + # Database connection + DB_CLIENT: ${DB_CLIENT:-pg} + DB_HOST: db + DB_PORT: ${DB_PORT:-5432} + DB_DATABASE: ${DB_DATABASE:-directus} + DB_USER: ${DB_USER:-directus} + DB_PASSWORD: ${DB_PASSWORD:-directus} + + # Instance security — REQUIRED; set non-placeholder values in .env + KEY: ${KEY} + SECRET: ${SECRET} + + # Admin bootstrap — only applied on first init when the users table is empty + ADMIN_EMAIL: ${ADMIN_EMAIL} + ADMIN_PASSWORD: ${ADMIN_PASSWORD} + + # Public URL used in emails and OAuth redirects + PUBLIC_URL: ${PUBLIC_URL:-http://localhost:8055} + + # Logging + LOG_LEVEL: ${LOG_LEVEL:-info} + LOG_STYLE: ${LOG_STYLE:-pretty} + + # Optional: cache, CORS, WebSockets (inherit from .env if set) + CACHE_ENABLED: ${CACHE_ENABLED:-false} + CORS_ENABLED: ${CORS_ENABLED:-false} + CORS_ORIGIN: ${CORS_ORIGIN:-false} + WEBSOCKETS_ENABLED: ${WEBSOCKETS_ENABLED:-true} + + volumes: + # Persist Directus file uploads across container restarts. + # No uploads use-case in Phase 1, but the volume is cheap to declare now. + - directus-uploads:/directus/uploads + + restart: unless-stopped + healthcheck: + test: ['CMD-SHELL', 'wget -qO- http://localhost:8055/server/health || exit 1'] + interval: 30s + timeout: 10s + start_period: 60s + retries: 3 + +volumes: + # Named volume so `down` preserves data and `down -v` wipes it. + directus-pg-data: + directus-uploads: diff --git a/db-init/.gitkeep b/db-init/.gitkeep new file mode 100644 index 0000000..410a0d3 --- /dev/null +++ b/db-init/.gitkeep @@ -0,0 +1,6 @@ +# Numbered SQL migration files land here in Phase 1 task 1.3: +# 001_extensions.sql — CREATE EXTENSION timescaledb +# 002_positions_hypertable.sql +# 003_faulty_column.sql +# PostGIS extension (Phase 2) will be added as 004_postgis.sql. +# Files are applied in numeric order by scripts/apply-db-init.sh. diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..2300ac9 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/sh +# TRM directus — image entrypoint (placeholder). +# +# Real flow (db-init runner → directus schema apply --yes → directus start) +# lands in Phase 1 task 1.7. Until then, this script replicates the upstream +# Directus image CMD so the container boots normally during tasks 1.4 and 1.5 +# (admin UI schema work). +# +# Upstream CMD (from directus/directus:11.17.4 Dockerfile): +# node cli.js bootstrap && pm2-runtime start ecosystem.config.cjs +# +# bootstrap: idempotent — initialises the DB schema on first run, reads +# ADMIN_EMAIL / ADMIN_PASSWORD to create the initial admin user. +# pm2-runtime: starts the Directus process under PM2 so the container stays +# alive and restarts on crash without an outer supervisor. +# +# Exit codes are propagated: any non-zero exit causes the container to exit +# with that code, which compose reports as an error. + +set -e + +node /directus/cli.js bootstrap + +exec pm2-runtime start /directus/ecosystem.config.cjs diff --git a/extensions/.gitkeep b/extensions/.gitkeep new file mode 100644 index 0000000..72b67b9 --- /dev/null +++ b/extensions/.gitkeep @@ -0,0 +1,3 @@ +# TypeScript extensions (hooks, endpoints, operations) land here in Phase 5. +# Each extension is a subdirectory with its own package.json and dist/ build output. +# The Dockerfile COPYs this directory into /directus/extensions/ inside the image. diff --git a/package.json b/package.json new file mode 100644 index 0000000..05d61ca --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "directus", + "version": "0.1.0", + "description": "TRM business-plane: Directus 11 instance owning the relational schema, APIs, and admin UI", + "private": true, + "engines": { + "node": ">=22", + "pnpm": ">=9" + }, + "scripts": { + "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", + "schema:snapshot": "bash scripts/schema-snapshot.sh", + "schema:apply": "bash scripts/schema-apply.sh", + "db:init": "bash scripts/apply-db-init.sh" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..9b60ae1 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,9 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: {} diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..c30744d --- /dev/null +++ b/scripts/.gitkeep @@ -0,0 +1,4 @@ +# Shell scripts land here across Phase 1: +# apply-db-init.sh — numeric-order, guard-table-protected runner (task 1.2) +# schema-snapshot.sh — wraps `directus schema snapshot --yes` (task 1.6) +# schema-apply.sh — wraps `directus schema apply --yes` (task 1.6) diff --git a/snapshots/.gitkeep b/snapshots/.gitkeep new file mode 100644 index 0000000..5518ff2 --- /dev/null +++ b/snapshots/.gitkeep @@ -0,0 +1,2 @@ +# schema.yaml is generated here by `pnpm run schema:snapshot` (Phase 1 task 1.6). +# Do not hand-edit schema.yaml — always round-trip through the Directus admin UI.