Files
omarchy-pkgs/ci/runner-cloud-init.yaml
T
Ryan Hughes 537c377fa5 Build PRs on ephemeral droplets; publish merged packages from CI
Every pull request now builds the package directories it touches on
ephemeral DigitalOcean droplets, and every merge to master publishes the
resulting artifacts into the channels each package belongs to. The
repository host's timers become the fallback rather than the pipeline.

Build (.github/workflows/build-pr.yml)
  One job per package per architecture, always against edge. The artifact
  is labelled with the package directory's git tree hash. Tooling (bin/,
  helpers/, build/) is checked out from the base branch; the PR supplies
  only pkgbuilds/, so a PR can change what is built, never how. Builds
  run only for trusted authors: collaborators, .github/VOUCHED.td, or a
  PR carrying the build-approved label. A single required check, result,
  aggregates the matrix.

Publish (.github/workflows/publish.yml, bin/publish-artifact)
  One job per merge. It collects the PR artifacts for the merged tree,
  builds anything that has none, then walks each channel/architecture
  slot once: pull that database, repo-add every package that belongs in
  it, upload packages, signatures, then the database. A published
  filename is immutable; identical bytes under an existing name only
  gain a database entry, different bytes are refused. Fast-ring packages
  reach edge, rc and stable in the same run from the same file.

Matrix (bin/build-matrix)
  Package x architecture, with the channels the artifact ships to,
  decided by package_builds_for_mirror so CI and the host agree.
  arch=any packages build once and land in every architecture database.

Builder (build/build.sh, bin/build, build/Dockerfile)
  With no local published tree, plan against and resolve from the public
  channel over HTTPS. PACKAGER is set to Omarchy. rclone is in the image.

Runners (ci/)
  A controller droplet polls GitHub with curl and creates one g5 droplet
  per queued job from cloud-init, deleting them when off or over-age.
  Builders carry QEMU with credential support for aarch64. Operator SSH
  keys come from GitHub. tests/controller.sh and tests/publish-artifact.sh
  cover the decisions against fixtures and real makepkg output.

Tests run on pull requests only; branch protection requires result,
self-tests and build-isolation with up-to-date branches.
2026-09-18 11:25:32 -04:00

78 lines
2.9 KiB
YAML

#cloud-config
# Ephemeral GitHub Actions runner for omarchy-pkgs package builds.
#
# Boots an Ubuntu droplet, installs docker and the runner, registers ONCE with
# --ephemeral, runs exactly one job, then powers off. The controller (or the
# reaper) deletes the powered-off droplet. Nothing here holds a long-lived
# credential: the registration token is single-use and expires in an hour.
#
# Substitute before use:
# __REPO__ owner/name
# __RUNNER_TOKEN__ registration token (gh api -X POST repos/O/R/actions/runners/registration-token)
# __RUNNER_LABELS__ e.g. omarchy-builder
# __RUNNER_VERSION__ e.g. 2.329.0
# Operators can reach a builder by key while it lives; it powers off after
# one job anyway. Keys are substituted by the controller (__SSH_KEYS_JSON__).
disable_root: false
chpasswd:
expire: false
ssh_authorized_keys: __SSH_KEYS_JSON__
package_update: true
packages:
- docker.io
- docker-buildx
- unzip
- git
- curl
- jq
- rsync
users:
- name: runner
groups: [docker]
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
write_files:
# defer: write after users/groups exist, so /home/runner is created by
# useradd (owned by runner) rather than by this module as root.
- path: /home/runner/start.sh
permissions: "0755"
owner: runner:runner
defer: true
content: |
#!/bin/bash
set -euo pipefail
cd /home/runner
mkdir -p actions-runner && cd actions-runner
arch=$(uname -m); [[ $arch == x86_64 ]] && arch=x64
curl -fsSL -o runner.tgz \
"https://github.com/actions/runner/releases/download/v__RUNNER_VERSION__/actions-runner-linux-${arch}-__RUNNER_VERSION__.tar.gz"
tar xzf runner.tgz && rm runner.tgz
./config.sh --unattended --ephemeral \
--url "https://github.com/__REPO__" \
--token "__RUNNER_TOKEN__" \
--name "do-$(hostname)" \
--labels "__RUNNER_LABELS__" \
--replace
./run.sh
# One job done. Power off; the controller deletes powered-off droplets.
sudo poweroff
runcmd:
# With no account ssh key attached, DO expires root's password, and sshd
# then refuses every non-interactive session. Clear it first so operators
# can read the logs of a builder that never registers.
- chage -d "$(date +%F)" -M -1 root
- systemctl enable --now docker
# aarch64 builds run under user-mode emulation (DO has no arm droplets).
# Register QEMU with the F and C flags via the multiarch image, exactly as
# helpers/docker-helpers.sh setup_qemu does: Ubuntu's qemu-user-static
# package registers without C, so sudo inside the emulated container fails
# with "effective uid is not 0". Best-effort: an x86-only job never needs it.
- docker run --rm --privileged docker.io/multiarch/qemu-user-static --reset -p yes --credential yes || true
- chown -R runner:runner /home/runner
- sudo -u runner /home/runner/start.sh > /home/runner/runner.log 2>&1