Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
23 changes: 16 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,39 @@ APP_PRIVATE_KEY=
# Organization the runners register to
APP_LOGIN=

# How many runners this host runs; `hosts/sizing.sh [light|large]` suggests a
# value from its CPUs, memory and disk.
RUNNERS=3
# Host directory for every runner's work dir and caches, mounted at the same
# path in the containers (slots/<id>/work, slots/<id>/cache). Put it on a disk
# with room; old named volumes from before slots can be removed.
# RUNNER_DATA_DIR=/var/lib/github-runner

# Runner identity. The runner adds self-hosted, the OS and the architecture to
# LABELS itself; add the capability labels your workflows select on.
RUNNER_NAME_PREFIX=runner
LABELS=docker
RUNNER_GROUP=Default
# One runner can join an extra pool, e.g. a host whose runners are `light` with
# one that also builds:
# one that also builds (slot 3):
# RUNNER_3_LABELS=docker,x86,light,large

# Coordination runners (two, labelled `control`), for the short jobs a merge
# waits on. Off unless the profile is set:
# COMPOSE_PROFILES=control
# Coordination runners labelled CONTROL_LABELS, for the short jobs a merge
# waits on. None unless set:
# CONTROL_RUNNERS=2
# CONTROL_LABELS=control

# Small hosts without room for per-runner caches (e.g. ARM64 boards):
# COMPOSE_FILE=docker-compose.yml:hosts/no-caches.compose.yml
# Small hosts without room for caches (e.g. ARM64 boards): jobs then start
# cold. The pruner's budget already shrinks with the disk, so try that first.
# RUNNER_CACHES=off

# Action archive cache (see README). Space-separated owner/repo@ref lists.
# PREFETCH_SOURCES=my-org/my-workflows@main # repos scanned for `uses:`
# PREFETCH_EXTRA=actions/checkout@v4 # actions fetched as-is

# Other tunables (defaults shown)
# RUNNER_IMAGE=ghcr.io/dodi-smart/github-runner
# CACHE_CAP_BYTES=21474836480
# CACHE_BUDGET_BYTES=64424509440 # host-wide cache budget, split across slots
# PRUNE_INTERVAL_SECONDS=86400
# PREFETCH_INTERVAL_SECONDS=3600
# Any non-empty value disables runner self-update ("false" too).
Expand Down
76 changes: 62 additions & 14 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,42 @@ jobs:
scripts:
name: Shell scripts
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7

