# 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)" trap 'rm -rf "${BUILD_ROOT}"' 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 python3 "${BUILD_CONTEXT}/tools/fetch_sources.py" \ --config "${BUILD_CONTEXT}/source-inventory.config.json" \ --destination "${BUILD_CONTEXT}/_sources" \ --policy-revision "${REF}" NETKINGDOM_REVISION="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["repositories"]["net-kingdom"]["revision"])' "${BUILD_CONTEXT}/_sources/source-lock.json")" SOURCE_SET_DIGEST="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1]))["source_set_digest"])' "${BUILD_CONTEXT}/_sources/source-lock.json")" SOURCE_TAG="source-${SOURCE_SET_DIGEST}" REVISION_TAG="git-${REF}-sources-${SOURCE_SET_DIGEST:0:16}" 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 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}" | awk '/digest: sha256:/{print $2}' | tail -1)" test -n "${IMAGE_DIGEST}" 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