#!/usr/bin/env bash # Checks that Signage's CI cleans up every stack it starts, even when the runner kills a job (the # platform's rule: docs/development/security.md, "Disposable stacks never outlive their job"). # # bash ci/check-cleanup.sh # # The `integration` job is the one that starts Docker stacks. It must have its own # EKKLESIA_CI_JOB_KEY, sweep stale CI stacks before it starts anything, and remove its own in a final # `if: always()` step. The pinned platform smoke owns and labels every image and container. set -Eeuo pipefail root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" workflow="$root/.gitea/workflows/ci.yml" failures=0 check() { if eval "$1"; then echo "ok $2" else echo "FAIL $2" failures=$((failures + 1)) fi } # The integration job's lines, from its key to the next job (or the end of the file). job="$(awk '/^ integration:$/ { inside = 1; print; next } inside && /^ [a-z][a-z-]*:$/ { exit } inside { print }' "$workflow")" line_of() { grep -n -F -- "$1" <<<"$job" | head -1 | cut -d: -f1 || true; } stale="$(line_of 'ci-sweep.sh --stale')" run="$(line_of 'bash ci/integration.sh')" own="$(line_of 'ci-sweep.sh --job')" last_step="$(grep -n -- " - name:" <<<"$job" | tail -1 | cut -d: -f1 || true)" check '[[ -n "$job" ]]' "the workflow has an integration job" check '! grep -q "if: false" <<<"$job"' "the integration job is enabled" check 'grep -q "EKKLESIA_CI_JOB_KEY:" <<<"$job"' "the integration job names its stacks with its own key" check '[[ -n "$stale" && -n "$run" ]] && ((stale < run))' "it sweeps stale CI stacks before it starts anything" check '[[ -n "$own" && -n "$run" ]] && ((own > run)) && ((own > last_step))' \ "its last step removes its own stacks" check 'sed -n "${last_step},\$p" <<<"$job" | grep -q "if: always()"' "that last step always runs" check 'grep -q "EKKLESIA_SMOKE_MODULE_RELEASE_DIR" "$root/ci/integration.sh" && grep -q "bash scripts/smoke-deploy.sh" "$root/ci/integration.sh"' \ "the integration delegates its external package to the platform smoke" check 'grep -Eq "^EKKLESIA_PLATFORM_REF=refs/(pull/[0-9]+/head|heads/main)$" "$root/ci/platform.env" && grep -Eq "^EKKLESIA_PLATFORM_COMMIT=[0-9a-f]{40}$" "$root/ci/platform.env"' \ "the platform harness is pinned by full ref and commit" check 'grep -q "rev-parse refs/remotes/pinned" "$root/ci/fetch-platform.sh" && grep -q "does not resolve to EKKLESIA_PLATFORM_COMMIT" "$root/ci/fetch-platform.sh"' \ "the fetch verifies that the pinned ref resolves to the pinned commit" # The package job is the only place that builds the canonical archive. Both integration and release # receive the same Gitea Actions v3 artifact, and release cannot run until every required job passed. package_job="$(awk '/^ package:$/ { inside = 1; print; next } inside && /^ [a-z][a-z-]*:$/ { exit } inside { print }' "$workflow")" release_job="$(awk '/^ release:$/ { inside = 1; print; next } inside && /^ [a-z][a-z-]*:$/ { exit } inside { print }' "$workflow")" check 'grep -q "tags:" "$workflow" && grep -q '\"v\*\"' "$workflow"' \ "v tags start the gated workflow" check 'grep -q "bash ci/package.sh" <<<"$package_job" && grep -q "actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de" <<<"$package_job"' \ "the package job builds and transfers one canonical artifact" check 'grep -q "actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b" <<<"$job" && ! grep -Eq "ci/package.sh|ekklesia-pack-module" <<<"$job"' \ "integration downloads that artifact and never rebuilds it" check 'grep -q "needs.package.outputs.sha256" <<<"$job" && grep -q "GITHUB_OUTPUT" "$root/ci/package.sh"' \ "integration binds the downloaded bytes to the package job's digest" check 'for required in lint typecheck test build package integration; do grep -q " - $required" <<<"$release_job" || exit 1; done' \ "release waits for every required job" check 'grep -q "if: startsWith(github.ref, '\''refs/tags/v'\'')" <<<"$release_job" && grep -q "contents: write" <<<"$release_job"' \ "only a v tag gives the release job write permission" check 'grep -q "actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b" <<<"$release_job" && ! grep -Eq "ci/package.sh|ekklesia-pack-module" <<<"$release_job"' \ "release downloads the tested artifact and never rebuilds it" check 'grep -q "EXPECTED_PACKAGE_SHA256:.*needs.package.outputs.sha256" <<<"$release_job"' \ "release binds its selected and uploaded bytes to the package job's digest" check 'grep -q "ekklesia-corresponding-source verify" <<<"$release_job" && grep -q "release-assets.py upload" <<<"$release_job"' \ "the redistribution gate runs before the exact assets are uploaded" check 'grep -q "GITHUB_TOKEN:.*github.token" <<<"$release_job"' \ "upload uses Gitea's automatic per-job token" ((failures == 0)) || { echo "check-cleanup: ${failures} check(s) failed." >&2 exit 1 }