# Task 1.8 — Gitea CI dry-run workflow **Phase:** 1 — Slice 1 schema + deploy pipeline **Status:** ⬜ Not started **Depends on:** 1.7 **Wiki refs:** `docs/wiki/entities/directus.md` (Schema management section) ## Goal Build a Gitea Actions workflow that on push to `main` (when relevant paths change): builds the image, spins up a throwaway Postgres + TimescaleDB in CI, runs the entrypoint flow as a **dry-run** to catch snapshot/migration breakage, and only publishes the image to the registry if the dry-run succeeds. Mirrors the `processor` and `tcp-ingestion` workflow shape. ## Deliverables - `.gitea/workflows/build.yml`: ```yaml name: Build directus image on: push: branches: [main] paths: - 'snapshots/**' - 'db-init/**' - 'extensions/**' - 'scripts/**' - 'entrypoint.sh' - 'Dockerfile' - '.gitea/workflows/build.yml' workflow_dispatch: jobs: build-and-publish: runs-on: ubuntu-22.04 services: postgres: 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 POSTGRES_DB: directus ports: ['5432:5432'] options: >- --health-cmd "pg_isready -U directus" --health-interval 5s --health-timeout 5s --health-retries 10 steps: - uses: actions/checkout@v4 - name: Build image run: docker build -t trm-directus:ci . - name: Dry-run boot against throwaway Postgres env: DB_HOST: postgres DB_PORT: 5432 DB_USER: directus DB_PASSWORD: directus DB_DATABASE: directus KEY: ci-key-not-secret SECRET: ci-secret-not-secret ADMIN_EMAIL: ci@example.com ADMIN_PASSWORD: ci-password-not-secret PUBLIC_URL: http://localhost:8055 run: | docker run --rm \ -e DB_CLIENT=pg \ -e DB_HOST=$DB_HOST -e DB_PORT=$DB_PORT \ -e DB_USER=$DB_USER -e DB_PASSWORD=$DB_PASSWORD -e DB_DATABASE=$DB_DATABASE \ -e KEY=$KEY -e SECRET=$SECRET \ -e ADMIN_EMAIL=$ADMIN_EMAIL -e ADMIN_PASSWORD=$ADMIN_PASSWORD \ -e PUBLIC_URL=$PUBLIC_URL \ --network host \ --entrypoint bash \ trm-directus:ci \ -c '/directus/scripts/apply-db-init.sh && /directus/scripts/schema-apply.sh && echo "dry-run ok"' - name: Login to Gitea registry uses: docker/login-action@v3 with: registry: git.dev.microservices.al username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - 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 }} - name: Trigger Portainer redeploy (optional) if: secrets.PORTAINER_WEBHOOK_URL != '' run: curl -X POST "${{ secrets.PORTAINER_WEBHOOK_URL }}" ``` ## Specification - **Dry-run runs the entrypoint scripts only**, not `directus start`. Starting the server and waiting for it to serve is slow and unnecessary — the goal is to catch DDL / snapshot apply errors. Override the `ENTRYPOINT` and run the two scripts directly. - **Service container is the throwaway Postgres.** `services:` block in Gitea Actions (compatible syntax with GitHub Actions). Use the pinned TimescaleDB image; mismatch with prod hides bugs. - **Path filter on `on.push.paths`** keeps CI quiet for unrelated repo changes (docs-only commits, etc.). Mirrors the processor workflow. - **Two image tags published:** `:main` (always points at latest main) and `:` (specific commit, immutable). The deploy stack can pin to either. - **Portainer webhook is optional** (gated by secret presence). If unset, no auto-deploy. - **No integration tests in CI for Phase 1.** The dry-run boot *is* the integration test — it proves the snapshot+db-init combination works against a fresh Postgres. Phase 5+ adds extension-specific tests as those land. - **Required Gitea secrets:** - `REGISTRY_USERNAME`, `REGISTRY_PASSWORD` — for the image push. - `PORTAINER_WEBHOOK_URL` — optional, for auto-deploy. ## Acceptance criteria - [ ] Workflow file is committed at `.gitea/workflows/build.yml`. - [ ] First push to `main` after this lands triggers the workflow. - [ ] Workflow steps in order: checkout → build → dry-run boot → registry login → tag/push → optional Portainer ping. - [ ] Dry-run step exits 0 with logs showing "db-init complete" and "schema apply: no changes" (after the snapshot has been applied once, subsequent runs against fresh Postgres still apply from scratch — verify the apply step works in both cases). - [ ] Intentionally break the snapshot (manually edit `snapshots/schema.yaml` to a malformed YAML) → workflow fails at the dry-run step → image is NOT pushed. - [ ] Intentionally break a migration (introduce SQL syntax error in `db-init/`) → workflow fails at the dry-run step → image is NOT pushed. - [ ] Push a docs-only change → workflow does NOT trigger. - [ ] Image pushed to registry under `git.dev.microservices.al/trm/directus:main` and `:`. - [ ] Portainer webhook fires if configured. ## Risks / open questions - **Gitea Actions `services:` syntax compatibility.** Gitea's runner is mostly GitHub-Actions-compatible but has historically had quirks with the `services:` block (especially around image pulls from private registries). If the throwaway Postgres can't be brought up via `services:`, fall back to a `docker run` step that backgrounds the container and a wait-loop on `pg_isready`. Document the chosen approach. - **Network access between job container and service container.** `--network host` is the simplest solution if Gitea's runner allows it. If not, use the Docker network created by the runner and reference the service by name (`postgres:5432`). ## Done (Fill in commit SHA + one-line note when this lands.)