From 441a21204dbe272ffc9c2c2cbb8c24d20641d5c4 Mon Sep 17 00:00:00 2001 From: Brian Bjarke Jensen Date: Thu, 9 Jul 2026 22:39:33 +0200 Subject: [PATCH] Add local CI image build script and document bootstrap flow. Co-authored-by: Cursor --- docs/ci-image.md | 36 ++++++++++++++++- scripts/ci/build-ci-image.sh | 78 ++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100755 scripts/ci/build-ci-image.sh diff --git a/docs/ci-image.md b/docs/ci-image.md index cbbc386..5380c24 100644 --- a/docs/ci-image.md +++ b/docs/ci-image.md @@ -118,14 +118,48 @@ After label changes, roll runner pods so they re-register with Gitea. 1. Ensure homelab-platform runners have `gitea-registry-dockerconfig` and the `python-repositories-ci` label (see homelab-platform docs). 2. Merge `ci-image.yml`, `docker/ci/Dockerfile`, and workflow migrations to `main`. -3. Run **Build CI Image** manually once (Actions → Build CI Image → Run workflow). +3. Seed the registry with a first image (see below). 4. Confirm a test workflow job starts on `python-repositories-ci`. Until step 3 completes, jobs targeting `python-repositories-ci` will fail because the image does not exist in the Gitea registry yet. +### Seed the image + +**After merge to `main`:** Actions → **Build CI Image** → **Run workflow**. + +**Before merge (e.g. PR branch):** the workflow file is not on `main` yet — build +and push locally with [`scripts/ci/build-ci-image.sh`](../scripts/ci/build-ci-image.sh): + +```bash +export CI_RUNNER_TOKEN='ci-bot-personal-access-token' +bash scripts/ci/build-ci-image.sh --push +``` + +Run from the repo root on the branch you want to test. Runners pull +`gitea.lille-vemmelund.dk/brian/python-repositories-ci:latest` from the registry; +they do not care which git branch built it. + +Override registry settings if needed: + +```bash +export REGISTRY=gitea.lille-vemmelund.dk +export REGISTRY_USER=ci-bot +export CI_RUNNER_TOKEN='...' +bash scripts/ci/build-ci-image.sh --push +``` + ## Local build +Build only (no registry login or push): + +```bash +export CI_RUNNER_TOKEN='ci-bot-personal-access-token' +bash scripts/ci/build-ci-image.sh --local +``` + +Or manually: + ```bash echo "$CI_RUNNER_TOKEN" > /tmp/uv_token docker build -f docker/ci/Dockerfile \ diff --git a/scripts/ci/build-ci-image.sh b/scripts/ci/build-ci-image.sh new file mode 100755 index 0000000..7c0f3cc --- /dev/null +++ b/scripts/ci/build-ci-image.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash +# Build and optionally push the Gitea Actions CI base image locally. +# +# Use when ci-image.yml is not yet on main (e.g. bootstrapping a PR branch) or +# when you need to rebuild without waiting for the nightly workflow. +# +# Build only (smoke test): +# export CI_RUNNER_TOKEN='ci-bot-personal-access-token' +# bash scripts/ci/build-ci-image.sh --local +# +# Build and push to Gitea (unblocks python-repositories-ci jobs): +# export CI_RUNNER_TOKEN='ci-bot-personal-access-token' +# bash scripts/ci/build-ci-image.sh --push +# +# Optional overrides: +# REGISTRY=gitea.lille-vemmelund.dk +# REGISTRY_USER=ci-bot +# IMAGE_OWNER=brian +# IMAGE_NAME=python-repositories-ci +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" + +REGISTRY="${REGISTRY:-gitea.lille-vemmelund.dk}" +REGISTRY_USER="${REGISTRY_USER:-ci-bot}" +IMAGE_OWNER="${IMAGE_OWNER:-brian}" +IMAGE_NAME="${IMAGE_NAME:-python-repositories-ci}" +IMAGE="${IMAGE:-${REGISTRY}/${IMAGE_OWNER}/${IMAGE_NAME}}" + +MODE="push" +if [[ "${1:-}" == "--local" ]]; then + MODE="local" +elif [[ "${1:-}" == "--push" || -z "${1:-}" ]]; then + MODE="push" +elif [[ -n "${1:-}" ]]; then + echo "Usage: build-ci-image.sh [--local | --push]" >&2 + exit 1 +fi + +: "${CI_RUNNER_TOKEN:?CI_RUNNER_TOKEN is required (ci-bot token with read/write package access)}" + +TOKEN_FILE="$(mktemp)" +cleanup() { + rm -f "$TOKEN_FILE" +} +trap cleanup EXIT + +printf '%s' "$CI_RUNNER_TOKEN" >"$TOKEN_FILE" + +cd "$REPO_ROOT" + +if [[ "$MODE" == "local" ]]; then + echo "=== Building local CI image (no push): python-repositories-ci:local ===" + docker build -f docker/ci/Dockerfile \ + --secret "id=uv_token,src=${TOKEN_FILE}" \ + -t python-repositories-ci:local \ + . + echo "Built python-repositories-ci:local" + exit 0 +fi + +echo "=== Logging in to ${REGISTRY} as ${REGISTRY_USER} ===" +echo "$CI_RUNNER_TOKEN" | docker login "$REGISTRY" -u "$REGISTRY_USER" --password-stdin + +echo "=== Building ${IMAGE}:latest ===" +docker build -f docker/ci/Dockerfile \ + --secret "id=uv_token,src=${TOKEN_FILE}" \ + -t "${IMAGE}:latest" \ + . + +STAMP="$(date -u +%Y%m%d%H%M)" +echo "=== Pushing ${IMAGE}:latest and ${IMAGE}:${STAMP} ===" +docker tag "${IMAGE}:latest" "${IMAGE}:${STAMP}" +docker push "${IMAGE}:latest" +docker push "${IMAGE}:${STAMP}" + +echo "Done. Re-run failing CI jobs — runners pull ${IMAGE}:latest"