From 67263cb75c5410813dbf3a0aae46f393fca803d4 Mon Sep 17 00:00:00 2001 From: Sergei Poljanski Date: Thu, 2 Jul 2026 18:32:53 +0400 Subject: [PATCH] ci: gate build on tests, deploy waits for container health --- .forgejo/workflows/deploy.yaml | 53 +++++++++++++++++++++++++++++++--- .forgejo/workflows/test.yaml | 6 ++-- CLAUDE.md | 4 ++- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index 1cc2673..2309836 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -6,8 +6,44 @@ on: - main jobs: + # Same job as in test.yaml (which covers non-main branches); duplicated here + # so a red suite blocks the build+deploy — Forgejo has no cross-workflow needs. + test: + runs-on: nix-latest + services: + postgres: + image: postgres:17-alpine + env: + POSTGRES_USER: postgres + POSTGRES_HOST_AUTH_METHOD: trust + POSTGRES_DB: asxpio_test + steps: + - name: Prepare container for actions + run: | + echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf + # node for JS actions (checkout); git-minimal is already in the image + nix-env -iA nixpkgs.nodejs_22 + + - name: Checkout + uses: actions/checkout@v4 + + - name: Wait for Postgres + run: | + for i in $(seq 1 30); do + nix develop -c pg_isready -h postgres -U postgres && exit 0 + sleep 2 + done + echo "Postgres service never became ready" >&2 + exit 1 + + - name: Run test suite + env: + TEST_DATABASE_URL: postgres://postgres@postgres:5432/asxpio_test + run: nix develop -c bundle exec rake test + build: runs-on: arch-latest + needs: test container: image: gcr.io/kaniko-project/executor:debug outputs: @@ -115,7 +151,16 @@ jobs: docker pull $NEW_IMAGE docker compose up -d - sleep 5 - docker ps | grep asxpio || exit 1 - - echo "Deployment successful!" + # Wait for the image HEALTHCHECK (GET /healthz) to report healthy; + # `docker ps | grep` passed even while the app crash-looped. + for i in $(seq 1 18); do + status=$(docker inspect --format '{{.State.Health.Status}}' asxpio 2>/dev/null || echo missing) + if [ "$status" = "healthy" ]; then + echo "Deployment successful!" + exit 0 + fi + sleep 5 + done + echo "asxpio never became healthy (last status: $status)" >&2 + docker logs --tail 50 asxpio + exit 1 diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml index 36d6631..19c46df 100644 --- a/.forgejo/workflows/test.yaml +++ b/.forgejo/workflows/test.yaml @@ -1,9 +1,11 @@ name: Tests +# main is covered by the test job inside deploy.yaml (which gates the build); +# this workflow covers every other branch. on: push: - branches: - - '**' + branches-ignore: + - main jobs: test: diff --git a/CLAUDE.md b/CLAUDE.md index dd39811..4eb5e66 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -84,6 +84,7 @@ Public: - `GET /i/:uuid` — landing page: client name, number, total, status badge, download button. - `GET /i/:uuid/pdf` — 302 to a 5-minute MinIO presigned URL (Content-Disposition: attachment). +- `GET /healthz` — liveness (plus `SELECT 1` when invoicing is configured); used by the Docker HEALTHCHECK and the deploy pipeline's post-deploy wait. ### Storage model @@ -138,10 +139,11 @@ The hedgehog is the user's pre-IE personal mark; once the IE has its own logo, t - **Run tests:** `nix develop -c bin/test` — full suite (minitest + rack-test) against an ephemeral Postgres it provisions and tears down itself. `bundle exec rake test` runs without a DB, skipping DB-backed tests. Mail uses `Mail::TestMailer`; S3 uses aws-sdk stubbed responses; tests never touch real services, and `.env` is deliberately not loaded when `RACK_ENV=test`. - **Local image build (sanity check):** `docker compose build` - **Production deploy:** automatic via `.forgejo/workflows/deploy.yaml` on push to `main`. Pipeline: + 0. Test suite runs on the `nix-latest` runner label (postgres service container); a red suite blocks the build. Non-main branches get the same job from `test.yaml`. 1. Kaniko builds the image and pushes it to the Forgejo registry. 2. SSH copies `docker-compose.yml` to the deploy directory on the prod host. 3. SSH writes `.env` (mode 600) from Forgejo secrets. The full set of invoicing envs (`DATABASE_URL`, `S3_*`, `ADMIN_*`) is built here; the per-app `ASXPIO_DB_PASSWORD` / `ASXPIO_S3_*` values must match the storage repo's Forgejo secrets, since storage's init containers provision the corresponding role + MinIO user. - 4. SSH `sed`-substitutes the image tag in `docker-compose.yml` and runs `docker compose up -d`. + 4. SSH `sed`-substitutes the image tag in `docker-compose.yml`, runs `docker compose up -d`, and waits (up to 90s) for the image `HEALTHCHECK` (`GET /healthz`) to report healthy — a crash-looping container fails the deploy. ## Where the secrets live