CI / verify (pull_request) Failing after 47s
CI / infrastructure-smoke (pull_request) Skipped
CI / migration-smoke (pull_request) Skipped
CI / image-build (pull_request) Skipped
CI / deploy-smoke (same-origin) (pull_request) Skipped
CI / deploy-smoke (split) (pull_request) Skipped
The production image now contains zero feature modules and is the same for every installation. Installed modules live in module storage that one Core resolver selects from EKKLESIA_MODULES_DIR: the checkout's modules/ when unset, an absolute path that must exist when set (/var/lib/ekklesia/modules in the image). A relative, blank, missing, or unreadable configured directory fails closed with a sanitized error that never contains the value. - Host configuration reads EKKLESIA_MODULES_DIR. The API, migration plan, apply and validate, module validation, module enablement, and Doctor all resolve it the same way; the test-only API override still takes precedence. - Doctor receives Core's migrations directory explicitly instead of deriving it from the modules directory's parent. - The migration runner retains and reports the applied history of a module that is not installed, and plans and applies Core's and present modules' migrations around it. Core's history and every installed module's history stay strict: a missing or changed applied migration, or a downgrade, still fails. No down migrations, no data deletion. - deploy/Dockerfile copies no module and installs nothing for the module template; .dockerignore excludes modules/ and the build refuses a context that contains it; the runtime has an empty root-owned /var/lib/ekklesia/modules and sets EKKLESIA_MODULES_DIR. - deploy/compose.yaml bind-mounts EKKLESIA_MODULES_HOST_DIR read-only into api and migrate, with create_host_path disabled. - scripts/check-image.sh proves the image holds no modules and that a populated modules/ directory leaves its runtime file system identical; CI runs it. - The split deploy smoke now uses the same generic image: it boots with no modules, installs a self-contained test module packaged from the template (test-only), migrates and restarts, removes it (Core runs, planning reports the retained history, data survives), and restores it with its data. Docs: deploy README (module storage, install/update/remove/restore, the runtime modules target), .env.example, API configuration, module development guide, testing, workflow, security, and the architecture overview. Co-Authored-By: Claude Opus 5.5 <[email protected]>
213 lines
9.2 KiB
YAML
213 lines
9.2 KiB
YAML
name: CI
|
|
|
|
# The server runs Gitea 1.27.2 (docs/development/security.md, "What this server honours"):
|
|
#
|
|
# * `concurrency:` is honoured: a newer push to a pull request cancels the run it supersedes, so a
|
|
# cancelled run's own cleanup is never guaranteed (see the CI stack sweep in each stack job).
|
|
# * `permissions:` is supported by 1.27 with Gitea's own scopes, but what the job token can do on
|
|
# this server is not yet proven, so nothing here relies on it being read-only.
|
|
# * `timeout-minutes:` is no longer listed as unsupported, but is not verified here either;
|
|
# teardown relies on the stack sweep and the runner's own reaper.
|
|
#
|
|
# `persist-credentials: false` below is unaffected by any of this and is enforced by the action
|
|
# itself. See docs/development/security.md.
|
|
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches:
|
|
- main
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
verify:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
|
|
# step — including one a pull request added to this file — could read or reuse it. Nothing
|
|
# in this workflow pushes, so the credential is never needed after checkout.
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version-file: .node-version
|
|
|
|
- name: Activate pinned pnpm
|
|
run: |
|
|
corepack enable
|
|
corepack prepare [email protected] --activate
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Verify repository
|
|
run: pnpm ci:verify
|
|
|
|
infrastructure-smoke:
|
|
needs: verify
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
env:
|
|
# Names this job's stacks, so its final step removes exactly these (scripts/lib/ci-project.sh).
|
|
EKKLESIA_CI_JOB_KEY: infrastructure-smoke
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
|
|
# step — including one a pull request added to this file — could read or reuse it. Nothing
|
|
# in this workflow pushes, so the credential is never needed after checkout.
|
|
persist-credentials: false
|
|
|
|
# A job the runner killed (cancelled, or past the runner's own time limit) never ran its
|
|
# cleanup. Anything a CI job left on this shared daemon more than three hours ago goes first.
|
|
- name: Sweep stale CI stacks from the runner
|
|
run: bash scripts/ci-sweep.sh --stale
|
|
|
|
- name: Prove the sweep removes exactly what it should, against real Docker resources
|
|
run: bash scripts/test-ci-sweep.sh
|
|
|
|
- name: Exercise PostgreSQL and S3 infrastructure
|
|
run: bash scripts/smoke-infrastructure.sh
|
|
|
|
# Whatever happened above, this job's own stacks do not outlive it.
|
|
- name: Remove this job's CI stacks
|
|
if: always()
|
|
run: bash scripts/ci-sweep.sh --job
|
|
|
|
migration-smoke:
|
|
needs: verify
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
env:
|
|
# Names this job's stacks, so its final step removes exactly these (scripts/lib/ci-project.sh).
|
|
EKKLESIA_CI_JOB_KEY: migration-smoke
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
|
|
# step — including one a pull request added to this file — could read or reuse it. Nothing
|
|
# in this workflow pushes, so the credential is never needed after checkout.
|
|
persist-credentials: false
|
|
|
|
# A job the runner killed (cancelled, or past the runner's own time limit) never ran its
|
|
# cleanup. Anything a CI job left on this shared daemon more than three hours ago goes first.
|
|
- name: Sweep stale CI stacks from the runner
|
|
run: bash scripts/ci-sweep.sh --stale
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version-file: .node-version
|
|
|
|
- name: Activate pinned pnpm
|
|
run: |
|
|
corepack enable
|
|
corepack prepare [email protected] --activate
|
|
|
|
- name: Install dependencies
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Exercise transactional migrations against PostgreSQL
|
|
run: pnpm test:migrations:integration
|
|
|
|
# Whatever happened above, this job's own stacks do not outlive it.
|
|
- name: Remove this job's CI stacks
|
|
if: always()
|
|
run: bash scripts/ci-sweep.sh --job
|
|
|
|
image-build:
|
|
needs: verify
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
env:
|
|
# Names this job's stacks, so its final step removes exactly these (scripts/lib/ci-project.sh).
|
|
EKKLESIA_CI_JOB_KEY: image-build
|
|
# Cache mounts in deploy/Dockerfile need BuildKit; this selects it even where the Docker CLI
|
|
# has no buildx plugin.
|
|
DOCKER_BUILDKIT: "1"
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
|
|
# step — including one a pull request added to this file — could read or reuse it. Nothing
|
|
# in this workflow pushes, so the credential is never needed after checkout.
|
|
persist-credentials: false
|
|
|
|
# A job the runner killed (cancelled, or past the runner's own time limit) never ran its
|
|
# cleanup. Anything a CI job left on this shared daemon more than three hours ago goes first.
|
|
- name: Sweep stale CI stacks from the runner
|
|
run: bash scripts/ci-sweep.sh --stale
|
|
|
|
# The one generic platform image (ADR 0044, ADR 0048), proved rather than assumed: it contains no
|
|
# module, no module template, and no module manifest; its module storage mount point is empty;
|
|
# EKKLESIA_MODULES_DIR names it; and a checkout with a populated modules/ directory builds an
|
|
# identical runtime file system. Build only — no registry login, no credentials, no push, no tag
|
|
# anywhere but this runner's local image store. Tagged per run and attempt, and labeled like
|
|
# every CI stack, so this job's final step removes the images even if a build is killed.
|
|
- name: Build the generic platform image and prove it holds no modules
|
|
run: bash scripts/check-image.sh
|
|
|
|
# Whatever happened above, this job's own stacks do not outlive it.
|
|
- name: Remove this job's CI stacks
|
|
if: always()
|
|
run: bash scripts/ci-sweep.sh --job
|
|
|
|
deploy-smoke:
|
|
needs: verify
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
# Both topologies (ADR 0045), each on the one generic image: same-origin, the default every
|
|
# installation gets, and split, which installs a test-only module into module storage, removes
|
|
# it, and restores it (ADR 0048), and drives a real headless browser across the two origins.
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
topology:
|
|
- same-origin
|
|
- split
|
|
env:
|
|
# Names this job's stacks, so its final step removes exactly these (scripts/lib/ci-project.sh).
|
|
EKKLESIA_CI_JOB_KEY: deploy-smoke-${{ matrix.topology }}
|
|
DOCKER_BUILDKIT: "1"
|
|
EKKLESIA_SMOKE_TOPOLOGY: ${{ matrix.topology }}
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# `actions/checkout` defaults to leaving the job token in `.git/config`, where any later
|
|
# step — including one a pull request added to this file — could read or reuse it. Nothing
|
|
# in this workflow pushes, so the credential is never needed after checkout.
|
|
persist-credentials: false
|
|
|
|
# A job the runner killed (cancelled, or past the runner's own time limit) never ran its
|
|
# cleanup. Anything a CI job left on this shared daemon more than three hours ago goes first.
|
|
- name: Sweep stale CI stacks from the runner
|
|
run: bash scripts/ci-sweep.sh --stale
|
|
|
|
# The reference deployment end to end (ADR 0044): the generic platform image, base + proxy with
|
|
# Caddy's internal CA (test-only), an explicit migration apply against this job's own fresh
|
|
# PostgreSQL, then the API, Dashboard, storage delivery, client-address chain, and verify-full
|
|
# database TLS through the proxy. Every secret is generated for this run and discarded with
|
|
# it; nothing is pushed, published, or logged in to.
|
|
- name: Smoke-test the reference deployment (${{ matrix.topology }})
|
|
run: bash scripts/smoke-deploy.sh
|
|
|
|
# Whatever happened above, this job's own stacks do not outlive it.
|
|
- name: Remove this job's CI stacks
|
|
if: always()
|
|
run: bash scripts/ci-sweep.sh --job
|