Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/branch-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ permissions:
jobs:
source_branch:
name: Merges into main come from staging
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Verify the head is this repository's staging branch
# Passed through the environment rather than interpolated into the
Expand Down
137 changes: 113 additions & 24 deletions .github/workflows/docker-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ jobs:
- quality
- pr_smoke
if: always() && github.event_name == 'pull_request'
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Require the quality checks to have passed
env:
Expand Down Expand Up @@ -104,7 +105,8 @@ jobs:
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
version: ${{ steps.metadata.outputs.version }}
steps:
Expand Down Expand Up @@ -133,8 +135,13 @@ jobs:
SECOND_FIELD=$(( SECOND + 10 )) # 10-69, always 2 digits
echo "version=$YEAR_FIELD.$DOY_FIELD.$MINUTE_OF_DAY_FIELD.$SECOND_FIELD" >> "$GITHUB_OUTPUT"

publish_images:
name: Publish ${{ matrix.target }} immutable image
# One runner per architecture rather than one emulating the other: arm64
# under QEMU takes the Bun build from a minute to the better part of an
# hour. Each leg pushes an untagged blob and reports its digest; the tag
# only ever appears on the manifest `publish_images` assembles from both,
# so a half-built pair can never be pulled.
build_platform_images:
name: Build ${{ matrix.target }} for ${{ matrix.platform.arch }}
needs: [quality, build_metadata]
if: >-
github.event_name == 'push' &&
Expand All @@ -146,7 +153,13 @@ jobs:
- feedfathom-server
- feedfathom-worker
- feedfathom-migrator
runs-on: blacksmith-4vcpu-ubuntu-2404
platform:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.platform.runner }}
timeout-minutes: 35
permissions:
contents: read
packages: write
Expand All @@ -157,8 +170,8 @@ jobs:
with:
ref: ${{ github.sha }}

- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
Expand All @@ -167,26 +180,98 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and publish immutable ${{ matrix.target }} image
uses: useblacksmith/build-push-action@v2
- name: Build ${{ matrix.target }} for linux/${{ matrix.platform.arch }}
id: build
uses: docker/build-push-action@v6
timeout-minutes: 30
with:
context: .
target: ${{ matrix.target }}
platforms: linux/amd64,linux/arm64
push: true
platforms: linux/${{ matrix.platform.arch }}
build-args: |
FEEDFATHOM_BUILD=${{ github.ref == 'refs/heads/main' && needs.build_metadata.outputs.version || github.sha }}
tags: ${{ env.IMAGE_PREFIX }}/${{ matrix.target }}:${{ github.sha }}
# Pushed by digest, unnamed: the tag is the manifest's to claim.
outputs: type=image,name=${{ env.IMAGE_PREFIX }}/${{ matrix.target }},push-by-digest=true,name-canonical=true,push=true
# Scoped per target and arch -- one shared scope and the three
# targets evict each other's layers on every run.
cache-from: type=gha,scope=${{ matrix.target }}-${{ matrix.platform.arch }}
cache-to: type=gha,mode=max,scope=${{ matrix.target }}-${{ matrix.platform.arch }}
provenance: false

- name: Hand the digest to the manifest job
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"
env:
DIGEST: ${{ steps.build.outputs.digest }}

