# Uses the estate's tier-2 container-build runner and organization-scoped # REGISTRY_USER / REGISTRY_TOKEN secrets. name: Build and publish policy-nexus image on: push: branches: - main paths: - ".forgejo/workflows/publish-image.yaml" - "Containerfile" - "deploy/**" - "publication.json" - "source-inventory.config.json" - "source-inventory.json" - "tests/**" - "tools/**" schedule: # Pull-based freshness audit. A failed source checkout, inventory drift, or # overdue document is visible as a failed scheduled Forgejo Actions run. - cron: "17 04 * * *" workflow_dispatch: concurrency: group: policy-nexus-publication cancel-in-progress: false env: REGISTRY: forgejo.coulomb.social IMAGE_NAME: coulomb/policy-nexus DOCKER_HOST: tcp://127.0.0.1:2375 jobs: build-and-push: runs-on: container-build steps: - name: Build, verify, and publish immutable policy artifact env: REGISTRY_USER: ${{ secrets.REGISTRY_USER }} REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | set -eu REF="${GITHUB_SHA:-}" test "${#REF}" -eq 40 SHORT="${REF:0:7}" BUILD_ROOT="$(mktemp -d)" PYTHON_CONTAINER="" cleanup() { if [ -n "${PYTHON_CONTAINER}" ] && [ -x "${HOME}/bin/docker" ]; then "${HOME}/bin/docker" rm -f "${PYTHON_CONTAINER}" >/dev/null 2>&1 || true fi rm -rf "${BUILD_ROOT}" } trap cleanup EXIT BUILD_CONTEXT="${BUILD_ROOT}/buildctx" mkdir -p "${BUILD_CONTEXT}" "${HOME}/bin" wget -qO "${BUILD_ROOT}/policy-nexus.tar.gz" \ "https://forgejo.coulomb.social/${GITHUB_REPOSITORY}/archive/${SHORT}.tar.gz" tar xzf "${BUILD_ROOT}/policy-nexus.tar.gz" \ -C "${BUILD_CONTEXT}" --strip-components=1 wget -qO- https://download.docker.com/linux/static/stable/x86_64/docker-27.3.1.tgz \ | tar xz --strip-components=1 -C "${HOME}/bin" docker/docker export PATH="${HOME}/bin:${PATH}" docker version PYTHON_IMAGE="docker.io/library/python@sha256:d09d15e60962ca365d1cd544a48773bac9d33f2fb1b00f2aa0deec78ade7dc31" PYTHON_CONTAINER="policy-source-fetch-${SHORT}-${GITHUB_RUN_ID:-0}" docker create --name "${PYTHON_CONTAINER}" "${PYTHON_IMAGE}" sleep 300 docker cp "${BUILD_CONTEXT}/." "${PYTHON_CONTAINER}:/workspace" docker start "${PYTHON_CONTAINER}" docker exec --workdir /workspace "${PYTHON_CONTAINER}" \ python3 tools/fetch_sources.py \ --config source-inventory.config.json \ --destination _sources \ --policy-revision "${REF}" NETKINGDOM_REVISION="$(docker exec "${PYTHON_CONTAINER}" python3 -c 'import json; print(json.load(open("/workspace/_sources/source-lock.json"))["repositories"]["net-kingdom"]["revision"])')" SOURCE_SET_DIGEST="$(docker exec "${PYTHON_CONTAINER}" python3 -c 'import json; print(json.load(open("/workspace/_sources/source-lock.json"))["source_set_digest"])')" docker cp "${PYTHON_CONTAINER}:/workspace/_sources" "${BUILD_CONTEXT}/" docker rm -f "${PYTHON_CONTAINER}" PYTHON_CONTAINER="" SOURCE_TAG="source-${SOURCE_SET_DIGEST}" REVISION_TAG="git-${REF}-sources-${SOURCE_SET_DIGEST:0:16}" echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \ -u "${REGISTRY_USER}" --password-stdin IMAGE="${REGISTRY}/${IMAGE_NAME}" docker build \ --file "${BUILD_CONTEXT}/Containerfile" \ --build-arg "VCS_REVISION=${REF}" \ --build-arg "NETKINGDOM_REVISION=${NETKINGDOM_REVISION}" \ --build-arg "SOURCE_SET_DIGEST=${SOURCE_SET_DIGEST}" \ --tag "${IMAGE}:${SOURCE_TAG}" \ --tag "${IMAGE}:${REVISION_TAG}" \ --tag "${IMAGE}:main" \ "${BUILD_CONTEXT}" PUSH_OUTPUT="$(docker push "${IMAGE}:${SOURCE_TAG}")" printf '%s\n' "${PUSH_OUTPUT}" IMAGE_DIGEST="$(printf '%s\n' "${PUSH_OUTPUT}" | tr '\r' '\n' | awk '{for (i=1; i<=NF; i++) if ($i ~ /^sha256:[a-f0-9]{64}$/) print $i}' | tail -1)" printf '%s' "${IMAGE_DIGEST}" | grep -Eq '^sha256:[a-f0-9]{64}$' docker push "${IMAGE}:${REVISION_TAG}" docker push "${IMAGE}:main" PUBLICATION_DIGEST=$(docker run --rm --entrypoint sha256sum \ "${IMAGE}:${SOURCE_TAG}" /usr/share/nginx/html/publication-manifest.json \ | awk '{print $1}') SOURCE_INVENTORY_DIGEST=$(docker run --rm --entrypoint sha256sum \ "${IMAGE}:${SOURCE_TAG}" /usr/share/nginx/html/source-inventory.json \ | awk '{print $1}') echo "published=${IMAGE}:${SOURCE_TAG}" echo "image_digest=${IMAGE_DIGEST}" echo "publication_manifest_digest=${PUBLICATION_DIGEST}" echo "source_inventory_digest=${SOURCE_INVENTORY_DIGEST}" echo "source_set_digest=${SOURCE_SET_DIGEST}" if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then { echo "## policy-nexus release candidate" echo echo "The scheduled pull only publishes an immutable candidate; it does not deploy." echo echo "- image: \`${IMAGE}@${IMAGE_DIGEST}\`" echo "- publication manifest: \`${PUBLICATION_DIGEST}\`" echo "- source inventory: \`${SOURCE_INVENTORY_DIGEST}\`" echo "- source set: \`${SOURCE_SET_DIGEST}\`" echo echo "Promotion requires an explicit paired digest update in rapp-policy-nexus and railiance-apps." } >> "${GITHUB_STEP_SUMMARY}" fi