Build and publish #10
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and publish | |
| # One multi-platform build on a single runner. Nothing is compiled — each | |
| # platform's stage downloads the matching RustCFML release binary — so the | |
| # arm64 half runs under QEMU only for `rustcfml --version` and the smoke test. | |
| # | |
| # tag vX.Y.Z[-N] -> :vX.Y.Z-N (if N), :vX.Y.Z, :X.Y, :latest | |
| # push to main -> :edge | |
| # pull request -> build only, no push | |
| # manual -> any engine version, pushed as :edge (or :vX.Y.Z when it is a release) | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ["v*"] | |
| pull_request: | |
| workflow_dispatch: | |
| inputs: | |
| rustcfml_version: | |
| description: "RustCFML release tag to package (e.g. v0.653.3). Defaults to the Dockerfile's ARG." | |
| required: false | |
| permissions: | |
| contents: read | |
| packages: write | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/rustcfml | |
| jobs: | |
| build: | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Resolve the engine version | |
| id: ver | |
| run: | | |
| DEFAULT=$(sed -nE 's/^ARG RUSTCFML_VERSION=(v[0-9.]+).*/\1/p' Dockerfile | head -1) | |
| VERSION="${{ github.event.inputs.rustcfml_version }}" | |
| VERSION="${VERSION:-$DEFAULT}" | |
| if [ "${{ github.ref_type }}" = "tag" ]; then | |
| # v0.653.3-2 -> engine v0.653.3, image build 2 | |
| BASE="${GITHUB_REF_NAME%-*}" | |
| if [ "$BASE" != "$DEFAULT" ]; then | |
| echo "::error::git tag ${GITHUB_REF_NAME} does not match ARG RUSTCFML_VERSION=${DEFAULT} in the Dockerfile"; exit 1 | |
| fi | |
| VERSION="$BASE" | |
| fi | |
| echo "version=$VERSION" >> "$GITHUB_OUTPUT" | |
| echo "image=$(echo '${{ env.IMAGE }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" | |
| echo "Packaging RustCFML $VERSION" | |
| - name: Compute tags | |
| id: tags | |
| run: | | |
| IMAGE='${{ steps.ver.outputs.image }}' | |
| V='${{ steps.ver.outputs.version }}' | |
| if [ "${{ github.ref_type }}" = "tag" ]; then | |
| MINOR="${V#v}"; MINOR="${MINOR%.*}" | |
| TAGS="$IMAGE:$V,$IMAGE:$MINOR,$IMAGE:latest" | |
| [ "$GITHUB_REF_NAME" != "$V" ] && TAGS="$IMAGE:$GITHUB_REF_NAME,$TAGS" | |
| else | |
| TAGS="$IMAGE:edge" | |
| fi | |
| echo "tags=$TAGS" >> "$GITHUB_OUTPUT" | |
| echo "$TAGS" | |
| - uses: docker/setup-qemu-action@v3 | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| if: github.event_name != 'pull_request' | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build (and push) | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: . | |
| platforms: linux/amd64,linux/arm64 | |
| push: ${{ github.event_name != 'pull_request' }} | |
| build-args: | | |
| RUSTCFML_VERSION=${{ steps.ver.outputs.version }} | |
| tags: ${{ steps.tags.outputs.tags }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| labels: | | |
| org.opencontainers.image.title=RustCFML | |
| org.opencontainers.image.description=RustCFML ${{ steps.ver.outputs.version }} — a CFML engine written in Rust | |
| org.opencontainers.image.version=${{ steps.ver.outputs.version }} | |
| org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| org.opencontainers.image.url=https://github.com/RustCFML/RustCFML | |
| org.opencontainers.image.licenses=MIT | |
| - name: Inspect the manifest | |
| if: github.event_name != 'pull_request' | |
| run: docker buildx imagetools inspect "${{ steps.ver.outputs.image }}:${{ github.ref_type == 'tag' && steps.ver.outputs.version || 'edge' }}" | |
| # A published image nobody can pull is not published. GHCR creates a | |
| # container package PRIVATE on first push regardless of how public the | |
| # repository is, and nothing in the push reports that — the build goes | |
| # green, the manifest inspects fine (the runner is authenticated), and the | |
| # `FROM ghcr.io/…` line in our own README fails for every user and for any | |
| # CI that tries to build on it. This step is the check that was missing: | |
| # it asks the registry for an ANONYMOUS pull token and fetches the | |
| # manifest with it, exactly as a stranger would. | |
| - name: Can a stranger pull it? | |
| if: github.event_name != 'pull_request' | |
| run: | | |
| set -uo pipefail | |
| REPO=$(echo "${{ github.repository_owner }}/rustcfml" | tr '[:upper:]' '[:lower:]') | |
| REF='${{ github.ref_type == 'tag' && steps.ver.outputs.version || 'edge' }}' | |
| # A PRIVATE package refuses the anonymous token itself, so a missing | |
| # token is the answer — do not let `set -e` and `curl -f` turn that | |
| # into a bare curl error. | |
| TOKEN=$(curl -sS "https://ghcr.io/token?scope=repository:${REPO}:pull&service=ghcr.io" \ | |
| | jq -r '.token // empty' 2>/dev/null || true) | |
| if [ -n "$TOKEN" ]; then | |
| CODE=$(curl -sS -o /dev/null -w '%{http_code}' \ | |
| -H "Authorization: Bearer ${TOKEN}" \ | |
| -H 'Accept: application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json' \ | |
| "https://ghcr.io/v2/${REPO}/manifests/${REF}") | |
| else | |
| CODE="401 — the registry would not even issue an anonymous pull token" | |
| fi | |
| if [ "$CODE" = "200" ]; then | |
| echo "ghcr.io/${REPO}:${REF} is publicly pullable." | |
| exit 0 | |
| fi | |
| echo "::warning title=Image is not publicly pullable::Anonymous pull of ghcr.io/${REPO}:${REF} gave ${CODE}. The image is published but nobody can use it." | |
| { | |
| echo "### :warning: \`ghcr.io/${REPO}\` is not publicly pullable" | |
| echo | |
| echo "An anonymous pull gave **${CODE}**. The push succeeded, but every" | |
| echo "\`docker pull\`, every \`FROM\` in a downstream Dockerfile and every CI" | |
| echo "builder — including Fly.io's — gets \`unauthorized\`." | |
| echo | |
| echo "GHCR creates a container package **private on first publish**, whatever the" | |
| echo "repository's visibility, and there is no REST API to change it. Fix it once," | |
| echo "by hand:" | |
| echo | |
| echo "1. <https://github.com/organizations/${{ github.repository_owner }}/settings/packages> → *Package creation* → tick **Public**." | |
| echo " (Until this is on, the option in step 2 is greyed out and does not say why.)" | |
| echo "2. <https://github.com/orgs/${{ github.repository_owner }}/packages/container/rustcfml/settings> → *Danger Zone* → **Change package visibility → Public**." | |
| echo | |
| echo "It stays public for every later push." | |
| } >> "$GITHUB_STEP_SUMMARY" |