mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
Every workflow action is pinned to a commit SHA, dependabot auto-merge is limited to patch and minor updates behind the now-required status checks on main, and the audit, Trivy and ESLint gates block instead of warning. The published image finally carries its build SHA and build timestamp so the version endpoint can prove which commit is running. The API-token HMAC key fails closed under 32 characters, compose pins the database minor and denies privilege escalation, and the changelog drops working-note references in favour of neutral wording.
145 lines
4.9 KiB
YAML
145 lines
4.9 KiB
YAML
name: Post-publish verify
|
|
|
|
# Informational reachability gate that runs AFTER `Build & Publish Docker
|
|
# Image` finishes a `main` (or tag) build. Pulls the just-published image
|
|
# from GHCR and probes:
|
|
# - the image manifest is fetchable (i.e. the push actually completed)
|
|
# - the container starts and `/api/version` returns the expected version
|
|
# - the container's `/api/health` endpoint reports OK
|
|
#
|
|
# Does NOT block deploys — `continue-on-error` is set on every probe step
|
|
# so a transient registry blip doesn't go red. This is a smoke gate the
|
|
# v1.4.14 release would have caught: the main push appeared to "succeed"
|
|
# from the deploy host's POV, but the host had to pull the tag image
|
|
# because main's manifest never finished pushing.
|
|
#
|
|
# Triggered by `workflow_run` so it only runs after the build workflow
|
|
# actually completes — chaining via `needs:` would force the build into
|
|
# the same workflow file.
|
|
|
|
on:
|
|
workflow_run:
|
|
workflows: ["Build & Publish Docker Image"]
|
|
types: [completed]
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
inputs:
|
|
image_tag:
|
|
description: "GHCR image tag to verify (defaults to `main`)"
|
|
required: false
|
|
default: "main"
|
|
|
|
permissions:
|
|
contents: read
|
|
packages: read
|
|
|
|
jobs:
|
|
verify:
|
|
name: Pull + smoke-test published image
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
# Skip if the upstream build itself failed — there's nothing to verify.
|
|
# Manual dispatch always runs.
|
|
if: >-
|
|
github.event_name == 'workflow_dispatch' ||
|
|
github.event.workflow_run.conclusion == 'success'
|
|
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_USER: healthlog
|
|
POSTGRES_PASSWORD: healthlog
|
|
POSTGRES_DB: healthlog
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd "pg_isready -U healthlog"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
|
|
steps:
|
|
- name: Resolve image tag
|
|
id: tag
|
|
run: |
|
|
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
|
echo "tag=${{ github.event.inputs.image_tag }}" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "tag=main" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Log in to GHCR
|
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Pull image
|
|
id: pull
|
|
continue-on-error: true
|
|
run: |
|
|
IMAGE="ghcr.io/${{ github.repository }}:${{ steps.tag.outputs.tag }}"
|
|
echo "Pulling $IMAGE"
|
|
docker pull "$IMAGE"
|
|
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Start container
|
|
if: steps.pull.outcome == 'success'
|
|
id: run
|
|
continue-on-error: true
|
|
run: |
|
|
# Minimal env to satisfy the strict-env validators on boot.
|
|
docker run -d --name healthlog-verify \
|
|
--network host \
|
|
-e DATABASE_URL='postgresql://healthlog:healthlog@localhost:5432/healthlog?schema=public' \
|
|
-e ENCRYPTION_KEYS='{"v1":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
|
|
-e ENCRYPTION_ACTIVE_KEY_ID=v1 \
|
|
-e API_TOKEN_HMAC_KEY=verify-hmac-key-32-bytes-padding-x \
|
|
-e AUTH_RP_NAME=HealthLog \
|
|
-e AUTH_RP_ID=localhost \
|
|
-e AUTH_RP_ORIGIN=http://localhost:3000 \
|
|
-e NODE_ENV=production \
|
|
-e HEALTHLOG_PROCESS_TYPE=web \
|
|
-e PORT=3000 \
|
|
"${{ steps.pull.outputs.image }}"
|
|
|
|
# Wait up to 60 s for the server to start answering.
|
|
for i in $(seq 1 30); do
|
|
if curl -sf -o /dev/null http://localhost:3000/api/version; then
|
|
echo "ready after ${i}*2s"
|
|
exit 0
|
|
fi
|
|
sleep 2
|
|
done
|
|
echo "::warning::Container did not become ready within 60 s"
|
|
docker logs healthlog-verify || true
|
|
exit 1
|
|
|
|
- name: Probe /api/version
|
|
if: steps.run.outcome == 'success'
|
|
continue-on-error: true
|
|
run: |
|
|
set -e
|
|
BODY=$(curl -sf http://localhost:3000/api/version)
|
|
echo "$BODY" | jq .
|
|
VER=$(echo "$BODY" | jq -r '.data.version // .version')
|
|
echo "::notice::published image ${{ steps.tag.outputs.tag }} reports version=$VER"
|
|
|
|
- name: Probe /api/health
|
|
if: steps.run.outcome == 'success'
|
|
continue-on-error: true
|
|
run: |
|
|
curl -sf http://localhost:3000/api/health | jq .
|
|
|
|
- name: Container logs (always)
|
|
if: always() && steps.run.outcome == 'success'
|
|
continue-on-error: true
|
|
run: docker logs healthlog-verify | tail -100
|
|
|
|
- name: Tear down
|
|
if: always()
|
|
continue-on-error: true
|
|
run: docker rm -f healthlog-verify || true
|