ef8bd91d77
Second CI dry-run failure exposed two more issues:
1. Schema-apply runs against a fresh Postgres → fails with "Directus
isn't installed on this database. Please run 'directus bootstrap'
first." Bootstrap is what creates Directus's system tables; schema
apply requires those tables to exist. Local dev never tripped this
because bootstrap had been done in earlier sessions.
2. `node cli.js schema apply` printed an ERROR but exited 0 in the
not-installed case. schema-apply.sh trusted the exit code,
reported "schema apply complete," and the chain continued — until
the post-schema migration tried to ALTER TABLE on user tables that
never got created.
Fixes:
- entrypoint.sh: reorder steps from
pre-schema → schema-apply → post-schema → bootstrap → start
to
pre-schema → bootstrap → schema-apply → post-schema → start
Bootstrap is idempotent ("Database already initialized, skipping
install" on warm DB) so adding it earlier costs nothing on warm
boots and unblocks fresh boots.
- .gitea/workflows/build.yml: dry-run chain updated to mirror the new
entrypoint order. Bootstrap is now part of the pre-boot validation,
not skipped for speed. CI dry-run now genuinely covers the same path
the production entrypoint takes (minus the final pm2-runtime step,
which doesn't add validation value).
- scripts/schema-apply.sh: defense in depth. After the apply call
succeeds (exit 0), grep the output for ' ERROR: ' and fail loudly if
found. Catches the silent-failure pattern Directus's CLI exhibits
when bootstrap hasn't run. Error message names the likely cause
(schema-apply before bootstrap) for fast operator triage.
This is the second Phase 1 architectural correction exposed by the CI
dry-run gate. The gate is paying for itself in the very first PR it
runs against.
155 lines
7.3 KiB
YAML
155 lines
7.3 KiB
YAML
name: Build directus image
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'snapshots/**'
|
|
- 'db-init/**'
|
|
- 'db-init-post/**'
|
|
- 'extensions/**'
|
|
- 'scripts/**'
|
|
- 'entrypoint.sh'
|
|
- 'Dockerfile'
|
|
- '.gitea/workflows/build.yml'
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
build-and-publish:
|
|
runs-on: ubuntu-22.04
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Throwaway Postgres for the dry-run boot step.
|
|
#
|
|
# Image: pinned to the same concrete tag used in compose.dev.yaml — NOT the
|
|
# floating :pg16-latest alias (which does NOT exist on Docker Hub).
|
|
#
|
|
# PGDATA: the timescaledb-ha image initialises at /home/postgres/pgdata/data;
|
|
# the healthcheck uses pg_isready, which doesn't depend on the PGDATA path.
|
|
#
|
|
# Port mapping: 15432:5432 — host port 15432 is the conventional
|
|
# Postgres-second-instance port. We deliberately do NOT use 5432 on the
|
|
# runner because the runner host typically has another Postgres on 5432
|
|
# (dev stack, stage instance) which would cause a port-allocation collision.
|
|
# The dry-run docker run uses --network host so DB_HOST=localhost reaches
|
|
# the service on the runner's loopback at port 15432.
|
|
# ---------------------------------------------------------------------------
|
|
services:
|
|
postgres:
|
|
image: timescale/timescaledb-ha:pg16.6-ts2.17.2-all
|
|
env:
|
|
POSTGRES_USER: directus
|
|
POSTGRES_PASSWORD: directus
|
|
POSTGRES_DB: directus
|
|
ports:
|
|
- '15432:5432'
|
|
options: >-
|
|
--health-cmd "pg_isready -U directus -d directus"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 20
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Build the image locally (trm-directus:ci).
|
|
#
|
|
# We use a plain `docker build` rather than docker/build-push-action because
|
|
# we need the image available in the *local Docker daemon* for the subsequent
|
|
# `docker run` dry-run step. docker/build-push-action with the
|
|
# docker-container Buildx driver exports into a separate buildkitd cache not
|
|
# accessible to `docker run`.
|
|
# -------------------------------------------------------------------------
|
|
- name: Build image
|
|
run: docker build -t trm-directus:ci .
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Dry-run boot — the gate that protects the registry from broken images.
|
|
#
|
|
# Runs the entrypoint's first FOUR steps against the throwaway Postgres:
|
|
# pre-schema db-init → bootstrap → schema-apply → post-schema db-init
|
|
#
|
|
# Bootstrap is required: schema-apply fails on a fresh DB with
|
|
# "Directus isn't installed on this database" if bootstrap hasn't created
|
|
# Directus's system tables first. The `directus schema apply` CLI prints
|
|
# an ERROR but exits 0 in that case, so an earlier "skip bootstrap for
|
|
# speed" version of this dry-run silently masked snapshot apply failures.
|
|
#
|
|
# Step 5 (`pm2-runtime start`) is intentionally skipped — that would
|
|
# require waiting for the HTTP server to come up, which adds minutes and
|
|
# tests nothing new beyond what the prior steps already validated.
|
|
#
|
|
# --network host: the service container is mapped on 127.0.0.1:5432; the
|
|
# docker run container sees it as localhost:5432 only when host networking
|
|
# is used. Without --network host, the container would be in a separate
|
|
# bridge network and could not reach the service by name or IP.
|
|
#
|
|
# --entrypoint bash: overrides /directus/entrypoint.sh so we execute only
|
|
# the script chain, not the full pm2-runtime boot.
|
|
#
|
|
# Required Directus env vars: DB_CLIENT + connection params are mandatory
|
|
# for `node cli.js schema apply`. KEY + SECRET are required by Directus's
|
|
# env initialisation even when only the schema subcommand is invoked.
|
|
# ADMIN_EMAIL + ADMIN_PASSWORD are included defensively (some Directus
|
|
# versions assert on them during CLI init). PUBLIC_URL silences the
|
|
# missing-public-url warning.
|
|
#
|
|
# If this step exits non-zero the workflow halts and the registry login /
|
|
# push steps are never reached — the broken image is never published.
|
|
# -------------------------------------------------------------------------
|
|
- name: Dry-run boot against throwaway Postgres
|
|
run: |
|
|
docker run --rm \
|
|
--network host \
|
|
--entrypoint bash \
|
|
-e DB_CLIENT=pg \
|
|
-e DB_HOST=localhost \
|
|
-e DB_PORT=15432 \
|
|
-e DB_USER=directus \
|
|
-e DB_PASSWORD=directus \
|
|
-e DB_DATABASE=directus \
|
|
-e KEY=ci-key-placeholder-not-secret \
|
|
-e SECRET=ci-secret-placeholder-not-secret \
|
|
-e ADMIN_EMAIL=ci@example.com \
|
|
-e ADMIN_PASSWORD=ci-password-not-secret \
|
|
-e PUBLIC_URL=http://localhost:8055 \
|
|
trm-directus:ci \
|
|
-c '/directus/scripts/apply-db-init.sh && node /directus/cli.js bootstrap && /directus/scripts/schema-apply.sh && DB_INIT_DIR=/directus/db-init-post /directus/scripts/apply-db-init.sh && echo "dry-run ok"'
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Registry login — runs only if the dry-run succeeded (default: workflow
|
|
# halts on non-zero exit, so reaching this step implies dry-run passed).
|
|
# -------------------------------------------------------------------------
|
|
- name: Login to Gitea registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: git.dev.microservices.al
|
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Tag and push two tags:
|
|
# :main — mutable; always points at the latest commit on main.
|
|
# :<sha> — immutable; pinned to this specific commit.
|
|
# The deploy stack can reference either; :main for rolling updates,
|
|
# :<sha> for pinned deployments that need explicit rollback control.
|
|
# -------------------------------------------------------------------------
|
|
- name: Tag and push
|
|
run: |
|
|
docker tag trm-directus:ci git.dev.microservices.al/trm/directus:main
|
|
docker tag trm-directus:ci git.dev.microservices.al/trm/directus:${{ github.sha }}
|
|
docker push git.dev.microservices.al/trm/directus:main
|
|
docker push git.dev.microservices.al/trm/directus:${{ github.sha }}
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Optional Portainer redeploy webhook.
|
|
# Fires only when PORTAINER_WEBHOOK_URL secret is configured in the repo.
|
|
# If the secret is absent the condition evaluates false and the step is
|
|
# skipped — no error, no noise.
|
|
# -------------------------------------------------------------------------
|
|
- name: Trigger Portainer redeploy (optional)
|
|
if: ${{ secrets.PORTAINER_WEBHOOK_URL != '' }}
|
|
run: curl -fsS -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}"
|