A merged aarch64 tree without a PR build artifact (expired after 7 days,
or a dispatch) was rebuilt on the x86 droplet under QEMU: omarchy-mac-boot
took ~167 of the 240 minutes. A new job builds it on ubuntu-24.04-arm the
way build-pr.yml does and uploads it under the same label, so the publish
job signs and uploads it on the droplet like a PR artifact. The plan logs
reuse or rebuild for every package; x86_64, signing and the publish
concurrency are unchanged.
actions/upload-artifact rejects any path containing ':', and makepkg names
a package with an epoch `name-1:ver-rel-arch.pkg.tar.zst`. Every PR that
built such a package (cursor-cli in the sync PRs, omasnap once it gained an
epoch) failed at "Upload artifact" after a successful build, and publish
then rebuilt from scratch on merge.
The files now ride inside packages.tar for the artifact hop and come back
out with makepkg's names untouched: pacman clients and bin/publish-artifact
both require the filename to match PKGINFO, and the channels already carry
these names. publish.yml still accepts bare pre-packing artifacts until the
7-day retention drains them.
helpers/artifact-helpers.sh holds both halves; tests/artifact-helpers.sh
covers the round trip and runs with the other self-tests.
A workflow_dispatch runs from master's head. That commit's PR merged
something unrelated, so looking the PR up by commit attached a failed
elsewhen report to #515, whose merge had nothing to do with elsewhen.
Dispatch runs now go to the log only. The log append also moves ahead
of the PR comment so the record exists by the time anyone follows the
comment to it.
Re-running publish for a package that is already live (a dispatch for
something that turned out fine, or a retry after a partial failure) made
bin/build report nothing to build and exit 2, which the publish step
treated as an error. The collect step now dry-runs first: if the channel
already holds master's version the package is recorded as
already-published and skipped, and a run where every package is in that
state exits cleanly with a record saying so.
When a package had no PR artifact and its build failed, the publish
step never ran, no record was written, and the report job failed
looking for it. The collect step now records each package's source
(PR artifact, built here, or build-failed) and writes the record itself
when a build fails, so the report can say plainly that nothing was
published and why.
The publish job now writes publish-record.json describing every
channel/architecture slot it touched: the packages, whether the slot was
published or failed, the target (live or a proof prefix), the commit and
the run. A report job renders that as a comment on the PR the merge
commit came from (looked up by commit, so squash and rebase merges work)
and appends the record as one line to publish-log.jsonl in the bucket,
served next to the packages at https://pkgs.omarchy.org/publish-log.jsonl.
Failures are reported too, with the slots that landed before the failure,
which is when a human most needs to know.
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.