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.
multiarch/qemu-user-static stopped at QEMU 7.2 (January 2023). Under it,
qmake's compiler probe (`g++ -E -v` in toolchain.prf) returns nothing on the
current gcc 16 toolchain, so every qmake package fails on aarch64 with
"failed to parse default include paths from compiler output" (hype, PR #517).
The same PKGBUILD builds under QEMU 10.2.3 and 11.1.
Register through tonistiigi/binfmt at a pinned tag, on both the ephemeral
builder droplets and the rootful-Docker path of setup_qemu, uninstalling any
existing entry first because the tool keeps an older registration in place.
The tag is now the one place that decides what every emulated build runs
under. Flags stay F and C, which rootless sudo inside the builder needs.
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.
A PR whose diff against its base is empty has already landed some other
way, typically a sync PR carrying the same bump or a merge from master
that swallowed it. Merging it records a change that isn't one and could
mask a real mistake. result now fails with a message saying to close it.
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.
The root-run package hook changed ownership of paths below a
user-controlled home directory. A config symlink could redirect chown to
an arbitrary root-owned file during installation or upgrade.
Run the config writer as the target desktop user and remove the privileged
ownership changes. This also prevents the missing-config path from writing
through a user-controlled pathname as root. Add regression coverage and
bump the package release.
Reported-by: piratemoo (Esther) <22439214+piratemoo@users.noreply.github.com>
Link: https://github.com/piratemoo/Arbitrary-File-Ownership-Change-via-Symlink-LPE
Unset CONFIG_ZERO_CALL_USED_REGS to disable the kernel hardening
feature, allowing for older NVIDIA drivers to build successfully
against the new kernel.
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
The Panther Lake XPS 13 (audio subsystem 1028:0e54) drives all of its
speakers through two CS35L56 amplifiers behind the CS42L43 codec. Their
DSP firmware aliases (cs35l56-b2-dsp1-misc-10280e54-spkid{1,2,3}) were
added to linux-firmware on 2026-08-18 and ship in Arch's
linux-firmware-cirrus 20260910-2, but the stable channel's Arch snapshot
is still on 20260810-2. Without them the amps run ROM firmware and the
machine is completely silent; upstream 7.2 already selects the sidecar
amplifier path for this SSID, so no kernel change is involved.
Ship the 20260910-2 payload to stable as a self-retiring shim:
- fast ring, no upstream watch (sync: false): the version is deliberately
20260810-3, above the snapshot's 20260810-2 and below Arch's real
20260910-2, so the genuine package supersedes it in the same
transaction that upgrades linux-firmware-other once the snapshot
advances. Bumping pkgver would defeat that.
- the signed Arch package is verified against the Arch packager key in
keys/pgp/ and reinstalled as-is, minus the cs42l45 SDCA tree that Arch
moved out of linux-firmware-other in 20260910: on the stable snapshot
those 190 files are still owned by -other 20260810-2 and would
conflict. The nine 10280e54 links and the 39 new SDCA files are kept.
Verified on a DX13260: cold boot loads 10280e54-spkid1 v4.5.9 on both
amps with "Calibration applied", and a 440 Hz tone measured through the
internal microphones peaks 238x over the noise floor on the stock UCM
bridge route. Delete this recipe once stable's snapshot carries
linux-firmware >= 20260910.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Upstream 0.9.1 bumps vendored libghostty-vt's minimum_zig_version to
0.16.0, so the pinned Zig tarballs move from 0.15.2 to 0.16.0 with
checksums taken from ziglang.org's download index.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Upstream sync has failed on every run since flea v0.3.0 was published, with
"Release v0.3.0 does not contain every required upstream security fix". Eight
of the nine required fixes are present. The ninth is too: the check is wrong.
The check pinned the literal call `regfile::open_if_regular(src, O_NOFOLLOW)`.
v0.3.0 introduced directory-relative opens and the first argument became
`src.at`. O_NOFOLLOW is still passed to the same function, on the same line,
under the same comment, and the release hardened symlink handling further --
it added copy_symlink_at, opens directories with O_DIRECTORY | O_NOFOLLOW, and
reaches every child through this process's own descriptor. The guard refused a
release that is strictly safer than the one it accepted.
The property worth asserting is that the copy opens its source with
O_NOFOLLOW, so a symlink swapped in cannot redirect the read. Pinning the
exact expression asserted the spelling instead, which is why a rename read as
a removed fix. The check now matches the call and the flag together.
Verified against the real archives rather than by inspection:
v0.3.0 (src.at, O_NOFOLLOW) accepted
v0.2.1 (src, O_NOFOLLOW) accepted, so the change is backwards
compatible with what is packaged today
first argument renamed accepted
extra flag or argument added accepted
O_NOFOLLOW dropped refused
call replaced with File::open refused
flag left only in a comment refused
End to end with the real feed: the hook on master exits 1 with the refusal,
and with this change exits 0 and reports 0.3.0 with its verified checksum.
The other eight literals are untouched.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>