mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-09-29 16:24:22 +10:00
fix(ci): restore Docker publishing with portable runner fallbacks (#3533)
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
This commit is contained in:
co-authored by
Amruth Pillai
parent
0ac320b0e9
commit
f0bc26cb3d
@@ -13,7 +13,7 @@ env:
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
runs-on: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
|
||||
@@ -10,7 +10,7 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
crowdin-sync:
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
runs-on: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -24,7 +24,7 @@ env:
|
||||
|
||||
jobs:
|
||||
mode:
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
runs-on: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
|
||||
outputs:
|
||||
nightly: ${{ steps.mode.outputs.nightly }}
|
||||
@@ -61,10 +61,10 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- platform: linux/amd64
|
||||
runner: blacksmith-32vcpu-ubuntu-2404
|
||||
runner: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
arch: amd64
|
||||
- platform: linux/arm64
|
||||
runner: blacksmith-32vcpu-ubuntu-2404-arm
|
||||
runner: ${{ vars.CI_RUNNER_ARM64 || 'ubuntu-24.04-arm' }}
|
||||
arch: arm64
|
||||
|
||||
runs-on: ${{ matrix.runner }}
|
||||
@@ -80,12 +80,38 @@ jobs:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Blacksmith Docker Builder
|
||||
uses: useblacksmith/setup-docker-builder@v2
|
||||
with:
|
||||
cache-key: Dockerfile-${{ matrix.arch }}
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- ®istries
|
||||
name: Determine registries
|
||||
id: registries
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
dockerhub=false
|
||||
ghcr_image="${GHCR_IMAGE,,}"
|
||||
docker_image="${DOCKER_IMAGE,,}"
|
||||
images="$ghcr_image"
|
||||
if [[ -n "$DOCKER_USERNAME" && -n "$DOCKER_PASSWORD" ]]; then
|
||||
dockerhub=true
|
||||
images="${images}"$'\n'"$docker_image"
|
||||
fi
|
||||
|
||||
{
|
||||
echo "ghcr_image=$ghcr_image"
|
||||
echo "docker_image=$docker_image"
|
||||
echo "images<<EOF"
|
||||
echo "$images"
|
||||
echo "EOF"
|
||||
echo "dockerhub=$dockerhub"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ steps.registries.outputs.dockerhub == 'true' }}
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
@@ -102,15 +128,13 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_IMAGE }}
|
||||
${{ env.DOCKER_IMAGE }}
|
||||
images: ${{ steps.registries.outputs.images }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,suffix=-${{ matrix.arch }}
|
||||
|
||||
- name: Cache-only smoke build
|
||||
if: ${{ needs.mode.outputs.canary == 'true' }}
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
platforms: ${{ matrix.platform }}
|
||||
@@ -118,7 +142,7 @@ jobs:
|
||||
|
||||
- name: Build and Push by Digest
|
||||
id: build
|
||||
uses: useblacksmith/build-push-action@v2
|
||||
uses: docker/build-push-action@v7
|
||||
with:
|
||||
context: .
|
||||
sbom: true
|
||||
@@ -148,7 +172,11 @@ jobs:
|
||||
- mode
|
||||
- build
|
||||
timeout-minutes: 30
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
runs-on: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
|
||||
env:
|
||||
DEPLOY: ${{ secrets.SSH_KEY != '' && secrets.SSH_HOST != '' && secrets.SSH_USER != '' }}
|
||||
PURGE_CLOUDFLARE: ${{ secrets.CLOUDFLARE_ZONE_ID != '' && secrets.CLOUDFLARE_API_TOKEN != '' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -177,7 +205,10 @@ jobs:
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- *registries
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: ${{ steps.registries.outputs.dockerhub == 'true' }}
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
@@ -204,9 +235,7 @@ jobs:
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: |
|
||||
${{ env.GHCR_IMAGE }}
|
||||
${{ env.DOCKER_IMAGE }}
|
||||
images: ${{ steps.registries.outputs.images }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-
|
||||
type=raw,value=canary-${{ github.run_id }}-${{ github.run_attempt }},enable=${{ needs.mode.outputs.canary == 'true' }}
|
||||
@@ -241,14 +270,17 @@ jobs:
|
||||
--annotation "index:org.opencontainers.image.documentation=https://docs.rxresu.me" \
|
||||
--annotation "index:org.opencontainers.image.source=https://github.com/${{ github.repository }}" \
|
||||
--annotation "index:org.opencontainers.image.version=${{ steps.version.outputs.version }}" \
|
||||
$(printf '${{ env.GHCR_IMAGE }}@sha256:%s ' *)
|
||||
$(printf '${{ steps.registries.outputs.ghcr_image }}@sha256:%s ' *)
|
||||
|
||||
# Get the digest of the multi-arch manifest
|
||||
GHCR_DIGEST=$(docker buildx imagetools inspect ${{ env.GHCR_IMAGE }}:${FINAL_TAG} --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
DOCKER_DIGEST=$(docker buildx imagetools inspect ${{ env.DOCKER_IMAGE }}:${FINAL_TAG} --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
GHCR_DIGEST=$(docker buildx imagetools inspect ${{ steps.registries.outputs.ghcr_image }}:${FINAL_TAG} --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
echo "final_tag=$FINAL_TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "ghcr_digest=$GHCR_DIGEST" >> "$GITHUB_OUTPUT"
|
||||
echo "docker_digest=$DOCKER_DIGEST" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [[ "${{ steps.registries.outputs.dockerhub }}" == "true" ]]; then
|
||||
DOCKER_DIGEST=$(docker buildx imagetools inspect ${{ steps.registries.outputs.docker_image }}:${FINAL_TAG} --format '{{json .Manifest.Digest}}' | tr -d '"')
|
||||
echo "docker_digest=$DOCKER_DIGEST" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Install Cosign
|
||||
uses: sigstore/cosign-installer@v3
|
||||
@@ -256,15 +288,19 @@ jobs:
|
||||
- name: Sign images with Cosign
|
||||
run: |
|
||||
# Sign GHCR image
|
||||
cosign sign --yes ${{ env.GHCR_IMAGE }}@${{ steps.manifest.outputs.ghcr_digest }}
|
||||
cosign sign --yes ${{ steps.registries.outputs.ghcr_image }}@${{ steps.manifest.outputs.ghcr_digest }}
|
||||
|
||||
# Sign Docker Hub image
|
||||
cosign sign --yes ${{ env.DOCKER_IMAGE }}@${{ steps.manifest.outputs.docker_digest }}
|
||||
if [[ "${{ steps.registries.outputs.dockerhub }}" == "true" ]]; then
|
||||
# Sign Docker Hub image
|
||||
cosign sign --yes ${{ steps.registries.outputs.docker_image }}@${{ steps.manifest.outputs.docker_digest }}
|
||||
fi
|
||||
|
||||
- name: Inspect image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.GHCR_IMAGE }}:${{ steps.manifest.outputs.final_tag }}
|
||||
docker buildx imagetools inspect ${{ env.DOCKER_IMAGE }}:${{ steps.manifest.outputs.final_tag }}
|
||||
docker buildx imagetools inspect ${{ steps.registries.outputs.ghcr_image }}:${{ steps.manifest.outputs.final_tag }}
|
||||
if [[ "${{ steps.registries.outputs.dockerhub }}" == "true" ]]; then
|
||||
docker buildx imagetools inspect ${{ steps.registries.outputs.docker_image }}:${{ steps.manifest.outputs.final_tag }}
|
||||
fi
|
||||
|
||||
- name: Verify anonymous pulls on both architectures
|
||||
run: |
|
||||
@@ -273,7 +309,11 @@ jobs:
|
||||
trap 'rm -rf "$registry_config"' EXIT
|
||||
# Prevent Docker from discovering a system credential helper.
|
||||
printf '%s\n' '{"auths":{"ghcr.io":{},"https://index.docker.io/v1/":{}}}' > "$registry_config/config.json"
|
||||
for image in "$GHCR_IMAGE" "$DOCKER_IMAGE"; do
|
||||
images=("${{ steps.registries.outputs.ghcr_image }}")
|
||||
if [[ "${{ steps.registries.outputs.dockerhub }}" == "true" ]]; then
|
||||
images+=("${{ steps.registries.outputs.docker_image }}")
|
||||
fi
|
||||
for image in "${images[@]}"; do
|
||||
for platform in linux/amd64 linux/arm64; do
|
||||
docker --config "$registry_config" pull --quiet --platform "$platform" \
|
||||
"$image:${{ steps.manifest.outputs.final_tag }}"
|
||||
@@ -281,7 +321,7 @@ jobs:
|
||||
done
|
||||
|
||||
- name: Redeploy Stack
|
||||
if: ${{ needs.mode.outputs.release == 'true' }}
|
||||
if: ${{ needs.mode.outputs.release == 'true' && env.DEPLOY == 'true' }}
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
key: ${{ secrets.SSH_KEY }}
|
||||
@@ -292,7 +332,7 @@ jobs:
|
||||
./manage_stack.sh up reactive_resume
|
||||
|
||||
- name: Purge Cloudflare cache
|
||||
if: ${{ needs.mode.outputs.release == 'true' }}
|
||||
if: ${{ needs.mode.outputs.release == 'true' && env.PURGE_CLOUDFLARE == 'true' }}
|
||||
env:
|
||||
CLOUDFLARE_ZONE_ID: ${{ secrets.CLOUDFLARE_ZONE_ID }}
|
||||
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
||||
|
||||
@@ -20,7 +20,7 @@ env:
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
runs-on: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
timeout-minutes: 30
|
||||
|
||||
services:
|
||||
@@ -58,12 +58,7 @@ jobs:
|
||||
|
||||
- name: Install Playwright Browser
|
||||
timeout-minutes: 10
|
||||
run: |
|
||||
# The runner's Ubuntu archive/security mirrors time out during dependency installation.
|
||||
printf '%s\n' 'https://mirrors.edge.kernel.org/ubuntu/' | sudo tee /etc/apt/blacksmith-ubuntu-mirrors.txt > /dev/null
|
||||
sudo find /etc/apt -type f \( -name '*.list' -o -name '*.sources' \) \
|
||||
-exec sed -i -E 's#https?://(archive|us\.archive|security)\.ubuntu\.com/ubuntu#https://mirrors.edge.kernel.org/ubuntu#g' {} +
|
||||
pnpm exec playwright install --with-deps chromium
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
|
||||
- name: Generate Test Secrets
|
||||
run: |
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
label:
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
runs-on: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
|
||||
@@ -10,7 +10,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: blacksmith-32vcpu-ubuntu-2404
|
||||
runs-on: ${{ vars.CI_RUNNER_X64 || 'ubuntu-latest' }}
|
||||
|
||||
steps:
|
||||
- name: Close Inactive Issues Awaiting Information
|
||||
|
||||
@@ -6,31 +6,38 @@ The repository is `reactive-resume/reactive-resume`. Docker Hub remains
|
||||
|
||||
## Builds and release safety
|
||||
|
||||
`.github/workflows/docker-build.yml` builds AMD64 and ARM64 on matching native Blacksmith
|
||||
32-vCPU runners. Blacksmith's persistent Docker builder caches layers and cache mounts;
|
||||
the architecture-specific cache keys keep the two builders separate.
|
||||
`.github/workflows/docker-build.yml` builds AMD64 and ARM64 on matching native runners.
|
||||
Repository variables `CI_RUNNER_X64` and `CI_RUNNER_ARM64` select the runner labels;
|
||||
they default to `ubuntu-latest` and `ubuntu-24.04-arm`, respectively. Other CI workflows
|
||||
also use `CI_RUNNER_X64`. Docker Buildx shares its local cache between steps within a job;
|
||||
no cache is persisted between workflow runs.
|
||||
|
||||
| Trigger | Published aliases | Production deployment |
|
||||
| --- | --- | --- |
|
||||
| Push to `main` | `sha-*`, `nightly`, timestamped nightly | No |
|
||||
| Manual dispatch, default `release=false` | `sha-*`, `canary-<run-id>-<attempt>` | No |
|
||||
| Push of a `v*` tag or explicit `release=true` | `sha-*`, `latest`, version/major/minor | Yes: SSH redeploy and Cloudflare purge |
|
||||
| Push of a `v*` tag or explicit `release=true` | `sha-*`, `latest`, version/major/minor | When configured: SSH redeploy and Cloudflare purge |
|
||||
|
||||
Manual `release=true` republishes the version already in `package.json` and redeploys
|
||||
production. It does not create a Git tag, GitHub release, or version bump. Use this for
|
||||
an approved current-version rebuild; it replaces the existing stable image aliases.
|
||||
Manual `release=true` republishes the version already in `package.json` and runs configured
|
||||
production integrations. SSH redeployment requires `SSH_KEY`, `SSH_HOST`, and `SSH_USER`;
|
||||
Cloudflare purging requires `CLOUDFLARE_ZONE_ID` and `CLOUDFLARE_API_TOKEN`. Each integration
|
||||
is skipped if any of its required secrets is missing. Dispatch does not create a Git tag,
|
||||
GitHub release, or version bump. Use this for an approved current-version rebuild;
|
||||
it replaces the existing stable image aliases.
|
||||
|
||||
Manual canaries first run a cache-only build on each architecture, then publish, merge,
|
||||
and sign both registry images. Run one with:
|
||||
and sign images in the enabled registries. Run one with:
|
||||
|
||||
```bash
|
||||
gh workflow run docker-build.yml --repo reactive-resume/reactive-resume --ref main -f release=false
|
||||
```
|
||||
|
||||
Both registries retain SBOMs, maximum provenance, and Cosign signatures. Publishing uses
|
||||
`DOCKER_USERNAME` / `DOCKER_PASSWORD` for Docker Hub and the destination repository's
|
||||
`GITHUB_TOKEN` with `packages: write` for GHCR. New GHCR packages need public visibility,
|
||||
repository linkage, and Actions access before consumers can pull anonymously.
|
||||
Published images retain SBOMs, maximum provenance, and Cosign signatures. GHCR always uses
|
||||
the destination repository's `GITHUB_TOKEN` with `packages: write`. Docker Hub publishing
|
||||
is enabled only when both `DOCKER_USERNAME` and `DOCKER_PASSWORD` secrets are present;
|
||||
otherwise login, publishing, signing, and verification target GHCR only. Image references
|
||||
are normalized to lowercase. New GHCR packages need public visibility, repository linkage,
|
||||
and Actions access before consumers can pull anonymously.
|
||||
|
||||
## Verification and historical images
|
||||
|
||||
@@ -62,7 +69,7 @@ rm -r "$registry_config"
|
||||
|
||||
On September 11, 2026, `latest`, `v5`, `v5.3`, and `v5.3.0` were copied to the then-current
|
||||
public GHCR package, `ghcr.io/reactive-resume/app`. Both architectures were pulled anonymously; the original Cosign signature,
|
||||
SBOMs, provenance, and image digest were verified. The signed Blacksmith canary
|
||||
SBOMs, provenance, and image digest were verified. The signed canary
|
||||
[`canary-34582818410-1`](https://github.com/reactive-resume/reactive-resume/actions/runs/34582818410)
|
||||
also passed on both registries without deploying production.
|
||||
|
||||
@@ -108,6 +115,5 @@ OIDC trust policies; the repository URL alone does not describe the subject.
|
||||
Track availability and supported copied tags in [migration issue #3503](https://github.com/reactive-resume/reactive-resume/issues/3503).
|
||||
No database reset, volume deletion, or resume-data migration is required.
|
||||
|
||||
References: [Blacksmith Docker caching](https://docs.blacksmith.sh/blacksmith-caching/docker-builds),
|
||||
[GitHub package permissions](https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages),
|
||||
References: [GitHub package permissions](https://docs.github.com/en/packages/learn-github-packages/about-permissions-for-github-packages),
|
||||
[Cosign verification](https://docs.sigstore.dev/cosign/verifying/verify/).
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
it("rewrites Ubuntu APT sources while preserving unrelated repositories and signature settings", () => {
|
||||
const workflow = readFileSync(new URL("../.github/workflows/e2e.yml", import.meta.url), "utf8");
|
||||
const expression = workflow.match(/-exec sed -i -E '([^']+)'/)?.[1];
|
||||
if (!expression) throw new Error("Ubuntu mirror rewrite is missing");
|
||||
const input = [
|
||||
"deb http://archive.ubuntu.com/ubuntu noble main",
|
||||
"URIs: http://us.archive.ubuntu.com/ubuntu/",
|
||||
"URIs: https://security.ubuntu.com/ubuntu/",
|
||||
"Signed-By: /usr/share/keyrings/ubuntu-archive-keyring.gpg",
|
||||
"URIs: mirror+file:/etc/apt/blacksmith-ubuntu-mirrors.txt",
|
||||
"deb https://packages.microsoft.com/ubuntu/24.04/prod noble main",
|
||||
].join("\n");
|
||||
const output = execFileSync("sed", ["-E", expression], { input, encoding: "utf8" });
|
||||
expect(output.trimEnd()).toBe(
|
||||
input
|
||||
.replace("http://archive.ubuntu.com/ubuntu", "https://mirrors.edge.kernel.org/ubuntu")
|
||||
.replace("http://us.archive.ubuntu.com/ubuntu", "https://mirrors.edge.kernel.org/ubuntu")
|
||||
.replace("https://security.ubuntu.com/ubuntu", "https://mirrors.edge.kernel.org/ubuntu"),
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user