- name: shellcheck
run: shellcheck -x pruner.sh tests/*.sh
run: |
set -euo pipefail
# nullglob: a pattern that matches nothing (e.g. hosts/*.sh) must
# drop out instead of being passed to shellcheck as a literal.
shopt -s nullglob
files=(*.sh hosts/*.sh tests/*.sh)
if [ "${#files[@]}" -eq 0 ]; then
echo "::error::No shell scripts found to check"
exit 1
fi
shellcheck -x "${files[@]}"
- name: docker compose config
run: |
set -euo pipefail
cp .env.example .env
docker compose config -q
- name: tests
run: |
set -euo pipefail
for t in tests/*.sh; do bash "$t"; done
for t in tests/*.sh; do
# image-smoke.sh needs a built image; it runs in the build job.
[ "$(basename "$t")" = image-smoke.sh ] && continue
bash "$t"
done
matrix:
name: Generate build matrix
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
matrix: ${{ steps.generate.outputs.matrix }}
steps:
Expand All @@ -37,26 +59,35 @@ jobs:
run: |
set -euo pipefail
# PR builds deliberately cover less than deploy.yml: the full
# java x node x arch product is ~28 images, which is far more than a
# dependency bump needs. Instead we cover every JDK against the
# default Node, every Node against the default JDK, and add a single
# arm64 build of the default pair so arch-specific breakage (e.g. the
# TARGETARCH-derived JAVA_HOME) is still caught before merge.
# java x node x arch product is far more than a dependency bump
# needs. Instead we cover every JDK against the default Node, every
# Node against the default JDK, and add a single arm64 build of the
# default pair so arch-specific breakage (e.g. the TARGETARCH-derived
# JAVA_HOME) is still caught before merge. The amd64 default pair
# also gets the image smoke test (smoke: true), and the opt-in
# runner-ndk and runner-slim targets get one amd64 build + smoke test
# each, since deploy.yml publishes them.
MATRIX=$(jq -c '
.default_jdk as $dj | .default_node as $dn
| ( [ .java_versions[] | {java_version: ., node_version: $dn} ]
+ [ .node_versions[] | {java_version: $dj, node_version: .} ]
| unique ) as $combos
| { include:
( [ $combos[] | . + {platform: "linux/amd64", arch: "amd64", runner: "ubuntu-latest"} ]
+ [ {java_version: $dj, node_version: $dn,
platform: "linux/arm64", arch: "arm64", runner: "ubuntu-24.04-arm"} ] ) }' matrix.json)
echo "matrix=${MATRIX}" >> $GITHUB_OUTPUT
| def leg($arch; $runner): {platform: "linux/\($arch)", arch: $arch, runner: $runner};
def std: {variant: "std", target: "runner"};
{ include:
( [ $combos[] | . + std + leg("amd64"; "ubuntu-latest")
+ {smoke: (.java_version == $dj and .node_version == $dn)} ]
+ [ {java_version: $dj, node_version: $dn} + std + leg("arm64"; "ubuntu-24.04-arm") + {smoke: false} ]
+ [ {java_version: $dj, node_version: $dn} + leg("amd64"; "ubuntu-latest") + {smoke: true, variant: "ndk", target: "runner-ndk"} ]
+ [ {java_version: $dj, node_version: $dn} + leg("amd64"; "ubuntu-latest") + {smoke: true, variant: "slim", target: "runner-slim"} ] )
| map(. + {cache_scope: (if .variant == "std" then .arch else "\(.variant)-\(.arch)" end)}) }' matrix.json)
echo "matrix=${MATRIX}" >> "$GITHUB_OUTPUT"
build:
name: Build ${{ matrix.arch }} JDK ${{ matrix.java_version }} Node ${{ matrix.node_version }}
name: Build ${{ matrix.arch }} JDK ${{ matrix.java_version }} Node ${{ matrix.node_version }} (${{ matrix.variant }})
needs: matrix
runs-on: ${{ matrix.runner }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
Expand All @@ -77,29 +108,46 @@ jobs:
exit 1
fi
echo "runner_tag=${RUNNER_TAG}" >> $GITHUB_OUTPUT
echo "runner_tag=${RUNNER_TAG}" >> "$GITHUB_OUTPUT"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

# Layer cache lives in the GitHub Actions cache, one scope per platform (and
# per ndk/slim variant, matching deploy.yml) so
# the amd64 and arm64 legs never overwrite each other. PR builds read and
# write it too; branch scoping keeps a PR from polluting main's cache.
# `load` is only set on the smoke leg: the other legs have nothing to run.
- name: Build (no push)
uses: docker/build-push-action@v7
with:
context: .
target: ${{ matrix.target }}
platforms: ${{ matrix.platform }}
push: false
load: ${{ matrix.smoke }}
tags: github-runner:smoke-${{ matrix.variant }}
cache-from: |
type=gha,scope=${{ matrix.arch }}
${{ matrix.cache_scope != matrix.arch && format('type=gha,scope={0}', matrix.cache_scope) || '' }}
cache-to: type=gha,mode=max,scope=${{ matrix.cache_scope }}
build-args: |
VERSION=${{ steps.versions.outputs.runner_tag }}
JAVA_VERSION=${{ matrix.java_version }}
NODE_VERSION=${{ matrix.node_version }}
RUNNER_IMAGE_TAG=pr-${{ github.event.pull_request.number }}
RUNNER_IMAGE_REVISION=${{ github.event.pull_request.head.sha }}
- name: Smoke test image
if: matrix.smoke
run: bash tests/image-smoke.sh github-runner:smoke-${{ matrix.variant }} "${{ matrix.java_version }}" "${{ matrix.node_version }}" "${{ matrix.variant }}"

gate:
name: All builds passed
needs: [scripts, matrix, build]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Check that all required jobs succeeded
run: |
Expand Down
Loading
Loading