diff --git a/.github/workflows/publish-server-image.yml b/.github/workflows/publish-server-image.yml index 4da8ac5..0d9d3c5 100644 --- a/.github/workflows/publish-server-image.yml +++ b/.github/workflows/publish-server-image.yml @@ -3,6 +3,10 @@ name: publish-server-image # Release tags publish the source-addressed image used by in-app updates plus the human-readable # semver tags. Main publishes `edge`. Pull requests build with a read-only token and an isolated # cache, so untrusted code can validate the images without receiving package-publish authority. +# Self-hosted runners on one host often share ~/.docker; build/publish set DOCKER_CONFIG under +# RUNNER_TEMP so docker/login-action post-logout cannot evict a concurrent job's GHCR session. +# `ci---` handoff tags are registry labels on the per-arch digest; publish +# removes them via the distribution API (tag only, not the digest edge/sha tags reference). on: push: branches: [main] @@ -182,10 +186,14 @@ jobs: context: infra/sandboxes/computer - arch: amd64 runner: [self-hosted, evoo-ci, Linux] + env: + DOCKER_CONFIG: ${{ runner.temp }}/docker-config steps: - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 with: persist-credentials: false + - name: Prepare isolated Docker config directory + run: mkdir -p "$DOCKER_CONFIG" - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to GHCR uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 @@ -249,7 +257,11 @@ jobs: - name: app - name: updater - name: computer + env: + DOCKER_CONFIG: ${{ runner.temp }}/docker-config steps: + - name: Prepare isolated Docker config directory + run: mkdir -p "$DOCKER_CONFIG" - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0 - name: Log in to GHCR uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0 @@ -339,13 +351,22 @@ jobs: subject-name: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} subject-digest: ${{ steps.manifest.outputs.digest }} push-to-registry: true - - name: Remove CI handoff tags - continue-on-error: true + - name: Delete CI handoff tags env: + GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} IMAGE: ghcr.io/${{ steps.ns.outputs.repo }}/${{ matrix.name }} IMAGE_NAME: ${{ matrix.name }} run: | set -euo pipefail + repository="${IMAGE#ghcr.io/}" + token="$(curl -fsSL -u "${GITHUB_ACTOR}:${GHCR_TOKEN}" \ + "https://ghcr.io/token?service=ghcr.io&scope=repository:${repository}:pull,push" \ + | jq -er .token)" for arch in amd64; do - docker buildx imagetools rm "${IMAGE}:ci-${GITHUB_RUN_ID}-${IMAGE_NAME}-${arch}" + tag="ci-${GITHUB_RUN_ID}-${IMAGE_NAME}-${arch}" + curl -fsSL -o /dev/null -X DELETE \ + -H "Authorization: Bearer ${token}" \ + -H "Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json" \ + "https://ghcr.io/v2/${repository}/manifests/${tag}" + echo "Removed handoff tag ${tag} from ${repository}." done diff --git a/infra/updater/src/publish-workflow.test.ts b/infra/updater/src/publish-workflow.test.ts index 0c4b6b1..f7d4e80 100644 --- a/infra/updater/src/publish-workflow.test.ts +++ b/infra/updater/src/publish-workflow.test.ts @@ -62,6 +62,14 @@ describe("server image publish workflow", () => { expect(workflowText).toContain("ci-${GITHUB_RUN_ID}-"); }); + it("isolates Docker registry auth per job and deletes handoff tags without imagetools rm", () => { + expect(workflowText).toContain("DOCKER_CONFIG: ${{ runner.temp }}/docker-config"); + expect(workflowText).toContain("Prepare isolated Docker config directory"); + expect(workflowText).not.toContain("imagetools rm"); + expect(workflowText).toContain("Delete CI handoff tags"); + expect(workflowText).toContain("/v2/${repository}/manifests/${tag}"); + }); + it("names every image under the lowercase repository namespace", () => { expect(workflowText).not.toContain(`ghcr.io/\${{ github.repository }}`); const references =