- name: Upload digest
uses: actions/upload-artifact@v4
with:
name: digest-${{ matrix.target }}-${{ matrix.platform.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

publish_images:
name: Publish ${{ matrix.target }} immutable image
needs: build_platform_images
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
strategy:
fail-fast: false
matrix:
target:
- feedfathom-server
- feedfathom-worker
- feedfathom-migrator
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: write

steps:
- name: Download digests
uses: actions/download-artifact@v4
with:
pattern: digest-${{ matrix.target }}-*
path: /tmp/digests
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Assemble the multi-arch manifest
run: |
set -euo pipefail
# Both legs or nothing: a manifest with one arch silently strands
# whichever node runs the other, and `promote` would copy it on.
count=$(find /tmp/digests -type f | wc -l)
if ((count != 2)); then
printf 'expected 2 platform digests, found %s\n' "$count" >&2
exit 1
fi
docker buildx imagetools create \
--tag "${IMAGE_PREFIX}/${{ matrix.target }}:${GITHUB_SHA}" \
$(find /tmp/digests -type f -printf "${IMAGE_PREFIX}/${{ matrix.target }}@sha256:%f ")

build_extension:
name: Build and sign browser extension
needs: [quality, build_metadata]
if: >-
github.event_name == 'push' &&
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/staging')
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
build_date: ${{ steps.metadata.outputs.build_date }}
extension_version: ${{ needs.build_metadata.outputs.version }}
Expand Down Expand Up @@ -343,7 +428,8 @@ jobs:
name: Validate existing deployment manifests
needs: quality
if: github.event_name == 'workflow_dispatch'
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: read
Expand All @@ -358,8 +444,8 @@ jobs:
exit 1
fi

- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
Expand Down Expand Up @@ -424,7 +510,7 @@ jobs:
await_turn:
name: Wait for older merges to finish
needs: release_smoke_push
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 90
steps:
- name: Wait until no older run of this workflow is in flight
Expand Down Expand Up @@ -465,16 +551,17 @@ jobs:
- feedfathom-server
- feedfathom-worker
- feedfathom-migrator
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: write
env:
CHANNEL_TAG: ${{ github.ref == 'refs/heads/main' && 'latest' || 'staging' }}

steps:
- name: Setup Blacksmith Builder
uses: useblacksmith/setup-docker-builder@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
Expand All @@ -492,7 +579,8 @@ jobs:
create_release:
name: Create GitHub release
needs: [build_extension, promote]
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
env:
Expand Down Expand Up @@ -589,7 +677,7 @@ jobs:

deploy:
name: Deploy to production Swarm
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
# Each path funnels through exactly one terminal job, and the two are
# mutually exclusive by event, so this needs no per-event breakdown: the
# transitive needs already require quality, the image publish/validation,
Expand Down Expand Up @@ -915,7 +1003,7 @@ jobs:
needs.deploy.result == 'success' &&
needs.deploy.outputs.skipped != 'true' &&
vars.EMAIL_WORKER_SCRIPT != ''
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
Expand Down Expand Up @@ -1013,7 +1101,8 @@ jobs:
# extension: the site is rendered from scratch each run, so gating this on
# the extension meant a docs-only commit never reached the published site.
if: github.ref == 'refs/heads/main'
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
pages: write
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ permissions:
jobs:
lint:
name: Lint
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
# Capped well above the real runtime (seconds, not minutes). A runner that
# hangs instead of failing used to sit at GitHub's 6-hour default, and
# docker-build's await_turn holds every later staging run behind it, so a
Expand All @@ -34,7 +34,7 @@ jobs:

compile:
name: Compile every deployable target
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout repository
Expand All @@ -48,7 +48,7 @@ jobs:

unit:
name: Unit and migration tests
runs-on: blacksmith-2vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 15
env:
DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/feedfathom_migration_test
Expand Down Expand Up @@ -95,7 +95,7 @@ jobs:

browser:
name: Browser tests
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout repository
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ permissions:
jobs:
smoke:
name: Smoke test
runs-on: blacksmith-4vcpu-ubuntu-2404
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.build_locally && 45 || 15 }}
env:
FEEDFATHOM_TAG: ${{ inputs.feedfathom_tag }}
Expand All @@ -42,9 +42,9 @@ jobs:
with:
playwright: "true"

- name: Setup Blacksmith Builder
- name: Set up Docker Buildx
if: inputs.build_locally
uses: useblacksmith/setup-docker-builder@v1
uses: docker/setup-buildx-action@v3

# Only the pull path touches GHCR; a local build reads its bases from
# Docker Hub and tags the result under the same name compose expects.
Expand Down
Loading