Compare commits

Author SHA1 Message Date
ZacharyZhang-NY a4cc32340c Merge upstream master so local Omarchy builds carry the published version 2026-10-08 23:48:13 -04:00
ZacharyZhang-NY 44d4d231be Revert "Add rime-ice-installer with offline assets and no sudo when its packages are present"
This reverts commit 5e0ff0df95.
2026-10-08 23:48:13 -04:00
ZacharyZhang-NY 7000c95661 Revert "Write fcitx5 config as key=value so fcitx5 reads the Ctrl+Space trigger"
This reverts commit a84ad64c9e.
2026-10-08 23:48:13 -04:00
Spencer Bull 5c6444e604 Update cua-driver-bin to 0.33.4, which captures multi-monitor Hyprland desktops again (#807)
Driver 0.28.3 through 0.33.3 refused desktop capture on Hyprland with more than one output or one output away from the origin (trycua/cua#4161), which is why the package was held at 0.28.2; 0.33.4 fixes it (trycua/cua#4305). The plugin sources are unchanged from 0.32.0 through 0.33.4 and speak the same input protocol, so the plugin package stays and only its README names the new pairing.
2026-10-08 22:08:08 -05:00
Gabriel AramburuandClaude 17ae27fb21 Add OmaKeyboard: keyboard remapping and shortcuts (#564)
A Qt Quick application for remapping keys, building Fn-style layers and
managing shortcuts, per keyboard. It generates real XKB keymaps with
libxkbcommon and loads them through Hyprland's per-device kb_file; no
input daemon or third-party remapping engine is involved.

Builds from the published v0.1.0 tag. check() runs the project's tests,
which need no display.

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-09 01:10:24 +00:00
Ryan Hughes 5abe08fbab Merge pull request #697 from omacom/auto/sync-rebuilds
chore: rebuild against updated dependencies
2026-10-08 19:39:59 -04:00
dhh 3a0d01876f chore: rebuild against updated dependencies 2026-10-08 23:32:58 +00:00
Emir Beganović 0aa4c721c1 Merge pull request #851 from omacom/t3code/appimage-lib-names
T3 Code: accept the AppImage's compatibility libraries under their new names
2026-10-09 00:36:48 +02:00
Marcelo Alcantara c71cfabed6 Re-pin omarchy-mac-boot to omarchy-mac-pkgs 162f3599e (#873)
main now has omarchy-mac-pkgs#20: a factory reset's reboot unlocks the disk
with a key from the Boot partition, and systemd could mount that partition
again just before switching to the real root without unmounting it
(systemd/systemd#28021), leaving /boot read-only so owner setup's re-key
failed. An initramfs drop-in makes the unmount finish before the switch. It
also has #18 (first-boot encryption progress on the splash) and test-only
changes (#11, #19). New UTC commit date, so 20261008-1. omarchy-mac stays at
2a3ed89.
2026-10-08 22:06:39 +00:00
Ryan Hughes 7d18e16925 Merge pull request #860 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6815.g50d687a, omarchy-settings-dev 4.0.0.r6815.g50d687a
2026-10-08 16:23:28 -04:00
Ryan Hughes 2cce621d95 Retire the elephant packages and omarchy-walker (#872)
Omarchy 4 no longer uses Elephant or the omarchy-walker meta package, and
omarchy-upgrade-to-quattro removes them; every channel serves Omarchy 4.0.4.
Nothing else here depends on them. walker itself stays.

Deleting a recipe stops it building but leaves it in the channel databases,
and the only removal tool worked on the old repository host's local tree.
Add bin/unpublish-packages, publish-artifact's counterpart: pull the channel
database, repo-remove every entry built from the named pkgbases, upload it.
Package files stay in the bucket. unpublish.yml runs it per channel and
architecture under the publish lock, for packages with no recipe on master.
2026-10-08 16:18:30 -04:00
ryanrhughes 2ac2319b55 Track upstream branches: omarchy-dev 4.0.0.r6815.g50d687a, omarchy-settings-dev 4.0.0.r6815.g50d687a 2026-10-08 20:02:51 +00:00
Timothy Wright cb3909f052 Automate Grok Bot releases (#583)
* Automate Grok Bot releases

* Refresh Grok Bot to apt 0.66.0 and harden upstream.sh

Bump both architectures to Cursor apt 0.66.0 with matching SHA256s,
introduce a versioned _pool URL helper, and filter Packages stanzas by
Package: grok-bot so sync stays correct if the index grows.

* grok-bot: bump to 0.68.1, drop manual hold, validate pool filename

Bump both architectures to Cursor apt 0.68.1 with the SHA256s from the
amd64/arm64 Packages indexes (verified against the downloaded debs).

Drop grok-bot from the manual holds list in docs/upstream-sources.md and
have upstream.sh check that the newest stanza's Filename is the versioned
pool path the PKGBUILD downloads from. Both additions come from #848.
2026-10-08 15:50:44 -04:00
Ryan Hughes 678d7400ed Don't ask for a builder to rebuild what is already published (#870)
The split in #869 sent every tree without an artifact to the rebuild job,
so a dispatch whose x86 halves were already published waited eight minutes
for eleven droplets that each found nothing to build in five seconds. Make
the publish job's plan check in the changes job, on its hosted runner,
before a builder is requested.
2026-10-08 15:16:31 -04:00
Ryan Hughes ebd2d6a766 Publish in a minute: build outside the lock, sign on a hosted runner (#869)
A one-package merge took 9 to 15 minutes to publish for about 15 seconds of
signing and upload. The run-wide concurrency group made each merge wait for
every earlier run, builds included (#854 waited 13 minutes behind an aarch64
batch), and the publish job waited about 3 minutes for a builder droplet
even when every package had a PR artifact and nothing needed building.

Build x86_64 trees that have no artifact in the rebuild job, one droplet per
entry, as aarch64 already builds there on arm64 runners: in parallel, with
no secrets, and outside any lock. The publish job now only collects
artifacts, signs and uploads, on ubuntu-latest with the GHCR builder image
(#850), and only it holds the publish group.
2026-10-08 15:03:28 -04:00
Ryan Hughes 93b1655ca8 Auto-merge package PRs that bring their own test (#868)
#867 auto-merged only PRs changing nothing outside pkgbuilds/, so it would
have left #854 open too: like voxtype-bin and the IPU7 camera before it,
Superwhisper added tests/superwhisper-bin-install.sh and one test.yml line
running it.

Count those as package changes: a new file under tests/, and a test.yml
change whose every line adds a ./tests/*.sh call. test.yml runs on PRs only.
Editing an existing test still needs a maintainer, since builder-images.yml
runs tests/build-isolation.sh on master with packages: write.
2026-10-08 14:59:25 -04:00
Emir BeganovićandRyan Hughes 5d9783b85e Publish pulls the tested builder image instead of building it on every run (#850)
A builder droplet starts with no images, so publish.yml built
omarchy-pkg-builder from the Dockerfile each time: about 100 s of
pacstrap, keyring setup and toolchain install, to run gpg, repo-add,
bsdtar and rclone for about 14 s.

builder-images.yml already publishes the tested image for the current
build inputs to ghcr.io/omacom/omarchy-pkg-builder under bin/builder-image
key. Pull that, check its org.omarchy.builder.key label, and tag it as the
local name the rest of the step uses.

Build as before when no image carries the key, which is what a merge
that changes build/ sees until the refresh it triggered has finished.

Co-authored-by: Ryan Hughes <ryan@heyoodle.com>
2026-10-08 14:57:21 -04:00
Ryan Hughes 48d6217ff7 Auto-merge package PRs that are trusted to build (#867)
A package PR approved to build, by its author being trusted or by the
build-approved label, sat open after going green until someone merged it by
hand, so nothing it built was published. Enable GitHub's auto-merge on it
with PKGS_BOT_TOKEN, so the merge lands once the required checks pass and
starts publish.yml.

The trust rule is build-pr.yml's. Only PRs changing nothing outside
pkgbuilds/ qualify: a PR's own tooling never runs in its build, and after
merge it runs with the publish secrets. The upstream sync, which labels its
own PRs, stays on the reviewed lane. Removing build-approved withdraws the
auto-merge.
2026-10-08 14:33:54 -04:00
David Heinemeier Hansson 7d2c7c50af Package Superwhisper with upstream beta tracking (#854)
* Package Superwhisper with upstream beta tracking

* Seed fresh Superwhisper shortcuts before daemon startup

* Refresh checksum for Superwhisper user setup

* Fix Superwhisper panel setup and packaging review findings

* Read shell replies when retrying Superwhisper panel setup

* Exclude Superwhisper portable menu integration
2026-10-08 20:29:07 +02:00
Ryan Hughes e285432795 omatrack: fetch the pinned source from a copy, upstream is gone (#865)
tobi/omatrack no longer exists on GitHub, so the pinned commit's archive is
404 and 1.8.6 cannot be rebuilt; the old repository host's sync deleted the
published 1.8.6-1 from R2, leaving edge at 1.2.0. A public copy serves the
same commit with an archive matching the pinned sha256 byte for byte.

pkgrel 2 gives the rebuild a new filename, so no cache can serve the
deleted 1.8.6-1 bytes for it.
2026-10-08 13:38:28 -04:00
Ryan Hughes 5961a3ff5d Replace builder droplets stuck provisioning (#864)
A droplet DigitalOcean still reports as "new" never registers a runner, but
the controller counted it as one booting and created no replacement until
MAX_AGE_MINUTES. A publish job sat queued for minutes behind one in mkc1.

Delete droplets still provisioning after MAX_BOOT_MINUTES (10) and leave them
out of the live count, so the same tick creates a replacement.
2026-10-08 11:59:48 -04:00
Ryan Hughes 259a3fa8b8 quickshell-git: build v0.3.2, which compiles against Qt 6.12 (#863)
The pinned 0.3.0.r20 commit fails Qt 6.12's "Meta Types must be fully
defined" assertion in the PipeWire service, which kept the rebuild PR red.
2026-10-08 11:40:40 -04:00
37288aada4 Auto-merge rebuild PRs once their builds pass (#862)
sync-rebuilds now runs on the unattended lane like track-branches: it
opens the pkgrel bump PR with PKGS_BOT_TOKEN and enables auto-merge, so a
Qt (or any rebuild_on) update reaches users without a maintainer merge.
Branch protection still requires result, self-tests and build-isolation
to pass; a failed rebuild stays an open red PR.

The PAT is required because a GITHUB_TOKEN merge does not start
publish.yml. PAT pushes are not held for approval, so the approve job,
the build-approved label and the review request go away.

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:08:41 -04:00
Ryan Hughes 128c5647ba Keep builders coming when DigitalOcean sells out a size or region (#861)
* Keep builders coming when DigitalOcean sells out a droplet size

ric1 sold out of g5-32vcpu-64gb-50gb, and every create came back 422. curl -f
dropped the reason and set -e ended the tick, so builders only appeared when
capacity happened to free up, and the journal showed nothing but "curl: (22)".

Try each of SIZES in turn, logging DigitalOcean's refusal message, and fail
the tick only when every size is refused. Builders now power off however
start.sh exits, so a failed registration is reaped instead of counting as a
booting runner until MAX_AGE_MINUTES. The controller unit pulls the checkout
before each tick, so merged controller fixes reach the box.

* Create builders in any region that has the size in stock

ric1 sold out of g5-32vcpu-128gb-50gb within minutes of the box switching to
it. Builders need nothing from a particular region, so read DigitalOcean's
size catalog once per tick and try each of SIZES in every region it lists in
stock, REGIONS first if set. A refused pair is dropped for the rest of the
tick.
2026-10-08 10:50:06 -04:00
David Heinemeier Hansson 024943141d Count the full builder queue across workflow states (#859) 2026-10-08 15:38:31 +02:00
David Heinemeier Hansson a1ad25bbae Bump releases for packages with mismatched cached archives (#858) 2026-10-08 15:01:06 +02:00
David Heinemeier HanssonandClaude Opus 5.5 77d62dd156 Add quickshell 0.3.2 built from upstream releases (#855)
Tracks tagged releases from the quickshell-mirror GitHub repository
through an upstream watch, so new versions arrive via sync-upstream.
The recipe follows Arch's extra/quickshell, builds for x86_64 and
aarch64, ships the Qt compatibility check hook, and rebuilds on Qt
updates like quickshell-git. It conflicts with quickshell-git.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 11:51:47 +02:00
David Heinemeier Hansson 7d70768c3f Retire redundant edge package overrides (#853)
* Prepare OPR overrides and rebuild Quickshell for Qt 6.12

* Retire redundant edge package overrides

* Drop Quickshell rebuild from retirement scope

* Remove retirement notes from upstream source documentation
2026-10-08 10:45:07 +02:00
Ryan Hughes efc09808c6 Merge pull request #852 from omacom/omarchy-hyprland-titlebars
Add omarchy-hyprland-titlebars
2026-10-08 02:35:38 -04:00
Ryan Hughes f004f5fa20 Merge pull request #788 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6807.g902fd8a, omarchy-settings-dev 4.0.0.r6807.g902fd8a
2026-10-08 02:33:16 -04:00
ryanrhughes 963cbf1230 Track upstream branches: omarchy-dev 4.0.0.r6807.g902fd8a, omarchy-settings-dev 4.0.0.r6807.g902fd8a 2026-10-08 06:31:32 +00:00
Ryan HughesandClaude Opus 5.5 a57d0fdb7f Add omarchy-hyprland-titlebars
The native Hyprland plugin behind Omarchy's floating workspaces: themed
titlebars and edge snapping, built from a hyprbars fork. It is rebuilt
with every Hyprland change, since a plugin only loads into the exact
build it was compiled against.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-08 02:23:16 -04:00
Emir Beganovic 00a98d2aa7 T3 Code: accept the AppImage's compatibility libraries under their new names
The 2026-10-07 nightly ships the same six compatibility libraries with
their full versioned file names beside the short ones (libXss.so.1.0.0),
and two under their GTK 3 names (libappindicator3, libindicator3). The
guard in package() listed the short names only, so it stopped the build
on both architectures and with it the bundled upstream sync (#837).

Accept an optional 3 and trailing version numbers. Anything else in
usr/ still stops the build. t3code-bin carries the same guard and gets
the same pattern before the change reaches a stable release.
2026-10-08 03:06:40 +02:00
Emir Beganović d37fcd09d1 Merge pull request #849 from omacom/omasnap-git/smoke-output-dir
omasnap-git: give the smoke suite its output directory as --output-dir
2026-10-08 02:45:49 +02:00
Emir Beganovic c19b48c445 omasnap-git: give the smoke suite its output directory as --output-dir
Upstream's 6771b19 ("keep smoke artifacts in the build directory") made
omasnap-smoke reject positional paths; the directory is now passed with
--output-dir. check() still passed it positionally, so every build of a
pin at or past that commit failed with "Unexpected positional argument;
use --output-dir <directory>."

Pass it as --output-dir and move the pin to 6771b19, the tip the branch
tracker has been trying to merge since October 4. The two have to land
together: the old pin does not know --output-dir.
2026-10-08 00:49:37 +02:00
Krzysztof Wilczyński 0e90a7652b Merge pull request #846 from kwilczynski/feature/add-muqss-version
Add MuQSS kernel based on v7.2.8-5 with latest MuQSS from CK tree
2026-10-07 23:52:36 +02:00
Krzysztof Wilczyński 4b6cc8ba0f Add MuQSS kernel based on v7.2.8-5 with latest MuQSS from CK tree
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-10-08 05:43:59 +09:00
David Heinemeier Hansson 2d56129a55 Keep OWE lock feed in its separate package (#844) 2026-10-07 21:34:00 +02:00
David Heinemeier Hansson fe4b80b0a6 Update OWE to 0.2.10 and package its lock feed (#843)
* Update OWE to 0.2.10 and package its lock feed

* Print OWE regression failures during package checks

* Make OWE package regression independent of timestamp precision
2026-10-07 21:11:46 +02:00
Ryan Hughes be91aebbf3 Merge pull request #834 from omacom/nautilus-dropbox-aarch64
Build nautilus-dropbox for aarch64
2026-10-07 13:49:24 -04:00
Ryan Hughes ffa906bf5a Merge pull request #831 from omacom/dropbox-aarch64
Build dropbox and dropbox-cli for aarch64
2026-10-07 13:39:36 -04:00
Ryan Hughes 409865ccc0 Merge pull request #833 from omacom/box64
Add box64 to run x86_64-only programs on AArch64
2026-10-07 13:32:16 -04:00
Ryan Hughes 3381cae267 Merge pull request #842 from omacom/steam-aarch64
Build steam for aarch64 on Valve's native arm64 client
2026-10-07 11:32:59 -04:00
Ryan Hughes 97a8032cd5 Build steam for aarch64 on Valve's native arm64 client
Arch ships steam for x86_64 only, so on AArch64 `omarchy-pkg-add steam`
found nothing. This aarch64 build installs the same Valve launcher files
(desktop entry, icons, steam-devices udev rules) but replaces the x86
bootstrap with Valve's native arm64 client, which then updates itself from
the stable steam_client_linuxarm64 channel.

The bootstrap is just the client binary, which links only glibc, taken
from the manifest's bins_linuxarm64_linuxarm64 zip and pinned to the sha2
the manifest publishes. /usr/bin/steam unpacks it on first launch, keeps
the ~/.steam links the client requires, and restarts the client when it
exits with 42 after updating. Valve's bin_steam.sh cannot do this: it only
knows the ubuntu12_32 bootstrap.

Beyond Arch's dependencies the client needs gtk2 and libibus for its UI,
and lsof, which it runs to find its own IPC ports.
2026-10-07 11:26:45 -04:00
Ryan Hughes 17724a8018 Add gtk2 for aarch64
Valve's native arm64 Steam client loads GTK 2 from the host, and Arch
Linux ARM no longer ships it. Imported from the AUR recipe unchanged
apart from the architecture: x86_64 Steam brings its own copy in its
runtime, so gtk2 builds for aarch64 only.
2026-10-07 11:26:45 -04:00
David Heinemeier Hansson ffd056182f Merge pull request #840 from omacom/slack/fast-ring
Put slack-desktop on the fast ring
2026-10-07 13:14:28 +02:00
David Heinemeier HanssonandClaude Opus 5.5 4d4c95678f Put slack-desktop on the fast ring
Omarchy is getting an Install > Service > Slack menu entry, and that
needs the package on every channel. slack-desktop has only been built
for edge so far. The fast ring builds it for rc and stable as well,
like the other service packages (Spotify, Dropbox, 1Password, NordVPN).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-07 07:02:53 -04:00
Krzysztof Wilczyński 6371bfb24a Merge pull request #836 from kwilczynski/feature/update-kernel-releases
Update Linux kernel release to v7.2.8-5 for base and BORE kernels
2026-10-07 12:44:06 +02:00
Ryan Hughes e815ac6a27 Merge pull request #794 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-10-07 00:16:52 -04:00
Ryan Hughes 483b685411 strata: skip a 0.21.0 GTK test that cannot run headless 2026-10-06 23:51:25 -04:00
Krzysztof Wilczyński 223f1d3438 Update Linux kernel release to v7.2.8-5 for base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-10-07 11:14:42 +09:00
Ryan Hughes 64e7914900 Merge pull request #832 from omacom/sync-self-approve
Let sync PRs approve their own builds
2026-10-06 20:45:53 -04:00
Ryan Hughes cfb74f87af Build nautilus-dropbox for aarch64
The extension compiles natively and depends on dropbox, which now builds for
aarch64 through box64.
2026-10-06 20:34:19 -04:00
Ryan Hughes 3761f122b8 Build dropbox and dropbox-cli for aarch64
Dropbox publishes its Linux client for x86_64 only, so on AArch64 the
dropbox package installs that same client and runs it under box64. Only
dropboxd changes: it execs the client through box64, and /usr/bin/dropbox
points at it, so the systemd units and `dropbox-cli start` both go through
the emulator. The x86_64 package is unchanged.

dropbox-cli is a Python script and becomes arch=any.
2026-10-06 20:34:19 -04:00
Ryan Hughes df2e5ac82e Add box64 to run x86_64-only programs on AArch64
Dropbox publishes its Linux client for x86_64 only. box64 lets the aarch64
dropbox package run that client, and it has to be published before dropbox
can build for aarch64.

Pinned past v0.4.5-1 because that release crashes in its glib wrapper when
Dropbox starts its tray icon. It ships without a binfmt handler so it does
not compete with qemu-user-static-binfmt.
2026-10-06 20:34:19 -04:00
Ryan Hughes 5fb29fe547 Let sync PRs approve their own builds
The upstream and rebuild syncs push with GITHUB_TOKEN, so GitHub holds
their build and test runs for approval. Their approve job only released
those runs once a maintainer had applied build-approved, and never ran
for the push that opened the PR, so every sync PR sat waiting.

The sync now labels its own PR build-approved, and the approve job runs
for created PRs as well as updated ones.
2026-10-06 20:32:42 -04:00
Spencer BullandCodex GPT-6.1-Sol XHigh 71154a8fdc Add XPS 13 Panther Lake speaker firmware aliases (#823)
Give the DX13260 aliases their own ownership paths so a stable refresh can retain them while Arch continues updating the stock firmware. Include every speaker ID and target in the boot image. Retain the package identity across channel transitions so pacman can restore the overlay on downgrades. Keep the existing shim for the staged consumer migration.

Co-authored-by: Codex GPT-6.1-Sol XHigh <noreply@openai.com>
2026-10-06 16:36:55 -05:00
dhh b4055cfd86 chore: sync upstream releases 2026-10-06 18:31:01 +00:00
Krzysztof Wilczyński e3dfdd376c Merge pull request #819 from kwilczynski/feature/update-kernel-releases
Update Linux kernel release to v7.2.8-2 for base and BORE kernels
2026-10-06 09:45:39 +02:00
Krzysztof Wilczyński 8d103ac6e3 Update Linux kernel release to v7.2.8-2 for base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-10-06 16:10:37 +09:00
ZacharyZhang-NY a84ad64c9e Write fcitx5 config as key=value so fcitx5 reads the Ctrl+Space trigger 2026-10-06 01:49:27 -04:00
ZacharyZhang-NY 5e0ff0df95 Add rime-ice-installer with offline assets and no sudo when its packages are present 2026-10-05 23:32:21 -04:00
Ryan Hughes 69c8193e2a Merge pull request #816 from omacom/slack-desktop-x86_64
Build slack-desktop for x86_64 too
2026-10-05 19:15:47 -04:00
Ryan Hughes 906b981d21 Build slack-desktop for x86_64 too
Ship Slack's own x86_64 build unchanged so Omarchy controls the package
on both architectures instead of relying on the AUR recipe for x86_64.
The Electron swap and native module replacements stay AArch64-only.
2026-10-05 19:05:18 -04:00
Marcelo Alcantara 9c44e10e02 Merge pull request #810 from joshuaswarren/mac/v0.7.28-bump
omarchy-mlx v0.7.28
2026-10-06 08:20:24 +10:00
Marcelo Alcantara bc55cc7ba0 Merge pull request #812 from scottjones/touchid/libfprint-review-fixes
Guard the Touch ID patch by its content, and check it
2026-10-06 08:01:49 +10:00
bjarneo d91645b6ee Update owe and owe-lockfeed to 0.2.9 (#799)
Update both package recipes and source checksums to OWE 0.2.9. The release adds owe intro --start first-frame, so a login intro can start on its own first frame. Package builds pass on x86_64 and aarch64.
2026-10-05 21:56:32 +02:00
David Heinemeier Hansson 48d93a479f Merge pull request #811 from omacom/omawrite-sync-upstream
Track Omawrite GitHub releases via sync-upstream
2026-10-05 21:28:04 +02:00
Ryan Hughes dfe492f8df Merge pull request #814 from omacom/slack-desktop-aarch64
Add slack-desktop for aarch64 on a native Electron runtime
2026-10-05 13:36:54 -04:00
Ryan Hughes 418a48bef6 Add slack-desktop for aarch64 on a native Electron runtime
Slack only publishes an x86_64 Linux build, so the AUR recipe produces an
aarch64 package full of x86_64 binaries that cannot start. Run Slack's
application code on the AArch64 build of the Electron release it ships
with, replace the three native addons Slack requires at boot with
JavaScript equivalents, and drop the x86_64 addons it can do without.
2026-10-05 12:26:23 -04:00
7b1fbb1cbb Guard the Touch ID patch by its content, and check it
Marcelo's review: prepare() skipped the patch whenever aurora.c existed, a
file the patch itself creates, so a tree a failed run left half-patched
would build. It now skips only when every hunk is already in, and a
half-patched tree fails prepare(). The patch has a real b2sum, and the
recipe says the driver accepts only /dev/sep-bio interface version 4, so a
kernel bump and this patch move together.

Co-authored-by: Chris Kearney <303316+iconidentify@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 10:37:47 -04:00
Marcelo Alcantara 049d7671e1 Merge pull request #806 from scottjones/touchid/libfprint-package
Build libfprint-git for aarch64 with the Apple Touch ID driver
2026-10-05 23:00:44 +10:00
David Heinemeier Hansson 4f8fa58c14 Track Omawrite GitHub releases via sync-upstream 2026-10-05 09:00:38 -04:00
Joshua Warren deb8ed4449 omarchy-mlx v0.7.28: wheel + vendor tar + source tar shas; pkgver 0.7.28 2026-10-05 06:43:13 -05:00
Marcelo Alcantara 90594317f8 Merge pull request #803 from omacom/mac/repin-mac-boot-6bd123a
omarchy-mac-boot 20261004-3: boot check follows an unmerged update-m1n1
2026-10-05 17:27:59 +10:00
6be6e98870 Build libfprint-git for aarch64 with the Apple Touch ID driver
Omarchy's fingerprint setup installs libfprint-git, which built for x86_64
only, so an Apple Silicon Mac had no Omarchy libfprint at all. It now builds
for aarch64 too, and there it carries Chris Kearney's aurora driver for
Touch ID, which the aurora kernel's Secure Enclave driver exposes as
/dev/sep-bio. The x86_64 build applies nothing new.

The patch is his libfprint-1.94.100-apple-sep.patch from
iconidentify/aurora-linux sep-7.1.12.aurora2-11.35, less its
tests/meson.build hunk: libfprint 3f1e2817, already in this pin, made the
same fix. The libfprint provide is now versioned, so aurora-touchid's
libfprint>=1.94.100-1.1 still resolves when this replaces his build.

Built on an M2 Max: 131 libfprint tests pass, none fail.

Co-authored-by: Chris Kearney <303316+iconidentify@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 00:51:34 -04:00
Emir Beganović 5cff4dfcb2 Merge pull request #805 from omacom/fix/redirect-watch-probe
upstream-watch: probe redirect watches past a staged rollout
2026-10-05 00:08:13 +02:00
Emir Beganovic 811a149728 upstream-watch: probe redirect watches past a staged rollout
The dropbox watch failed with "no matching releases" on some runs
(e.g. actions run 37236931512). It is not the network: Dropbox's download
redirect sends a share of requests to a newer build the pattern rejects
on purpose. On 2026-10-04, 96 of 100 HEADs ended at
dropbox-lnx.x86_64-272.4.3798 and 4 at 274.3.4801, which is not an x.4.y
stable build. The redirect provider made one probe, so one unlucky
answer failed the whole watch.

It now probes up to five times and keeps the first final URL that
matches, and curl gets --retry 2 as Fetcher.file already has. Live,
100 discovers against Dropbox all found 272.4.3798 in 103 probes. Two
tests cover a rollout answer before the stable one and every probe
missing.
2026-10-05 00:06:22 +02:00
Marcelo Alcantara e2440ed297 Re-pin omarchy-mac-boot to omarchy-mac-pkgs 6bd123a
main now has omarchy-mac-pkgs#10: the boot check leaves device tree
overlays out when /etc/default/update-m1n1 does not apply them (an
owner's copy pacman kept over the .pacnew, or no file), instead of
failing a correct boot.bin and stopping omarchy update. #9 is test-only.
Same UTC commit date as 20261004-2, so pkgrel 3. omarchy-mac stays at
2a3ed89.
2026-10-05 06:42:24 +10:00
Marcelo Alcantara 075f20f401 Merge pull request #791 from joshuaswarren/add-omarchy-mac-ml-meta
Add omarchy-mac-ml meta package
2026-10-04 20:42:21 +10:00
Marcelo Alcantara 96f90c9fa2 Merge pull request #745 from joshuaswarren/add-omarchy-ane-dkms
Add omarchy-ane-dkms, the Apple Neural Engine driver
2026-10-04 19:51:46 +10:00
Marcelo Alcantara 46120ece93 Merge pull request #789 from omacom/mac/52-pin-mac-pkgs
Build omarchy-mac and omarchy-mac-boot from omarchy-mac-pkgs
2026-10-04 19:46:28 +10:00
Spencer Bull fe154888db Ship OpenClaw itself again until a release can seed it (#796)
This reverts f6b9267b (#651), at pkgrel 3 so it replaces 2026.9.6-2. OpenClaw rides the fast ring, so the seed package went straight to edge, rc and stable, where omarchy is still 4.0.4: nothing there seeds ~/.openclaw or moves an existing install, so every OpenClaw user lost the openclaw command on their next update while 4.0.4's --check went on calling it installed. The seed comes back with the Omarchy release that carries omacom/omarchy#13296.
2026-10-04 02:25:08 -05:00
Spencer BullandCodex XHigh a79eec1006 Add T3 Code nightly package (#793)
* Add T3 Code nightly package

* Hold a T3 Code nightly until its AppImages have uploaded

Upstream's release workflow publishes the GitHub release before softprops/action-gh-release uploads its assets: across the last 70 nightlies the AppImages finished 13 to 133 seconds after publication. A scheduled sync that lands in that window selects the release, finds no ARM AppImage to hash, and fails the shared sync run. A 30-minute minimum age, the hold omarchy-dev already uses, makes the watcher keep the previous nightly until the new one is complete.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
2026-10-04 01:35:33 -05:00
Joshua Warren 76e9aead49 omarchy-ane-dkms: 0.4.5, the omarchy-mac-boot overlay layout
The source is the published v0.4.5 release of joshuaswarren/omarchy-ane
(commit d882f48). sha256sums is the checksum of the GitHub archive
archive/refs/tags/v0.4.5.tar.gz.

0.4.5 moves the overlays to /usr/lib/omarchy-mac-boot/dtb-overlays and the
opt-in file to /etc/omarchy-mac-boot/dtb-overlays.opt-in, the layout of
omarchy-mac-boot 20261004-2 (omacom/omarchy-mac-pkgs 2a3ed89).

- conflicts=('omarchy-mac-boot<20261004-2'): an older omarchy-mac-boot does
  not read the new overlay directory, and its boot check fails on a DKMS
  module. A system without omarchy-mac-boot is not affected.
- prepare() keeps T6000 and T6020 opt-in with the release's own
  tools/promote_chip.py, until a row from this package passes on each.
- check() asserts that both stay opt-in: the overlay key, DEFAULT_ON of the
  firmware hook, and the UNTESTED line of omarchy-ane-check.
2026-10-04 01:05:53 -05:00
Marcelo Alcantara 550a9dc382 Re-pin omarchy-mac and omarchy-mac-boot to omarchy-mac-pkgs 2a3ed89
main now has the docs and CI pin from omarchy-mac-pkgs#7 and the device
tree overlays from #3. omarchy-mac 0.1.0-12 and omarchy-mac-boot
20261004-2 (same commit date, so pkgrel 2). omarchy-mac-boot's overlay
tests need dtc in checkdepends.
2026-10-04 14:56:27 +10:00
Marcelo Alcantara 3dcab43846 Merge pull request #764 from joshuaswarren/add-omarchy-mlx
Add omarchy-mlx, omarchy-mlx-vulkan and omarchy-mac-ml: MLX on the Apple GPU (opt-in)
2026-10-04 13:40:38 +10:00
Spencer BullandCodex XHigh a2c3571672 Rebase the Cua Hyprland plugin on Driver 0.32.0 for Hyprland 0.56.2-4 (#772)
* Rebase the Cua Hyprland plugin on Driver 0.32.0 source

Driver 0.31.0 took the independent agent keymaps and Num Lock handling from #473 upstream, so independent-keymaps.patch goes. Upstream also added an up-front check that every key the KEY command can press types the US keysym, modifier keys included, which refuses all foreground typing under ctrl:swapcaps, compose:ralt, altwin:swap_alt_win or compose:102. foreground-remaps.patch makes that check run the per-chord check every key already passes over exactly the chords Driver types text with, with Num Lock off and on, so a string is still admitted or refused before its first key and other remaps are left to the per-chord check.

0.32.0 also gives agent keyboards a real repeat rate, which stops single-seat clients like imv crashing on an agent seat (trycua/cua#4257).

cua-driver-bin stays at 0.28.2, which speaks the same input protocol v3: Driver 0.28.3 through 0.32.0 refuse desktop capture on Hyprland with more than one output or with one away from the origin (trycua/cua#4161).

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Keep restarting fcitx5 from crashing Hyprland under the Cua plugin

With plugin input enabled, fcitx5 requests an input method for each of the three seats, and when it exits Hyprland 0.56.2 can still hold an input-method popup that is mapped although its wl_surface is gone: the popup's surface-destroy handler emits unmap but never clears m_mapped. Tearing down the input method then updates every popup, CInputPopup::updateBox dereferences the missing surface and Hyprland restarts in safe mode. Omarchy's omarchy-restart-xcompose reaches this in normal use.

The plugin now hooks CInputPopup::updateBox and CInputPopup::shouldBeRendered so a popup whose getSurface() is empty is neither placed nor rendered, reports the guard in cua:status, and removes the hooks on unload. The guard is compiled into the module only, so the mock-based tests are unchanged. foreground-remaps.patch becomes downstream.patch now that it carries both changes.

* Pin the Cua Hyprland plugin to Arch's hyprland 0.56.2-4 rebuild

Arch rebuilt Hyprland 0.56.2 against vulkan-sdk 1.4.363 and glslang. The executable and two headers change, so the plugin's exact compositor pin and hashes would block hyprland upgrades for anyone with it installed, and Cua's kit has no profile for it (trycua/cua#4216). The release, compiler and runtime are unchanged, so the derivation now also takes the re-measured compositor executable and header inventory; the build's verifier checks both against the installed package.

* Qualify the Cua plugin README on its input-method guard

The guard is installed best effort, so the README promises the fix only while it is active, and the activation checklist now asks for ime_popup_guard: true. 'No hooks' meant package-manager hooks, which the plugin's new function hooks made ambiguous.

Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Codex XHigh <noreply@openai.com>
2026-10-03 22:37:44 -05:00
Joshua Warren 3f781d8024 Move omarchy-mac-ml to its own PR
The meta package depends on omarchy-mlx/omarchy-mlx-vulkan, which this
PR publishes, so its CI job stays red here and the required result
check can never pass. Split out at review; see #791.
2026-10-03 22:17:57 -05:00
Joshua Warren f15abdfd89 Add omarchy-mac-ml meta package for Apple Silicon Macs
Pulls omarchy-mlx, omarchy-mlx-vulkan, omarchy-ane-dkms and
mil-hwx-compiler: MLX on the GPU and Core ML models on the Neural
Engine. Opt-in only; installs through the Install > AI row.

Depends on #745 (omarchy-ane-dkms) and #764 (omarchy-mlx) publishing
first: CI resolves each package against published edge.
2026-10-03 22:17:32 -05:00
Marcelo Alcantara 0de65be033 Drop the removed migration verifier's enable link on upgrade
The engine enabled omarchy-mac-migrate-verify.service with systemctl; with the unit gone the link would dangle. Also correct which omarchy-mac pull requests every omarchy-mac-pkgs pin includes (#544 has not merged).
2026-10-04 12:26:48 +10:00
Marcelo Alcantara c8f2257abb Build omarchy-mac and omarchy-mac-boot from omacom/omarchy-mac-pkgs
Both recipes pin omacom/omarchy-mac-pkgs main 4399105 and stage the
top-level omarchy-mac/ and omarchy-mac-boot/ directories, each tested
from its own copy. omarchy-mac 0.1.0-11 and omarchy-mac-boot 20261004-1
sort above edge and every lab candidate. They carry the depends and the
platform-root provide from #672 and #673 and drop the migration engine,
legacy files and the Apple setup core now owns.
2026-10-04 12:20:59 +10:00
Joshua Warren 5a82fd1c81 Repin omarchy-mlx to v0.7.24
Corrects GPU sin/cos wrong values for 1e4 <= |x| < 1e7 (v0.7.17-0.7.23
defect). Sources re-downloaded twice, hashes equal; wheel and vendor
match the release SHA256SUMS. Mesa pin unchanged; vendor lock delta vs
v0.7.22 is the mlx-omarchy pin only, no new runtime deps.
2026-10-03 20:37:34 -05:00
Emir Beganović 03b472525f Merge pull request #706 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-10-04 03:32:48 +02:00
emirb 285d290199 chore: sync upstream releases 2026-10-04 01:23:03 +00:00
Emir Beganović aa99d2c147 Merge pull request #785 from omacom/rustdesk-1.5.0
rustdesk: update to 1.5.0 and drop pam from its dependencies
2026-10-04 03:14:13 +02:00
Emir Beganovic 2f9d1a0b31 rustdesk: pre-seed the vcpkg sources rustdesk 1.5.0 builds
rustdesk 1.5.0 and its newer vcpkg pin moved five of the ports the recipe
pre-seeds into vcpkg's downloads:

  aom            3.12.1 -> 3.14.1  (rustdesk's overlay port)
  ffmpeg         7.1    -> 7.1.1   (rustdesk's overlay port)
  libjpeg-turbo  3.1.1  -> 3.2.0
  meson          1.8.2  -> 1.9.0
  pkgconf        2.5.1  -> 3.0.3

vcpkg found none of the old archives useful and fetched all five itself
mid-build, aom from googlesource. Each new tarball matches the SHA512 in
its vcpkg port.
2026-10-04 02:55:47 +02:00
Emir Beganovic fd2fbe3821 rustdesk: retire the bindgen patch, upstream carries it in 1.5.0
0004-bindgen raised libs/scrap's bindgen to 0.72.1 for Clang 22 and
added a git override for it. rustdesk 1.5.0's libs/scrap/Cargo.toml
already requires bindgen 0.72.1, so the patch no longer applies (the
Cargo.toml hunk fails and the scrap hunk is detected as applied) and
prepare() stopped. Comment it out like the retired 0001 patch and drop
its checksum entries.
2026-10-04 02:37:44 +02:00
Emir Beganovic c050643155 rustdesk: fetch aom and libyuv as git sources
googlesource's +archive tarballs are generated per request: their bytes
change on every download, so the recipe skipped their checksums, and the
endpoint refuses CI runners at times (503s on aom failed two #785
builds). Clone both at their pinned commits instead, which git verifies.
_prepare_vc writes downloads/<port>-<ref>.tar.gz with the same git
archive command vcpkg_from_git uses, so vcpkg finds them cached. The
archives hold exactly the files of the old tarballs (aom 1347, libyuv
183).
2026-10-04 02:15:22 +02:00
Emir Beganovic 7246c2a3d5 rustdesk: build 1.5.0 against upstream's vcpkg snapshot
rustdesk 1.5.0 moved VCPKG_COMMIT_ID in flutter-build.yml from 120deac
to 9e593bb (vcpkg, 2026-07-30), and _prepare_vc stops the build when the
recipe's pin differs. Add the 1.5.0 row to the vcpkg table and the new
archive's checksums. Flutter 3.24.5, Rust 1.75 and the README's vcpkg
library list are unchanged from 1.4.9.
2026-10-04 01:57:35 +02:00
Emir Beganović 01f6c85948 Merge pull request #784 from omacom/omakade-1.14.2
omakade: update to 1.14.2 and follow upstream's dependencies
2026-10-04 01:53:31 +02:00
Emir Beganovic d7e69bb58c rustdesk: update to 1.5.0 and drop pam from its dependencies
Upstream's res/PKGBUILD no longer depends on pam as of 1.5.0. The
recipe's prepare() checks _dpr against that list, so the sync's version
and checksum bump stopped in _dpr_check with "Update _dpr from
res/PKGBUILD/depends=()". The bump is the sync's, unchanged; _dpr now
matches upstream's 1.5.0 depends.
2026-10-04 01:46:35 +02:00
Emir Beganovic 27b5c480af omakade: update to 1.14.2 and follow upstream's dependencies
Upstream's release PKGBUILD added python (packaged skill scripts, since
1.12.0), layer-shell-qt (a required build dependency for the Game Mode
overlay, since 1.14.0) and the libpulse and umu-launcher optdepends.
The upstream sync only rewrites pkgver and checksums, so the recipe kept
1.12.0's dependency list: the published 1.12.0 lacks python, and the
1.14 builds in the sync PR fail at CMake on LayerShellQt.

The recipe now matches upstream's PKGBUILD-1.14.2, comments aside.
2026-10-04 01:43:34 +02:00
Emir Beganović 426670f614 Merge pull request #769 from dyl-joseph/fix/tmog-linux-release-manifest
fix(tmog-bin): follow the Linux release manifest
2026-10-04 01:29:43 +02:00
Emir Beganović 32f7994744 Merge pull request #783 from omacom/fix/build-pr-up-to-date-package
Skip packing a PR build when edge already holds the version
2026-10-04 01:13:38 +02:00
Emir Beganovic f51d504a66 Flag a recipe change that will not ship without a version bump
When edge already holds the version, a PKGBUILD whose recipe changed
(comments and blank lines aside) builds nothing and publishes nothing.
Arch bumps pkgrel only when the built package changes, which is the
reviewer's call, so this is a warning annotation on the PKGBUILD and a
job summary line, not a failure. Comment-only and hook-only changes
stay silent.
2026-10-04 01:10:54 +02:00
Emir Beganovic 5bd85a785f Skip packing a PR build when edge already holds the version
A PR can change a package's directory without bumping its version: an
upstream hook, its tests, a README. bin/build then plans nothing, and
the pack step failed on the empty build output, turning the required
result check red (seen on #769). Make the same dry-run check
publish.yml's rebuild job already makes, and skip packing and uploading
when nothing was built. publish.yml finds no artifact for such a merge
and records the package as already published.
2026-10-04 01:03:49 +02:00
Joshua Warren 80adc10c6c omarchy-ane-dkms: back to 0.4.2
0.4.4 keeps T6000 and T6020 on by default. Both flips rest on community rows
from kernels with the in-tree ANE driver, and this first edge package keeps
those two chips opt-in, so the recipe stays at 0.4.2 until a row from this
package exists on each.

The recipe directory is the same as at 48018d1.
2026-10-03 17:43:07 -05:00
Joshua Warren 63b8340e03 omarchy-ane-dkms: 0.4.4
The source is the published v0.4.4 release of joshuaswarren/omarchy-ane
(commit df902f5). sha256sums is the checksum of the GitHub archive
archive/refs/tags/v0.4.4.tar.gz.

0.4.4 is a cleanup release of 0.4.3: no driver, libane or dkms.conf change,
and the same 58 packaged paths. It keeps T6000 and T6020 on by default.
The check() change for kernels with the in-tree driver stays.
2026-10-03 17:38:55 -05:00
Joshua Warren 61835c99de Retrigger CI: GitHub API 503 killed the trust check before build planning 2026-10-03 16:55:01 -05:00
Joshua Warren 8866531afb Address review: renamed repo watch, full checkdepends, llvm rebuild
- watch joshuaswarren/omarchy-mlx (the url= repo) instead of mlx-omarchy
- global checkdepends gains 'lapack' 'blas' 'vulkan-icd-loader' alongside
  'openblas', the shared libraries the wheel loads at check() import
- rebuild_on llvm: omarchy-mlx-vulkan links the shared libLLVM
2026-10-03 16:49:15 -05:00
Ryan Hughes 88c82f3f24 Merge pull request #782 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6720.g8e02fc8, omarchy-settings-dev 4.0.0.r6720.g8e02fc8
2026-10-03 17:46:46 -04:00
ryanrhughes 56e0434e64 Track upstream branches: omarchy-dev 4.0.0.r6720.g8e02fc8, omarchy-settings-dev 4.0.0.r6720.g8e02fc8 2026-10-03 21:39:34 +00:00
Joshua Warren 48018d1fa5 omarchy-ane-dkms: back to 0.4.2
0.4.3 turns T6000 and T6020 on by default. Each flip rests on one community
row from a kernel with the in-tree ANE driver, not from this package, so
this first edge package keeps them opt-in and stays at 0.4.2. The check()
change for kernels with the in-tree driver stays.

pkgver and sha256sums are those of 7980d7b.
2026-10-03 15:02:52 -05:00
Joshua Warren 3e2be18590 omarchy-ane-dkms: 0.4.3
The source is the published v0.4.3 release of joshuaswarren/omarchy-ane
(commit 0477f72). sha256sums is the checksum of the GitHub archive
archive/refs/tags/v0.4.3.tar.gz.

No other recipe line changes: the DKMS build files and the 58 packaged paths
are those of 0.4.2. In 0.4.3 the T6000 and T6020 overlays are enabled, and
the firmware hook also fetches on T6020.
2026-10-03 14:55:45 -05:00
Joshua Warren 274d4f6e0f Install openblas for check() in a fresh build container
makepkg's upfront dependency install covers only pkgbase-level arrays, so the
per-package openblas depends never reach a fresh container and check()'s
mlx.core import failed with a missing libopenblas.so.0. Declare it as a
checkdepends instead; real installs still get it through depends.
2026-10-03 13:58:27 -05:00
Joshua Warren 5ef9ebdcac omarchy-ane-dkms: check() passes on kernels with the in-tree ANE driver
dkms.conf sets BUILD_EXCLUSIVE_CONFIG="!CONFIG_DRM_ACCEL_ANE", so on a kernel
that ships the in-tree driver "dkms build" exits 77 and builds nothing.
check() treated that as a failure, and makepkg stopped in check().

check() now takes exit 77 as "no DKMS build" when the kernel's config sets
CONFIG_DRM_ACCEL_ANE, and prints it. After a build it lists ane.ko and
ane_t6021.ko from the DKMS tree. Every other dkms exit status still fails
check().
2026-10-03 13:52:22 -05:00
Joshua Warren fdb491076c Repin omarchy-mlx to v0.7.22
v0.7.22 (tag 58724762e) supersedes the burned 0.7.15/16/18/20; 0.7.21 stays
the previous release. Mesa pin unchanged. Checksums verified against two
independent downloads per source.
2026-10-03 12:11:19 -05:00
Joshua Warren 7980d7b199 omarchy-ane-dkms: 0.4.2
The source is the published v0.4.2 release of joshuaswarren/omarchy-ane
(commit 545d059). sha256sums is the checksum of the GitHub archive
archive/refs/tags/v0.4.2.tar.gz.

No other recipe line changes: the DKMS build files, the packaged files and
the check() tests are those of 0.4.1. dkms.conf now sets
BUILD_EXCLUSIVE_CONFIG="!CONFIG_DRM_ACCEL_ANE", so DKMS skips a kernel that
ships its own ANE driver and builds on every other kernel.
2026-10-03 05:26:23 -05:00
Ryan Hughes 31b8fdb3ad Merge pull request #777 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6713.ga85e29a, omarchy-settings-dev 4.0.0.r6713.ga85e29a
2026-10-03 01:43:35 -04:00
ryanrhughes 1436a0a210 Track upstream branches: omarchy-dev 4.0.0.r6713.ga85e29a, omarchy-settings-dev 4.0.0.r6713.ga85e29a 2026-10-03 05:37:06 +00:00
97925fbc84 Apply the IPU7 camera's ISP tuning, fix its gain, range and exposure, harden PSYS pinning, and support Lunar Lake (#723)
* Apply the Intel IPU7 camera's ISP tuning and fix its gain, range and exposure

The XPS 14 / 16 webcam looked soft and grainy because almost none of the
image pipeline was tuned:

- 0011: the graph asks for ISP tuning mode 4, which the OV08X40 tuning
  does not carry, so the generic AIC found no tunings and noise
  reduction, TNR and sharpening ran on library defaults. Fall back to
  the tuning's default ISP container.
- 0010: AIQ emits the raw analog gain register code, which the in-tree
  ov08x40 driver halves, so the sensor ran at twice the gain AE and the
  ISP noise model assumed.
- 0008: the HAL ignored the requested YUV range and always produced
  full-range frames that consumers decode as limited range.
- 0009 + relay config: icamerasrc pinned auto exposure to 1/30 s; a new
  fps-range property lets AE lengthen frames in dim light, with gain
  capped at 27 dB.

* Guard fps-range against NULL and correct two descriptions

Setting icamerasrc's new fps-range property to NULL, its own default, handed NULL to gst_camerasrc_parse_range, which crashed in strlen(). NULL now leaves the last range in effect, because the HAL has no way to drop a range once set. The value the relay sets goes through unchanged.

The relay comment said AE raises gain past 27 dB once frames reach 15 fps, but gain-range becomes a hard ISO ceiling (manual_iso_max), so gain never goes past it. The 0008 message credited the sensor JSON's yuvColorRangeMode, which only the mock HAL reads.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Free fps-range when icamerasrc is destroyed

finalize never freed the string the fps-range setter allocates, so every icamerasrc element that had the property set leaked it. The other string properties leak the same way upstream and are left to an upstream fix for all of them.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Harden IPU7 PSYS userptr pinning

(cherry picked from commit 5f1d0013b0e37d9229dfb906806dee0e98199035)

* Avoid unverified IPU7 permission assurances

(cherry picked from commit 9ce97d9788f2be508743fe785f8434ccfec7842e)

* Check IPU7 against Omarchy headers without a runtime dependency

Use linux-omarchy-headers only for check(), avoiding Arch headers on installed Omarchy systems and matching the supported kernel build configuration.

Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
(cherry picked from commit d0d382c07ea99eca5bb5c52240841db779d24b14)

* Test IPU7 build-only headers and kernel-tree selection

Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
(cherry picked from commit eb37cc828fac201a360c35aa1e87a1daf2d5fed1)

* Restrict the IPU7 PSYS node to root

Intel's PSYS driver has two buffer-lifecycle bugs that this package does not fix: a GETBUF that is never mapped leaves the buffer owned by both the PSYS handle and the exported dma-buf, so closing the two is a use-after-free and a double free, and UNMAPBUF drops its mapping reference before clearing the attachment, racing a concurrent dma-buf release. Any account that can open /dev/ipu7-psys0 can reach both.

Nothing but v4l2-relayd@ipu7, which runs as root, opens the node; applications use the v4l2loopback device. With the node at 0600 root:root the camera streams the same, so the video group and the seat user lose nothing and the bugs need root. GROUP and MODE are explicit so the upgrade's change event also tightens nodes on running machines.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Drop the DKMS Intel CVS driver now that linux-omarchy ships it

linux-omarchy and linux-omarchy-bore build drivers/media/i2c/cvs in-tree (CONFIG_VIDEO_INTEL_CVS=m) and carry the same wake-IRQ fix as our 0007 (0542), plus the Nova Lake ACPI ID (0541) that our copy lacks. The DKMS module has the same name and installs under updates/, so on every 7.2+ kernel it displaced the kernel's signed module with an unsigned, older copy: on Nova Lake that copy cannot bind the CVS device and the camera fails, and any later fix in the kernel's driver would be masked. Krzysztof Wilczyński reported the conflict on #723.

Skipping the build only on kernels that carry the driver (BUILD_EXCLUSIVE_CONFIG) would have kept it for stock Arch kernels, but the pacman dkms hook reports every such skip as "exited 77", so every Omarchy kernel update would print a warning. A stock Arch kernel left installed as the fallback boot entry now has no camera.

On upgrade the dkms hook removes intel-cvs and restores the kernel's original module. vision-drivers still covers kernels before 7.2.

* intel-ipu7-camera: build the Lunar Lake HAL plugin and stop rotating its frames

Two Panther Lake assumptions in this package break Lunar Lake boards, where it
is installed by the same hardware detection.

Build both HAL platforms. libcamhal picks its plugin by platform, and a Lunar
Lake machine asks for one that was never built:

  CamHAL[ERR] HalAdaptor: load_camera_hal_library, failed to open library:
    /usr/lib/libcamhal/plugins/ipu7x.so: No such file or directory
  CamHAL[WAR] CameraParserInvoker: parseSensors: No sensors available

so the camera cannot work at all. The proprietary side of it is already
shipped -- libia_aic-ipu7x.so and the rest of that set come from
ipu7-camera-bins today; only the plugin the HAL loads was missing. Upstream
builds both platforms from one configure run and `make install` lays down
/etc/camera/ipu7x/ alongside ipu75xa, including the tuning this hardware
wants (OV08X40_BBG802N3_LNL.aiqb, gcss/OV08X40_BBG802N3_LNL.IPU7X.bin), so the
change is the IPU_VERSIONS list plus 0008, the ipu7x twin of 0006: the
"Intel CVS" pad formats that 1.0.6 added to the ipu75xa sensor config are
needed in the ipu7x one for the same reason, or link validation fails at
stream-on behind the Linux 7.2 bridge entity.

Pick the relay pipeline per board. v4l2-relayd-ipu7.conf rotates every frame
180 degrees, which is right where the sensor is mounted inverted and wrong
here: the sensor reports camera_sensor_rotation = 0 and camera_orientation =
Front, and the picture arrives upside down in every application. camera-init
now writes VIDEOSRC to /run based on the bridge ACPI id and the relay drop-in
reads it. Only INTC10DE takes the new path; INTC10E1, INTC10CF, INTC10E0 and
anything unrecognised keep the packaged pipeline byte for byte, and a boot
where camera-init did not run falls back to it as well.

Verified on a Dell Pro 14 Premium PA14250 (Lunar Lake, INTC10DE, OV08X40 +
HM1092) against intel-ipu7-camera 1.0.6-2 on linux-omarchy 7.2.5-3: with the
package's own intel-cvs DKMS the sensor joins the graph behind "Intel CVS",
the ipu7x plugin built from the pinned commit with 0005 and 0008 resolves
ov08x40-uf on CSI port 0, v4l2-relayd streams 30 fps to /dev/video50 and the
image is upright in a browser.

Note for reviewers: necessary but not sufficient on Lunar Lake. Three more
things this board needs are not in this PR: the sensor probe races the
bridge's runtime suspend (camera-init's `sleep 2` lands while the I2C bus is
still owned by the bridge firmware and ov08x40 reads its chip id as -110),
the in-tree cvs driver's quirk for the Synaptics SVP7500 (06cb:0701) hands
the privacy LED to the host so it never lights, and ipu-bridge before 7.3
does not know the HM1092 IR sensor. Details in #366.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 9be51b47104da9d115a7d79e04bc2ae5445809fc)

* Derive the Lunar Lake relay pipeline from the packaged one

camera-videosrc-init wrote VIDEOSRC for every board, with a copy of the pipeline the relay config carried when it was written, and its file is read after /etc/v4l2-relayd.d/ipu7.conf. On Panther Lake that replaced this branch's fps-range, gain-range and color-range settings with the old sharpness=80 ev=-1 saturation=10 pipeline. It now writes nothing unless the board is Lunar Lake, and there it takes the packaged pipeline and drops only the rotation, so a later change to the relay config reaches both platforms. It also removes a file left by an earlier run, which camera-init's restart on resume would otherwise keep.

The ipu7x CVS format patch becomes 0012: 0008 is already the YUV range patch.

* Regenerate the Lunar Lake pipeline on every relay start

camera-videosrc-init ran from camera-init.service and sourced /etc/v4l2-relayd.d/ipu7.conf as bash. A config that is valid for systemd but not for bash, such as an unquoted VIDEOSRC, made it fail and Lunar Lake fell back to the rotated pipeline; and because camera-init stays active, editing the config and restarting only the relay kept the override cached since boot.

It now runs as the relay's ExecStartPre, where systemd hands it VIDEOSRC already parsed from the relay's own environment files, and writes the override quoted for systemd's parser. ExecStart re-reads the drop-in's EnvironmentFile and ExecStopPost removes it, so each start sees the current config.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Scale the Lunar Lake ov08x40's analog gain codes too

The Lunar Lake tuning (OV08X40_BBG802N3_LNL.aiqb) carries the same CMC gain table as the Panther Lake one, 1x = code 256, and both platforms use the same in-tree ov08x40 driver, which takes 1x = 128. Without the shift a 4x request ran the Lunar Lake sensor at 8x, the mismatch 0010 fixes on Panther Lake. 0010 now sets analogGainCodeShift in the ipu7x sensor config as well.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Remove the Lunar Lake override before the generator runs

systemd passes ExecStartPre the unit's environment files as they stand when
that command starts, so the generator saw a /run override left behind by a
crash or SIGKILL (anything that skipped ExecStopPost) and took its VIDEOSRC
for the configured one: an edited /etc/v4l2-relayd.d/ipu7.conf would lose to
the stale file. Removing the file in its own ExecStartPre first means the
generator's environment is re-read without it.

Co-Authored-By: Codex XHigh <noreply@openai.com>

* Author the HAL and icamerasrc patches from the Omarchy address

---------

Signed-off-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
Co-authored-by: Afonso Oliveira <afonso.oliveira707@gmail.com>
Co-authored-by: Kolbas <pkolbas@pm.me>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-02 21:27:53 -05:00
Spencer Bull f6b9267b3b Ship OpenClaw as the seed for a self-updating copy under ~/.openclaw (#651)
A copy under /usr/lib/node_modules belongs to pacman, and upstream's updater refuses to write there, so neither `openclaw update` nor the Control UI's Update Gateway worked. The package now carries the release instead: the sha256-pinned npm tarball and the install-cli.sh that came in it, from which Omarchy seeds a copy under ~/.openclaw that updates itself.
2026-10-02 21:21:37 -05:00
Marcelo Alcantara 282bc1c43e Merge pull request #747 from joshuaswarren/add-mil-hwx-compiler
Add mil-hwx-compiler, the Apple Neural Engine compiler
2026-10-03 09:31:49 +10:00
Marcelo Alcantara 4466021b04 Merge pull request #773 from maralcbr/settings-runtime-detector
Build settings for #13362's HOOKS baseline without a detector copy
2026-10-03 09:21:24 +10:00
Marcelo Alcantara 8e1284fff6 omarchy-settings: bump pkgrel for the hooks baseline check
The build planner only builds a recipe whose version changed, so the aarch64 builds had nothing to check. The packages' contents are unchanged.
2026-10-03 09:09:56 +10:00
Marcelo Alcantara 6711b64f8f Build settings for #13362's HOOKS baseline without a detector copy
omacom/omarchy#13362 (d0b94d98c) dropped the omarchy-hw-platform copy
omarchy-settings shipped beside its platform guard. Its 00-omarchy-hooks.conf
now asks the runtime's own detector on PATH, and keeps the busybox line where
there is none. The aarch64 settings recipes still refused any source whose
baseline names omarchy-hw-platform without that copy, so every aarch64 build
of quattro would fail once #13362 merges.

Both recipes drop that check. The copy is still installed when a source ships
the guard, as before. The test fixture follows #13362's current baseline, and
the split-layout test checks the package builds from it and ships no copy.
2026-10-03 09:04:44 +10:00
Joshua Warren 80d5711617 omarchy-ane-dkms: 0.4.1
The source is the published v0.4.1 release of joshuaswarren/omarchy-ane
(commit 4f01bb3). sha256sums is the checksum of the GitHub archive
archive/refs/tags/v0.4.1.tar.gz.

- prepare(): both modules now compile ane/ane_stats_show.c and include
  ane/include/, so the DKMS tree copies them. Without them the DKMS build
  fails on the missing ane_stats.h.
- package(): install tools/omarchy-ane-probe and the H13 add program that
  omarchy-ane-smoke runs on the M1 family. packaging/build-dtbo also installs
  the twelve data/ane-soc tables to /usr/share/omarchy-ane/soc and installs
  none of the data-only overlays.
- check(): add test_ane_probe, test_validate_ane_soc and
  test_promotion_check. test_promote_chip and test_promote_from_verdict need
  a git checkout and stay out.
2026-10-02 17:04:31 -05:00
Joshua Warren 82ee4875ee Repin omarchy-mlx to v0.7.19
v0.7.19 (tag 539d870e9) is the published Latest; v0.7.18 was never published.
Asset-pin check() unchanged. Mesa pin unchanged.
2026-10-02 13:44:58 -05:00
David Heinemeier Hansson 8d4ef2dbcf Merge pull request #770 from kevinmcconnell/update-gliff-0.2.0
Update gliff to 0.2.0
2026-10-02 20:41:18 +02:00
Joshua Warren 0485d455b7 Repin omarchy-mlx to v0.7.17 and verify staged ANE assets against the pin
v0.7.17 (tag a33a4e395) is the published Latest; v0.7.15/16 were burned and
never shipped. check() now runs the release's scripts/verify_runtime_assets.py
against the staged parakeet share tree so a stage whose ANE bytes disagree with
the wheel's runtime pin fails the build instead of shipping. Mesa pin unchanged.
2026-10-02 10:46:25 -05:00
Kevin McConnell eda220d878 Update gliff to 0.2.0
gliff now lives at omacom/gliff and cuts tagged releases, so build
the tag archive instead of a pinned commit and watch its tags.

The codec moved to VA-API: add libva, and dbus for notifications.
Install the launcher entry and icon, with the icon cache hook, and
ship the OpenH264 license.
2026-10-02 15:56:54 +01:00
Dylan 9b77934e0e fix(tmog-bin): follow the Linux release manifest 2026-10-02 08:19:58 -05:00
Joshua Warren d4736cd624 Repin omarchy-mlx to v0.7.14
v0.7.14 (tag 07729f401) carries the macOS collector dump-cap fix; v0.7.11
burned, v0.7.12 collector privacy leak, v0.7.13 superseded. Mesa pin unchanged.
2026-10-02 02:27:29 -05:00
Ryan Hughes 1ede739640 Merge pull request #766 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6694.g821ae58, omarchy-settings-dev 4.0.0.r6694.g821ae58
2026-10-02 02:07:49 -04:00
ryanrhughes 8fe3683579 Track upstream branches: omarchy-dev 4.0.0.r6694.g821ae58, omarchy-settings-dev 4.0.0.r6694.g821ae58 2026-10-02 06:01:16 +00:00
Joshua Warren cdd2593bac Repin omarchy-mlx to v0.7.13
v0.7.12's collector leaks private data; v0.7.13 (tag 85998d135) replaces it.
Mesa pin unchanged.
2026-10-01 23:52:07 -05:00
Joshua Warren 0ff123bfae Repin omarchy-mlx to v0.7.12
v0.7.11 is burned (no release); v0.7.12 publishes the tested wheel and vendor
tarball from tag 8995ddf2e. Mesa pin unchanged.
2026-10-01 22:39:41 -05:00
Joshua Warren fd81838909 omarchy-ane-dkms: 0.4.0
The source is the published v0.4.0 release of joshuaswarren/omarchy-ane
(commit 16d13ef). sha256sums is the checksum of the GitHub archive
archive/refs/tags/v0.4.0.tar.gz.
2026-10-01 20:31:46 -05:00
Joshua Warren 7f2014bb98 omarchy-ane-dkms: ship omarchy-ane-smoke and its runner
omarchy-ane-smoke (joshuaswarren/omarchy-ane#50) runs the add program 20
times on the ANE and checks each output against the exact fp16 sum. It
runs omarchy-ane-run from its own directory with the program in
/usr/share/omarchy-ane/fixtures.

- build(): make tools/ane-run. It links libane statically and needs only
  libc and libm at run time. libdrm is a makedepend for drm.h.
- check(): add tools/test_ane_smoke.py (stub runner, fake root, no device).
- package(): install omarchy-ane-smoke, tools/ane-run as omarchy-ane-run,
  and fixtures/h14-anec/add/program-0.anec.
2026-10-01 20:04:22 -05:00
Joshua Warren 7d643349b2 Point omarchy-mlx sources at the renamed repo
GitHub archives now extract to omarchy-mlx-<ver> for every tag, so the
source filename and the build cd follow the omarchy-mlx name. Tarball
sha256 unchanged: 7ad98f7df4df54c1e194b2be1469200da767dc3e374d79e66dcb98bdbb958a9d.
2026-10-01 19:22:50 -05:00
Ryan Hughes 250edad378 Merge pull request #762 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6693.gf45461a, omarchy-settings-dev 4.0.0.r6693.gf45461a
2026-10-01 20:10:15 -04:00
ryanrhughes 577ed75dd0 Track upstream branches: omarchy-dev 4.0.0.r6693.gf45461a, omarchy-settings-dev 4.0.0.r6693.gf45461a 2026-10-02 00:03:00 +00:00
Joshua Warren 8e5c2d2a8b omarchy-ane-dkms: ANE on by default on T6021
omarchy-ane removed omarchy-ane-m2-enable and the modprobe gate
packaging/modprobe/ane_t6021.conf, and added 90-omarchy-ane-firmware.hook,
which fetches the T6021 firmware after each install and upgrade
(omarchy-ane receipts/2026-10-01-t6021-default-on).

- package(): install the firmware hook; drop omarchy-ane-m2-enable, the gate
  file and its backup=() entry. On upgrade pacman deletes an unchanged gate
  file; a changed one becomes ane_t6021.conf.pacsave, which modprobe does not
  read (it reads only *.conf).
- post_remove: remove both firmware files that omarchy-ane-firmware-fetch
  writes (T602x and T8112). Drop the ane-t6021 opt-in edit: no overlay reads
  that key now.
- The overlay comment names /usr/share/omarchy-platform/dtb-overlays.
2026-10-01 18:49:47 -05:00
Joshua Warren 2dfb7779f6 Rename omarchy-mac-ml description to match its menu label
Drops the "Local AI" name; the description now reads the same promise as
the menu label MLX + Core ML (Apple Silicon).
2026-10-01 18:31:32 -05:00
Joshua Warren de50d8444a Repin omarchy-mlx to v0.7.10 and mesa to e7631595d
omarchy-mlx builds the v0.7.10 release wheel and vendor tarball from
github.com/joshuaswarren/omarchy-mlx; omarchy-mlx-vulkan pins the
honeykrisp-omarchy-v3 driver at mesa-1 e7631595df6281748ea5e643d74db59c5f783b01
with MESA_GIT_SHA1_OVERRIDE set to the short sha the driver reports and the
runtime compares. omarchy-mac-ml now pulls omarchy-mlx-vulkan directly and its
description names MLX on the GPU and Core ML models on the Neural Engine.
2026-10-01 18:29:54 -05:00
Ryan Hughes 2c1d7c0346 Merge pull request #753 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6692.gc05d901, omarchy-settings-dev 4.0.0.r6692.gc05d901
2026-10-01 06:54:25 -07:00
ryanrhughes 5c4abb5146 Track upstream branches: omarchy-dev 4.0.0.r6692.gc05d901, omarchy-settings-dev 4.0.0.r6692.gc05d901 2026-10-01 13:46:24 +00:00
Joshua Warren da8b4dd6f7 Add omarchy-mac-ml, the local AI meta package for Apple Silicon Macs
It pulls in omarchy-mlx, omarchy-ane-dkms and mil-hwx-compiler.
aarch64 and edge only.
2026-10-01 01:13:59 -05:00
Joshua Warren 51c8769998 Add omarchy-mlx and omarchy-mlx-vulkan
Both packages come from one PKGBUILD, so the MLX wheel and the Vulkan
driver it was qualified on publish together.

omarchy-mlx-vulkan builds only the Honeykrisp Vulkan driver from
joshuaswarren/mesa-1 9b97b82ab1. It installs the driver, an ICD
manifest with an absolute library_path, and the driver's git SHA in
/usr/lib/omarchy-mlx/vulkan/. The manifest is outside the loader's
search path, so only omarchy-mlx uses the driver. It provides nothing
generic and conflicts with mesa-honeykrisp-omarchy.

omarchy-mlx builds the private venv offline from the release's
hash-locked vendor wheels and depends on the exact omarchy-mlx-vulkan
build.

The mlx-omarchy sources are placeholders until v0.7.7 is published.
2026-10-01 01:13:59 -05:00
Joshua Warren 40c38ab69e mil-hwx-compiler: use the published v0.1.0 release archive 2026-09-30 23:03:02 -05:00
Joshua Warren a2b5c4000f omarchy-ane-dkms: use the published v0.3.0 release archive 2026-09-30 19:19:31 -05:00
Joshua Warren d00ac79e91 omarchy-ane-dkms: take the v0.3.0 archive from omarchy-ane main d6babeb 2026-09-30 18:51:20 -05:00
Joshua Warren 184adf1693 Add mil-hwx-compiler, the Apple Neural Engine compiler
The package builds mil-hwxc from a joshuaswarren/mil-hwx-compiler
release. It compiles textual MIL into ANEC and HWX programs for the
Apple Neural Engine. aarch64 and edge only.

The compiler needs the libobjc2 runtime and a gnustep-base built for
it. Arch's gnustep-base uses gcc's libobjc, so the recipe builds both
from the commits the release pins and ships their runtime libraries in
/usr/lib/mil-hwx-compiler. mil-hwxc finds them through its RUNPATH.
check() runs the release's Linux verifier.

The watch follows published GitHub releases after 24 hours, on the
reviewed lane.
2026-09-30 18:17:39 -05:00
Joshua Warren 4d29b46a6c Add omarchy-ane-dkms, the Apple Neural Engine driver
The package builds ane.ko and ane_t6021.ko with DKMS from a
joshuaswarren/omarchy-ane release. It installs the device-tree
overlays, the readiness check, the M2 opt-in tools and their pacman
hooks. aarch64 and edge only.

ane_t6021 stays blocked by /etc/modprobe.d/ane_t6021.conf until the
user opts in. Removal takes out the firmware and the opt-in line that
omarchy-ane-m2-enable wrote.

The watch follows published GitHub releases after 24 hours, on the
reviewed lane.
2026-09-30 17:02:16 -05:00
5a49a3fab5 Keep Hermes Desktop opening on its runtime after Hermes updates (#718)
* Accept the fifth Hermes desktop launch option

Hermes now returns five values from _desktop_launch_options(), appending the renderer accessibility switch. The launcher unpacked exactly four, so once a user's runtime updated, every launch died with "too many values to unpack" before the app opened. The launcher now takes the first four and reads the fifth when present, so it keeps working with the packaged release and with newer runtimes, and it bridges an explicit accessibility opt-out the same way Hermes' own launcher does. The launch check now also runs a five-value helper, which fails against the previous launcher.

Fixes basecamp/omarchy#13491

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* hermes-desktop: launch against the installed runtime, accept the helper's new arity

_desktop_launch_options() returns five values since Hermes grew a trailing
renderer_accessibility field, so the launcher died with "too many values to
unpack (expected 4)" before os.execve — and gtk-launch discards stderr, so the
app icon became a silent no-op that left no process and no log line behind.

The launcher also exported HERMES_DESKTOP_IGNORE_EXISTING=1 unconditionally
while preferring the runtime's own app binary a few lines above it: with a
runtime installed, Desktop skipped that runtime and offered first-run setup
instead of the user's sessions. Ignoring an existing runtime is only correct for
the bundled /opt app, which is built from the package's release commit and
cannot drive a runtime built from another one.

pkgrel bumped for the changed artifact.

* Tell only the packaged Hermes Desktop to skip an existing install

The runtime's own app now finds its runtime the way `hermes desktop` launches it, through the installed-runtime lookup, instead of being pinned to it with HERMES_DESKTOP_HERMES_ROOT. That variable is upstream's developer override: it resolves before the lookup that Repair install bypasses, so pinning the runtime turned a hard repair into a restart against the same broken venv. HERMES_DESKTOP_IGNORE_EXISTING is set only when the launcher falls back to the packaged /opt app, and an explicit value in the environment still wins, so the in-app updater's relaunch of the runtime app no longer inherits it.

The fifth launch option is read the way it was accepted in the previous commit but one, with the checksums the two earlier commits left stale brought up to date. runtime-test.py now records both variables, so it fails if the unconditional export or the root pin comes back.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: manuaudio <manu@arimaka.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Omni <omni@omninova.com.mx>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-09-29 16:35:30 -05:00
Ryan Hughes 0c1996bb68 Merge pull request #730 from omacom/update/t3code-bin-0.0.44
Update t3code-bin to 0.0.44
2026-09-29 17:19:57 -04:00
Ryan Hughes 5e34b440bb Update t3code-bin to 0.0.44 2026-09-29 17:12:35 -04:00
Ryan Hughes adc0be3432 Merge pull request #728 from omacom/update/t3code-0.0.43
Update t3code-bin to 0.0.43
2026-09-29 16:49:14 -04:00
Ryan Hughes cb75bd381d Update t3code-bin to 0.0.43 2026-09-29 16:41:57 -04:00
Ryan Hughes f96114a9cc Merge pull request #727 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6691.g8b4eae6, omarchy-settings-dev 4.0.0.r6691.g8b4eae6
2026-09-29 15:42:33 -04:00
ryanrhughes bfad81267a Track upstream branches: omarchy-dev 4.0.0.r6691.g8b4eae6, omarchy-settings-dev 4.0.0.r6691.g8b4eae6 2026-09-29 19:34:56 +00:00
Ryan Hughes b7a7ad1068 Merge pull request #722 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6689.gb421b1b, omarchy-settings-dev 4.0.0.r6689.gb421b1b
2026-09-29 09:22:33 -04:00
ryanrhughes c830be2720 Track upstream branches: omarchy-dev 4.0.0.r6689.gb421b1b, omarchy-settings-dev 4.0.0.r6689.gb421b1b 2026-09-29 13:14:04 +00:00
Spencer Bull 3dbba7e478 Rebase the Cua Hyprland plugin on Driver 0.28.2 and pin glibc r50 (#717)
cua-hyprland-plugin 0.26.1-6 pins glibc=2.44+r24+g16be1518495f-1 exactly. Arch core has moved to 2.44+r50+g1848099f063e-1, so on any upgraded edge system pacman refuses the plugin with "unable to satisfy dependency", even though cua-driver-bin installs.

Derive a new profile, omarchy-edge-20260928-remaps, that pins the current glibc and takes its source from the Driver 0.28.2 release that cua-driver-bin ships. The plugin files in that release are byte-identical to 0.26.1 and 0.27.0, so the keymap patch applies unchanged and only the upstream base in DOWNSTREAM-PROVENANCE.json moves. The download wrapper now substitutes the 0.28.2 archive and manifest into the verified upstream kit and renders the recipe from the kit's own PROFILE-PKGBUILD.in, so the derived kit is one Cua's profile_verify.py accepts as complete.

pkgrel starts at 2 because profile validation refuses package release 1.
2026-09-28 23:15:36 -05:00
Ryan Hughes 660cbe3940 Merge pull request #716 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6688.ge332dc9, omarchy-settings-dev 4.0.0.r6688.ge332dc9
2026-09-28 19:33:31 -04:00
ryanrhughes af19ab222b Track upstream branches: omarchy-dev 4.0.0.r6688.ge332dc9, omarchy-settings-dev 4.0.0.r6688.ge332dc9 2026-09-28 23:27:06 +00:00
Ryan Hughes 6005050023 Merge pull request #710 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6687.gb18ab49, omarchy-settings-dev 4.0.0.r6687.gb18ab49
2026-09-28 14:22:47 -04:00
ryanrhughes f0ebc8a80b Track upstream branches: omarchy-dev 4.0.0.r6687.gb18ab49, omarchy-settings-dev 4.0.0.r6687.gb18ab49 2026-09-28 18:14:32 +00:00
Marcelo Alcantara 29465fb750 Merge pull request #701 from omacom/settings-dev/runtime-profile
omarchy-settings-dev: ship the full set on aarch64 for runtime-profile sources
2026-09-28 22:20:09 +10:00
Marcelo Alcantara ea7738975a omarchy-settings-dev: ship the full set on aarch64 for runtime-profile sources
#638 gave omarchy-settings this and -dev was never ported, so edge, the only
channel aarch64 machines are qualified for, still stripped zram, oomd, zswap,
the sysctl tuning and USB autosuspend there: on Snapdragon, migration
1790328426 installs zram-generator, finds no dev-zram0.swap and completes
without zram. A source with default/settings-runtime-profile now gets the same
files, backup and optdepends on aarch64 as on x86_64, Thunderbolt drop-in
included, and its HOOKS files ship as they are. Older sources, including the
current quattro pin, keep today's aarch64 package. The platform guard and its
detector check stay aarch64-only (#691, #694). The keyboard backlight unit
first-run enables ships whenever the source has it, as in omarchy-settings.

tests/settings-runtime-profile.sh now runs both recipes. pkgrel 5 so the
recipe change builds; the payload from the current pin is unchanged.
2026-09-28 21:28:47 +10:00
David Heinemeier Hansson 47a3024683 Merge pull request #696 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-28 12:05:36 +02:00
Krzysztof Wilczyński f5e1b9fb79 Merge pull request #263 from AFOliveira/codex/harden-yt6801-private-ioctl
Remove YT6801 private ioctl interface
2026-09-28 11:39:44 +02:00
dhh db5f9bef51 chore: sync upstream releases 2026-09-28 05:36:36 +00:00
Tobias LütkeandClaude Opus 5.5 dc2b39bce4 Add disktree (#624)
* Add disktree

* disktree-bin: 0.9.1

* disktree-bin: 0.10.1, for aarch64 too

0.10.1 ships gpui-omarchy 0.1.3, and the release now builds an aarch64
Linux tarball, so the recipe and the upstream asset map cover both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 23:57:21 -05:00
eee77d5a38 voxtype-bin: update to 1.1.0 (#640)
* voxtype-bin: update to 1.1.0

Ship the signed 1.1.0 release assets, baseline and ARM binaries, and the complete Quickshell and OSD style trees. Add OpenVINO optional dependencies from #526 and select the baseline binary automatically on pre-AVX2 hosts.

* voxtype-bin: bump pkgrel past the published 1.1.0-1

voxtype-bin 1.1.0-1 is already published to edge, rc and stable from master's upstream sync. This branch changes that package's contents (baseline binary, OSD styles and recipes, the install hook, OpenVINO optdepends) without changing its version, so publish.yml would call it already published and skip it, and publish-artifact refuses different bytes under an existing filename. pacman would not offer it as an upgrade either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* voxtype-bin: list accelerator optdepends for x86_64 only

makepkg appends optdepends_$CARCH to optdepends rather than replacing it, so optdepends_aarch64 did not drop the Vulkan, CUDA, ROCm and MIGraphX entries on aarch64: it listed all sixteen shared ones and then eleven of them a second time. The accelerator runtimes move to optdepends_x86_64 beside the OpenVINO ones, and the aarch64 array goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* voxtype-bin: move pre-AVX2 hosts to the baseline build on upgrade

The baseline pick in _set_default_backend only runs when no backend was saved, which is a fresh install. Every release before 1.1.0 gave a CPU without AVX2 voxtype-avx2, and pre_upgrade saves that path, so post_upgrade restored the build the host cannot run and the machines the baseline binary exists for never reached it. A saved AVX2 or AVX-512 build whose instruction set the CPU lacks is now picked again; GPU and ONNX choices are left alone.

tests/voxtype-bin-install.sh covers the fresh-install pick and the upgrade cases, and runs with the other self-tests. It borrows the hook's fixed /tmp/.voxtype-backend-upgrade, so it refuses to start when anything is already there, a dangling symlink included: CI runs it as root, and writing through a planted link would land outside the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* tests: keep voxtype-bin-install's upgrade state out of /tmp

The test borrowed the hook's /tmp/.voxtype-backend-upgrade and checked it was free only once, so a real voxtype-bin upgrade writing its state during the run could have that state overwritten or deleted, and the hook would then lose the user's backend. The test now redefines the sourced _preserve_or_set_backend with the path moved into its own directory, and fails outright if the hook stops using that path rather than passing without reading it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

* tests: never parse voxtype-bin-install's temp path as code

The redefined _preserve_or_set_backend went through eval with the mktemp path pasted in, so a TMPDIR holding shell syntax -- a directory named $HOME, or $(...) -- was expanded or run when the function was called. It now carries a reference to $SAVED, which expands to the path only at run time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>

---------

Co-authored-by: Spencer Bull <spencer@omarchy.org>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex XHigh <noreply@openai.com>
2026-09-27 23:55:23 -05:00
Marcelo Alcantara bcdd78683b Merge pull request #694 from omacom/settings-dev/scope-detector-check
omarchy-settings-dev: check for the detector copy on aarch64 only
2026-09-28 13:57:12 +10:00
Marcelo Alcantara e196eb3c63 Merge pull request #638 from omacom/mac/14-settings-profile
omarchy-settings: ship the full set on aarch64 for runtime-profile sources
2026-09-28 13:51:49 +10:00
Marcelo Alcantara 45fb64fedb omarchy-settings-dev: check for the detector copy on aarch64 only
The guard and its detector copy now ship on aarch64 only (#691), so the check that 00-omarchy-hooks.conf has its detector failed every x86_64 build from a source that carries the HOOKS baseline. x86_64 keeps the busybox line without a detector. Same scoping as omarchy-settings in #638.
2026-09-28 13:51:12 +10:00
Marcelo Alcantara a701a8dbbe Merge pull request #691 from omacom/settings/platform-guard-aarch64-only
omarchy-settings-dev: ship the platform guard on aarch64 only
2026-09-28 13:50:45 +10:00
Spencer Bull 0dcdee4637 Move cua-driver-bin off the fast ring and keep its bumps manual (#692)
Build it in edge and promote through rc to stable like other packages,
matching cua-hyprland-plugin, which left the fast ring in #432.

#693 held upstream sync while 0.28.3+ breaks screenshots. Make that the
standing policy: Omarchy bumps the driver by hand, so the PKGBUILD no
longer says to re-enable sync after the fix, and docs/upstream-sources.md
records the hold. The hook and min_release_age stay, so lifting the hold
is a one-line revert.

Publish as 0.28.2-3 so the PR builds; the payload is unchanged.
2026-09-27 22:43:52 -05:00
Marcelo Alcantara c2c3f0a4ed omarchy-settings: ship the platform guard on aarch64 only
No platform package is built for x86_64, so an x86 machine has nothing to
guard. The check that 00-omarchy-hooks.conf has its detector copy follows it:
without the copy, x86_64 keeps the busybox line it would have anyway, and
leaving the check unscoped would fail every x86_64 build from a source that
asks omarchy-hw-platform. Taken from #691, which keeps the -dev recipe.
2026-09-28 13:41:10 +10:00
Marcelo Alcantara 7f77d82c0f omarchy-settings-dev: ship the platform guard on aarch64 only
No platform package is built for x86_64 and pacman refuses another architecture's package there, so x86 transactions gain nothing from the guard. The x86 HOOKS baseline keeps the busybox line without a detector, so the detector copy goes too. omarchy-settings gets the same change in #638.
2026-09-28 13:40:50 +10:00
Marcelo Alcantara e8544c4eb5 Merge pull request #630 from omacom/mac/10-sync-rebuilds-aarch64
Detect aarch64 rebuilds, including against carried dependencies
2026-09-28 13:38:32 +10:00
Marcelo Alcantara 2fecfe548e Check the exact network-only sysctl an older source's aarch64 package ships 2026-09-28 13:31:31 +10:00
Ryan Hughes 52da7153c7 Roll cua-driver-bin back to 0.28.2 and hold upstream updates (#693) 2026-09-28 03:17:38 +00:00
Marcelo Alcantara e0eb841a2e omarchy-settings: ship the full set on aarch64 for runtime-profile sources
A source with default/settings-runtime-profile decides at runtime which
platform-specific files apply, so its aarch64 package now gets the same files,
backup and optdepends as x86_64: the Thunderbolt request and the memory stack
stay, and its HOOKS files ship as they are. The check that a Mac's asahi line
survives still runs on every aarch64 build. Older sources, including the pinned
v4.0.4, keep #380's aarch64 package: Thunderbolt removed, the HOOKS line guarded
for asahi, the memory stack stripped. The keyboard backlight unit first-run
enables ships whenever the source has it.

pkgrel 4 so the recipe change builds: edge already holds 4.0.4-3 on both
architectures. The payload built from v4.0.4 is unchanged.
2026-09-28 13:17:22 +10:00
Ryan Hughes 1e74d370f3 Merge pull request #644 from thisisgm/flea-0.3.5
flea: update to 0.3.5, install ui/boot and the removal hook
2026-09-27 23:13:18 -04:00
Ryan Hughes dc1950520a flea: bump pkgrel so the ui/boot and hook additions publish
master already published 0.3.5-1 (#680) without them; the same version
would be skipped as already up to date.
2026-09-27 23:05:11 -04:00
Ryan Hughes 132199cb9b Merge remote-tracking branch 'origin/master' into flea-0.3.5 2026-09-27 23:05:03 -04:00
Ryan Hughes 0d8623dc60 Merge pull request #689 from omacom/ci/plan-from-pr-files
Plan PR builds from the PR's own files, not a diff to master's tip
2026-09-27 21:47:29 -04:00
Ryan Hughes 7abad3786d Plan PR builds from the PR's own files, not a diff to master's tip
The planner took 'git diff base.sha head.sha', a two-dot diff between
the current master tip and the PR head. For a PR that branched before
later merges, that counts every package master has changed since, so
the PR plans those too and builds its own stale copies of them: wasted
builds, and 'already up to date' Pack failures that turn the PR red for
packages it never touched. #397 (lazyjournal) planned 22 packages for a
one-package change. The checkout is shallow, so ask GitHub for the PR's
files, which are listed against the merge base.
2026-09-27 21:45:40 -04:00
Ryan Hughes d17caa7524 Merge pull request #688 from omacom/ci/overlay-status-sigpipe
Stop the PR overlay preview failing stale PRs with SIGPIPE
2026-09-27 21:38:02 -04:00
Ryan Hughes aa143d79b7 Stop the PR overlay preview failing stale PRs with SIGPIPE
'git status --short | head' under set -o pipefail: once the PR's
pkgbuilds/ differs from base in more than ten files, head exits, git
takes SIGPIPE and the step fails with 141 before anything builds. Every
PR branched far enough behind master hit it (omadev #423, llmman-bin
#428 and others on 2026-09-28). sed -n '1,10p' prints the same preview
and drains the stream.
2026-09-27 21:36:12 -04:00
Ryan Hughes 4f476e49a2 Merge pull request #600 from scottjones/fix/steam-normal-updates
Allow Steam FEX updates while preserving the login patch
2026-09-27 21:31:33 -04:00
Ryan Hughes 731bf88de9 Merge pull request #587 from omacom/hermes-desktop-default-agent
Stop the Hermes Desktop launcher reconciling a mise install
2026-09-27 21:31:21 -04:00
Ryan Hughes 5709db91f5 Merge pull request #686 from jdvmi00/lmstudio-desktop-entry-20260927
Install LM Studio's launcher under its own desktop ID
2026-09-27 21:31:10 -04:00
Ryan Hughes a3a72789b5 Merge pull request #682 from maralcbr/fix/omazed-font-helper
omazed: package the 2.2.0 font helper and its dependencies
2026-09-27 21:30:58 -04:00
Ryan Hughes e8e3e28135 Merge pull request #639 from zz8011/supergfxctl-drop-sudo-dbus-group
supergfxctl: drop nonexistent sudo group from D-Bus policy
2026-09-27 21:30:47 -04:00
Ryan Hughes 1f60ba7b20 Merge pull request #319 from omacom/auto/sync-rebuilds
chore: rebuild against updated dependencies
2026-09-27 21:30:35 -04:00
Ryan Hughes 49b3c621e8 Merge pull request #301 from jeremydixon22/update/tobi-try-1.10.1
Update tobi-try to 1.10.1
2026-09-27 21:30:24 -04:00
Ryan Hughes 8f1bdfb257 Merge pull request #229 from jeremydixon22/update/omatrack-1.5.5
Update Omatrack to 1.8.6
2026-09-27 21:30:13 -04:00
Ryan Hughes 547a54d097 Merge pull request #621 from jacob-vincent-mink/bump/omawake-openvino-optdepends
Package Omaspeak 0.1.0 and Omawake 0.1.1 with OpenVINO 2026.4 options
2026-09-27 21:30:03 -04:00
Ryan Hughes 7f7a7f2fb3 Merge pull request #620 from jacob-vincent-mink/bump/openvino-genai-2026.4.0
Bump OpenVINO GenAI to 2026.4.0.0
2026-09-27 21:29:52 -04:00
Marcelo Alcantara cc43782285 Merge pull request #601 from jdvmi00/spark/nvidia-dp-detach
Carry NVIDIA DisplayPort detach fix for aarch64
2026-09-28 11:29:09 +10:00
Marcelo Alcantara 6eb4ad1a85 Merge pull request #380 from jdvmi00/spark/aarch64-settings-boot-dropins
omarchy-settings: keep the Limine and mkinitcpio drop-ins on aarch64
2026-09-28 11:29:04 +10:00
Marcelo Alcantara e4b00e594c Merge remote-tracking branch 'upstream/master' into spark/nvidia-dp-detach 2026-09-28 11:21:39 +10:00
Ryan Hughes 29e0309986 Merge pull request #680 from omacom/fix/flea-0.3.5-nofollow-guard
flea: drop the source-grep security gate and update to 0.3.5
2026-09-27 20:08:32 -04:00
Ryan Hughes 31b10abd75 Merge pull request #663 from omacom/ci/sync-pr-churn
Keep sync PRs building across bot pushes
2026-09-27 20:07:45 -04:00
Ryan Hughes 45ae938a58 flea: put only copymanifest's O_TMPFILE test on tmpfs
Moving the whole module broke the rest of it: Writer::create needs a
runtime directory on a different filesystem from the copy, and with the
test root on /dev/shm none qualifies. Only
a_garbage_stream_falls_back_with_the_tree_untouched opens its manifest
in its own test dir, which the builder's /tmp refuses (EOPNOTSUPP).
2026-09-27 20:01:44 -04:00
Marcelo Alcantara 4d6b627a13 Merge remote-tracking branch 'upstream/master' into spark/aarch64-settings-boot-dropins 2026-09-28 07:54:08 +10:00
Marcelo Alcantara caac8448be omarchy-settings: pass #13362's platform-aware hooks through on aarch64
omacom/omarchy#13362 sets HOOKS per platform inside 00-omarchy-hooks.conf,
with no column-0 HOOKS= line, so the aarch64 build failed on it; settings-dev
tracks quattro automatically and would break as soon as it lands. Wrap only a
single-line HOOKS=(...), refuse any other column-0 HOOKS=, then source the
shipped files onto a Mac line and a stock line: the Mac line must come through
unchanged and the stock one must not. Back up 00-omarchy-hooks.conf when it
ships, and refuse it without the omarchy-hw-platform copy it asks.

The test now uses the real v4.0.4 hooks file, #13362's two files and
omarchy-mac-boot's 90-94 fragments, and covers upgrades over a hand-restored
file.
2026-09-28 07:53:14 +10:00
David Heinemeier Hansson f50a8e0ca4 Merge pull request #683 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-27 23:50:24 +02:00
David Heinemeier HanssonandClaude Opus 5.5 b398111b0e Drop NASM from ttfx's build dependencies
ttfx 0.5 has no assembly engine; it is Rust only.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxJcVYpBxFDam6czK4bGjp
2026-09-27 17:43:50 -04:00
dhh 29e03f68a2 chore: sync upstream releases 2026-09-27 21:43:16 +00:00
Marcelo Alcantara 919b084b93 Merge remote-tracking branch 'upstream/master' into spark/aarch64-settings-boot-dropins 2026-09-28 07:42:17 +10:00
Ryan Hughes 7228983fc2 Merge pull request #684 from omacom/auto/track-branches
Track upstream branches: omarchy-dev 4.0.0.r6663.g3faafba, omarchy-settings-dev 4.0.0.r6663.g3faafba
2026-09-27 17:38:06 -04:00
dhh 4bf72efc0a chore: rebuild against updated dependencies 2026-09-27 21:34:39 +00:00
ryanrhughes 62f3df2378 Track upstream branches: omarchy-dev 4.0.0.r6663.g3faafba, omarchy-settings-dev 4.0.0.r6663.g3faafba 2026-09-27 21:31:46 +00:00
Marcelo Alcantara d41fb09903 Merge pull request #681 from omacom/mac/109-publish-arm64-rebuild
Rebuild aarch64 natively when publish finds no artifact
2026-09-28 07:20:45 +10:00
Marcelo Alcantara 1a94606fc4 omazed: package the 2.2.0 font helper and its dependencies
omazed 2.2.0 added omazed-font.sh, which the recipe did not install, so font
sync was silently skipped and `omazed set-font` failed. The helper also needs
jq and perl.
2026-09-28 07:15:47 +10:00
Jim Martin 3dc749e4f2 Install LM Studio's launcher under its own desktop ID and URL scheme 2026-09-27 15:41:48 -05:00
Marcelo Alcantara 97bcb320ab Rebuild aarch64 natively when publish finds no artifact
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.
2026-09-28 06:41:39 +10:00
GM f234bfffc6 flea: update to 0.3.5, install ui/boot and the removal hook
0.3.2 moved the Quickshell entries into ui/boot, and the 0.3.4-1 package here does not
install them, so flea exits with "the shell config is missing" (thisisgm/flea#216).
This installs ui/boot with its Commons and Ui links, bumps to 0.3.5, and ships the
pacman hook that notes on removal what flea --default and --picker left behind.
2026-09-27 16:13:28 -04:00
Ryan Hughes 0059492899 flea: run copymanifest's tests on tmpfs
0.3.5's copymanifest::tests::a_garbage_stream_falls_back_... opens its
anonymous (O_TMPFILE) manifest directly in its /tmp test dir, which the
x86_64 builder's /tmp refuses with EOPNOTSUPP. The sibling tests pass
because Writer::create falls back across directories. Same reason the
other filesystem_tests already run on /dev/shm; none of these spawn.
2026-09-27 16:09:38 -04:00
Ryan Hughes 0cbcd890fd flea: drop the source-grep security gate and update to 0.3.5
The upstream hook refused v0.3.5 as missing a required security fix. It
was not missing: copy_file_at now opens through
open_if_regular_with_meta(src.at, O_NOFOLLOW), which open_if_regular
wraps, and the gate's regex wanted '(' right after open_if_regular.

The gate dates from 0.1.x, when Omarchy carried four upstream security
patches and needed releases to prove they had absorbed them. Every
release since 0.1.5 has, and matching literal source lines has since
caught only renames (#488 and this one), never a regression. Keep the
real checks -- SHASUMS256.txt match, tarball root, minimum release age
-- and drop the greps.

Update written by bin/sync-upstream; the checksum matches upstream's
SHASUMS256.txt.
2026-09-27 16:01:35 -04:00
David Heinemeier Hansson e2bc7586e2 Merge pull request #675 from omacom/drop-elsewhen
Drop elsewhen now that the world clock ships with Omarchy
2026-09-27 21:56:11 +02:00
David Heinemeier Hansson 532ca3a08f Merge pull request #676 from omacom/build-pr-skip-deleted-packages
Skip packages a PR deletes when planning PR builds
2026-09-27 21:55:32 +02:00
David Heinemeier Hansson ab7d6102fa Merge pull request #679 from omacom/monologue/track-releases
Track Monologue releases from its git tags and update to 0.2.0
2026-09-27 21:54:16 +02:00
David Heinemeier HanssonandClaude Opus 5.5 3dba6036ae Track Monologue releases from its git tags and update to 0.2.0
Monologue now publishes a GitHub release for every version, so declare
its v{pkgver} tags as the upstream feed, as Hype does. The scheduled sync
picks up new releases and hashes the tag archive, instead of pinning a
commit by hand. This first release brings Monologue to 0.2.0, which
builds trimming in, so the omacut optdepend goes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 21:45:46 +02:00
David Heinemeier HanssonandClaude Opus 5.5 7552b8be49 Skip packages a PR deletes when planning PR builds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 12:21:37 -07:00
David Heinemeier HanssonandClaude Opus 5.5 d84c720e60 Drop elsewhen now that the world clock ships with Omarchy
Elsewhen moved into the Omarchy shell as omarchy.elsewhen (basecamp/omarchy#13429),
and a migration there removes the installed package. Nothing else in the repo
references it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 12:17:41 -07:00
Ryan Hughes a251ed58c2 Merge pull request #572 from omacom/track-branches
Track upstream Git branches through automatic package PRs
2026-09-27 13:49:22 -04:00
Ryan Hughes 3de5f0af4b Pin the tracked packages to today's upstream tips
The first pins (quattro ee8ebf6, omasnap eb22bfe) date from 2026-09-21.
Edge has since published omarchy-dev from quattro 7b336b1 through a
manual rebuild, so merging the old pin would ship older code under a
higher version. Moved with the tracker's own code:
  bin/sync-upstream --lane auto-merge (BYPASS_MIN_RELEASE_AGE=1)
2026-09-27 12:41:43 -04:00
Ryan Hughes 13ed2e9f8d Use the existing controller PAT for branch tracking 2026-09-27 12:39:53 -04:00
Ryan Hughes 7fe542cde7 Keep branch syncs together and simplify tracker setup 2026-09-27 12:39:53 -04:00
Ryan Hughes d87686ca4f Track upstream branches as pinned releases on an unattended lane
Since publishing moved to CI on merge, a package whose PKGBUILD never
changes while its source moves was never rebuilt: omarchy-dev and
omarchy-settings-dev followed quattro through "#branch=" and a pkgver()
function, and nothing in this repository changed when quattro did. The
host timers that used to notice are off, so edge fell days behind.

The rule now: no git source without a commit or tag pin
(tests/pinned-sources.sh, run in CI). A package that has to follow a
branch declares a git_branch upstream watch, and the pin moves through
the same PR/build/publish path as every other version bump.

Watch (helpers/upstream-watch.py)
  git_branch gains tag_pattern: the newest release tag in the pinned
  commit's own history, exposed as {tag}/{version}/{distance}, so a
  branch build is versioned <tag>.r<n>.g<sha>, above the release it
  follows and below the next one. One blobless clone per branch per
  run, shared by every package on it. min_release_age selects the
  newest commit older than the window, so a push burst builds once.

Lane (helpers/package-metadata.sh, bin/sync-upstream --lane)
  "auto_merge": true moves a package from the reviewed 6-hourly sync
  PR to the unattended lane. Packages pinned from the same branch move
  together: a failure on one restores the others and fails the group,
  so the dev pair can never ship from two quattro commits.

Tracker (.github/workflows/track-branches.yml)
  Every two hours: pin, open one PR with a GitHub App token, enable
  auto-merge. Branch protection still gates the merge on result,
  self-tests and build-isolation. A tip that fails to build stays an
  open red PR until the next tick supersedes it. The App is required:
  a PR opened with GITHUB_TOKEN has its checks held for approval and
  its auto-merge would not fire publish.yml.

The reviewed workflows (sync-upstream, sync-rebuilds) open their PRs
with the same App so their builds start without a maintainer clicking
"Approve workflows to run"; without the App they fall back to
GITHUB_TOKEN and behave as before.

Recipes
  The dev pair pins _commit and a real sha256sum, keeps the OMARCHY_SRC
  override, and drops pkgver(). Its r-number stays the branch's total
  commit count because the published history used it and pacman must
  never see the version go down. omasnap-git is new: omacom/omasnap
  main, versioned <tag>.r<distance>.g<sha>, provides/conflicts omasnap.
2026-09-27 12:39:53 -04:00
Ryan Hughes e7da505280 Merge pull request #660 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-27 11:28:50 -04:00
ryanrhughes 6e27936545 chore: sync upstream releases 2026-09-27 15:09:14 +00:00
Ryan Hughes e4a3b6adf3 Merge pull request #665 from omacom/fix/check-strata-flea-owe
strata 0.20.1, flea 0.3.4, owe 0.2.7: fix check() on the builders
2026-09-27 11:06:11 -04:00
Ryan Hughes 61bf0c26a5 strata: skip two more QEMU-only aarch64 tests
With the search race skipped, the aarch64 main suite ran to the end:
2339 passed, 2 failed. a_missing_working_directory_... expects spawn to
fail on a missing cwd, which posix_spawn cannot report under QEMU
user-mode. in_place_retirement_... identifies its process by
/proc/<pid>/exe, which names /usr/bin/qemu-aarch64 there, not sleep.
2026-09-27 10:38:47 -04:00
Marcelo Alcantara 93c13910e3 Merge pull request #671 from omacom/mac/109-aarch64-native-ci
Build aarch64 PR packages natively on arm64 runners
2026-09-28 00:32:35 +10:00
Marcelo Alcantara 2ff4dda480 Match the runner comments to the measured times 2026-09-28 00:30:33 +10:00
Marcelo Alcantara ee001efd34 Build every aarch64 package natively
Native pilots of the heavy packages fit the arm64 runner: linux-aurora 30
minutes, strata 17, obs-studio 7, with over 90 GB disk free throughout.
2026-09-28 00:24:12 +10:00
Jim Martin 21f3c4a8c4 omarchy-settings: keep the Mac initramfs hooks on aarch64
Apple Silicon Macs install the aarch64 package too, and an unconditional
HOOKS= line would replace their asahi hooks. Apply Omarchy's HOOKS line only
when the incoming hooks lack asahi, in whichever drop-in carries it, and
rebuild omarchy-settings (4.0.4-3) so existing installs get the fix.
2026-09-27 09:08:49 -05:00
Marcelo Alcantara 7c646625c2 Build aarch64 PR packages natively on GitHub's arm64 runners
The droplet pool is x86, so aarch64 builds ran under QEMU about 30x slower;
omarchy-mac-boot's check() took 2h47m of the 180-minute job limit. Heavy
packages stay on the droplets until a native build of each is shown to fit.
2026-09-27 23:44:28 +10:00
Marcelo Alcantara 3a6c921c73 Merge pull request #666 from omacom/mac/omarchy-mac-boot-check-checkout
Re-pin omarchy-mac 0.1.0-6 and omarchy-mac-boot 20260927-1 to quattro-upstream ff7ce0d4d (edge)
2026-09-27 23:30:26 +10:00
Marcelo Alcantara 31644221e6 omarchy-mac: declare the runtime dependencies its source names
From omacom/omarchy-mac#567 the add-on enables speakersafetyd itself and its
README says the recipe declares mkinitcpio and the protected speaker stack
(asahi-audio, which pulls speakersafetyd, alsa-ucm-conf-asahi, rtkit,
pipewire-alsa).
2026-09-27 20:39:21 +10:00
Marcelo Alcantara 4d0c217634 Merge pull request #649 from scottjones/m3/hyprland-software-renderer
hyprland: detect software rendering from the GL renderer
2026-09-27 20:36:56 +10:00
Marcelo Alcantara 8168e33032 omarchy-mac, omarchy-mac-boot: pin quattro-upstream ff7ce0d4d
Both packages move to the same omarchy-mac commit, so speakersafetyd keeps
one owner (#567 with #535) and the boot entrypoints the runtime needs publish
first. omarchy-mac gains the Apple Silicon platform group its source's guard
contract names.
2026-09-27 20:29:25 +10:00
bjarneoandBjarne Oeverli c62e9d70fe Upload meson test logs when a package build fails (#661)
makepkg runs check() inside the build container and meson writes each
test's output to the build tree, not to stdout. A failing test therefore
leaves only a summary line in the job log. Diagnosing it means reading
the package source and inferring the cause.

bin/build bind-mounts $SRC_DIR at /src, so the logs outlive the
container at src/**/meson-logs/ on the runner. Upload them when the
build step fails.

owe 0.2.7 showed the cost: owe:transition failed on aarch64 and passed
on x86_64, and CI carried no record of which assertion tripped.

Co-authored-by: Bjarne Oeverli <1419214+bjarneo@users.noreply.github.com>
2026-09-27 08:50:59 +02:00
Scott Jones f6f0da2a98 Clarify Hyprland backport updates and rebuild scheduling 2026-09-26 23:44:18 -04:00
Marcelo Alcantara e01943a86e omarchy-mac-boot: run the tests in the full checkout and document the next pin's publish order 2026-09-27 13:43:54 +10:00
Marcelo Alcantara 592c85c476 Merge pull request #654 from scottjones/fix/aurora-m3-usb
linux-aurora: enable M3 USB-PD controller support
2026-09-27 11:10:16 +10:00
Ryan Hughes b07c6605c5 Merge pull request #662 from omacom/fix/upstream-sync-tmog-voxtype
Fix upstream sync for voxtype-bin and tmog-bin
2026-09-26 19:17:00 -05:00
Ryan Hughes 08bfc45b7e strata 0.20.1, flea 0.3.4, owe 0.2.7: fix check() on the builders
strata: the example actions' image test needs ImageMagick with WebP
(checkdepends), the checksum example dies printing a non-UTF-8 name
under the builder's en_US.UTF-8 locale (upstream bug, skipped), and on
aarch64 a search refresh test loses a race to the index worker.

flea: on aarch64 the 1900-deep dirsize walk hits its 2 s deadline, and a
process-group cancel test loses its 5 s race under emulation.

owe: on aarch64 the transition test cannot render enough blended frames
under emulation; run every other test there.

Versions and checksums as in sync PR #660.
2026-09-26 20:16:06 -04:00
Ryan Hughes 4aca3bdbc7 Keep sync PRs building across bot pushes
Three things kept the upstream sync PR (#589) from ever finishing a build:

Scoped dispatches wiped the shared PR. A workflow_dispatch with `packages`
regenerates only those packages from master, and pushing that to
auto/sync-upstream replaced 38 pending updates with one. Scoped runs now
push to their own auto/sync-{upstream,rebuilds}-<packages> branch and PR;
scheduled runs keep the shared branch.

build-approved stopped working after the first bot push. A GITHUB_TOKEN
push creates pull_request runs held for approval but no pull_request_target
run, so approve-pr.yml never saw it: its last run on the branch was the
label itself (2026-09-25T19:26), and each of the next four syncs sat at
action_required. The sync workflows now release the held runs for the
commit they just pushed, from a separate job holding actions: write, and
only for their own bot-authored, same-repo PR while build-approved is on
it.

Each approved push cancelled the in-flight build. Approving the 21:43
sync's build cancelled the label-triggered one still queued on strata and
schist-bin. On auto/sync-* branches a new build now waits for the running
one instead, then reuses its artifacts. The approval script no longer
waits for a lone approved build to start before releasing tests, which a
queued build would have turned into a timeout.
2026-09-26 20:01:17 -04:00
Ryan Hughes 0c91711a9e tmog-bin: follow tmog.org's versioned downloads and update to 1.0.0
TMOG 1.0.0 moved the site under /rtm/ and publishes each Linux tarball
under a versioned name with a .sha256 sidecar. The versionless
/downloads/TMOG-Task-Manager-Linux-x86_64.tar.gz path the hook fetched
now returns 404, so every upstream sync since 1.0.0 has failed.

Point source=() at the versioned tarball and have the hook read the
version from /rtm/version.txt and the checksum from the sidecar, which
drops the 8 MB download and the directory-name check the mutable URL
needed.
2026-09-26 19:57:09 -04:00
Ryan Hughes 3a70d3279c sync-upstream: ignore ")" in comments when rewriting checksum arrays
set_pkgbuild_array ended its skip over the old array at the first line
holding a ")", comments included. voxtype-bin annotates its arrays with
"# Quickshell OSD launcher + audio-bridge sidecar (new in v0.7.5)", so
the rewrite stopped there and left the rest of the old array behind a
stray ")". Every scheduled sync since voxtype 1.1.0 failed with
"Rewritten PKGBUILD is not valid shell".

Strip comments before looking for the closing paren, add a self-test
fixture that fails on the old awk, and take the update it was blocking:
voxtype-bin 1.0.1 -> 1.1.0.
2026-09-26 19:57:09 -04:00
Jim Martin 9d05b3fb06 Merge remote-tracking branch 'upstream/master' into spark/aarch64-settings-boot-dropins 2026-09-26 17:38:24 -05:00
David Heinemeier Hansson 6df9953d8f Merge pull request #659 from omacom/browsers/package-aur-browsers
Package the browsers Omarchy installs from the AUR
2026-09-26 23:06:26 +02:00
David Heinemeier HanssonandClaude Opus 5.5 41e6307d03 microsoft-edge-stable-bin: drop the Debian cron job
The deb's /etc/cron.daily/microsoft-edge runs a script that writes an
apt keyring to /usr/share/keyrings every day. pacman doesn't track that
file, so it stays behind after Edge is removed. Remove the cron job and
its script the way the Chrome recipe already does, and skip the empty
/etc left behind. pkgrel 2 so this build replaces AUR-installed copies.

Found in a Codex review.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:53:24 -04:00
David Heinemeier HanssonandClaude Opus 5.5 1299a82d79 Package the browsers Omarchy installs from the AUR
omarchy-install-browser still reaches for yay to install Chrome, Edge,
Brave, Brave Origin and Zen. These were the only packages anywhere in
Omarchy's installers and menus that aren't available from core, extra,
multilib or OPR. Serving them from OPR means installing any Omarchy
browser no longer depends on the AUR being up.

Imported once from the AUR with bin/add-package, on the fast ring so
browser security releases reach stable promptly, each with a direct
vendor watch:

- google-chrome: Google's stable apt index
- microsoft-edge-stable-bin: Microsoft's stable apt index
- brave-bin, brave-origin-bin: Brave's stable apt index. Brave's
  GitHub releases can't be used because nightlies aren't marked as
  prereleases there.
- zen-browser-bin: GitHub releases (the twilight tag doesn't match)

Package names match what Omarchy's scripts already check for, so the
existing omarchy-pkg-aur-add calls resolve to the repo package without
any change on the Omarchy side.

Validation: upstream-watch check reports all five as current. A sync
of downgraded copies of zen-browser-bin and brave-origin-bin reproduces
the checked-in pkgver and both architectures' checksums. All five build
with makepkg on x86_64, and the launchers, desktop IDs and /opt paths
match what omarchy-default-browser and omarchy-theme-set-browser use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 16:46:01 -04:00
David Heinemeier Hansson fc1e037490 Merge pull request #589 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-26 21:23:48 +02:00
dhh bc78d15bcd chore: sync upstream releases 2026-09-26 19:05:32 +00:00
David Heinemeier Hansson b85235928c Merge pull request #655 from omacom/ttfx/watch-github-releases
Track ttfx releases from GitHub and build its assembly engine
2026-09-26 20:53:45 +02:00
David Heinemeier HanssonandClaude Opus 5.5 9d3d374058 Track ttfx releases from GitHub and build its assembly engine
ttfx releases are now published on GitHub only, so sync-upstream picks up new
versions through a github watch instead of ttfx's release script editing this
recipe. The recipe also points at the repository's current home, and x86_64
builds pull in NASM, which the new assembly engine needs (without it the
build silently falls back to the Rust engine).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MxJcVYpBxFDam6czK4bGjp
2026-09-26 14:38:42 -04:00
Scott Jones 5edd81d9f2 Enable Aurora USB-PD controller support for M3 Macs 2026-09-26 13:37:49 -04:00
Scott JonesandClaude Opus 5.5 88629b46b5 Match Hyprland software renderer names case-insensitively
Refresh the #16343 backport to carry the upstream follow-up that
matches llvmpipe/softpipe/Software Rasterizer with ASCII
case-insensitive, string_view-based matching.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-26 10:44:18 -04:00
David Heinemeier Hansson dcee15a58e Merge pull request #653 from omacom/hype/track-upstream-tags
Track Hype releases from its git tags
2026-09-26 07:04:23 -05:00
David Heinemeier HanssonandClaude Opus 5.5 a692f96467 Track Hype releases from its git tags and update to 0.4.2
Hype now publishes a GitHub release for every version, so declare its
v{pkgver} tags as the upstream feed. The scheduled sync picks up new
releases and hashes the tag archive, instead of a hand-written PR each
time. This first sync brings Hype to 0.4.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 13:51:35 +02:00
Marcelo Alcantara 539f27b1d1 Merge pull request #648 from omacom/mac/realign-dev-pair
omarchy-settings-dev: rebuild so the dev pair shares one quattro commit
2026-09-26 12:10:20 +10:00
Scott JonesandClaude Opus 5.5 f7d4e94540 Hold Hyprland upstream updates while carrying the #16343 backport
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 21:31:25 -04:00
Scott JonesandClaude Opus 5.5 09ef472bcb Detect Hyprland software rendering from the GL renderer
Carry a v0.56.2 backport of hyprwm/Hyprland#16343 so display-only KMS
drivers paired with llvmpipe are classified as software rendering.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 21:31:25 -04:00
Marcelo Alcantara e841c87036 omarchy-settings-dev: rebuild so the dev pair shares one quattro commit 2026-09-26 11:18:24 +10:00
Marcelo Alcantara 83f85a4051 Merge pull request #636 from omacom/mac/17-platform-guard
omarchy-settings: ship the pacman platform guard
2026-09-26 10:48:43 +10:00
Marcelo Alcantara ab053e8872 Merge pull request #646 from omacom/mac/omarchy-mac-boot-repin-4
omarchy-mac-boot 20260925-4: re-pin to quattro-upstream 84352fdb
2026-09-26 09:49:54 +10:00
Marcelo Alcantara c3fa2ba65d omarchy-mac-boot: say backup= covers /etc files, not the hook symlinks
The snapshot restore hooks under /etc/boot/hooks are symlinks, which the
backup list leaves out. omarchy-drive-password needs luks-slots only for the
system disk.
2026-09-26 09:28:34 +10:00
Marcelo Alcantara e70090513c omarchy-mac-boot: document the publish order the reset and key-slot entrypoints need
omarchy-lifecycle-dispatch requires reset-* (#552) and luks-slots (#553) on
Apple Silicon, so a runtime carrying them before this package blocks factory
reset, owner setup and omarchy-drive-password on Macs.
2026-09-26 09:20:37 +10:00
Marcelo Alcantara cfb8f9a403 omarchy-mac-boot: depend on binutils for objcopy
The boot check and, from #560, the snapshot restore check read the kernel out
of the Limine UKI with objcopy. mkinitcpio pulls binutils in today, but the
package calls it directly.
2026-09-26 09:20:37 +10:00
Marcelo Alcantara 23bfb75f07 omarchy-mac-boot 20260925-4: pin quattro-upstream 84352fdb
Moves the pin from f7ea7ecc to the head of quattro-upstream, which adds
#552's reset-prepare, reset-verify, reset-commit and reset-rollback
entrypoints, #553's luks-slots entrypoint and recovery key handling,
#556's encrypt unit output to kmsg at notice, and #560's snapshot restore
check with its limine-snapper-sync pre and post hooks. The pin's UTC date is
still 20260925, so pkgrel goes to 4. #544 is not in it, so the Plymouth
fragment still ships and no HOOKS baseline dependency applies.
2026-09-26 09:20:37 +10:00
Ryan Hughes b1f2d77ef8 Merge pull request #413 from omacom/fix-remote-neovim-yanks
Fix remote Neovim yanks without blocking paste
2026-09-25 14:52:06 -05:00
Ryan Hughes d9748c6a15 Merge current package defaults into clipboard fix 2026-09-25 15:35:16 -04:00
Ryan Hughes 15e96abac5 Merge pull request #594 from tcballard/task-manager-v0.0.4-all-channels
Update Task Manager to 0.1.1 and promote beyond Edge
2026-09-25 14:22:03 -05:00
Marcelo Alcantara 8b4c4a4051 Merge pull request #641 from omacom/mac/09-mac-boot-hooks-baseline
omarchy-mac-boot 20260925-3: re-pin to quattro-upstream f7ea7ecc
2026-09-26 01:42:57 +10:00
Marcelo Alcantara 8f80d46049 omarchy-mac-boot 20260925-3: pin quattro-upstream f7ea7ecc
Moves the pin from the boot-set integration commit to quattro-upstream,
which now has #541 with its review fixes (first boot hands off only for a
failed step, and the retry waits for NetworkManager), #543's update-verify
entrypoint, #545's provision entrypoints and #549's keyslot-only LUKS check.
#544 is not in it, so the Plymouth fragment still ships and no HOOKS baseline
dependency applies.
2026-09-26 01:25:33 +10:00
Marcelo Alcantara f3a96c195b omarchy-mac-boot: document the publish order its pinned source needs
A pin with omacom/omarchy-mac#544 needs the settings HOOKS baseline (#542)
published first; a pin with #543's update-verify entrypoint must publish
before any runtime carrying #543. pkgrel 3 so this rebuild does not reuse the
published 20260925-2 filename.
2026-09-25 23:37:40 +10:00
Marcelo Alcantara 67dd0f7e6b omarchy-mac-boot: require the HOOKS baseline once the source relies on it
From omacom/omarchy-mac#544 the Apple drop-ins no longer add Plymouth
themselves and build on omarchy-settings' 00-omarchy-hooks.conf (#536). A
settings version cannot express that (quattro candidates sort below stock
releases and omarchy pins its settings exactly), so a build whose payload has
no 93-omarchy-mac-plymouth.conf depends on the name
omarchy-mkinitcpio-hooks-baseline, which settings packages that ship the
baseline must provide.
2026-09-25 23:37:24 +10:00
Marcelo Alcantara cd1ffe0f7e Merge pull request #637 from omacom/mac/09-omarchy-mac-boot
Add omarchy-mac-boot for aarch64 edge
2026-09-25 23:23:59 +10:00
Marcelo Alcantara 020b28dd69 omarchy-mac-boot: say the platform guard is still to ship, and drop the stale pkgrel from the README 2026-09-25 23:08:14 +10:00
Vincent Zhang 71abb9f0aa supergfxctl: drop nonexistent sudo group from D-Bus policy
Arch has no sudo group, so dbus-broker rejects that policy block by name on
every bus reload: "Invalid group-name in org.supergfxctl.Daemon.conf +9:
group=\"sudo\"". The block grants nothing here; the wheel policy in the same
file is what authorises an unprivileged user.

Remove the dead block with a checked-in patch and bump pkgrel.
2026-09-25 21:08:13 +08:00
Marcelo Alcantara 61bc72d006 omarchy-mac-boot: pin the boot-set integration commit
Pin a09ee0ca, the head of omacom/omarchy-mac integ/boot-set-1 (#547): quattro-upstream with #527, #540, #545 and #541 merged. The package then carries the Apple provisioning entrypoints from #545 and can publish before those PRs merge.
2026-09-25 22:49:39 +10:00
Marcelo Alcantara bbb5c32854 Merge master into mac/09-omarchy-mac-boot 2026-09-25 21:41:51 +10:00
Marcelo Alcantara 134f2facc5 omarchy-mac-boot: tag it for Apple Silicon and pin #541's current head
groups=('omarchy-platform-apple-silicon') is the platform tag the pacman
platform guard reads (omacom/omarchy-mac#539); the package is not published
yet, so it carries the tag from its first build. The pin moves to 3a58abb5,
omacom/omarchy-mac#541 with #527's current head merged in.
2026-09-25 21:36:39 +10:00
Marcelo Alcantara d12fa816b7 Merge pull request #628 from omacom/mac/10-aquamarine-carry
Carry aquamarine 0.15.1 on edge for Apple Silicon
2026-09-25 21:06:52 +10:00
Marcelo Alcantara 7ed92ad039 omarchy-settings: bump pkgrel for the platform guard lines
CI builds a changed recipe and needs a new package; 4.0.4-1 is already
published. The pinned upstream source has no guard files, so 4.0.4-2 has the
same contents.
2026-09-25 20:57:33 +10:00
Marcelo Alcantara d62c3d6840 Merge remote-tracking branch 'upstream/master' into mac/17-platform-guard 2026-09-25 20:46:03 +10:00
Marcelo Alcantara 4e58d1c77f Give cc1plus enough stack for aquamarine under emulation
DRM.cpp needs more than 8 MiB of compiler stack. GCC raises its own limit
natively, but the aarch64 builder runs under QEMU user-mode emulation, which
fixes the guest stack at exec and ignores setrlimit, so cc1plus segfaulted.
Unpatched 0.15.1 fails the same way. QEMU_STACK_SIZE sizes it at exec; native
builds ignore it and the object code is identical.
2026-09-25 20:45:29 +10:00
Marcelo Alcantara c48f02942a omarchy-mac-boot: pin the first-boot manifest check, and say which dependency is Apple-only
Moves the pin to 602266c8, where first boot of a fresh image refuses to finish
without the build manifest that defers its hardware setup. asahi-scripts is
the one Apple-only dependency; the README no longer calls them all generic.
2026-09-25 20:20:01 +10:00
Tom Ballard 587b8c3226 Update Task Manager package to v0.1.1 and sync upstream 2026-09-25 11:14:03 +01:00
Marcelo Alcantara 7d7a7542bf Merge pull request #627 from omacom/mac/07-m1n1-uboot
Add m1n1-aurora and the silent uboot-asahi, aarch64 and edge only
2026-09-25 20:06:03 +10:00
Marcelo Alcantara f94fd56cea Merge pull request #631 from omacom/mac/55-limine-apple-gate
limine-mkinitcpio-hook 1.39.0-2: Apple Silicon activation gate
2026-09-25 20:03:17 +10:00
Marcelo Alcantara 865aaa3f65 Add omarchy-mac-boot for aarch64 edge
The Apple Silicon boot package (Mac initramfs, in-place encryption, first
boot and the Limine activation gate) now builds from packages/omarchy-mac/boot
in omacom/omarchy-mac at a pinned commit: the recipe runs the source's tests
and install staging, and keeps only metadata, backup= and the pacman
scriptlet from the fork recipe. aarch64 and edge only; 20260925-1 upgrades
the fork's 20260921-10. It needs the Apple-gated limine-mkinitcpio-hook
1.39.0-2.
2026-09-25 20:02:38 +10:00
Marcelo Alcantara d6be5b09b3 Merge pull request #626 from omacom/mac/08-omarchy-mac-recipe
Add omarchy-mac for aarch64 edge
2026-09-25 20:02:06 +10:00
Marcelo Alcantara fb2ade61fc Give the platform guard its own detector copy instead of moving it
Moving omarchy-hw-platform from omarchy to omarchy-settings loses the file
when the dev pair installs in separate transactions, since omarchy-dev does
not pin omarchy-settings-dev's version. Settings now installs a copy beside
the guard script and omarchy keeps its detector, so no file changes owner.
2026-09-25 20:01:13 +10:00
Marcelo Alcantara 0712513342 Merge pull request #625 from omacom/mac/06-linux-aurora-edge
linux-aurora: drop the generic linux provide, ship on edge only
2026-09-25 20:01:09 +10:00
Marcelo Alcantara 0a65168b09 Move the platform detector to omarchy-settings with the guard
The settings package's platform guard runs omarchy-hw-platform before the
omarchy runtime is installed. omarchy leaves the detector out of its bin/
loop and settings ships it, with the /usr/share/omarchy/bin link the Apple
predicate and the CLI router use, whenever the source has it.
2026-09-25 19:56:22 +10:00
Marcelo Alcantara 00fac82ad3 omarchy-settings: ship the pacman platform guard when the source has it
The guard hook and the script it runs install together from omarchy-settings,
so they are resident before the runtime and hardware packages. Conditional, so
builds from sources without them are unchanged.
2026-09-25 19:23:36 +10:00
Jeremy Dixon 6a2561fa7a Declare Omatrack Lua and sol2 build sources 2026-09-25 04:59:36 -04:00
Jeremy Dixon ef6bf03935 Update Omatrack to 1.8.6 2026-09-25 04:55:40 -04:00
Marcelo Alcantara baf6df3e80 limine-mkinitcpio-hook 1.39.0-2: Apple Silicon activation gate on aarch64
On aarch64 the Limine hooks and the mkinitcpio wrapper run through
limine-apple-gate. Only an Apple Silicon Mac (device tree "apple,")
behaves differently: Limine's kernel and removal hooks wait until
Omarchy activates Limine (/var/lib/omarchy/limine.enabled and
/etc/default/limine), mkinitcpio's own kernel hook keeps /boot current
before and after activation, Limine's EFI deploy hook is left to
omarchy-mac-boot, and the wrapper is plain mkinitcpio. Skipped hooks
drain the socket pacman streams targets over.

x86_64 packages exactly what 1.39.0-1 did; generic aarch64 and
Snapdragon keep upstream behaviour.
2026-09-25 18:20:07 +10:00
Marcelo Alcantara 33ab3d2bf8 Spell a carried version without a zero epoch, as makepkg publishes it 2026-09-25 17:51:54 +10:00
Marcelo Alcantara b27a8c609e Drop the aquamarine comment that depends on rebuild detection 2026-09-25 17:46:20 +10:00
Marcelo Alcantara 584fa5732e Leave Hyprland and rebuild detection to their own changes
PR builds link against published edge, so a Hyprland bump in the same merge
cannot see the carry, and as a fast-ring package it would reach every aarch64
channel. The rebuild detection fix that bumps it once the carry publishes
moves to its own PR, so this change only adds the aquamarine carry.
2026-09-25 17:46:06 +10:00
Marcelo Alcantara fc2a8aeed0 Detect aarch64 rebuilds, including against carried dependencies
sync-rebuilds followed PUBLISHED_ARCHES (x86_64 by default), so the scheduled
run skipped every aarch64-only package, and it read aarch64 triggers from Arch
Linux ARM even when this repository carries them. It now follows the
architectures a merge builds (CI_ARCHES, shared with build-matrix), and reads a
carried trigger from its recipe once edge publishes that version; until then
its dependents wait. Published databases are verified before use and loaded
once per run.
2026-09-25 17:45:42 +10:00
Marcelo Alcantara a090edeb7f Carry aquamarine 0.15.1 on edge for Apple Silicon and rebuild Hyprland
Carry Arch's aquamarine 0.15.1-1 with one patch that re-reads a connector's
possible CRTCs on rescan, so a Type-C port the Apple DCP fabric reroutes to
another pipeline gets a CRTC without restarting the compositor. aarch64 and
edge only; pkgrel 1.1 sits above Arch Linux ARM's 0.15.1-1.

Bump Hyprland for aquamarine 0.15.1 and make rebuild detection see aarch64.
sync-rebuilds followed PUBLISHED_ARCHES (x86_64 by default), so the scheduled
run skipped every aarch64-only package, and it read aarch64 triggers from Arch
Linux ARM even when this repository carries them. It now follows the
architectures a merge builds, and reads a carried trigger from its recipe once
edge publishes that version.
2026-09-25 17:42:53 +10:00
Marcelo Alcantara 73d8b1e84e Say how uboot-asahi gets installed rather than where 2026-09-25 17:37:04 +10:00
Marcelo Alcantara e4521c1bbd Say which omarchy-mac configuration applies before setup runs 2026-09-25 17:35:23 +10:00
Marcelo Alcantara d61e51af75 Record the boot recipes as manual holds and state pacman's repository order
A bare uboot-asahi comes from whichever repository is listed first, so the
comment now says when this build wins and when asahi-alarm's does.
2026-09-25 17:34:34 +10:00
Marcelo AlcantaraandScott Jones 4a2224c680 Add the omarchy-mac Apple Silicon add-on for aarch64 edge
Packages only packages/omarchy-mac from omacom/omarchy-mac at an exact
quattro-upstream commit, restricted to aarch64 and the edge channel.
Ported from the omarchy-mac/omarchy-pkgs-aarch64 recipe.

Co-authored-by: Scott Jones <scottajones@gmail.com>
2026-09-25 17:28:47 +10:00
Marcelo Alcantara 14609a71a2 linux-aurora: drop the wireguard-arch replaces rule
A replaces rule applies to any root with the omarchy repo, so a generic
aarch64 system carrying wireguard-arch would be offered this Apple-only
kernel on -Syu. ALARM's linux-aarch64 carries no replaces either.
2026-09-25 17:26:47 +10:00
Marcelo Alcantara d7906a4b1a Add m1n1-aurora and the silent uboot-asahi for Apple Silicon, aarch64 and edge only
Ported from the asahi-quattro lane. Both are pinned by hand like
linux-aurora and restricted to aarch64 and the edge channel from their
first merge, since merging publishes. pkgrel sits one above the fork
lanes' builds so the same filename never carries two sets of bytes.
2026-09-25 17:26:11 +10:00
Marcelo Alcantara 0a16d437cc linux-aurora: drop the generic linux provide and ship on edge only
The kernel provided linux=, so on any aarch64 root with the omarchy repo
listed before core, a dependency on linux resolved to this Apple-only kernel
instead of ALARM's linux-aarch64. linux-asahi and WIREGUARD-MODULE stay.

Channel membership narrows to edge until M1 and M2 cold-boot qualification;
promotion widens it again. pkgrel moves to 10 because 7.1.12.aurora2-9 is
already published and a filename is immutable.
2026-09-25 17:15:50 +10:00
Jacob Mink b44166a9bc Package Omaspeak 0.1.0 and Omawake 0.1.1 2026-09-24 16:22:18 -05:00
Ryan Hughes ed6a3869c7 Merge pull request #619 from jankeesvw/add-omarchy-meeting-recorder
Add Meeting Recorder
2026-09-24 15:25:26 -05:00
Jacob Mink a1549d8724 Allow later Oma package revisions in removal test 2026-09-24 14:53:32 -05:00
Jacob Mink fd78ec0e14 Add OpenVINO GenAI optional dependency to Omawake 2026-09-24 14:47:58 -05:00
Jacob Mink c330ee34a1 Fix OpenVINO GenAI build with GCC 16 2026-09-24 14:26:29 -05:00
Jacob Mink 9719f061e6 Bump OpenVINO GenAI to 2026.4.0.0 2026-09-24 14:05:50 -05:00
Jankees van Woezik 04dd3e8b68 Add Meeting Recorder 2026-09-24 20:17:02 +02:00
Scott Jones 4c3f31e972 Preserve Steam UI patch during update checks 2026-09-24 07:13:06 -04:00
Tom Ballard 619b36b3a6 Update Task Manager to first non-preview v0.1.0 2026-09-24 12:06:24 +01:00
Afonso Oliveira cda21f1d62 Merge master and preserve YT6801 ioctl removal 2026-09-24 11:58:23 +01:00
Marcelo Alcantara 93b1d62ef0 Merge pull request #614 from scottjones/add/qemu-static-aarch64
Add static QEMU packages for aarch64
2026-09-24 17:30:23 +10:00
Marcelo Alcantara c902687518 Merge pull request #613 from scottjones/add/obsidian-aarch64
Add canonical Obsidian desktop package for aarch64
2026-09-24 17:27:25 +10:00
Marcelo Alcantara 90fee672ce Register 32-bit ARM binfmt rules and keep QEMU changes package-local
qemu-binfmt-conf.sh groups arm with aarch64 and skipped it; Apple Silicon has
no AArch32 execution, so --ignore-family is needed for ARM32 programs. The
bin/sync-upstream extension, its tests and the Tests workflow edit are
reverted; QEMU updates become reviewed pins.
2026-09-24 16:05:38 +10:00
Marcelo Alcantara 2a47ad775b Drop setuid from chrome-sandbox and keep Obsidian's tests in its package
Chromium sandboxes through unprivileged user namespaces on Arch-family
kernels, so the setuid-root helper is unnecessary. The watcher tests now run
from check() as checksummed package sources, leaving tests/ and the shared
Tests workflow untouched.
2026-09-24 16:05:38 +10:00
Marcelo Alcantara 3b62326ced Merge pull request #612 from scottjones/add/zed-aarch64
Add Zed vendor package for aarch64
2026-09-24 15:44:35 +10:00
Scott Jones aa64ec9a4f Track Debian QEMU revisions through verified immutable snapshots 2026-09-23 23:26:34 -04:00
Scott Jones fe0cf953d7 Add static QEMU packages for aarch64 2026-09-23 21:53:30 -04:00
Scott Jones 94ce4e5a46 Add canonical Obsidian desktop package for aarch64 2026-09-23 21:52:03 -04:00
Scott Jones 6fddfa268e Add Zed vendor package for aarch64 2026-09-23 21:51:55 -04:00
Marcelo Alcantara 3f2395db7c Merge pull request #591 from maralcbr/linux-aurora-upstream
Add linux-aurora: Apple Silicon kernel for the edge and rc channels
2026-09-24 07:52:55 +10:00
Tom Ballard e25f641757 Update Task Manager to published v0.0.5 2026-09-23 16:12:58 +01:00
Marcelo Alcantara 59732a3e6f Merge pull request #599 from scottjones/fix/steam-launcher-review
Fix Steam FEX patch detection and preserve user desktop overrides
2026-09-23 17:34:43 +10:00
Marcelo Alcantara d37ce2f897 Merge pull request #598 from scottjones/fix/dotnet-arm-only
Restrict dotnet-core-bin to aarch64 and disable debug packages
2026-09-23 17:34:33 +10:00
Marcelo Alcantara 09784fcc92 Merge pull request #451 from scottjones/contrib/avd
Add Apple Silicon video-decoder firmware and VA-API packages
2026-09-23 17:34:21 +10:00
Marcelo Alcantara 3aece780f2 Merge pull request #449 from scottjones/contrib/cursor-aarch64
Build cursor-bin for aarch64 using the vendor Debian package
2026-09-23 17:34:07 +10:00
Jim Martin 26f47a021f Carry NVIDIA DisplayPort detach fix for aarch64 2026-09-22 20:19:46 -05:00
Scott Jones 1e1d524305 Allow normal Steam update and repair checks 2026-09-22 20:22:55 -04:00
Scott Jones 947eeea0dd Fix Steam patch detection and preserve desktop overrides 2026-09-22 20:21:53 -04:00
Scott Jones 1cd0ffcef2 Restrict binary .NET packages to aarch64 2026-09-22 20:21:45 -04:00
Scott Jones c1fcff4dec Merge upstream and restrict AVD firmware builds to aarch64 2026-09-22 20:07:34 -04:00
Scott Jones ee8431a661 Merge current Cursor packaging and disable updates on ARM 2026-09-22 20:07:30 -04:00
Jim Martin 19976635bc Merge remote-tracking branch 'upstream/master' into spark/aarch64-settings-boot-dropins
# Conflicts:
#	.github/workflows/test.yml
2026-09-22 18:35:30 -05:00
Tom Ballard 01b6ac90eb Pin v0.0.4 source with deterministic PID selection regression 2026-09-22 11:53:13 +01:00
Tom Ballard 21cb201d83 Update Task Manager to 0.0.4 and enable all channels 2026-09-22 11:45:31 +01:00
Marcelo Alcantara 8fcea0cdf3 Add linux-aurora: Apple Silicon kernel for the edge and rc channels
Aurora Silicon's fork of the Asahi kernel (DisplayPort alt-mode and USB4
for external displays, VRR, ISP and AOP drivers) as an aarch64-only split
package, linux-aurora and linux-aurora-headers, pinned to an immutable
commit that was cold-boot qualified on a MacBook Pro 14" M1 Pro and 16"
M2 Max. It provides and conflicts with linux-asahi so Macs move over in
one explicit transaction, and pkgrel 9 orders it after the last build the
maralcbr fork published, so already-installed Macs upgrade on -Syu.

Metadata: skip_build like the x86 kernels (built only by its own PR),
release_ring fast with channels edge and rc, so one merge publishes the
same artifact to edge/aarch64 and rc/aarch64 and stable keeps asahi-alarm's
linux-asahi. No x86 job is emitted and no shared tooling changes.
2026-09-22 18:20:22 +10:00
bjarneo 7721248925 Update owe and owe-lockfeed to 0.2.6 (#586)
Update both package recipes and source checksums to OWE 0.2.6. This release includes the transition-image timeout and output-selection fixes. Package builds pass on x86_64 and aarch64.
2026-09-22 08:42:35 +02:00
d2d79ce8fd Stop the Hermes Desktop launcher reconciling a mise install
The launcher called omarchy-install-hermes-cli with no arguments on every start to remove a mise-built Hermes left beside the app. Omarchy no longer builds Hermes through mise: it sets the app's runtime up before the app is opened, and the installer now only answers a named --check or --now, so the call had nothing left to reconcile and only printed usage. The launch check keeps a forbidden stand-in for that command on its PATH, so a launcher that reaches for it again fails the build.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-21 18:04:29 -05:00
Ryan Hughes e0959b06f5 chore: vouch for tcballard and DanWahlin 2026-09-21 15:26:43 -04:00
Ryan Hughes 4012fb1699 Merge pull request #579 from tcballard/add/task-manager-0.0.3
Add Task Manager for Omarchy 0.0.3 (edge preview)
2026-09-21 15:22:17 -04:00
Tom Ballard e768c4b1ca Backport verified Bridge shutdown fix as 0.0.3-3; bound Qt test crashes 2026-09-21 18:27:38 +01:00
Ryan Hughes 74500ac10b Merge pull request #435 from DanWahlin/learn-omarchy
Add learn-omarchy to the fast ring
2026-09-21 13:04:38 -04:00
Tom Ballard 45585daeaa Include recorder lifetime guard in pause/resume test backport 2026-09-21 17:43:22 +01:00
Tom Ballard 7043dc72d3 Backport pause/resume test observation fix for 0.0.3-2 2026-09-21 17:41:21 +01:00
Ryan Hughes 83e877a2bc Merge pull request #353 from kr40/fix/monokai-pro-repo-not-found
omarchy-nvim: fix monokai-pro "repository not found" (gthelding fork deleted)
2026-09-21 12:13:38 -04:00
Ryan Hughes aa2b28d611 Merge pull request #370 from stevederico/fix/cursor-bin-disable-bundled-updater
Disable Cursor bundled updater in cursor-bin
2026-09-21 12:12:27 -04:00
Ryan Hughes ccaab9aad5 Merge pull request #580 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-21 12:10:28 -04:00
Ryan Hughes 18db532910 Update PKGBUILD 2026-09-21 10:25:37 -04:00
dhh 46597aa24e chore: sync upstream releases 2026-09-21 13:02:55 +00:00
David Heinemeier Hansson e40f5a5da9 Merge pull request #578 from omacom/hype-0.4.0
Update Hype to 0.4.1
2026-09-21 14:38:47 +02:00
David Heinemeier HanssonandClaude Fable 5.1 dbc6b07b62 Update Hype to 0.4.1
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 07:12:40 -05:00
Tom Ballard 9543da587c Add Task Manager for Omarchy 0.0.3 preview 2026-09-21 13:04:18 +01:00
David Heinemeier HanssonandClaude Fable 5.1 7ce9c9230f Update Hype to 0.4.0
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 06:59:22 -05:00
David Heinemeier Hansson b3e781868f Merge pull request #577 from omacom/update-hype-0.3.3
Update Hype to 0.3.3
2026-09-21 13:18:36 +02:00
David Heinemeier HanssonandClaude Opus 5 7db7133ea2 Update Hype to 0.3.3
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 06:54:26 -04:00
David Heinemeier Hansson 23394639cc Merge pull request #575 from omacom/update-hype-0.3.2
Update Hype to 0.3.2
2026-09-21 12:38:11 +02:00
David Heinemeier HanssonandClaude Opus 5 87eb95bf66 Update Hype to 0.3.2
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-21 06:12:53 -04:00
bjarneoandBjarne Oeverli 36d785fbc6 Update owe and owe-lockfeed to 0.2.3 (#574)
0.2.3 adds one-shot intro playback: owe intro, intro-status, and intro-stop, plus the renderer once/mute load and keep-open hold. Both PKGBUILDs take the v0.2.3 tarball; checksums verified with makepkg --verifysource.

Co-authored-by: Bjarne Oeverli <1419214+bjarneo@users.noreply.github.com>
2026-09-21 11:43:46 +02:00
David Heinemeier Hansson c7aa553537 Merge pull request #573 from omacom/add-gliff-package
Add Gliff remote desktop package
2026-09-21 11:22:13 +02:00
David Heinemeier Hansson ecef7e791b Add Gliff remote desktop package 2026-09-21 11:08:21 +02:00
David Heinemeier Hansson 90ef25ca62 Update ttfx to v0.3.3 2026-09-21 10:39:15 +02:00
Ryan Hughes b850ae3132 Merge pull request #571 from omacom/rust/parallel-check-builds
strata, flea: compile the test harness in parallel
2026-09-21 04:19:30 -04:00
Steve Derico ef22991e6f Disable Cursor bundled updater in cursor-bin 2026-09-21 02:40:21 -04:00
Ryan Hughes 94b7a853a2 Bump omarchy-nvim pkgrel 2026-09-21 02:39:48 -04:00
Kartik RaoandClaude Sonnet 5 b2e3469827 omarchy-nvim: point monokai-pro at loctvl842 (repo not found)
The all-themes.lua spec references "gthelding/monokai-pro.nvim", a fork
that no longer exists on GitHub (HTTP 404). On every launch lazy.nvim
fails to clone it and reports "repository not found", and leaves a stale
lazy/monokai-pro.nvim.cloning marker so the error repeats.

Restore the canonical, actively maintained upstream
"loctvl842/monokai-pro.nvim" -- the value this line held before
80a278206 ("Match nvim themes"). It registers the same "monokai-pro"
colorscheme and still supports filter = "ristretto" plus the setup
override used by Omarchy 3.8's themes/ristretto/neovim.lua, so nothing
downstream changes except that the plugin resolves again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011FcsS1gPeUiLSqqi4iKgzm
2026-09-21 02:39:30 -04:00
Jeremy Dixon 3efb5c8e2a Update tobi-try to 1.10.1 2026-09-21 02:36:15 -04:00
Ryan Hughes ec81434684 strata, flea: compile the test harness in parallel
Both upstreams set fat LTO in their release profile (strata also
codegen-units = 1), which compiles the final crate on one thread. That
is a reasonable trade for the binary users run, and build() keeps it.
check() then compiled the same crate a second time under the same
profile for the test harness, and threw it away. On the aarch64 builds,
which run under QEMU, those two serial compiles were 106 of strata's
118 minutes.

check() now builds the harness with thin LTO and 16 codegen units in
its own target directory. The shipped binary is unchanged. Measured on
the x86_64 builder for strata's test binary: 708 s serial, 223 s with
this profile. Local run: shipped strata still 7m30s from target/, tests
3m43s from target-check/, 1998 passed.

ttfx has the same upstream profile but no check(), so nothing to do.
2026-09-21 02:23:36 -04:00
Ryan Hughes cb8aae545e Merge pull request #512 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-21 00:54:31 -04:00
ryanrhughes 016491b41c chore: sync upstream releases 2026-09-21 04:47:32 +00:00
Ryan Hughes bc7ea4d3ef Merge pull request #569 from omacom/flea/0.3.1-shelfundo-tmpfs
flea: update to 0.3.1, run shelfundo tests on tmpfs, skip three under QEMU
2026-09-21 00:45:03 -04:00
Ryan Hughes 5e98f9d810 flea: update to 0.3.1, run shelfundo tests on tmpfs, skip three under QEMU
flea 0.3.0 never published: its shelfundo suite (new in 0.3.0) failed check() on the 2026-09-18 publish build, and 0.3.1 fails the same way on the sync PR. undo_refuses_to_walk_a_stranger_back deletes a file, creates another under the same name, and expects undo to tell them apart by (dev, ino, kind). ext4 hands the freed inode number straight back to the next create, so on the droplet's /tmp the stranger is identical and undo walks it back into a directory that does not exist. tmpfs allocates inode numbers from a counter and never reuses one. The suite joins the filesystem tests that already run with TMPDIR on /dev/shm.

On aarch64 three more tests fail: two in backend::child and one in menu_registry expect spawning a missing program to be reported as not started. Under QEMU user-mode emulation glibc's posix_spawn cannot observe the child's failed execve; the spawn succeeds with exit 127. Skipped on aarch64 only.
2026-09-21 00:13:46 -04:00
Ryan Hughes 1b3cc7703d Merge pull request #560 from omacom/strata/0.19.0-skip-restart-waiter
strata: update to 0.19.0, skip two tests the build host cannot run
2026-09-20 23:36:12 -04:00
David Heinemeier Hansson e35af28dac Merge pull request #563 from omacom/update-owe-0.2.2
Update OWE and lock feed to 0.2.2
2026-09-21 03:49:34 +02:00
David Heinemeier Hansson 42493fa731 Update OWE and lock feed to 0.2.2 2026-09-20 20:35:00 -05:00
Ryan Hughes 1c0ea5be84 strata: update to 0.19.0, skip two tests the build host cannot run
0.19.0 adds two restart_waiter tests that run `sh -c 'while kill -0 "$1"; do sleep; done'` with the pid u32::MAX. Arch's sh is bash 5.3, whose kill builtin in POSIX mode prints "not a pid or valid job spec" for that number but exits 0, so the loop never ends; every droplet build of 0.19.0 sat there until the 180-minute job timeout. Upstream CI runs on Ubuntu, where sh is dash and rejects the number.

It also adds ownership_probe_errors_disable_in_place_updates, which points the pacman path at a directory and expects Command::output() to fail. aarch64 builds run under QEMU user-mode emulation, where glibc's posix_spawn cannot observe the child's failed execve; the spawn succeeds with exit 127 and the assertion fails. Skipped on aarch64 only; it passes natively.

Both skips carry comments; the sync bot rewrites only version fields, so they survive future syncs.
2026-09-20 21:34:14 -04:00
Ryan Hughes 955ebc0127 Merge pull request #562 from omacom/ci/reuse-pr-artifacts
Reuse existing build artifacts when a PR's package tree is unchanged
2026-09-20 21:21:21 -04:00
Ryan Hughes c74c708f35 Reuse existing build artifacts when a PR's package tree is unchanged
Every push to a PR rebuilt every package the PR touches, on every
architecture, even when only one of them changed. The daily sync PR
carries around thirty package/arch pairs; fixing one package meant
rebuilding all of them, and an aarch64 build under QEMU takes up to an
hour. Nine runs of that PR cost about 36 droplet-hours in two days.

The planner now asks the artifact store for <pkg>-<arch>-<tree hash>
before adding an entry to the matrix and drops entries that already
have one. That is the same lookup publish.yml makes on merge, so a
reused entry publishes exactly the file it would have anyway. Dry run
against the current sync PR: 27 of 31 entries reused, 4 built.

Pack and Upload no longer run with always(): only a successful build
uploads, so an artifact's existence means that tree built.

workflow_dispatch always builds; it is an explicit request.
2026-09-20 21:09:55 -04:00
David Heinemeier HanssonandClaude Fable 5.1 b7706e8f62 Update Hype to 0.3.1 (#561)
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 01:48:15 +02:00
Ryan Hughes 5164cc2a44 Merge pull request #550 from omacom/update-omasnap-1.3.0
omasnap: update to 1.21.0
2026-09-20 16:01:34 -07:00
Ryan Hughes cb59806455 omasnap: update to 1.21.0
Upstream moved to omacom/omasnap and cut 1.21.0 from the same line as 1.20.1, so the version simply increases and no epoch is needed.

check() flushes dirty pages before the smoke suite. The suite fsyncs its working documents under /tmp; on the CI droplets the build leaves about a gigabyte of dirty pages, and the flush that starts a few seconds into the suite made those fsyncs stall past the suite's 5-second settle windows on the first run after every build.
2026-09-20 18:47:21 -04:00
Ryan Hughes 4b60e4cd95 Merge pull request #551 from omacom/ci/daily-builder-images
Publish tested daily package builder images
2026-09-20 13:49:44 -07:00
Ryan Hughes 8919d9f83e Merge pull request #555 from omacom/ci/artifact-helpers-errexit
artifact-helpers: survive bash -e
2026-09-20 13:44:22 -07:00
Ryan Hughes f0d18a8f7c Merge pull request #554 from omacom/ci/tooling-from-base-branch
Build PR tooling from the base branch tip, not the event's base sha
2026-09-20 13:42:29 -07:00
Ryan Hughes 451280ace1 artifact-helpers: survive bash -e
`restore=$(shopt -p nullglob)` exits 1 when nullglob is off, which it is
in the workflow shell. Under the `bash -e` that GitHub runs steps with,
that ended "Pack artifact" before tar ran: every job on #512 and #550
built fine and then failed with nothing in the log but the command.

The self-test never saw it because `pack_packages ... || fail` suppresses
errexit. Enumerate package files with a loop instead of toggling shell
options, and add a test that calls both helpers under `bash -e` exactly
as the workflows do; it fails against the old helper.
2026-09-20 16:42:24 -04:00
Ryan Hughes 2c22669d65 Build PR tooling from the base branch tip, not the event's base sha
github.event.pull_request.base.sha is a snapshot taken when the PR was
last pushed, not the current tip of the base branch. A reopened or rerun
PR therefore builds with whatever master looked like at its last push,
and a tooling fix that landed on master since then never reaches it:
the daily sync PR reopened after #553 merged checked out a base without
helpers/artifact-helpers.sh and failed at "Pack artifact".

Check out base.ref instead. The plan's diff and the empty-PR check still
compare base.sha to head.sha, so the list of changed packages is
unaffected; only the tooling that runs on the droplet moves to the tip.
2026-09-20 16:40:23 -04:00
Ryan Hughes 30e862935e Merge pull request #553 from omacom/ci/pack-artifacts-for-epoch-names
Carry PR build artifacts inside a tar so epoch package names survive
2026-09-20 13:17:02 -07:00
Ryan Hughes 124b067694 Carry PR build artifacts inside a tar so epoch package names survive
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.
2026-09-20 15:52:02 -04:00
Ryan Hughes 30af71af24 Validate proposed builder images on both native architectures 2026-09-20 15:41:27 -04:00
Ryan Hughes ad328b725d Build and test daily package builder images 2026-09-20 15:36:18 -04:00
Dan WahlinandCopilot bb80d2c4f6 Support omarchy-dev installations
Depend on the unversioned omarchy provider so development-channel systems do not need to replace omarchy-dev.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b31dac41-3f83-47ea-b9cb-5a843bd88f20
2026-09-20 14:42:13 -04:00
Dan WahlinandCopilot eea7ce6ba6 Add learn-omarchy to the fast ring
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b31dac41-3f83-47ea-b9cb-5a843bd88f20
2026-09-20 14:42:13 -04:00
Ryan Hughes dc82479210 Merge pull request #470 from scottjones/add-omarchy-steam-fex
Add omarchy-steam-fex for Apple Silicon (aarch64)
2026-09-20 11:40:47 -07:00
Ryan Hughes ca3433c3f1 Merge pull request #548 from omacom/fix/elsewhen-install-path
Fix Elsewhen shell plugin install path
2026-09-20 11:21:38 -07:00
Ryan Hughes 54d17b9e43 Merge pull request #529 from jdvmi00/spark/nvidia-utils-rmapi-tegra
nvidia-utils: include missing ARM graphics library
2026-09-20 11:13:13 -07:00
534f007d6b Add Steam FEX launcher for Apple Silicon (aarch64)
Package the tested Asahi Steam launcher as omarchy-steam-fex, with
runtime-only dependencies and offline launcher checks in makepkg.
Preserve the original launcher behavior and document its runtime
requirements and downstream ownership handoff.

Co-authored-by: dl-alexandre <166029845+dl-alexandre@users.noreply.github.com>
Co-authored-by: Santeri Hernejärvi <santeri@santeri.se>
2026-09-20 13:45:47 -04:00
Ryan Hughes 2e01fabfd6 Fix Elsewhen shell plugin install path 2026-09-20 13:45:17 -04:00
Ryan Hughes 3553c690a6 Merge pull request #478 from scottjones/share-picker-stable-rust
Build hyprland-preview-share-picker with stable Rust
2026-09-20 10:43:25 -07:00
Ryan Hughes 31e23bc538 Merge pull request #546 from omacom/fix/pending-build-approval
Keep unapproved PR builds pending instead of failing
2026-09-20 10:31:20 -07:00
Ryan Hughes 158133f15a Keep unapproved PR builds pending instead of failing 2026-09-20 02:31:44 -04:00
Ryan Hughes bda2a070f4 Merge pull request #545 from omacom/fix/build-approved-workflow-approval
Make build-approved release pending PR workflows
2026-09-19 23:18:55 -07:00
Ryan Hughes 3628915c5d Make build-approved release pending PR workflows 2026-09-20 02:11:34 -04:00
Ryan Hughes cce11a6917 Merge pull request #394 from physikal/add-bambustudio-bin
Add bambustudio-bin, Bambu Studio, to the fast ring
2026-09-19 23:06:10 -07:00
Ryan Hughes 49c44db179 Merge pull request #543 from omacom/chore/vouch-maintainers
Add maintainers to the vouched contributors list
2026-09-19 15:51:08 -07:00
Ryan Hughes 72e8b2b3bb Add maintainers to the vouched contributors list 2026-09-19 18:48:53 -04:00
Scott Jones a1a32908c5 Build hyprland-preview-share-picker with stable Rust 2026-09-19 18:40:12 -04:00
Ryan Hughes e2d1f4f387 Fix Bambu Studio packaging and track upstream releases 2026-09-19 18:40:03 -04:00
Ryan Hughes 54ce26ccb8 Merge pull request #398 from omacom/add-omarchy-billboard-generator
Add omarchy-billboard-generator, the animated domain video maker
2026-09-19 15:21:16 -07:00
David Heinemeier Hansson 65ad77a63d Update Hype to 0.3.0 (#539) 2026-09-19 21:43:08 +02:00
David Heinemeier Hansson 85a89659dc Update Hype to 0.2.0 (#532) 2026-09-19 15:39:22 +02:00
Jim Martin 0a1ae34275 nvidia-utils: carry missing ARM EGL library fix 2026-09-19 00:45:52 -05:00
Ryan Hughes 6afd935759 Merge pull request #64 from jacob-vincent-mink/feature/openvino-genai
Add OpenVINO GenAI C runtime package
2026-09-18 21:29:17 -07:00
Spencer BullandGPT-6 34257e0820 Update OpenVINO GenAI maintainer email
Co-Authored-By: GPT-6 (Codex) <noreply@openai.com>
2026-09-18 22:51:46 -05:00
Spencer BullandGPT-6 f2805a5a39 Keep the GenAI runtime PR independent of Voxtype 1.1
Move the Voxtype optional dependency metadata and release bump to a separate draft pending upstream 1.1. The GenAI runtime package can ship independently.

Co-Authored-By: GPT-6 (Codex) <noreply@openai.com>
2026-09-18 22:45:23 -05:00
Spencer BullandGPT-6 7bd8f5de1c Ship Voxtype OpenVINO metadata in a new package release
Bump pkgrel so the builder produces an artifact and installed packages receive the optional dependency metadata. Restrict the Intel/OpenVINO recommendations to x86_64, where these packages are available.

Co-Authored-By: GPT-6 (Codex) <noreply@openai.com>
Co-Authored-By: GPT-6 Astra XHigh (Codex) <noreply@openai.com>
2026-09-18 21:58:04 -05:00
Jacob Mink 3024302725 Ensure OpenVINO GenAI package includes both C bindings 2026-09-18 21:47:44 -05:00
Jacob Mink c779955714 Fix OpenVINO GenAI C runtime loading 2026-09-18 21:47:44 -05:00
Jacob Mink 2294bfa19c Update OpenVINO GenAI for Voxtype NPU support 2026-09-18 21:47:44 -05:00
Spencer Bull b7f44e4744 Add OpenVINO GenAI runtime package 2026-09-18 21:47:44 -05:00
David Heinemeier HanssonandClaude Fable 5.1 00685ab3e7 Package v0.1.2, the release carrying the reviewed fixes
Upstream merged the installer rollback, renderer Host check, contrast warning and playback fixes and cut v0.1.2 with them, plus a fullscreen intro that is canvas geometry only: no new dependencies, files or runtime paths. The checksum is the one its SHA256SUMS manifest publishes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-18 22:21:52 -04:00
02f63ae1f2 Add omarchy-billboard-generator, the animated domain video maker
Packages llstrk/omarchy-billboard-generator from its GitHub release archive: the runtime tree plus locked production npm dependencies under /usr/lib, entry-point symlinks in /usr/bin, a desktop entry whose WM class matches the Chromium app-mode window, and the fonts and provenance notices the project bundles. Chromium and ffmpeg are runtime dependencies found on PATH rather than downloaded, which is how the project itself works.

The desktop entry is written from the PKGBUILD rather than shipped as a second source because sync-upstream rewrites the checksum array wholesale from the release manifest. The catalog checks pin the data directories to empty scratch paths so the build reads bundled data rather than a synced snapshot in the builder's home.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-18 22:21:52 -04:00
Josh Owen 29c9561a3f Add bambustudio-bin, Bambu Studio, to the fast ring
Bambu Studio is the official slicer for Bambu Lab printers. Linux builds
are GitHub AppImages with a changing timestamp in the filename, so this
tracks the AUR bambustudio-bin package that already resolves those
assets, and puts it on the fast ring so stable users get updates with
omarchy update.

Two small Omarchy patches: skip stripping the extracted AppImage, and
clear the AppImage ELF magic byte so --appimage-extract works in the
build container. The desktop entry is labeled Bambu Studio and grouped
under Graphics.

Validation: bin/add-package + bin/sync-aur reapply the patches; AUR +
.omarchy reproduces the checked-in tree. Built and launched the 2.8.2.61
AppImage on Omarchy 4.0.3 (x86_64); bambu-studio --help reports
BambuStudio-02.08.02.61. AUR is currently 02.08.02.60; the next AUR bump
will sync through.
2026-09-18 22:14:31 -04:00
fbfbda4eca Package Omawake 0.0.3 and Omaspeak 0.0.3 with service-removal cleanup hooks (#503)
* Package Omawake 0.0.3 and Omaspeak 0.0.2

Bump both -bin packages to the model-support roadmap delivery:

Omawake 0.0.3:
- W02-W05 setup/activation/cache gates audited and closed
- W07 pinned catalog URL health checks and import diagnostics
- W08 Moonshine Small/Medium benchmarked; both deferred (Tiny default)
- W09 Spanish wake profile (multilingual Whisper Base INT8, es)
- W10 connection-owned playback pauses (HoldPause)

Omaspeak 0.0.2:
- S09 Kokoro 82M: Kokoro-capable packaged provider (supertonic;kokoro_tts)
  with espeak-ng-data.bin shipped beside the executable, 54 named voices
- S10 catalog URL checks, Spanish speech profile, consistent status shape
- S07 streaming deferred at the current pin

Upstream: omawake v0.0.3, omaspeak v0.0.2 (aarch64 + x86_64 verified on
promaxgb10-d666 CUDA and local NPU installs).

* omaspeak-bin: install espeak-ng-data.bin beside the packaged library

* omaspeak-bin: bump to 0.0.3-rc.1 (catalog-managed eSpeak data)

- The tarball no longer ships espeak-ng-data.bin: the Kokoro catalog row
  pins the data package as a model asset (downloaded/verified/installed
  into the model directory with the GGUF), so the core package ships no
  model data at all.
- Both arch checksums taken from the v0.0.3-rc.1 SHA256SUMS.txt.

* omaspeak-bin: finalize at 0.0.3

* Stop setup-created Oma services before pacman removes their binaries

* Drop stale release-verification fixtures from the branch

These were swept in by git add -A during the version bumps: packaged
copies of old releases (0.0.1 tarballs and extracted trees, ~80 MB)
belong to the local verification workflow, not to the package repo.
The consolidated upstream PR should carry only the package changes,
hooks and the removal regression suite.

* Update removal-test fixture versions to the packaged finals

* Ask systemd to reset only an Oma unit that actually failed

The removal helper reset the failed state of every unit it stopped, but
systemd accepts ResetFailed for a unit that is in the failed state alone.
For any other state it answers that the unit is not loaded and exits
non-zero, and because the helper runs under errexit while the hook aborts
on failure, a healthy unit then aborted the whole transaction:

  (2/2) Stop and remove omaspeak user services before package removal
  Failed to reset failed state of unit omaspeak.service: Unit omaspeak.service not loaded.
  :: Could not clean up omaspeak for jacob; removal aborted.

That is the ordinary case, as the packaged service ships disabled and an
enabled one is commonly stopped rather than failed. Read the active state
after the stop and ask for the reset only where it applies, so a failed
unit still loses its failed state along with its rate and restart counters
while a clean unit no longer fails the removal. A reset that a reachable
manager still refuses stays fatal.

Model the rule in the removal suite, where reset-failed now follows the
active state the way a real manager does, and cover both outcomes: an
inactive unit must not be asked for, a failed one must be reset between
the stop and the disable, and a refused reset must still fail the hook.

* Keep removal cleanup faithful to how systemd reads configuration

Both defects from the review of e025111 sat in the shared package-remove
helper, so both packages were affected the same way.

An offline user's drop-in was recognised by grep '^ExecStart=', while the
configuration parser throws away the whitespace around an assignment
(parse_line() strips the line and both halves of the assignment).  A drop-in
naming a development build as

  ExecStart =
  ExecStart = /home/alice/build/omawake daemon

therefore went unmatched, and the helper cleared away the generated base unit
beside with its enablement links, right behind a service that was never meant
to be the package's.  Match an assignment the way the parser accepts one.  The
gate that decides whether a unit file is the generated one stays strict on
purpose: only the exact generated shape is ever deleted.

systemctl show-environment also prints every value the way a shell would read
it, through shell_maybe_quote(SHELL_ESCAPE_POSIX), so an XDG_CONFIG_HOME with a
space arrives as $'/home/alice/custom config'.  The XDG_CONFIG_HOME=/* case saw
neither form and kept the home's .config directory quietly, leaving the unit in
the directory the manager really reads pointing at the removed binary.  Decode
that quoting character by character, without letting the text become shell
syntax, and refuse a value that is neither a plain path nor a closed $'...'
quote rather than delete what would have to be guessed at.  A value that is not
an absolute path stays the fallback it is in systemd itself.

The fixtures now hand the helper the very text a manager prints, quoted by a
mirror of that printer, and cover a quoted path with a space, an apostrophe and
a backslash, an unreadable quoted value, a relative one, and each spacing of an
offline override.  Verified with the removal suite, 15 tests; the eight new
assertions fail against the helper as it was.  The other suites were not run
here, as they reach for the network.

pkgrel 3 -> 4 and the helper's checksum, in both recipes.
Reported-by: spencerbull

* Decode systemd control escapes during service removal

systemctl C-escapes control bytes in show-environment output. Rejecting those valid values aborted package removal for every user, even when the affected account had no Oma service. Decode the printer’s named and octal escapes without evaluating shell syntax or stripping trailing newlines, and cover the real printer format in the fixtures.

Co-Authored-By: GPT-6 XHigh <noreply@openai.com>

---------

Co-authored-by: Spencer Bull <spencer@omarchy.org>
Co-authored-by: GPT-6 XHigh <noreply@openai.com>
2026-09-18 20:32:11 -05:00
Ryan Hughes 722d65daff Merge pull request #523 from omacom/strict-off
Note why strict up-to-date is off
2026-09-18 15:57:19 -07:00
Ryan Hughes 3d208b340a Merge pull request #519 from omacom/add-owe
Add owe and owe-lockfeed
2026-09-18 15:57:06 -07:00
Ryan Hughes 868f2a1e18 Tests: note that publish, not strict protection, guards the merged tree 2026-09-18 18:46:54 -04:00
Ryan Hughes 81db8fa9a1 Merge pull request #522 from omacom/elsewhen-1.0.0
elsewhen: 1.0.0, the only tag upstream has
2026-09-18 15:44:43 -07:00
Ryan Hughes 2c3fbe38cb Merge branch 'master' into add-owe 2026-09-18 15:12:02 -07:00
Ryan Hughes fae6eaec1b elsewhen: 1.0.0, the only tag upstream has
The package was added pointing at v0.1.0 with a placeholder checksum,
but that tag was never cut; upstream went straight to v1.0.0. The
package has never built anywhere. Point at the real tag and fill the
digest. The upstream watch keeps it current from here.
2026-09-18 18:11:35 -04:00
Ryan Hughes bcb80f08ee Merge pull request #520 from omacom/report-dispatch-fix
Publish report: dispatch runs skip the PR comment; log before comment
2026-09-18 15:09:23 -07:00
Ryan Hughes da4e1b55a8 Publish report: no PR comment on dispatch runs; append the log before commenting
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.
2026-09-18 18:07:43 -04:00
Bjarne Oeverli 0db6153420 Add owe-lockfeed, the lock screen video module
The lock screen draws video through Owe.LockFeed in omarchy#12429. The
module maps the frame slots the OWE renderer publishes, with no media
pipeline of its own. It builds from the qml-plugin directory of the owe
release and installs to the Qt QML module path.
2026-09-18 22:54:51 +02:00
Bjarne Oeverli e751da36fd Update owe to 0.2.0
The release brings the lock feed: the daemon hands the locked session's
video to the shell as shared memory frames.
2026-09-18 22:54:51 +02:00
Ryan Hughes ddeb75aa4f Merge pull request #517 from omacom/hype/add-package
Add Hype presentation editor package
2026-09-18 13:05:39 -07:00
David Heinemeier Hansson 4fb4dafa1d Package 4K PowerPoint export rendering 2026-09-18 21:29:00 +02:00
Bjarne Oeverli 9013b97fcc Add owe, the wallpaper engine for video backgrounds
The OWE engine owns desktop video backgrounds in omarchy#12429. This
recipe is imported from the owe AUR package and watches the vX.Y.Z tags
on omacom/owe. It builds for x86_64 and aarch64.

0.1.1 is the first release that plays the wallpaper audio track.
2026-09-18 21:26:58 +02:00
Ryan Hughes 509c24b8c2 Merge branch 'master' into hype/add-package 2026-09-18 12:14:15 -07:00
Ryan Hughes 5580f21725 Merge pull request #518 from omacom/builder-qemu-10
Builders emulate aarch64 with QEMU 10.2.3 instead of the abandoned 7.2 image
2026-09-18 12:13:49 -07:00
David Heinemeier Hansson ae0e7407e3 Package animated-image PowerPoint export support 2026-09-18 21:04:53 +02:00
David Heinemeier Hansson 16087f19b4 Verify Hype Git source with makepkg checksum 2026-09-18 20:57:38 +02:00
Ryan Hughes efff828746 Builders emulate aarch64 with QEMU 10.2.3 instead of the abandoned 7.2 image
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.
2026-09-18 14:56:03 -04:00
David Heinemeier Hansson 5cdaca6048 Include animated WebP support in Hype package 2026-09-18 20:18:08 +02:00
David Heinemeier Hansson 8411b99fc4 Add Hype presentation editor package 2026-09-18 20:16:05 +02:00
Ryan Hughes 7a3f00b924 Merge pull request #515 from omacom/report-build-failures
Publish report covers build failures and package sources
2026-09-18 11:03:19 -07:00
Ryan Hughes a24c56cd52 Dispatch: a package already published at master's version is a no-op, not a failure
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.
2026-09-18 14:01:07 -04:00
Ryan Hughes 4717cfec4e Publish record covers build failures, and says where each package came from
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.
2026-09-18 13:39:56 -04:00
Ryan Hughes 16ce7ef109 Merge pull request #514 from omacom/empty-pr-fails
Fail the PR check when the diff against base is empty
2026-09-18 09:57:17 -07:00
Ryan Hughes 54685a55a1 PR check fails when the PR changes no files relative to its base
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.
2026-09-18 12:55:18 -04:00
Ryan Hughes 25862ce7bb Merge pull request #513 from omacom/publish-report
Report each publish on the PR and in a public JSON log
2026-09-18 09:52:57 -07:00
Ryan Hughes 902f6d3da9 Report each publish: comment on the merged PR, append to a JSON log in the bucket
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.
2026-09-18 12:42:47 -04:00
Ryan Hughes f1c8da41f5 Merge pull request #504 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-18 09:26:33 -07:00
Jim Martin a035d841aa Merge pull request #1 from birkskyum/fix/jim-settings-boot-refresh-20260916
Refresh ARM boot settings against master and add regression tests
2026-09-18 11:11:55 -05:00
Ryan Hughes 3260b21deb Merge branch 'master' into auto/sync-upstream 2026-09-18 09:02:19 -07:00
Ryan Hughes 290793fdcc Merge pull request #511 from omacom/upstream/ci-builds
Build PRs on ephemeral droplets; publish merged packages from CI
2026-09-18 08:55:11 -07:00
Ryan Hughes 5a701be9d1 PR plan job: bootstrap when the base branch has no bin/build-matrix yet 2026-09-18 11:46:50 -04:00
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
Basti b422d37fa2 Drop privileges when seeding Dell haptic config (#497)
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
2026-09-18 15:24:41 +02:00
dhh d7fe983681 chore: sync upstream releases 2026-09-18 11:30:05 +00:00
Spencer Bull 5cccebaa17 Merge pull request #507 from omacom/cua-aquamarine-0151
Refresh Cua plugin profile for Aquamarine 0.15.1
2026-09-18 01:54:11 -05:00
Spencer BullandCodex GPT-6 Astra xhigh f7577b59a6 Refresh Cua plugin profile for Aquamarine 0.15.1
Derive an exact Aquamarine 0.15.1-1 profile from the verified upstream kit so edge installations resolve without weakening native compatibility checks.

Co-Authored-By: Codex GPT-6 Astra xhigh <noreply@openai.com>
2026-09-18 01:34:01 -05:00
Spencer Bull 686c599f53 Merge pull request #483 from omacom/add-elsewhen
Add elsewhen, the Omarchy shell world clock plugin
2026-09-17 20:59:21 -05:00
Krzysztof Wilczyński 03ef2e3ef7 Merge pull request #501 from kwilczynski/feature/update-kernel-releases
Update Linux kernel release to v7.2.5-6 for base and BORE kernels
2026-09-18 04:45:34 +09:00
Krzysztof Wilczyński 18433c2499 Update Linux kernel release to v7.2.5-6 for base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-18 04:18:47 +09:00
Krzysztof Wilczyński ab5a4f9c5e Merge pull request #500 from kwilczynski/feature/disable-register-zeroing-on-exit
Disable register zeroing on function exit for base and BORE kernels
2026-09-18 03:44:11 +09:00
Krzysztof Wilczyński e2cea36daf Disable register zeroing on function exit for base and BORE kernels
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>
2026-09-18 03:32:34 +09:00
Krzysztof Wilczyński 326cd6c7a0 Merge pull request #499 from kwilczynski/fix/add-missing-signature-files
Add missing patch signature files to the base and BORE kernels
2026-09-18 03:18:44 +09:00
Krzysztof Wilczyński 3e0cba033a Add missing patch signature files to the base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-18 03:15:43 +09:00
Ryan Hughes 1f025695d5 Build t3code-bin for aarch64 2026-09-17 13:35:33 -04:00
Spencer Bull 164e4a4f05 Merge pull request #496 from omacom/xps13-dx13260-firmware
Add linux-firmware-cirrus stable-snapshot shim for the Dell XPS 13 DX13260
2026-09-17 11:38:01 -05:00
Spencer BullandClaude Fable 5.1 afd75bc571 Add linux-firmware-cirrus stable-snapshot shim for the Dell XPS 13 DX13260
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>
2026-09-17 10:58:46 -05:00
David Heinemeier Hansson 259aa68129 Add Monologue webcam recorder package (#495)
* Add Monologue webcam recorder package

* Update Monologue to latest published source
2026-09-17 11:36:14 -04:00
David Heinemeier HanssonandClaude Fable 5.1 750eb611f5 Update herdr to 0.9.1 with Zig 0.16.0 for the vendored libghostty-vt (#493)
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>
2026-09-17 15:54:55 +02:00
Ryan Hughes 5434d7c6c6 Merge pull request #492 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-17 06:37:50 -07:00
Ryan Hughes 1beee4695b Package Flea 0.3.0 phone backends and shelf 2026-09-17 09:34:56 -04:00
dhh 1bce595733 chore: sync upstream releases 2026-09-17 11:54:13 +00:00
github-actions[bot]anddhh afcd481422 chore: sync upstream releases (#481)
Co-authored-by: dhh <2741+dhh@users.noreply.github.com>
2026-09-17 13:20:13 +02:00
OmarchybotandClaude Opus 5 ce33f28414 Match flea's O_NOFOLLOW fix by behaviour, not by spelling (#488)
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>
2026-09-17 12:40:45 +02:00
Krzysztof Wilczyński 72b7998935 Merge pull request #485 from kwilczynski/feature/update-kernel-releases
Update Linux kernel release to v7.2.5-5 for base and BORE kernels
2026-09-17 18:41:40 +09:00
Krzysztof Wilczyński 99b8005e73 Update Linux kernel release to v7.2.5-5 for base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-17 18:39:05 +09:00
Krzysztof Wilczyński a38c04c84a Merge pull request #487 from kwilczynski/fix/amd-zen5-tlb-sizes-display
Add small AMD Zen 5 TLB sizes display fix to base and BORE kernels
2026-09-17 18:38:28 +09:00
Krzysztof Wilczyński d06bf4660a Add small AMD Zen 5 TLB sizes display fix to base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-17 18:32:22 +09:00
Krzysztof Wilczyński 8b30e302b3 Merge pull request #486 from kwilczynski/feature/enable-o3-build-optimization
Use -O3 build optimization flag for base and BORE kernels
2026-09-17 18:29:31 +09:00
Krzysztof Wilczyński 1b8e0f3845 Use -O3 build optimization flag for base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-17 18:25:54 +09:00
Spencer Bull 52cba6cd95 Merge pull request #447 from jacob-vincent-mink/feature/omawake-omaspeak-rc
Add Omawake and Omaspeak to edge
2026-09-17 00:05:59 -05:00
Spencer BullandClaude Fable 5.1 c2f2345f0d Put Omawake and Omaspeak on the fast release ring
A channels list of edge alone is the outer bound on where a package may build, so both packages were refused for rc and stable and could only ever reach edge users. The fast ring builds them natively for all three channels, each against its own base mirror, which is how the other prebuilt -bin applications here ship. The channels key has to go rather than sit beside the ring: package_builds_for_mirror checks it first, so an edge-only list would still block the rc and stable builds the ring asks for.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 23:47:49 -05:00
Krzysztof Wilczyński 1f65553df3 Merge pull request #484 from kwilczynski/fix/drm-ttm-swapout-bulk-move
Add fix for swapped-out TTM resources never leaving their bulk_move range
2026-09-17 09:01:20 +09:00
Krzysztof Wilczyński f5c9441888 Add fix for swapped-out TTM resources never leaving their bulk_move range
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-17 08:54:42 +09:00
Birk Skyum 16b1a730f6 Run boot settings regression explicitly from tests 2026-09-16 20:54:59 +02:00
Birk Skyum 203b7340ab Test packaged boot settings on ARM and x86_64
Exercise both settings recipes with synthetic runtime files. Check the exact Limine templates, boot drop-ins and backup metadata, installer-owned configuration, notifier copies and architecture-specific Thunderbolt handling. Run package regressions in the existing self-test job.
2026-09-16 20:37:43 +02:00
Birk Skyum 39722554db Refresh dev settings version and package revision
Record the current quattro-derived version before bumping pkgrel, so makepkg does not discard the revision bump when updating a stale pkgver.
2026-09-16 20:37:43 +02:00
Birk Skyum 6a6e170fe4 Merge current master into ARM settings boot fix
Preserve the v4.0.4 source pin and checksum while retaining the settings package revision bump. Keep Jim Martin’s boot-configuration fix and original commit intact.
2026-09-16 20:30:59 +02:00
Spencer Bull 56eced522e Add elsewhen, the Omarchy shell world clock plugin 2026-09-16 13:17:37 -05:00
b836c23037 Declare the licenses of the bundled audio.cpp provider
Each package ships libaudiocpp, which upstream's own third-party notices describe as Apache-2.0 with BSD-3-Clause PocketFFT-derived code retained in it, and installs those texts under /usr/share/licenses. A license array of MIT alone describes only the project's own source, so pacman -Qi misreports what the package contains.

Co-Authored-By: GPT-6 Astra XHigh <noreply@openai.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:10:50 -05:00
Spencer BullandClaude Fable 5.1 6ea162d2a4 Turn off debug packaging alongside !strip
The builder's makepkg.conf enables debug and emptydirs. With strip off, makepkg still takes the debug branch of tidy_strip, creates usr/src/debug/<pkgbase> inside the package to hold debug sources, finds none in a prebuilt tree, and emptydirs then ships the empty directory to every user. Seven of the ten -bin packages here that disable strip already disable debug with it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:10:50 -05:00
Spencer BullandClaude Fable 5.1 10a451abc7 Drop the .SRCINFO and .gitignore files the repository ignores
The top-level .gitignore excludes pkgbuilds/*/.SRCINFO and pkgbuilds/*/.gitignore, and none of the packages under pkgbuilds/ tracks either. These four were added under the retired pkgbuilds/edge/ path, which that rule does not cover, and the move to pkgbuilds/ carried them along as already-tracked files. The per-package .gitignore negates the repository rule for its own directory and its leading * hides every future file there, so a patch or an .omarchy/upstream.sh dropped beside the PKGBUILD would never show up in git status. The build planner reads PKGBUILD and .omarchy/package.json; the only other reader, bin/package-worktree, takes .SRCINFO as a fallback while importing from the AUR and then removes both files as AUR-only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-16 11:10:50 -05:00
Ryan Hughes b8cdc38609 Merge pull request #464 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-16 08:04:36 -07:00
dhh 97871759bb chore: sync upstream releases 2026-09-16 11:50:57 +00:00
Krzysztof Wilczyński 42f3afd142 Merge pull request #475 from kwilczynski/feature/update-kernel-releases
Update Linux kernel release to v7.2.5-4 for base and BORE kernels
2026-09-16 14:19:01 +09:00
Krzysztof Wilczyński e95c6f37de Update Linux kernel release to v7.2.5-4 for base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-16 14:08:43 +09:00
Spencer Bull 1f5b7a0a30 Merge pull request #473 from spencerbull/cua-agent-keymap-remaps
Preserve user keyboard remaps in the Cua Hyprland plugin
2026-09-16 00:08:36 -05:00
Spencer Bull b2176d5cab Preserve Num Lock during compatible Cua foreground input 2026-09-15 23:55:59 -05:00
Krzysztof Wilczyński e68ded7f19 Merge pull request #474 from kwilczynski/fix/add-android-binder-support-back
Add Android Binder support back to the base and BORE kernels
2026-09-16 13:46:01 +09:00
Krzysztof Wilczyński b9a3863787 Add Android Binder support back to the base and BORE kernels
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-16 13:39:52 +09:00
Spencer BullandCodex XHigh f868f3c767 Preserve user keymaps in the Cua Hyprland plugin
Give background agent seats independent keymaps and check foreground keyboard compatibility per operation. Package the downstream patch with separate source integrity and build provenance, retaining the upstream ABI verifier.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-15 23:05:22 -05:00
Ryan Hughes 6c9bdbd0a2 Merge pull request #468 from omacom/chore/ghostty-fast-ring
Put Ghostty on the fast release ring
2026-09-15 19:24:00 -07:00
Ryan Hughes 3c671736ec Put Ghostty on the fast release ring 2026-09-15 22:21:52 -04:00
Jacob Mink 46306a12eb Package Omawake 0.0.2 and Omaspeak 0.0.1 2026-09-15 16:09:13 -05:00
Ryan Hughes 5fe2367366 Release omarchy 4.0.4 2026-09-15 17:07:20 -04:00
Ryan Hughes ea69034287 Merge pull request #462 from omacom/feat/ghostty-x86_64
Build Ghostty for x86_64 with pinned Zig toolchain
2026-09-15 13:19:44 -07:00
Ryan Hughes edc411ae4d Build Ghostty for x86_64 with pinned Zig toolchain 2026-09-15 16:13:12 -04:00
Ryan Hughes b27f3fe62e Merge pull request #453 from scottjones/contrib/ghostty-aarch64
Build stable Ghostty and its split packages for aarch64
2026-09-15 12:29:03 -07:00
Ryan Hughes eeb137e055 Merge pull request #452 from scottjones/contrib/obs-aarch64
Add OBS Studio for aarch64 without the CEF browser plugin
2026-09-15 12:28:21 -07:00
Ryan Hughes 5b8ef2617a Merge pull request #450 from scottjones/contrib/pinta-aarch64
Add Pinta and its .NET 10 dependencies for aarch64
2026-09-15 12:27:13 -07:00
Ryan Hughes 7275574e77 Merge pull request #448 from scottjones/contrib/grok-bot-aarch64
Build Grok Bot 0.47.0 for aarch64 and fix desktop packaging
2026-09-15 12:19:25 -07:00
Ryan Hughes 35bb9efba4 Merge pull request #457 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-15 12:14:23 -07:00
Jacob Mink 977e1c57ea Update Omawake to v0.0.1-rc.3 2026-09-15 14:06:38 -05:00
Jacob Mink f5b29f16e3 Update Omaspeak to v0.0.1-rc.3 2026-09-15 13:57:15 -05:00
Jacob Mink 4a5696c134 Support both Oma release architectures 2026-09-15 13:48:22 -05:00
Jacob Mink 55c564968c Update Oma apps to v0.0.1-rc.2 2026-09-15 13:46:55 -05:00
Spencer BullandGPT-6 Astra XHigh 9807177337 Register speech packages with the current edge builder
The builder discovers package directories directly under pkgbuilds and requires local package metadata. Move Omawake and Omaspeak out of the retired edge directory and restrict their channels to edge so the release pipeline builds them without promoting the release candidates.

Co-Authored-By: GPT-6 Astra XHigh <noreply@openai.com>
2026-09-15 13:46:55 -05:00
Jacob Mink e15d4aa61e Add Omawake and Omaspeak edge packages 2026-09-15 13:46:55 -05:00
dhh 1a40508b48 chore: sync upstream releases 2026-09-15 17:10:15 +00:00
Scott Jones c17a46e404 Use the MbedTLS CMake configuration for OBS 2026-09-15 08:16:34 -04:00
Scott Jones 5371afbbb3 Simplify Pinta packaging with the upstream release tarball 2026-09-15 08:16:34 -04:00
Ryan Hughes 34accaef7f Merge pull request #454 from omacom/fix/dkms-omarchy-headers
Remove kernel header dependencies from DKMS packages
2026-09-14 22:32:20 -07:00
Ryan Hughes dee4caf7bc Let DKMS packages rely on headers supplied by the base install 2026-09-15 00:46:31 -04:00
Ryan Hughes 267d84c9e2 Merge pull request #456 from omacom/fix/yaru-session-cleanup
Fix Yaru packaging after upstream removed its X11 session
2026-09-14 21:21:02 -07:00
Ryan Hughes 113ff62459 Allow absent X11 session files in Yaru split packaging 2026-09-15 00:18:51 -04:00
Ryan Hughes 2c7912fe59 Merge pull request #455 from omacom/fix/published-build-guard
Skip retained published archives when planning builds
2026-09-14 21:16:28 -07:00
Ryan Hughes 9d5c3eea19 Skip retained published archives when planning builds 2026-09-15 00:13:57 -04:00
Ryan Hughes 6064a14d47 Use Omarchy headers for IPU7 and MacBook SPI DKMS packages 2026-09-15 00:06:41 -04:00
Scott Jones 73d6337824 Simplify ARM Cursor packaging with the vendor Debian archive 2026-09-14 23:13:18 -04:00
Scott Jones afd8bcd754 Add Pinta and its .NET 10 package dependencies for aarch64 2026-09-14 22:00:59 -04:00
Scott Jones 6877d75e87 Package Apple Video Decoder firmware and the VA-API driver 2026-09-14 21:58:02 -04:00
Scott Jones 97d2e864e3 Build stable Ghostty and its split packages for aarch64 2026-09-14 21:58:02 -04:00
Scott Jones 60cb8ea9f9 Add an aarch64 OBS Studio build without the CEF browser plugin 2026-09-14 21:58:02 -04:00
Scott Jones 3c3df29faa Build cursor-bin for aarch64 with the vendor AppImage 2026-09-14 21:56:11 -04:00
Scott Jones d783f46f5d Build Grok Bot 0.47.0 for aarch64 and fix packaged desktop integration 2026-09-14 21:55:22 -04:00
Ryan Hughes cc143f7352 Merge pull request #446 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-14 18:37:42 -07:00
Ryan Hughes b65e187e78 Refresh Motorcomm compatibility patch for upstream 1.0.34 2026-09-14 21:33:21 -04:00
ryanrhughes 55f14524df chore: sync upstream releases 2026-09-15 01:26:53 +00:00
Ryan Hughes 191cd775cd Merge pull request #445 from omacom/refactor/direct-upstream-sync
Replace AUR sync with direct upstream release watches
2026-09-14 18:19:47 -07:00
Ryan Hughes b34de5c29e Replace scheduled AUR imports with direct upstream watches 2026-09-14 21:15:17 -04:00
Ryan Hughes f26a60aef7 Merge pull request #434 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-14 16:40:09 -07:00
Ryan Hughes 8b43589dc5 Merge pull request #433 from omacom/auto/sync-aur
chore: sync AUR packages
2026-09-14 16:39:39 -07:00
dhh f7af6cceff chore: sync upstream releases 2026-09-14 21:55:41 +00:00
Ryan Hughes 7b11c97603 Fix unsigned patch sources for Omarchy kernels 2026-09-14 15:54:03 -04:00
Ryan Hughes a01153595b Merge pull request #440 from kwilczynski/feature/add-linux-omarchy-kernels
Add new base and BORE-enabled Linux kernel packages for Omarchy
2026-09-14 12:20:14 -07:00
Krzysztof Wilczyński 770244c0e7 Add new base and BORE-enabled Linux kernel packages for Omarchy
Signed-off-by: Krzysztof Wilczyński <kwilczynski@omarchy.org>
2026-09-15 00:53:12 +09:00
Ryan Hughes 61c8f08aea Merge pull request #437 from omacom/fix/publish-successful-builds
Publish successful packages when another build fails
2026-09-13 23:54:59 -07:00
Ryan Hughes de4880081d Merge pull request #436 from omacom/fix/cua-hyprland-pkgrel-3
Accept Hyprland 0.56.2-3 in the Cua plugin profile
2026-09-13 23:54:33 -07:00
Ryan Hughes eb001edc2e Keep build manifest directories owned by the host 2026-09-14 02:35:18 -04:00
Ryan Hughes d96b950901 Publish completed packages when a peer build fails 2026-09-14 02:30:11 -04:00
Ryan Hughes c345656307 Accept Hyprland 0.56.2-3 in the Cua plugin profile 2026-09-14 02:29:45 -04:00
ryanrhughes 0f8a61e973 chore: sync AUR packages 2026-09-14 03:54:54 +00:00
Spencer Bull 3626590e94 Merge pull request #432 from omacom/enable-cua-hyprland-plugin-builds
Enable regular builds for Cua Hyprland plugin
2026-09-13 21:32:16 -05:00
Spencer BullandGPT-5.6 Sol XHigh 81122dc2df Enable regular Cua plugin builds
Move the package from the stable-only fast ring into the ordinary edge-to-RC-to-stable pipeline. Update the replay and promotion instructions while keeping the Driver 0.27.0 qualification boundary explicit.

Co-Authored-By: GPT-5.6 Sol XHigh <noreply@openai.com>
2026-09-13 21:14:32 -05:00
Spencer Bull 3f734e2f8c Merge pull request #346 from f-trycua/feat/cua-hyprland-plugin-package
Add optional ABI-pinned Cua Hyprland plugin package
2026-09-13 19:15:37 -05:00
Ryan Hughes 0022e278c6 Respect tmux clipboard policy and preserve empty reads 2026-09-13 19:48:41 -04:00
Ryan Hughes 2463ac2cf4 Merge pull request #409 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-13 15:59:11 -07:00
Ryan Hughes 0f2bc3f627 Merge pull request #430 from omacom/fix/cua-028-installer-guard
fix: handle embedded installer URLs in Cua 0.28.1
2026-09-13 15:58:42 -07:00
Ryan Hughes 603663a5a4 fix: handle embedded installer URLs in Cua 0.28.1 2026-09-13 18:54:52 -04:00
Ryan Hughes 39fe0b7858 Merge pull request #402 from omacom/auto/sync-aur
chore: sync AUR packages
2026-09-13 15:41:23 -07:00
dhh be237a2901 chore: sync upstream releases 2026-09-13 20:57:39 +00:00
ryanrhughes 54e8defdc6 chore: sync AUR packages 2026-09-13 20:30:52 +00:00
Spencer Bull c8799259ea Merge pull request #418 from omacom/fix/ipu7-camera-linux-7.2-cvs
Fix Intel IPU7 camera on Linux 7.2 by adopting the CVS V4L2 bridge
2026-09-13 12:36:39 -05:00
David Heinemeier HanssonandClaude Fable 5.1 28da766fba Protect the kyber I/O scheduler udev rule on upgrade (#425)
basecamp/omarchy#11653 added etc/udev/rules.d/60-omarchy-io-scheduler.rules. package() already ships the whole etc/ tree, so the rule lands on new installs with the next build; list it in backup= so pacman keeps a user's local edit of the rule across upgrades, like the other package-owned /etc drop-ins.


Claude-Session: https://claude.ai/code/session_01EqYNBv5cERVom2zox5epyE

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 17:58:22 +02:00
David Heinemeier Hansson 3bfc327f8f Run Neovim clipboard regression tests in CI 2026-09-13 17:49:36 +02:00
David Heinemeier Hansson c095aa4ccc Keep remote clipboard paste fast and cover provider transports 2026-09-13 17:46:10 +02:00
Ryan Hughes bce3b368e7 Merge pull request #383 from ReenigneArcher/fix/sunshine/patch-packager-info
fix(sunshine): patch packager build flags
2026-09-12 20:54:38 -07:00
Spencer Bull 462a6e404d intel-ipu7-camera: pkgrel=1 for the new 1.0.6 version 2026-09-12 21:03:15 -05:00
Spencer Bull 21c0630334 Stop the DKMS Intel CVS driver from claiming the amplifiers' speaker-ID GPIO
The in-tree 7.2 CVS driver we ship as the intel-cvs DKMS module requests its
"wake" line with devm_gpiod_get(). On the Dell XPS 14 DA14260 that pin
(\_SB.GPI1 20) is the shared GpioIo the four CS35L57 amplifiers read their
speaker ID from, so with this package installed every cs35l56 probe fails with
"error -EBUSY: Failed to get spk-id-gpios" and the machine has no sound card.

Carry Junjie Cao's upstream fix, "media: i2c: cvs: Get the wake IRQ without
claiming the GPIO" (Cc: stable, Fixes: 8e2b43d2c10b), applied to the
downloaded intel-cvs-core.c in prepare(). makepkg symlinks plain downloads
into srcdir and patch refuses symlinks, so the file is copied first. Drop the
patch once the stable tag we fetch from contains the fix.

Link: https://github.com/thesofproject/sof/issues/11152
Link: https://bugzilla.redhat.com/show_bug.cgi?id=2529031
2026-09-12 19:25:52 -05:00
Francesco Bonacci 6970a5da89 Document Driver 0.27.0 plugin pairing 2026-09-12 16:42:57 -05:00
Francesco Bonacci d1a130ba84 Name Omarchy plugin release owners 2026-09-12 16:42:57 -05:00
Francesco Bonacci 85e91947f6 Clarify distinct Cua and Omabot qualification runs 2026-09-12 16:42:57 -05:00
Francesco Bonacci 76bd68b2aa Document exact Cua plugin keymap activation 2026-09-12 16:42:57 -05:00
Francesco Bonacci 56adc00bb9 Update Cua plugin to qualified stable profile kit 2026-09-12 16:42:57 -05:00
Francesco Bonacci eec9dcef03 feat: add optional pinned Cua Hyprland plugin package
Import the unchanged recipe from the cua-driver-rs-v0.24.0 build kit. Keep fast-ring automatic builds disabled pending native channel qualification and document explicit ABI updates and compositor restarts.
2026-09-12 16:42:57 -05:00
Spencer Bull 5501e168f7 Fix Intel IPU7 camera on Linux 7.2 by adopting the CVS V4L2 bridge
Linux 7.2 changed how the Intel CVS controller (ACPI INTC10E1 on Panther
Lake) appears to the IPU. ipu-bridge now places it between the sensor and
the IPU CSI2 receiver, like IVSC, and the new in-tree drivers/media/i2c/cvs
driver provides the matching "Intel CVS" V4L2 bridge sub-device. Our
out-of-tree vision-drivers intel_cvs has no sub-device and displaces the
in-tree module of the same name, so on 7.2 the sensor never joins the
media graph and the HAL reports "No sensors available". Both linux-ptl
7.2.3 and linux-omarchy 7.2.5 fail this way.

Ship the in-tree 7.2 CVS driver verbatim as DKMS module intel-cvs for 7.2+
kernels. Arch-derived kernel configs cannot enable CONFIG_VIDEO_INTEL_CVS
themselves: it sits under a menu hidden by MEDIA_HIDE_ANCILLARY_SUBDRV, so
olddefconfig drops it. Keep vision-drivers for kernels before 7.2. Both
dkms.conf files carry a BUILD_EXCLUSIVE_KERNEL regex that the pacman dkms
hook honours.

Teach the camera HAL about the bridge with upstream ipu7-camera-hal commit
f167239 (MediaControl routes the sensor link through "Intel CVS" and finds
the I2C bus through it) and add "Intel CVS" pad formats to the ipu75xa
ov08x40 config so link validation passes. Links stay sensor to CSI2 in the
config and are rewritten at runtime, so the same files work on 7.1.
2026-09-12 14:34:36 -05:00
David Heinemeier Hansson db5f3791c5 Enable automatic clipboard yanks with the remote provider 2026-09-12 16:48:59 +02:00
OmarchybotandClaude Opus 5 19ef4b560f Skip flea's ctime-racy redo test so releases can drain (#411)
Every channel has failed since 0.2.1 landed: one package failing fails the
whole build, and the build step aborts the release before sign, promote or
sync. Nothing has published on edge, rc or stable since 2026-09-11, and 26
built packages have been rebuilt and discarded every cycle.

backend::redo::tests::redo_refuses_changed_sources_and_destination_collisions
writes a file and immediately asks redo to notice the edit. flea decides
"changed" from ctime alone -- src/backend/undo.rs records (ctime, ctime_nsec)
as the whole identity -- and this kernel stamps ctime from the coarse clock,
so two writes microseconds apart share a timestamp and the guard sees no
change. redo returns Ok where the test demands Err, which is why it fails
almost every run rather than intermittently.

Measured on this builder: 196/200 back-to-back write pairs on /dev/shm and
192/200 on the root filesystem produced an identical ctime. That also retires
the premise of 82363cb: /dev/shm does not buy finer timestamps here, so moving
the suite to tmpfs could never have fixed this, and the comment saying it does
is corrected. A probe cannot decide this at runtime either -- one using stat
reports the filesystem as fine-grained, because the stat subprocess alone
costs more than the granule it is trying to measure.

Both halves belong upstream in thisisgm/flea: the test assumes a resolution
the kernel never promised, and the identity it exercises cannot see a
same-granule edit, which is a real hole in undo/redo rather than only a test
artifact. Skipping one test is the narrow fix; holding the package back would
have stalled the other 26.

Only this test is affected. new_file_is_recreated_but_changed_or_replaced_
files_survive_undo asserts the same refusal and passes, because enough work
separates its write from the identity capture to cross a granule.

Verified with bin/repo build --package flea: filesystem suite 63 passed,
main suite 528 passed, flea 0.2.1-3 built.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 16:14:52 +02:00
Spencer Bull ecb967aa35 Merge pull request #395 from f-trycua/chore/cua-driver-0.27.0
Update cua-driver-bin to 0.27.0
2026-09-12 00:43:12 -05:00
Francesco BonacciandGPT-5.6 Sol XHigh 427720b02c Update cua-driver-bin to 0.27.0
Co-Authored-By: GPT-5.6 Sol XHigh <noreply@openai.com>
2026-09-12 00:01:17 -05:00
Spencer Bull fa2010ae63 Merge pull request #358 from omacom/t3code/update-hermes-package-version
Update Hermes Desktop to 2026.9.7 and use normal release promotion
2026-09-11 23:59:51 -05:00
Ryan Hughes 5083e02722 Merge pull request #407 from omacom/fix/flea-tmpfs-tests
fix: run flea filesystem tests on tmpfs
2026-09-11 20:19:14 -04:00
Ryan Hughes 82363cbd9d fix: run flea filesystem tests on tmpfs 2026-09-11 20:17:21 -04:00
Ryan Hughes fbd7a4e63a Merge pull request #406 from omacom/fix/1password-desktop-filename
fix: package renamed 1Password desktop file
2026-09-11 19:53:26 -04:00
Ryan Hughes 364d76e46b fix: package renamed 1Password desktop file 2026-09-11 19:50:55 -04:00
Ryan Hughes f711dbb111 Merge pull request #403 from omacom/chore/flea-0.2.1
Update flea to 0.2.1
2026-09-11 18:43:48 -04:00
Ryan Hughes b108406a42 Update flea to 0.2.1 2026-09-11 18:41:25 -04:00
Ryan Hughes 8a24c16b6e Merge pull request #392 from omacom/chore/update-strata-flea
Update strata to 0.15.0 and flea to 0.2.0
2026-09-11 18:24:35 -04:00
Ryan Hughes 92c9924515 Run Strata search fixtures on tmpfs for overlay builders 2026-09-11 18:22:10 -04:00
Ryan Hughes c98a58be34 Merge current master and use landed sync fixture fix 2026-09-11 18:20:40 -04:00
Ryan Hughes 1b770cc72b Merge pull request #393 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-11 17:47:11 -04:00
Ryan Hughes 953bc61515 Merge branch 'master' into auto/sync-upstream 2026-09-11 17:44:45 -04:00
Ryan Hughes 3d745a1c0b Merge pull request #396 from f-trycua/fix/sync-upstream-fixture
test: stabilize 1Password sync fixture
2026-09-11 17:44:19 -04:00
dhh 5a80694a56 chore: sync upstream releases 2026-09-11 21:04:30 +00:00
Francesco Bonacci 4b5aac67d3 test: stabilize 1password sync fixture 2026-09-11 01:33:17 -05:00
Ryan Hughes a42c2e2976 Make package validation independent of concurrency and release drift 2026-09-11 00:31:55 -04:00
Ryan Hughes 1961dd61b3 Update strata to 0.15.0 and flea to 0.2.0 2026-09-11 00:28:10 -04:00
Ryan Hughes c31ef469c5 Merge pull request #354 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-11 00:26:37 -04:00
Ryan Hughes add11b04f6 Merge pull request #363 from omacom/auto/sync-aur
chore: sync AUR packages
2026-09-10 23:47:00 -04:00
ryanrhughes c02a0f2166 chore: sync AUR packages 2026-09-11 03:35:00 +00:00
dhh 6ce22c3670 chore: sync upstream releases 2026-09-10 20:59:14 +00:00
Spencer Bull d2d1fd79ed Merge pull request #348 from spencerbull/add-claude-desktop
Add claude-desktop, Anthropic's Linux desktop beta
2026-09-10 15:30:35 -05:00
959fa52508 Keep every Cowork optdepend and shim x86_64-only
virtiofsd sat in the common optdepends, so aarch64 still advertised one third of a Cowork stack the package deliberately promises nothing else of there. It joins qemu and the firmware in optdepends_x86_64, and the virtiofsd shim moves under the same branch, so an aarch64 package carries no Cowork pieces at all.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-10 08:32:08 -05:00
ReenigneArcher aa0eb88d8c fix(sunshine): patch packager build flags 2026-09-10 08:54:24 -04:00
Jonty Brook d5a2f30de7 Update Herdr to 0.9.0 for upstream session and clipboard fixes (#345) 2026-09-10 11:15:24 +02:00
Ryan Hughes 8eb8427b0a Merge pull request #372 from jdx/codex/mise-2026.9.4
chore(mise): bump to 2026.9.4
2026-09-10 02:09:19 -04:00
cef906d03a Make the Cowork optdepends deliver Cowork, and name the Claude Code sandbox deps
The app probes Debian's paths for its VM stack: /usr/libexec/virtiofsd then /usr/bin/virtiofsd, and /usr/share/OVMF/OVMF_CODE_4M.fd with the VARS path derived from it. Arch ships virtiofsd at /usr/lib/virtiofsd and the firmware at /usr/share/edk2/x64/OVMF_CODE.4m.fd, so installing the advertised optdepends still left Cowork reporting QEMU missing. Symlinks at the probed paths map them onto Arch's; the app's probe reads through them, they dangle harmlessly until the optdepends arrive, and /usr/libexec keeps the virtiofsd link out of $PATH while it does.

The firmware optdepend moves into optdepends_x86_64, and aarch64 loses its Cowork optdepends entirely: the aarch64 builds run against Arch Linux ARM, which ships no UEFI firmware package at all, so both the edk2 name and the qemu-system-aarch64 entry promised a VM that cannot boot there.

The embedded Claude Code diagnoses missing sandbox dependencies when bubblewrap and socat are absent; they join the optdepends under the same wording as the standalone claude-code package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-09 22:56:10 -05:00
Jim Martin ccecdbde56 omarchy-settings: keep the Limine and mkinitcpio drop-ins on aarch64
The aarch64 packages removed etc/mkinitcpio.conf.d and
etc/limine-entry-tool.d wholesale. On an encrypted aarch64 install that boots
with Limine (DGX Spark, Snapdragon X), installing the published package
therefore drops omarchy_hooks.conf, HOOKS falls back to /etc/mkinitcpio.conf,
and the next kernel update builds an initramfs with no encrypt hook: the
machine hangs waiting for /dev/mapper/root with no prompt.

Ship both drop-in directories on aarch64 and remove only
thunderbolt_module.conf, whose module ARM kernels do not have. Keep the
Limine template and the snapper notifier autostart for the same reason. The
memory-stack removals (zram, oomd, zswap, USB autosuspend) are unchanged.
2026-09-09 22:36:53 -05:00
Spencer Bull be95483566 Merge pull request #375 from omacom/update-cua-driver-0.24.0
Update cua-driver-bin to 0.24.0
2026-09-09 19:15:39 -05:00
Spencer Bull 09b5f48136 Update cua-driver-bin to 0.24.0 2026-09-09 19:07:50 -05:00
Spencer Bull f538ae7fa6 Merge pull request #306 from omacom/add-cua-driver-bin
Add cua-driver-bin, the Cua computer-use driver, to the fast ring
2026-09-09 18:33:45 -05:00
Spencer Bull 623a6216e7 Merge pull request #347 from f-trycua/fix/cua-driver-release-pagination
Fix Cua Driver release discovery across feed pages
2026-09-09 17:28:10 -05:00
Spencer BullandCodex GPT-6 XHigh 9779715f16 Fix large Cua release pages and isolate hook fixtures
Feed GitHub release pages to jq through stdin so real pages do not exceed Linux argument limits. Generate an oversized fixture response in the curl mock, and run hook fixtures against a temporary pinned PKGBUILD so routine package updates do not break repository self-tests.

Co-Authored-By: Codex GPT-6 XHigh <noreply@openai.com>
2026-09-09 17:19:40 -05:00
default c3b7a8bc4e chore(mise): bump to 2026.9.4
AI-assisted — Tool: Codex; model: unavailable; version: unavailable.
2026-09-09 21:43:33 +00:00
Ryan Hughes a44e2d2e49 Merge pull request #362 from omacom/fix/sunshine-aur-arm
Fix Sunshine AUR sync and ARM submodule checkout
2026-09-09 00:06:15 -04:00
Ryan Hughes 4023356d3f Fix Sunshine AUR sync and ARM submodule checkout 2026-09-08 23:53:38 -04:00
Ryan Hughes 60a6b0f0c7 Merge pull request #361 from omacom/fix/intel-ipu7-jsoncpp-rebuild
Rebuild Intel IPU7 camera against updated jsoncpp
2026-09-08 23:08:06 -04:00
Ryan Hughes 72a628aead Rebuild Intel IPU7 camera against updated jsoncpp 2026-09-08 23:05:29 -04:00
Ryan Hughes fb675306e0 Merge pull request #360 from omacom/fix/isolated-package-builds
Isolate package builds to prevent release/dev dependency conflicts
2026-09-08 22:52:01 -04:00
Ryan Hughes fc3226ff94 Build each package in an isolated container 2026-09-08 22:49:08 -04:00
Ryan Hughes 5902ed9596 Update Flea to 0.1.5 and repair upstream source verification 2026-09-08 16:16:28 -04:00
Spencer Bull ff6264f633 Move Hermes Desktop out of the fast release ring 2026-09-08 14:43:28 -05:00
Ryan Hughes abd7606301 Release omarchy 4.0.3 2026-09-08 14:33:49 -04:00
Spencer Bull 9c7f00351c Update hermes-desktop to 2026.9.7 2026-09-08 12:17:07 -05:00
Ryan Hughes b3c1ef8605 Merge pull request #327 from cmyk/update-flea-0.1.4
Update Flea to 0.1.4
2026-09-08 02:23:55 -04:00
Ryan Hughes 63692b18b3 Release omarchy 4.0.3rc1 2026-09-07 22:01:05 -04:00
Spencer Bull 0b51b8ac8b Add claude-desktop, Anthropic's Linux desktop beta
Anthropic ships the Claude desktop app for Linux only through its own
Debian repository, so the package repacks the deb the way
openai-codex-desktop does: extract data.tar.xz, replace the bare
/usr/bin symlink with a launcher that asks Chromium for Wayland
directly, and keep the vendor copyright as the license.

Updates come through the declarative "debian" upstream provider: the
apt Packages index names the newest version, and the per-architecture
pool debs are hashed when one appears. Verified by pinning 1.44121.2
and watching bin/sync-upstream rewrite it back to 1.46388.2
byte-identically.
2026-09-07 20:56:20 -05:00
Ryan Hughes 3eba5f7510 Release omarchy 4.0.3rc1 2026-09-07 21:43:00 -04:00
Francesco Bonacci 2b2e880eed fix: paginate Cua Driver release discovery
Build on #306 without replacing its package or updater workaround. Exhaust the component feed before selecting a release and cover quarantine, version ordering, invalid input, and transport failures with offline fixtures.
2026-09-07 17:57:46 -05:00
Ryan Hughes 2b15c13e86 advance: skip same-name files with differing bytes instead of failing
Incremental advances collide with same-version artifacts that reached the
destination through another lineage — the stable -> rc bootstrap seed and
native rc builds are not byte-identical to edge's builds of the same
version. A collision means the package has not moved in the source since,
so keep the destination's published copy and continue; the hard failure
also aborted before the db rebuild and sync, leaving the advance half-done.

Also stop advancing pinned packages into rc: eligibility deferred to
package_builds_for_mirror, whose OMARCHY_RC_PINS gate is never set during
an advance, so edge's omarchy/omarchy-settings looked movable — and could
have overtaken an in-flight RC pin under a fresh filename.
2026-09-07 17:53:26 -04:00
Afonso Oliveira 3015e9f90e Exclude the removed YT6801 private implementation from DKMS sources 2026-09-07 22:25:36 +01:00
Afonso Oliveira 63f9583d47 Fail closed when YT6801 interface verification cannot read inputs 2026-09-07 22:20:24 +01:00
Afonso Oliveira a0f23f35e5 Merge remote-tracking branch 'refs/remotes/portfolio/master' into codex/portfolio-263-20260907 2026-09-07 22:18:42 +01:00
Ryan Hughes 18d1d01a13 Update linux-ptl to 7.2.3 2026-09-07 14:36:02 -04:00
David Heinemeier HanssonandClaude Opus 5 8b3ac7de1a 1password: start at a fixed device scale factor (#342)
1Password reads the display scale itself, the way Electron apps do, so
on a scaled monitor it comes up oversized next to every other window.
Pin it in the .desktop we install and let the compositor scale it.

Rewriting Exec on the way in is how spotify, perplexity and sublime-text
already fix up their entries here. Only the stable package: 1password-beta
syncs from the AUR, so a patch there would be overwritten.


Claude-Session: https://claude.ai/code/session_01JB9phxP56gnP7qSidkkUJE

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-07 19:56:25 +02:00
Ryan Hughes a190c0e6b4 Merge pull request #333 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-07 11:38:53 -04:00
Ryan Hughes 44607ecfe7 Bump omakade to 1.7.0 with its new build requirements
1.7.0 makes OpenSSL and libzstd hard CMake requirements; both are in
every build chroot transitively, so the sync's version-only bump in
auto/sync-upstream would have built and shipped them as undeclared
linkage rather than failing. Declare them, and pin the new optional
Wayland idle-inhibit feature on by adding wayland-protocols to
makedepends instead of leaving it to what the chroot happens to carry.
Checksum matches the release's SHA256SUMS manifest and an independent
download.
2026-09-07 11:38:13 -04:00
Ryan Hughes 6d56c03c7a Bump t3code-bin to 0.0.39 ahead of its release-age hold
Released 06:46 UTC today, inside the quarantine window; shipped by hand
per BYPASS_MIN_RELEASE_AGE's intent. The feed's asset name matches the
one the PKGBUILD builds, the download's size and SHA-512 match the
electron-builder feed exactly (byte-identical to what the app's own
updater installs), and the SHA-256 here is from that verified download.
2026-09-07 11:27:08 -04:00
dhh b7f71ec9e2 chore: sync upstream releases 2026-09-07 12:47:14 +00:00
Spencer Bull 626542be59 Merge pull request #336 from spencerbull/fix-hermes-desktop-launch
Fix Hermes desktop launch settings (2026.8.31-3)
2026-09-07 04:25:48 -05:00
Spencer BullandCodex XHigh f69f6ce364 Fix Hermes desktop launch settings and sandbox consistency
Release 2026.8.31-3 preserves upstream desktop settings and explicit launch environment without invoking the CLI sandbox setup. Keep incomplete runtimes from blocking the packaged fallback, and use the namespace sandbox consistently in both locations.

Co-Authored-By: Codex XHigh <noreply@openai.com>
2026-09-07 04:10:07 -05:00
Spencer Bull bf842f5a6f Merge pull request #335 from spencerbull/restore-hermes-desktop-package
Restore prebuilt Hermes with native in-app updates
2026-09-07 03:42:57 -05:00
Spencer Bull 0c2fa676f6 Use the next Hermes package release number 2026-09-07 03:35:15 -05:00
Spencer BullandGPT-6 Codex 2fb9ab2ba9 Launch native Hermes without privileged sandbox setup
Use the same direct executable path for menu launches and URLs, require working user namespaces, and retain only the first-update relaunch gate backport. This avoids the upstream CLI fallback that makes a helper in the user runtime setuid-root.

Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
2026-09-07 03:17:42 -05:00
Spencer BullandGPT-6 Codex 9588b28cf6 Prepare packaged Hermes for native in-app updates
Track main while pinning the initial release commit. Ship the matching upstream installer and Linux namespace sandbox backport, and launch the native user build prepared by Omarchy.

Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
2026-09-07 02:55:06 -05:00
Spencer Bull 3356e8c04c Restore packaged Hermes Desktop at 2026.8.31-2
Revert the native installer and updater packaging introduced by #325. Keep the prebuilt desktop and existing launcher, updating only the release tag, commit and archive checksum from the previous recipe.
2026-09-07 02:01:52 -05:00
Ryan Hughes ea9f615884 Bump strata to 0.11.2 ahead of its release-age hold
0.11.2 is 14 hours old, inside the package's 24h min_release_age, so the
upstream sync would not pick it up until tomorrow; ship it now by hand.
Tarball commit b000b79 verified against the v0.11.2 tag, checksum from
an independent download. The new Cargo dependencies (ashpd, zbus,
gdk4-wayland) are vendored crates needing no new system libraries.

0.11.2 also ships FileManager1/portal integration files. Stage the
FileManager1 service under /usr/share/strata the way upstream's own
PKGBUILDs do — the app copies it per-user on opt-in. The portal files
stay unpackaged, matching upstream: enablement is per-user and
consent-gated in the app.
2026-09-07 02:38:01 -04:00
Ryan Hughes a5d95385ca Merge pull request #325 from spencerbull/hermes-native-updates
Let Hermes Desktop update its native installation
2026-09-07 02:22:49 -04:00
Ryan Hughes 0518902619 Merge pull request #296 from omacom/auto/sync-aur
chore: sync AUR packages
2026-09-07 01:27:50 -04:00
ryanrhughes 6dc21c7763 chore: sync AUR packages 2026-09-07 05:26:37 +00:00
Ryan Hughes ca63e47ecd Stop auto-syncing retroarch-joypad-autoconfig-git from the AUR
The package carries a deliberate commit pin (59d557a) so every
architecture packages the same tree. The AUR sync kept trying to revert
that to the unpinned branch-tip recipe, recording a version downgrade in
the process. Mark it local so exactly one source owns the recipe; it is
Omarchy-maintained until it moves to an upstream release feed.
2026-09-07 01:23:14 -04:00
Ryan Hughes 2a3b3b9c36 Merge pull request #322 from btsouth/omakade-1.6.1
omakade: update to 1.6.1 with full runtime deps and aarch64
2026-09-07 00:02:13 -04:00
Tyler South 29c621f835 omakade: update to 1.6.1 with full runtime deps and aarch64 2026-09-07 00:01:38 -04:00
Spencer Bull b36bfce109 Restore Hermes PKGBUILD formatting 2026-09-06 22:53:12 -05:00
Ryan Hughes 34c12d99c7 Merge pull request #313 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-06 23:52:53 -04:00
Spencer Bull beb164a9a2 Resolve Hermes profile homes before package setup 2026-09-06 22:29:49 -05:00
Ryan Hughes cb85e6d289 Merge pull request #331 from omacom/fix/locate-service-defaults
Package the locate service defaults
2026-09-06 21:52:39 -04:00
Ryan Hughes 397cf6f527 Package the locate service options 2026-09-06 21:46:40 -04:00
dhh 908d99ad80 chore: sync upstream releases 2026-09-06 20:32:10 +00:00
David Heinemeier Hansson 2cfa93bfdc Revert "Package Muse Code from Meta's native binaries (#329)" (#330)
This reverts commit 377ca9cdfd.
2026-09-06 21:47:13 +02:00
David Heinemeier Hansson 377ca9cdfd Package Muse Code from Meta's native binaries (#329) 2026-09-06 21:46:10 +02:00
cmyk 06fe54a774 Update Flea to 0.1.4 2026-09-06 18:03:22 +02:00
707b5e6c51 Bump libfprint-git for Synaptics fingerprint reader 06cb:010b (#321)
* Bump libfprint-git for Synaptics 06cb:010b support

* Describe libfprint-git as the driver the fingerprint setup installs

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: powderluv <powderluv@powderluv.org>
Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:57:34 +02:00
Spencer BullandGPT-6 Codex 351f188d02 Register Hermes Desktop only after publishing its native CLI
Keep build-time registration private, verify the published launcher, and then let the native command register the final desktop entry. Keep that launcher first on the desktop environment PATH for subsequent registrations.

Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
2026-09-06 02:21:16 -05:00
Spencer BullandClaude Opus 5 f4adf308f9 Make native Hermes setup retryable without a late sudo step
Stage the initial clone before publishing its ownership marker and build the desktop through the same native CLI used by updates. Keep a concurrent checkout intact and reset pkgrel for the version change.

Co-Authored-By: Claude Opus 5 (default) <noreply@anthropic.com>
2026-09-06 02:17:27 -05:00
Spencer Bull 2b1edd47b2 Revert "Keep in-app updates on the verified Hermes CLI"
This reverts commit 68a4a6cc22.
2026-09-06 02:02:52 -05:00
Spencer Bull 68a4a6cc22 Keep in-app updates on the verified Hermes CLI 2026-09-06 01:59:58 -05:00
Spencer BullandGPT-6 Codex 01e1a8de0f Exercise lock release on a cold terminal launch
Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
2026-09-06 01:57:42 -05:00
Spencer BullandGPT-6 Codex 341299f477 Let the Hermes package bootstrap native desktop updates
Install Hermes into its writable native layout instead of shipping a frozen /opt desktop. Preserve the native update path, migrate tagged bootstraps, and keep existing CLI launchers until the desktop is ready.

Co-Authored-By: GPT-6 Codex (xhigh) <noreply@openai.com>
2026-09-06 01:56:23 -05:00
Ryan Hughes b81d678b3f Merge pull request #320 from omacom/fix-check-versions-carch
Read PKGBUILDs with CARCH set; unblock the channels stuck on 1password
2026-09-05 17:08:58 -04:00
Ryan Hughes d673d67a1c Read PKGBUILD variables with CARCH set and refuse to queue a package whose version cannot be read
makepkg always exports CARCH, so PKGBUILDs may branch on it at file
scope. Every place the tooling sourced a PKGBUILD did so without CARCH,
taking the wrong branch or aborting partway, and check-versions turned
the resulting empty pkgver/pkgrel into the version '-', which never
matches a published version and queues an endless rebuild.

package_pkgbuild_var reads one variable the way makepkg would see it,
for the architecture being checked, and reports whether the source
succeeded. check-versions, sync-rebuilds and omarchy-pkgs use it.
check-versions now warns and skips a package whose pkgver or pkgrel is
empty instead of comparing a partial version. A self-test covers a
PKGBUILD that branches on CARCH before assigning its version.
2026-09-05 16:59:22 -04:00
Ryan Hughes d57a9955d5 1password: map the tarball architecture inside package() so the PKGBUILD's version can be read without CARCH
The file-scope case on CARCH ended in 'return 1', which aborted any
'source PKGBUILD' that did not export CARCH before pkgver and pkgrel
were assigned. check-versions reads PKGBUILDs that way, saw an empty
version, queued 8.12.34-35 on every tick, and promotion then refused to
overwrite the already-published artifact. Every channel has been in
backoff since 06:41 UTC today because of it.
2026-09-05 16:59:22 -04:00
Ryan Hughes cb7a1a8868 Merge pull request #317 from omacom/add-omareel
Add omareel, a screen recorder and editor for Omarchy
2026-09-05 15:27:46 -04:00
Ryan Hughes 2944f5e802 omareel: pin the v0.1.0 archive checksum 2026-09-05 14:47:12 -04:00
Ryan Hughes 3682eaf718 Add omareel, a screen recorder and editor for Omarchy 2026-09-05 14:20:51 -04:00
David Heinemeier Hansson 5d656b0b8a Add original vi package (#314) 2026-09-05 16:07:29 +02:00
Ryan Hughes fe0a3da325 Merge pull request #312 from omacom/fix/upstream-sync-git-dependency
Fix missing Git in upstream sync container
2026-09-05 02:36:11 -04:00
Ryan Hughes 7397d1fbfd Install Git for upstream syncs 2026-09-05 02:28:08 -04:00
Ryan Hughes bceab72f8c Merge pull request #277 from maralcbr/omacom/aarch64-host-pipeline
Make aarch64 a first-class architecture in the scheduled pipeline
2026-09-05 02:24:36 -04:00
Ryan Hughes ca99c24b01 Add ARM builds for 1Password and Voxtype 2026-09-05 01:09:11 -04:00
Ryan Hughes de57b5dfc2 Publish only intended package artifacts 2026-09-05 00:07:11 -04:00
Ryan Hughes 520dd5c3bb Repair aarch64 package coverage 2026-09-04 23:40:49 -04:00
Ryan Hughes 0b67dbab8e Harden emulated ARM builds 2026-09-04 23:40:49 -04:00
Ryan Hughes 9e9acb071a Support rootless Podman builds 2026-09-04 23:40:49 -04:00
Ryan Hughes 76687fcc82 Harden multi-architecture release pipeline 2026-09-04 23:40:49 -04:00
Marcelo Alcantara b677f50358 Pin PKGEXT to .pkg.tar.zst in the builder image
Arch Linux ARM's makepkg.conf defaults PKGEXT to .pkg.tar.xz. build/sign.sh
only signs *.pkg.tar.zst, and push-build, sync-rebuilds and the notifier
parse the same suffix, so an aarch64 package built under the stacked
pipeline came out as .xz and would have been skipped at signing. Seen on a
plain x86_64 runner building aarch64 under QEMU (fork run 33642125077).
Same fix as the PKGEXT line in #240.
2026-09-04 23:40:49 -04:00
Marcelo Alcantara 91843ab099 Make aarch64 a first-class architecture in the scheduled pipeline
One list, PUBLISHED_ARCHES in helpers/paths.sh (default x86_64,
overridable with OMARCHY_ARCHES), now drives everything the repository
host schedules. check-versions compares PKGBUILDs against each
architecture's channel databases and writes one queue per channel and
architecture; auto-release works through the queues one architecture at
a time, each with its own backoff, so a failing build on one never
blocks the other; advance-channel --arch all re-runs an advance for every
published architecture and omarchy-release uses it for start and ship,
building the pinned pair once per architecture in its rc trigger; the
train observes channels through the reference (first) architecture
instead of a hard-coded x86_64. Queue and backoff files written under
the old per-channel names are treated as x86_64 until consumed.

Two things made an aarch64 builder image impossible to create: the
keyring bootstrap fetched omarchy-keyring from the target architecture's
own channel tree, which does not exist before that architecture has
published anything, and the QEMU probe only knew the x86_64-host,
aarch64-target case. The keyring (arch=any) now always comes from the
x86_64 tree, and the probe compares host and target architectures and
runs a container for the target platform.

clean-repo grouped versions with a regex that only knew any, x86_64 and
i686, so aarch64 packages would never have been pruned.
2026-09-04 23:40:49 -04:00
Spencer Bull 069ffc8c3a Merge pull request #297 from spencerbull/fix/perplexity-lib-conflict
perplexity: fix /lib filesystem conflict breaking every install of 26.9.1
2026-09-04 21:52:57 -05:00
Spencer Bull 32bc673863 Merge pull request #307 from spencerbull/add-openclaw
Add OpenClaw to the fast ring, following the npm registry
2026-09-04 21:49:50 -05:00
Spencer Bull fe409baf00 Add OpenClaw to the fast ring, following the npm registry
Package OpenClaw 2026.9.1 as a local PKGBUILD based on the AUR one,
tracking the npm registry's latest dist-tag through the repository's
declarative npm upstream provider: upstream's release cadence outruns
the AUR maintainer, and the dist-tag is the stable channel where a plain
version-max would ship next cycle's betas. A 24h min_release_age
quarantines fresh releases, which matters more than usual here because
the npm tarball is not vendored: package() resolves ~330 transitive
dependencies from the live registry without integrity pins. The pinned
sha256 was verified against the registry by hand. The initial pin was
taken inside its quarantine window through the documented
BYPASS_MIN_RELEASE_AGE maintainer path, deliberately, and lands through
this reviewed change as that path intends.

The AUR post_upgrade restart attempt is replaced with printed guidance:
it targeted a nonexistent openclaw.service, and the real
openclaw-gateway.service is a systemd user unit a root pacman hook
cannot reach (voxtype-bin sets the precedent).

The builder ships npm 12, which refuses install-time lifecycle scripts unless
the package is allow-listed, and for a local tarball the allow-list key is the
tarball's own file: spec rather than the package name. Without it openclaw's
postinstall never runs, the .openclaw-lifecycle-pending marker ships in the
package, and every invocation dies trying to finish the lifecycle inside the
root-owned /usr/lib/node_modules/openclaw. package() now passes
--allow-scripts and fails the build if the marker survives.

That postinstall also runs upstream's legacy-state migration against whatever
home it sees, so the npm call gets a scratch HOME under $srcdir with the
OPENCLAW_* location overrides unset: a maintainer's own ~/.openclaw is not
the build's to prune.
2026-09-04 21:15:37 -05:00
Spencer Bull 8ac12ec7d6 cua-driver-bin: point the binary's self-updater at pacman
cua-driver update --apply pipes the vendor installer into bash, which
installs a second copy under ~/.cua-driver and links it into ~/.local/bin,
stepping around pacman and the repository's release gate. Upstream offers
no switch for that path, and a /usr/bin wrapper would not cover it either:
the MCP configurations the binary generates record the resolved executable.

prepare() rewrites the installer URL inside the binary, in place and at
equal length, to file:///usr/lib/cua-driver/pm.sh, a stand-in that declines
and names pacman. The build asserts the URL appears exactly twice before
the rewrite and not at all after it, so an upstream change to the updater
stops the build instead of shipping a live self-updater.

Verified in a clean archlinux:base container: pacman -Qkk is clean, the
CLI and cursor-theme helper still run, and on the nightly channel
update --apply prints the notice, exits 1, and creates nothing under
~/.cua-driver/packages or ~/.local/bin.
2026-09-04 17:23:55 -05:00
Spencer Bull d9127d7124 Add cua-driver-bin, the Cua computer-use driver, to the fast ring
cua-driver ships prebuilt from the trycua/cua monorepo release feed. The
declarative github provider reads a release feed as a single product and
trips on the monorepo's foreign and hyphenated tags, so a bespoke
.omarchy/upstream.sh selects the newest stable cua-driver-rs release by
tag shape (upstream flags every driver release prerelease; nightlies are
distinguished by tag prefix instead) and reads its checksums.txt manifest.

The vendor tree stays together under /usr/lib/cua-driver with a /usr/bin
symlink, matching upstream's own layout: the CLI resolves its cursor-theme
compiler as a sibling of /proc/self/exe. Verified by installing the built
package into a clean archlinux:base container and exercising the CLI.
2026-09-04 16:52:37 -05:00
Spencer Bull 1fa158942a perplexity: make the stray-entry guard type-blind
Review caught that the allowlist only listed files and symlinks, so a
future deb shipping an empty top-level bin/, sbin/ or lib64/ -- each a
filesystem-owned symlink here, the exact conflict class this guard
exists to close -- would pass it, as would FIFOs and device nodes.

Delete the one known unit, rmdir its emptied parents, and treat any
remaining entry outside opt/ and usr/ as unexpected, whatever its type.
This also stops silently rm -rf'ing future /lib content: anything new
there now fails the build for a human to look at instead.

Verified: clean build ships only etc/, opt/ and usr/; an injected empty
bin/, a stray lib64/ file, and a FIFO each abort package() with the
entry listed. Built via bin/build; installs clean in a fresh container.
2026-09-04 16:16:54 -05:00
99234a4fbb Add schist-bin, the Schist image editor, to the fast ring (#293)
Schist is a layered image editor with PSD, Affinity and camera raw support,
developed by Infrawrench and packaged by its upstream author. The package
re-wraps the pacman-format payloads Schist's release workflow publishes for
x86_64 and aarch64, so the builder does no compiling, and both assets are
pinned by SHA-256.

Releases are tracked declaratively through the GitHub upstream provider,
which gains a "digests": true mode here: a vendor that publishes no checksum
manifest can have each asset's SHA-256 read from the digest GitHub's release
API reports, so the sync never downloads the artifacts. Exactly one of
"checksums" or "digests" must be set, and the provider enforces that itself
because scheduled runs reach it without the metadata validator.

Fresh releases wait 24 hours before the scheduled sync picks them up, as
mise-bin already does. vulkan-driver is an optional dependency rather than a
hard one: makepkg -s would otherwise satisfy the virtual package with
nvidia-utils in the build container, and Omarchy installs a Vulkan driver per
machine.

Co-authored-by: David Heinemeier Hansson <david@hey.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 13:09:12 +02:00
Jessyka_boatandClaude Opus 5 f620e9ee6b Add slap-notes-bin
Local-first block notes with a wiki-link graph and a built-in AI research agent. Electron over a Next.js server, x86_64 only, repackaged from the vendor tarball with a Wayland launcher.

Not in the AUR. The package follows its GitHub release feed through the declarative github upstream provider, reading each release's SHA256SUMS, with a 24h min_release_age quarantine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 12:29:12 +02:00
Spencer Bull 55bc67834b perplexity: stop shipping upstream's /lib, fail closed on strays
26.9.1 added /lib/systemd/system/perplexity-local-runtime-setup.service
to the deb, and wholesale extraction made the package own /lib -- a
symlink owned by filesystem -- so pacman refused every install and
upgrade. The unit could never work here anyway: its setup script
apt-installs Docker and the NVIDIA Container Toolkit and exits on any
distro but Ubuntu, so the Arch equivalents ride optdepends instead.

package() now allowlists what leaves the deb: opt/, usr/, and that one
known unit path (deleted). Anything else stops the build rather than
shipping the next filesystem conflict.

Verified in a clean container: the published -1 reproduces the /lib
conflict; -2 installs fresh and upgrades from 26.8.4 cleanly.
2026-09-03 22:52:22 -05:00
Ryan Hughes 7860c4b2e4 Merge pull request #195 from oceanapplications/aarch64-arch-support
Own and build nine ARM-compatible packages
2026-09-03 22:19:39 -04:00
Ryan Hughes b89770c1da Expand declarative upstream providers 2026-09-03 22:13:59 -04:00
Ryan Hughes d9705d0e2f Own ARM-compatible recipes and sync upstream directly 2026-09-03 22:04:04 -04:00
Ryan Hughes c3c1f8fbab Merge pull request #276 from maralcbr/omacom/builder-flags
Add builder flags for CI and resumed builds
2026-09-03 21:52:12 -04:00
Ryan Hughes a2e29a9463 Merge pull request #275 from maralcbr/omacom/pkgbuild-portability
Build the omarchy package pair for aarch64
2026-09-03 21:52:08 -04:00
Ryan Hughes 18f11555b6 Merge pull request #226 from jeremydixon22/update/jetbrains-mono-nerd-3.5.1
Update JetBrains Mono Nerd Font to 3.5.1
2026-09-03 19:23:15 -04:00
Ryan Hughes 0d803dd825 Merge pull request #236 from omacom/auto/sync-rebuilds
chore: rebuild against updated dependencies
2026-09-03 19:08:42 -04:00
Ryan Hughes 7f9726c00e Merge pull request #291 from omacom/sync-flea-0.1.3
Update Flea to 0.1.3
2026-09-03 18:06:57 -04:00
Ryan Hughes 05a4119b43 Update Flea to 0.1.3 2026-09-03 18:05:52 -04:00
dhh d7d79c20a1 chore: rebuild against updated dependencies 2026-09-03 21:18:13 +00:00
Ryan Hughes 0d94ae5b38 Merge pull request #261 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-03 15:10:41 -04:00
Ryan Hughes 1dfa9be45b Merge pull request #273 from omacom/auto/sync-aur
chore: sync AUR packages
2026-09-03 15:10:14 -04:00
Ryan Hughes 96f3a194e1 Merge pull request #290 from omacom/fix-flea-builder-tests
Fix Flea tests in constrained builders
2026-09-03 14:23:38 -04:00
Ryan Hughes 89e6e4d1cc Fix Flea tests in constrained builders 2026-09-03 14:20:07 -04:00
Ryan Hughes 3a78ac6eb4 Merge pull request #289 from omacom/add-strata-upstream-sync
Add safe upstream release sync for Strata
2026-09-03 14:16:32 -04:00
Ryan Hughes fc27de8a2e Add safe upstream sync for Strata 2026-09-03 14:03:25 -04:00
Ryan Hughes dcb8211050 Merge pull request #288 from omacom/add-flea-fast-ring
Add hardened Flea package to fast ring
2026-09-03 13:57:06 -04:00
Ryan Hughes 09e41d7d88 Add hardened Flea package 2026-09-03 13:41:54 -04:00
dhh 45b077387e chore: sync upstream releases 2026-09-03 16:33:12 +00:00
ryanrhughes 66f86213fb chore: sync AUR packages 2026-09-03 16:01:03 +00:00
Ryan Hughes 48e60a6539 Merge pull request #281 from omacom/add-strata-fast-ring
Add Strata file manager to fast ring
2026-09-03 00:44:35 -04:00
Ryan Hughes 1e6cde3916 Add Strata file manager 2026-09-03 00:43:30 -04:00
Marcelo Alcantara 59d557ad5e Pin retroarch-joypad-autoconfig-git to a commit
The recipe fetched the branch tip, so a rebuild on another architecture
could package a different tree than the one already published. Pin the
source to a commit and set pkgver to what that commit describes
(1.22.0.r96.g0331510, as an aarch64 build of it reports); bump both
together from now on.
2026-09-02 23:15:55 +10:00
Marcelo Alcantara 191e1e7db5 Add builder flags for CI and resumed builds
Three opt-in knobs for bin/build, each defaulting to today's behaviour:

OMARCHY_KEEP_BUILD_WORKSPACE=1 keeps build-output/$MIRROR/$ARCH instead
of wiping it, and build/build.sh now folds any packages already there
into omarchy-build.db even when no database exists yet, so packages
built by an earlier job (or a previous, interrupted run) resolve as
dependencies of what builds next.

OMARCHY_SKIP_BUILDER_IMAGE=1 uses the omarchy-pkg-builder image already
present instead of building it, so a workflow can build the image once
with an external BuildKit cache and fan out over package jobs that all
run the same bytes. A missing image is an error, not a silent rebuild.

OMARCHY_DEFER_RUNTIME_DEPS=true builds the omarchy/omarchy-settings pair
with --nodeps, installing only their makedepends and checkdepends
explicitly. The pair depends on each other and on packages a sharded
pipeline builds in other jobs, so they cannot resolve in isolation; the
assembled set is installed in one verified transaction downstream. The
request is refused for anything but exactly that pair, on the host
before Docker starts and again inside the container.

Also fix make_dir_writable: chown -R can succeed on part of the tree
and fail on files a previous container left behind as another uid, and
the old `|| chmod` fallback only ran when chown failed outright. Always
follow with chmod.
2026-09-02 23:15:30 +10:00
Marcelo Alcantara 4ed5f14629 Build the omarchy package pair for aarch64
The Omarchy payload is architecture-independent, but the package is not.
On x86_64 omarchy pulls the Limine + mkinitcpio hook + Snapper boot stack;
on Apple Silicon the system boots through m1n1 + GRUB from the Asahi
packages on Arch Linux ARM's kernel, so that stack does not apply, and
Wi-Fi on the Broadcom parts needs the iwd backend. The shipped /etc tree
differs too: mkinitcpio reads every file under /etc/mkinitcpio.conf.d/,
so shipping omarchy_hooks.conf on aarch64 injects the Limine hooks into
the Asahi kernel's initramfs, and the zram/zswap/oomd drop-ins and the
zram-tuned vm.* sysctls belong to the x86_64 memory stack.

makepkg only honours depends_<arch> and optdepends_<arch> on
arch-specific packages, so arch=('any') becomes ('x86_64' 'aarch64').
backup=() has no arch-suffixed form, so the x86_64-only entries are
appended under CARCH and each of those paths is removed from the aarch64
package in package(). The x86_64 package keeps exactly the contents it
had; only its filename suffix changes.

The install scriptlet applies the hardened cups-files.conf on every
platform, then on Apple Silicon keeps the Arch Linux ARM system identity
(/etc/os-release stays the distribution's symlink) instead of the
etc-overrides. The -dev pair carries the same change so the pairs stay in
lockstep.
2026-09-02 21:59:45 +10:00
Ryan Hughes b66905a437 Drop t3code-patched-bin
The environment-theme patches shipped as a forked AppImage while
upstream lacked them. Keeping it current means a hand-built artifact
per release, and it has fallen three behind — 0.0.35 against 0.0.38.

t3code-bin tracks the upstream feed on its own, so drop the fork.
Nothing else in the repo referenced the package.
2026-09-01 23:55:52 -04:00
Ryan Hughes d562ecb388 t3code-bin: 0.0.38 2026-09-01 23:55:52 -04:00
Ryan Hughes b0cba5bf8b Drop obsolete asusctl 6.4.0 GitHub-source patch (#269)
The AUR caught up: asusctl 6.4.0-1 (b0ec6ca) repoints source at the
GitHub repo upstream, which is what our patch existed to do. The new
PKGBUILD uses a release tarball instead of git+, so the patch context
no longer matches and every scheduled Sync AUR Packages run failed
applying it.

Remove the patch and resync from the AUR. With no .omarchy/patches
left, the package is no longer customized, so it tracks 6.4.0-1
without the .1 pkgrel suffix.
2026-09-01 23:30:10 -04:00
Spencer Bull 22e908d831 Merge pull request #250 from spencerbull/perplexity-desktop
Add perplexity, the official Perplexity desktop app
2026-09-01 22:17:04 -05:00
Afonso Oliveira c08171e544 Disable YT6801 private ioctl interface 2026-09-01 22:23:00 +01:00
Ryan Hughes 6774df1001 Merge pull request #259 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-01 10:38:15 -04:00
Ryan Hughes 8550bd3124 Merge pull request #258 from omacom/auto/sync-aur
chore: sync AUR packages
2026-09-01 10:38:04 -04:00
dhh 1bf35283bc chore: sync upstream releases 2026-09-01 11:48:49 +00:00
ryanrhughes a99c58d282 chore: sync AUR packages 2026-09-01 11:19:17 +00:00
Ryan Hughes a3d150e2b0 Merge pull request #253 from omacom/add-link-studio-opr
Add Link Studio to the fast ring
2026-09-01 02:01:09 -04:00
Ryan Hughes 2fad766013 Add Link Studio to the fast ring
Package Link Studio 1.0.2 with its AUR-only MediaPipe and sounddevice dependencies. Wire Link Studio to GitHub release checksums, keep all three packages on the fast ring, and make MediaPipe's Bazel bootstrap a checksummed makepkg source.
2026-09-01 01:55:23 -04:00
Ryan Hughes c6e34f7949 Merge pull request #252 from omacom/add-omakade-opr
Add Omakade to the fast ring
2026-09-01 01:51:10 -04:00
Ryan Hughes a42235e1a5 Add Omakade to the fast ring
Build the upstream 1.2.0 source release for x86_64 and follow stable GitHub releases through the published SHA256SUMS manifest.
2026-09-01 01:25:44 -04:00
Ryan Hughes 899d5030ce Merge pull request #231 from omacom/auto/sync-upstream
chore: sync upstream releases
2026-09-01 01:24:52 -04:00
Ryan Hughes eace5f13a9 Merge pull request #230 from omacom/auto/sync-aur
chore: sync AUR packages
2026-09-01 01:24:29 -04:00
Ryan Hughes 9995058806 Merge pull request #235 from jethrojones/add-omapresent-package
Add omapresent Markdown presentation package
2026-09-01 01:22:08 -04:00
Ryan Hughes e934aa9ee2 Mark omapresent for fast release ring 2026-09-01 01:21:34 -04:00
dhh 68bdaff26e chore: sync upstream releases 2026-09-01 05:20:42 +00:00
ryanrhughes 02e8df179e chore: sync AUR packages 2026-09-01 04:05:40 +00:00
Jethro Jones e5fe2690ce Update omapresent to 0.1.3 2026-08-31 20:23:13 -07:00
Spencer Bull eda9236c67 Add perplexity, the official Perplexity desktop app
Tracks Perplexity's own Debian repository, the feed the app updates
itself from, via .omarchy/upstream.sh -- same shape as
openai-codex-desktop. pkgver carries the build number from the pool
filename because the index's Version field drops it and upstream
rebuilds under the same marketing version; the pool wants the '+'
percent-encoded. The launcher replaces the postinst symlink pacman
never creates and defaults Chromium to Wayland.
2026-08-31 20:49:53 -05:00
Jethro Jones 3b96ebad44 Update omapresent to 0.1.2 2026-08-30 15:48:28 -07:00
Jethro JonesandClaude Opus 5 63b57a773b Update omapresent to 0.1.1
Picks up the audience-window fix released in v0.1.1: the audience view is now
an independent native Wayland top-level, so a compositor or share portal can
offer it on its own instead of only exposing the editor window. Without it,
screen sharing a deck on a call does not work correctly.

pkgver carries the source URL, which interpolates it, so the package now builds
the immutable v0.1.1 tag archive. The checksum is that archive's sha256.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzjoHuTSiuzFsMuJwnuQ7c
2026-08-28 20:52:28 -07:00
Jethro Jones 86d5dd3035 Add omapresent Markdown presentation package 2026-08-28 14:01:18 -07:00
Jeremy Dixon aad293a22d Update JetBrains Mono Nerd Font to 3.5.1 2026-08-27 22:11:38 -04:00
1560 changed files with 439464 additions and 3624 deletions

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
# Trust list for PR builds.
#
# A pull request only builds packages (and spins up builder droplets) when
# its author is trusted: repository collaborators are trusted automatically
# and do not need listing; external contributors listed here are trusted
# too. Anyone else gets the plan only, until a maintainer either adds them
# here or applies the "build-approved" label to that one PR. The label also
# releases GitHub's approval hold for that PR's build and test workflows.
# It remains effective while attached, without vouching for the author's
# other PRs. An explicit denouncement cannot be overridden by the label.
#
# Syntax:
# github:username
# -github:username reason for denouncement
#
# Keep entries sorted alphabetically.
github:bjarneo
github:DanWahlin
github:dhh
github:f-trycua
github:HANCORE-linux
github:kwilczynski
github:ryanrhughes
github:scottjones
github:spencerbull
github:tcballard
github:tobi
+88
View File
@@ -0,0 +1,88 @@
const BUILD = '.github/workflows/build-pr.yml';
const TESTS = '.github/workflows/test.yml';
// pullRequest/action/since default to the pull_request_target event. The
// sync workflows pass them explicitly: GitHub creates no pull_request_target
// run for a GITHUB_TOKEN push, so they release their own pushes' held runs.
module.exports = async function approve({ github, context, core, vouchStatus,
pullRequest = context.payload.pull_request, action = context.payload.action, since,
sleep = ms => new Promise(resolve => setTimeout(resolve, ms)), attempts = 36 }) {
// Missing/failed vouch lookups must not become approval. Denouncements
// remain absolute, just as they are in the package build gate.
if (!['unknown', 'bot', 'collaborator', 'vouched'].includes(vouchStatus)) {
throw new Error(`Cannot approve workflows: vouch status is ${vouchStatus || 'missing'}.`);
}
const expected = pullRequest;
const eventTime = Date.parse(since ?? expected.updated_at);
if (!Number.isFinite(eventTime)) throw new Error('Missing PR event timestamp.');
const approved = new Set();
let precedingBuild;
const stillApproved = async () => {
const { data: pr } = await github.rest.pulls.get({
...context.repo, pull_number: expected.number,
});
return pr.state === 'open' && pr.head.sha === expected.head.sha &&
pr.labels.some(label => label.name === 'build-approved');
};
// The label and PR-run events arrive independently. Wait for the build
// belonging to this event, rather than returning after approving an older
// run and leaving the new label-triggered run stuck behind GitHub's gate.
for (let attempt = 0; attempt < attempts; attempt++) {
if (attempt) await sleep(5000);
if (!await stillApproved()) {
core.info('PR closed, head changed, or build-approved removed; stopping.');
return;
}
const all = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, {
...context.repo, event: 'pull_request', head_sha: expected.head.sha, per_page: 100,
});
const runs = all.filter(run =>
run.event === 'pull_request' && run.head_sha === expected.head.sha &&
run.head_repository?.id === expected.head.repo.id && run.head_branch === expected.head.ref &&
[BUILD, TESTS].includes(run.path) &&
// Fork runs awaiting approval often have no pull_requests entries.
(!run.pull_requests?.length || run.pull_requests.some(pr => pr.number === expected.number))
).sort((a, b) => a.id - b.id);
const newestBuild = runs.findLast(run => run.path === BUILD);
if (!newestBuild || !(Date.parse(newestBuild.created_at) >= eventTime) ||
!runs.some(run => run.path === TESTS &&
(action === 'labeled' || Date.parse(run.created_at) >= eventTime))) continue;
if (precedingBuild) {
const { data: run } = await github.rest.actions.getWorkflowRun({
...context.repo, run_id: precedingBuild,
});
// Approve older builds first, and let them acquire concurrency before
// releasing a newer build. Otherwise an older queued run could start
// last and cancel the label-triggered build that carries approval.
if (!['in_progress', 'completed'].includes(run.status) || run.conclusion === 'action_required') continue;
precedingBuild = undefined;
}
const pending = runs.filter(run => run.conclusion === 'action_required' && !approved.has(run.id) &&
// If the newest build already runs (e.g. a maintainer approved it),
// don't resurrect an obsolete hold that could cancel that newer run.
(run.path !== BUILD || run.id === newestBuild.id || newestBuild.conclusion === 'action_required'));
if (!pending.length) return;
const run = pending[0];
// Recheck after the API reads, immediately before exercising write access.
if (!await stillApproved()) return;
await github.rest.actions.approveWorkflowRun({ ...context.repo, run_id: run.id });
approved.add(run.id);
core.info(`Approved ${run.path} run ${run.id} for PR #${expected.number}.`);
// Only a newer held build needs this one to take the concurrency slot
// first. A lone build may sit pending behind an in-flight build of an
// older commit (sync branches queue rather than cancel); waiting for it
// to start would time out before the tests run was released.
if (run.path === BUILD && pending.some(other => other.path === BUILD && other.id > run.id)) {
precedingBuild = run.id;
}
if (pending.length === 1) return;
}
throw new Error('Timed out waiting for PR workflows. Remove and reapply build-approved to retry.');
};
+34
View File
@@ -0,0 +1,34 @@
const approvePrWorkflows = require('./approve-pr-workflows.cjs');
const BOT = 'github-actions[bot]';
// A sync workflow pushes its branch with GITHUB_TOKEN. GitHub holds the
// resulting pull_request runs for approval and, unlike a person's push,
// creates no pull_request_target run, so approve-pr.yml never sees it. The
// sync workflow therefore releases the runs for the commit it just pushed,
// under the same rule approve-pr.yml applies: only while build-approved is
// on the PR. The sync applies that label itself, so its pushes build without
// a maintainer. It acts only on its own bot-authored, same-repository PR for
// the branch and commit it pushed.
module.exports = async function approveSyncPush({ github, context, core,
number, branch, headSha, since, approve = approvePrWorkflows, ...options }) {
if (!Number.isInteger(number) || !branch || !headSha || !since) {
throw new Error('Missing sync PR number, branch, head SHA or push time.');
}
const { data: pr } = await github.rest.pulls.get({ ...context.repo, pull_number: number });
const repository = `${context.repo.owner}/${context.repo.repo}`;
if (pr.user?.login !== BOT || pr.head.repo?.full_name !== repository ||
pr.base.repo?.full_name !== repository || pr.head.ref !== branch) {
throw new Error(`PR #${number} is not ${BOT}'s ${branch} PR in ${repository}; refusing to approve.`);
}
if (pr.state !== 'open' || pr.head.sha !== headSha) {
core.info(`PR #${number} is closed or has moved past ${headSha}; nothing to approve.`);
return;
}
if (!pr.labels.some(label => label.name === 'build-approved')) {
core.info(`PR #${number} has no build-approved label; its runs stay held.`);
return;
}
await approve({ github, context, core, vouchStatus: 'bot', pullRequest: pr,
action: 'synchronize', since, ...options });
};
+84
View File
@@ -0,0 +1,84 @@
// Whether a PR rides to master on its own once the required checks pass.
//
// The rule is the build gate's, from build-pr.yml: a PR trusted to build is
// trusted to ship. Collaborators, vouched authors and bots are trusted; an
// unknown author is trusted while the PR carries build-approved; a
// denouncement is absolute. Two limits on top of it:
//
// - Only package changes. A PR's workflows, scripts and build tooling never
// run in its own build (build-pr.yml overlays only its package directories
// onto base tooling), so green checks say nothing about them, and after
// merge they run with the publish secrets. Allowed: anything under
// pkgbuilds/, plus the test a package PR brings with it, which is a new
// file under tests/ and lines in test.yml that only run new tests/*.sh.
// test.yml runs on PRs only; an existing test may also run on master
// (builder-images.yml runs tests/build-isolation.sh with packages: write),
// so editing one still needs a maintainer.
// - Not the upstream sync. It labels its own PR build-approved to release
// GitHub's hold on its pushes, which is no one's approval; it stays on the
// reviewed lane.
const PACKAGES = 'pkgbuilds/';
const TESTS = 'tests/';
const TEST_WORKFLOW = '.github/workflows/test.yml';
const TEST_LINE = /^\+\s*\.\/tests\/[A-Za-z0-9._-]+\.sh\s*$/;
// Every changed line adds a ./tests/<name>.sh call; nothing removed. A diff
// too large for the API has no patch and does not qualify.
function onlyRunsTests(patch) {
if (!patch) return false;
const changed = patch.split('\n').filter(line =>
/^[+-]/.test(line) && !line.startsWith('+++') && !line.startsWith('---'));
return changed.length > 0 && changed.every(line => TEST_LINE.test(line));
}
function packageChange(file) {
if (file.previous_filename && !file.previous_filename.startsWith(PACKAGES)) return false;
if (file.filename.startsWith(PACKAGES)) return true;
if (file.filename.startsWith(TESTS)) return file.status === 'added';
if (file.filename === TEST_WORKFLOW) return file.status === 'modified' && onlyRunsTests(file.patch);
return false;
}
const REVIEWED_BRANCHES = /^auto\/sync-upstream(\/|$)/;
function decide({ pr, files, vouchStatus, repository }) {
if (pr.state !== 'open') return { enable: false, reason: 'PR is not open' };
if (pr.draft) return { enable: false, reason: 'PR is a draft' };
const labelled = pr.labels.some(label => label.name === 'build-approved');
let trusted;
switch (vouchStatus) {
case 'bot': case 'collaborator': case 'vouched': trusted = true; break;
case 'unknown': trusted = labelled; break;
default: trusted = false; // denounced, or a failed lookup
}
if (!trusted) {
return { enable: false, reason: `author not trusted to build (${vouchStatus || 'missing'}${labelled ? ', labelled' : ''})` };
}
if (pr.head.repo?.full_name === repository && REVIEWED_BRANCHES.test(pr.head.ref)) {
return { enable: false, reason: `${pr.head.ref} stays on the reviewed lane` };
}
if (!files.length) return { enable: false, reason: 'PR changes no files' };
const outside = files.filter(file => !packageChange(file));
if (outside.length) {
const names = outside.slice(0, 3).map(file => file.filename).join(', ');
return { enable: false, reason: `changes more than packages and their new tests: ${names}${outside.length > 3 ? ', ...' : ''}` };
}
return { enable: true, reason: `${vouchStatus === 'unknown' ? 'build-approved' : vouchStatus} author, package changes only` };
}
module.exports = async function autoMerge({ github, context, core, number, vouchStatus }) {
const { data: pr } = await github.rest.pulls.get({ ...context.repo, pull_number: number });
const files = await github.paginate(github.rest.pulls.listFiles, {
...context.repo, pull_number: number, per_page: 100,
});
const decision = decide({
pr, files, vouchStatus, repository: `${context.repo.owner}/${context.repo.repo}`,
});
core.info(`#${number}: ${decision.enable ? 'auto-merge' : 'leave for a maintainer'} (${decision.reason})`);
core.setOutput('enable', String(decision.enable));
core.setOutput('head_sha', pr.head.sha);
return decision;
};
module.exports.decide = decide;
+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# Usage: sync-pr-branch.sh BASE_BRANCH [PACKAGE...]
#
# Prints the branch a sync workflow run pushes to, as branch=/scope= lines for
# $GITHUB_OUTPUT. An unscoped (scheduled) run owns BASE_BRANCH and regenerates
# it from master every time. A run scoped to named packages regenerates only
# those, so it gets its own branch and PR: pushing it to BASE_BRANCH would
# replace every other pending update there with just the named packages.
set -euo pipefail
base=${1:?base branch required}
shift
if (( $# == 0 )); then
printf 'branch=%s\nscope=\n' "$base"
exit 0
fi
names=()
for name in "$@"; do
# Package directory names, as pacman allows them. Anything else is a typo
# or an attempt to smuggle something into a ref name or PR title.
if [[ ! $name =~ ^[a-z0-9@_+][a-z0-9@._+-]*$ ]]; then
echo "invalid package name: $name" >&2
exit 1
fi
names+=("$name")
done
mapfile -t names < <(printf '%s\n' "${names[@]}" | sort -u)
scope="${names[*]}"
slug=$(printf '%s\n' "${names[@]}" | sed 's/[^a-z0-9]\{1,\}/-/g; s/^-//; s/-$//' | paste -sd- -)
# Keep long package lists to a readable ref; the hash keeps distinct lists apart.
hash=$(printf '%s' "$scope" | sha256sum | cut -c1-10)
if [[ -z $slug ]]; then
slug=$hash
elif (( ${#slug} > 60 )); then
slug="${slug:0:48}"
slug="${slug%-}-$hash"
fi
printf 'branch=%s-%s\nscope=%s\n' "$base" "$slug" "$scope"
+46
View File
@@ -0,0 +1,46 @@
name: Approve PR workflows
# A pull_request workflow cannot approve itself: GitHub can hold it before
# any job starts. This workflow only runs trusted default-branch code and
# releases the ordinary, unprivileged PR workflows after build approval.
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled]
permissions:
contents: read
pull-requests: read
actions: write
concurrency:
group: approve-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
approve:
# Match build-pr.yml's events, including other labels applied while this
# PR still carries build-approved: each labeled event creates a build.
if: contains(github.event.pull_request.labels.*.name, 'build-approved')
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# Never check out the PR head or its merge ref with this write token.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- id: vouch
uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1
with:
user: ${{ github.event.pull_request.user.login }}
allow-fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Approve this PR's pending build and test runs
uses: actions/github-script@v7
env:
VOUCH_STATUS: ${{ steps.vouch.outputs.status }}
with:
script: |
const approve = require('./.github/scripts/approve-pr-workflows.cjs');
await approve({ github, context, core, vouchStatus: process.env.VOUCH_STATUS });
+108
View File
@@ -0,0 +1,108 @@
name: Auto-merge approved package PRs
# A package PR trusted to build is trusted to ship: once its builds are
# green it should merge and publish without a maintainer pressing the
# button. This enables GitHub's auto-merge on such PRs; branch protection
# still holds the merge until `result`, `self-tests` and `build-isolation`
# pass, and a red build stays an open PR. .github/scripts/auto-merge-pr.cjs
# has the rule.
#
# Auto-merge is enabled with the PAT in PKGS_BOT_TOKEN: a merge made with the
# built-in GITHUB_TOKEN does not start publish.yml.
#
# pull_request_target runs this default-branch code with secrets; the PR's
# code is never checked out here.
on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review, labeled, unlabeled]
workflow_dispatch:
inputs:
pr:
description: 'PR number to evaluate'
required: true
permissions:
contents: read
pull-requests: read
concurrency:
group: auto-merge-pr-${{ github.event.pull_request.number || github.event.inputs.pr }}
cancel-in-progress: true
jobs:
auto-merge:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- name: Find the PR's author
id: pr
uses: actions/github-script@v7
env:
NUMBER: ${{ github.event.pull_request.number || github.event.inputs.pr }}
with:
script: |
const { data: pr } = await github.rest.pulls.get({
...context.repo, pull_number: Number(process.env.NUMBER),
});
core.setOutput('number', String(pr.number));
core.setOutput('author', pr.user.login);
- id: vouch
uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1
with:
user: ${{ steps.pr.outputs.author }}
allow-fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Decide
id: decide
uses: actions/github-script@v7
env:
NUMBER: ${{ steps.pr.outputs.number }}
VOUCH_STATUS: ${{ steps.vouch.outputs.status }}
with:
script: |
const autoMerge = require('./.github/scripts/auto-merge-pr.cjs');
await autoMerge({ github, context, core,
number: Number(process.env.NUMBER), vouchStatus: process.env.VOUCH_STATUS });
- name: Enable auto-merge
if: steps.decide.outputs.enable == 'true'
env:
GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
PR: ${{ steps.pr.outputs.number }}
HEAD_SHA: ${{ steps.decide.outputs.head_sha }}
run: |
if [[ -z "$GH_TOKEN" ]]; then
echo "::error::Set PKGS_BOT_TOKEN; a GITHUB_TOKEN merge would not publish."
exit 1
fi
# Idempotent: enabling twice errors. Bot lanes enable their own.
if [[ "$(gh pr view "$PR" -R "$GITHUB_REPOSITORY" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; then
echo "auto-merge already enabled on #$PR"
exit 0
fi
# --match-head-commit: never arm a merge for a commit newer than
# the one just judged.
gh pr merge --auto --squash --match-head-commit "$HEAD_SHA" "$PR" -R "$GITHUB_REPOSITORY"
# Removing build-approved withdraws the approval, so withdraw the
# auto-merge it armed too. Only on that event: auto-merge a maintainer
# enabled by hand on any other PR is theirs to keep.
- name: Withdraw auto-merge
if: >-
steps.decide.outputs.enable == 'false' &&
github.event.action == 'unlabeled' && github.event.label.name == 'build-approved'
env:
GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
PR: ${{ steps.pr.outputs.number }}
run: |
if [[ "$(gh pr view "$PR" -R "$GITHUB_REPOSITORY" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; then
gh pr merge --disable-auto "$PR" -R "$GITHUB_REPOSITORY"
fi
+319
View File
@@ -0,0 +1,319 @@
name: Build changed packages
# Build every package directory a PR touches, one job per package per arch:
# x86_64 on the self-hosted droplet pool, aarch64 natively on GitHub's arm64
# runners. Artifacts are unsigned; publish.yml signs and publishes them on
# merge.
#
# Tooling runs from the base branch; a PR supplies only pkgbuilds/. The
# vouch gate limits who may spend compute; this limits what their PR can run.
# No paths filter: approved PRs must report the required `result` even when
# no package directory changed. Those PRs get an empty matrix and a passing
# result in seconds; unapproved PRs wait for maintainer approval.
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
workflow_dispatch:
inputs:
packages:
description: "Space-separated package directories to build"
required: true
# A new push normally cancels the PR's in-flight build. The sync bots'
# branches (auto/sync-*) are the exception: they are force-pushed with fresh
# upstream releases several times a day, which kept cancelling multi-hour
# aarch64 builds before they could finish. There the newest run waits
# instead (GitHub keeps at most one pending run per group, replacing older
# pending ones), and when it starts it reuses every artifact the finished
# build uploaded, so only packages whose tree changed are built again.
concurrency:
group: build-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: ${{ !(github.event.pull_request.head.repo.full_name == github.repository && startsWith(github.head_ref, 'auto/sync-')) }}
jobs:
# Builds cost real machines, so they run only for trusted authors:
# collaborators, anyone in .github/VOUCHED.td (read from the default
# branch, so a PR cannot vouch for itself), or a PR a maintainer has
# labelled "build-approved". Everyone else gets this job's plan output
# while the required `result` stays pending until a maintainer approves.
changes:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.list.outputs.matrix }}
count: ${{ steps.gate.outputs.count }}
trusted: ${{ steps.gate.outputs.trusted }}
vouch_status: ${{ steps.vouch.outputs.status }}
empty: ${{ steps.list.outputs.empty }}
steps:
# Same rule as the build job: bin/build-matrix comes from the base
# branch tip, the package directories from the PR head.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref || github.sha }}
fetch-depth: 0
persist-credentials: false
- if: github.event_name == 'pull_request'
run: |
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.head.sha }}"
git checkout "${{ github.event.pull_request.head.sha }}" -- pkgbuilds/
# Bootstrap: the PR that introduces this tooling has a base without
# it. Take the plan helper from the PR head in that one case; it
# runs on a hosted runner and only prints a plan.
if [[ ! -x bin/build-matrix ]]; then
git checkout "${{ github.event.pull_request.head.sha }}" -- bin/build-matrix helpers/
echo "::notice::base branch has no bin/build-matrix; using the PR's copy for planning"
fi
- id: vouch
if: github.event_name == 'pull_request'
uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1
with:
user: ${{ github.event.pull_request.user.login }}
allow-fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- id: approval
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const { data: pr } = await github.rest.pulls.get({
...context.repo, pull_number: context.payload.pull_request.number,
});
// Approving or rerunning a held run keeps its original event,
// which may predate the label. Read the current approval instead.
core.setOutput('approved', pr.state === 'open' &&
pr.head.sha === context.payload.pull_request.head.sha &&
pr.labels.some(label => label.name === 'build-approved'));
# One matrix entry per package per architecture. Every package builds
# once, against edge; the channels it ships to on merge are carried
# along for information. A filename means one set of bytes.
- id: list
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ -n "${{ github.event.inputs.packages }}" ]]; then
names="${{ github.event.inputs.packages }}"
else
# The PR's own files, as GitHub lists them against the merge base.
# A two-dot diff against the current base tip also counted every
# package master changed after the PR branched, so a stale PR
# planned dozens of unrelated packages at its old versions. The
# checkout here is shallow, so there is no merge base to diff from.
# A package the PR deletes has nothing to build.
names=$(gh api --paginate "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" --jq '.[].filename' \
| awk -F/ '$1=="pkgbuilds" && NF>2 {print $2}' | sort -u \
| while read -r name; do
if git cat-file -e "${{ github.event.pull_request.head.sha }}:pkgbuilds/$name" 2>/dev/null; then echo "$name"; fi
done)
fi
matrix=$(printf '%s\n' $names | bin/build-matrix)
# A package directory whose exact tree already has a build artifact
# (label <pkg>-<arch>-<tree hash>, uploaded only after a successful
# build) is not built again. Pushing a fix for one package to a PR
# that touches fifty rebuilds one, not fifty; publish.yml finds the
# same artifacts on merge. workflow_dispatch is an explicit request
# and always builds.
if [[ "${{ github.event_name }}" == pull_request ]]; then
head="${{ github.event.pull_request.head.sha }}"
kept=(); reused=()
while read -r entry; do
package=$(jq -r .package <<<"$entry"); arch=$(jq -r .arch <<<"$entry")
label="$package-$arch-$(git rev-parse "$head:pkgbuilds/$package")"
found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \
| jq -r '[.artifacts[] | select(.expired|not)] | length' || echo 0)
if (( found > 0 )); then reused+=("$label"); else kept+=("$entry"); fi
done < <(jq -c '.include[]' <<<"$matrix")
matrix=$(printf '%s\n' "${kept[@]}" | jq -sc '{include: .}')
if (( ${#reused[@]} )); then
printf '==> already built, reusing the artifact: %s\n' "${reused[@]}"
{ echo "Reused existing build artifacts (${#reused[@]}):"; printf -- '- %s\n' "${reused[@]}"; } >> "$GITHUB_STEP_SUMMARY"
fi
fi
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
echo "planned=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT"
jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix"
# A PR whose diff against its base is empty changes nothing: its
# content already landed some other way (a sync PR beat it, or a
# merge from master swallowed it). Merging it would record a change
# that isn't one. Flag it so `result` fails rather than passes.
if [[ "${{ github.event_name }}" == pull_request ]]; then
total=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | wc -l)
echo "empty=$([[ $total -eq 0 ]] && echo true || echo false)" >> "$GITHUB_OUTPUT"
echo "files changed vs base: $total"
else
echo "empty=false" >> "$GITHUB_OUTPUT"
fi
- id: gate
env:
STATUS: ${{ github.event_name == 'workflow_dispatch' && 'dispatch' || steps.vouch.outputs.status }}
AUTHOR: ${{ github.event.pull_request.user.login }}
APPROVED: ${{ steps.approval.outputs.approved || 'false' }}
PLANNED: ${{ steps.list.outputs.planned }}
run: |
case "$STATUS" in
bot|collaborator|vouched|dispatch) trusted=true ;;
# A denouncement is absolute: the label cannot override it.
denounced) trusted=false ;;
unknown) trusted=$APPROVED ;;
*) trusted=false ;;
esac
echo "trusted=$trusted" >> "$GITHUB_OUTPUT"
if [[ $trusted == true ]]; then
echo "count=$PLANNED" >> "$GITHUB_OUTPUT"
echo "Author $AUTHOR is trusted ($STATUS); building $PLANNED package(s)."
else
echo "count=0" >> "$GITHUB_OUTPUT"
echo "::warning::Author $AUTHOR is not trusted ($STATUS). $PLANNED package build(s) planned but not run."
if [[ $STATUS == denounced ]]; then
echo "::warning::The author is denounced in .github/VOUCHED.td; the build-approved label does not apply."
else
echo "::warning::A maintainer can add the author to .github/VOUCHED.td, or apply the 'build-approved' label to build this PR."
fi
fi
build:
needs: changes
if: needs.changes.outputs.count != '0'
# The droplets are x86, so aarch64 there runs under QEMU: omarchy-mac-boot
# took 2h47m of the 180 minutes, most of it in check().
# GitHub's arm64 runners (4 vCPU, 16 GB; ~100 GB disk free in our pilots)
# build it natively in 11 minutes, and linux-aurora in 30 (72 under QEMU).
runs-on: ${{ matrix.arch == 'aarch64' && 'ubuntu-24.04-arm' || fromJSON('["self-hosted","omarchy-builder"]') }}
timeout-minutes: 180
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.changes.outputs.matrix) }}
steps:
# Tooling from base: everything that executes on this runner's host
# (bin/, helpers/, build/) comes from the base branch. Only the PR's
# package directories are overlaid. A PR can therefore change what
# gets built, never how the runner builds it. A PR that changes both
# tooling and a package builds the package with the OLD tooling; land
# the tooling first. workflow_dispatch has no PR and runs as checked out.
# The base branch tip, not the event's base.sha: that sha is a snapshot
# taken at the PR's last push, so a tooling fix on master would never
# reach an open PR until someone pushed to it (seen on the daily sync
# PR after the artifact packing fix landed).
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref || github.sha }}
persist-credentials: false
- name: Overlay the PR's package directories onto base tooling
if: github.event_name == 'pull_request'
run: |
set -euo pipefail
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.head.sha }}"
git checkout "${{ github.event.pull_request.head.sha }}" -- pkgbuilds/
echo "tooling: $(git rev-parse --short HEAD) (base) packages: ${{ github.event.pull_request.head.sha }} (PR head)"
# A preview only. `head` exits after ten lines and, under pipefail,
# git's SIGPIPE (141) failed the step for any PR far enough behind
# master to differ in more files; sed reads the whole stream.
git status --short | sed -n '1,10p'
# A PR can change a package's directory without changing its version:
# an upstream hook, its tests, a README. edge already holds that
# version, so the planner builds nothing and there is nothing to pack.
# The same check publish.yml's rebuild job makes; without it the pack
# step failed on the empty output and the required result went red.
- name: Build ${{ matrix.package }} (${{ matrix.arch }}, ships to ${{ matrix.channels }})
id: build
env:
CONTAINER_ENGINE: docker
run: |
set -euo pipefail
plan=$(bin/build --dry-run --mirror edge --arch "${{ matrix.arch }}" --package "${{ matrix.package }}" 2>&1 | grep -E '^==> Plan complete' || true)
if [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then
echo "==> ${{ matrix.package }} (${{ matrix.arch }}): edge already holds this version, nothing to build"
echo "built=false" >> "$GITHUB_OUTPUT"
# Arch bumps pkgrel only when the built package changes, which is
# the reviewer's call. When the recipe itself changed (comments and
# blank lines aside), flag it on the PKGBUILD without failing: the
# change ships only once pkgver or pkgrel moves. HEAD is the base
# branch; the PR's package directories are overlaid on it.
recipe="pkgbuilds/${{ matrix.package }}/PKGBUILD"
recipe_lines() { grep -vE '^[[:space:]]*(#|$)' || true; }
if ! cmp -s <(git show "HEAD:$recipe" 2>/dev/null | recipe_lines) <(recipe_lines < "$recipe"); then
note="PKGBUILD changed, but edge already holds this version of ${{ matrix.package }}, so the change will not ship until pkgver or pkgrel changes. Bump pkgrel if it affects the built package."
echo "::warning file=$recipe,title=Change will not ship::$note"
echo "$note" >> "$GITHUB_STEP_SUMMARY"
fi
exit 0
fi
bin/build --mirror edge --arch "${{ matrix.arch }}" --package "${{ matrix.package }}"
echo "built=true" >> "$GITHUB_OUTPUT"
# makepkg's check() leaves meson's per-test output in the build tree,
# never on stdout, so a failing test shows only a summary line in this
# job log. bin/build bind-mounts $SRC_DIR at /src, so those logs outlive
# the container. Without this upload an arch-specific test failure
# cannot be diagnosed from CI at all (seen on owe 0.2.7, aarch64).
- name: Upload test logs
if: always() && steps.build.outcome == 'failure'
uses: actions/upload-artifact@v4
with:
name: test-logs-${{ matrix.package }}-${{ matrix.arch }}
path: src/**/meson-logs/
if-no-files-found: ignore
retention-days: 14
# The artifact label carries the package directory's git tree hash so
# the publish step can find the build for exactly the tree that merged.
# The package file inside keeps makepkg's standard name untouched.
# The artifact label uses the PR head's tree for this package: that is
# the tree that merges, and what publish looks up.
- name: Tree hash
id: tree
run: echo "hash=$(git rev-parse "${{ github.event.pull_request.head.sha || github.sha }}:pkgbuilds/${{ matrix.package }}")" >> "$GITHUB_OUTPUT"
# The upload action rejects a path containing ':', which is how makepkg
# names a package with an epoch. The files ride inside packages.tar
# (helpers/artifact-helpers.sh); publish.yml unpacks it. Only a
# successful build uploads: the artifact's existence is what lets the
# planner above and publish.yml skip rebuilding this exact tree.
- name: Pack artifact
if: steps.build.outputs.built == 'true'
id: pack
run: |
source helpers/artifact-helpers.sh
pack_packages build-output/edge/${{ matrix.arch }} packages.tar
tar -tvf packages.tar
- name: Upload artifact
if: steps.build.outputs.built == 'true'
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.package }}-${{ matrix.arch }}-${{ steps.tree.outputs.hash }}
path: packages.tar
if-no-files-found: error
retention-days: 7
# `result` is required by branch protection. An unvouched author awaiting
# approval gets a differently named informational check, leaving `result`
# unreported (pending). Skipping or passing a job named `result` would count
# as satisfying the requirement even though no build was authorized.
# Actual planning/build failures and denouncements still report `result`.
result:
name: ${{ needs.changes.result == 'success' && needs.changes.outputs.trusted == 'false' && needs.changes.outputs.vouch_status == 'unknown' && needs.changes.outputs.empty == 'false' && 'Awaiting build approval' || 'result' }}
needs: [changes, build]
if: always()
runs-on: ubuntu-latest
steps:
- run: |
echo "trusted=${{ needs.changes.outputs.trusted }} build=${{ needs.build.result }}"
if [[ "${{ needs.changes.result }}" != "success" ]]; then
echo "::error::Build planning or the trust check failed. See the changes job."
exit 1
fi
# Nothing to merge: the PR's diff against its base is empty. Its
# change already landed elsewhere. Close it rather than merge it.
if [[ "${{ needs.changes.outputs.empty }}" == "true" ]]; then
echo "::error::This PR changes no files relative to its base. Its content is already on the target branch; close it instead of merging."
exit 1
fi
if [[ "${{ needs.changes.outputs.trusted }}" == "false" && "${{ needs.changes.outputs.vouch_status }}" == "unknown" && "${{ needs.changes.outputs.empty }}" == "false" ]]; then
echo "::notice::Awaiting maintainer build approval. Apply 'build-approved' to this PR or vouch for the author in .github/VOUCHED.td."
echo "Package builds are waiting for maintainer approval. Apply **build-approved** to this PR to start them. The required **result** check remains pending." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if [[ "${{ needs.changes.outputs.trusted }}" != "true" ]]; then
echo "::error::Builds are blocked: the author is denounced or the trust result is invalid. The build-approved label cannot override this."
exit 1
fi
[[ "${{ needs.build.result }}" == "success" || "${{ needs.build.result }}" == "skipped" ]]
+118
View File
@@ -0,0 +1,118 @@
name: Refresh builder images
on:
schedule:
- cron: '23 4 * * *'
push:
branches: [master]
paths:
- build/**
- bin/builder-image
- helpers/paths.sh
- helpers/docker-helpers.sh
- tests/build-isolation.sh
- .github/workflows/builder-images.yml
workflow_dispatch:
pull_request:
paths:
- build/**
- bin/builder-image
- helpers/paths.sh
- helpers/docker-helpers.sh
- tests/build-isolation.sh
- .github/workflows/builder-images.yml
# Complete each refresh before another can replace its tested image tags.
concurrency:
group: builder-images-${{ github.event.pull_request.number || 'master' }}
cancel-in-progress: false
permissions:
contents: read
jobs:
# Exercise proposed image changes on native runners with a read-only token.
# Publishing is a separate master-only job with its own write permission.
validate:
if: github.event_name == 'pull_request'
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
env:
CONTAINER_ENGINE: docker
CANDIDATE_IMAGE: omarchy-builder-candidate:${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build a fresh environment
run: bin/builder-image build --arch "${{ matrix.arch }}" --mirror edge --tag "$CANDIDATE_IMAGE" --fresh
- name: Test isolated package builds
env:
TEST_BUILDER_IMAGE: ${{ env.CANDIDATE_IMAGE }}
run: tests/build-isolation.sh
refresh:
if: github.repository == 'omacom/omarchy-pkgs' && github.ref == 'refs/heads/master'
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
permissions:
contents: read
packages: write
env:
CONTAINER_ENGINE: docker
REGISTRY_IMAGE: ghcr.io/omacom/omarchy-pkg-builder
CANDIDATE_IMAGE: omarchy-builder-candidate:${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build a fresh environment
run: bin/builder-image build --arch "${{ matrix.arch }}" --mirror edge --tag "$CANDIDATE_IMAGE" --fresh
- name: Test isolated package builds
env:
TEST_BUILDER_IMAGE: ${{ env.CANDIDATE_IMAGE }}
run: tests/build-isolation.sh
- name: Publish tested image
env:
GH_TOKEN: ${{ github.token }}
GH_ACTOR: ${{ github.actor }}
DOCKER_CONFIG: ${{ runner.temp }}/builder-registry-auth
run: |
set -euo pipefail
mkdir -p "$DOCKER_CONFIG"
trap 'rm -rf "$DOCKER_CONFIG"' EXIT
printf '%s' "$GH_TOKEN" | docker login ghcr.io --username "$GH_ACTOR" --password-stdin
key=$(bin/builder-image key --arch "${{ matrix.arch }}" --mirror edge)
version="$REGISTRY_IMAGE:$key-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
docker tag "$CANDIDATE_IMAGE" "$version"
docker push "$version"
# GHCR creates new packages private. Do not advertise an image to
# fork PRs until it is public. This is a one-time package setting.
anonymous_config=$(mktemp -d "$RUNNER_TEMP/builder-anonymous.XXXXXX")
if ! DOCKER_CONFIG="$anonymous_config" docker manifest inspect "$version" >/dev/null; then
rm -rf "$anonymous_config"
echo "::error::Make the omacom/omarchy-pkg-builder GHCR package public, then rerun this job. The previous matching image remains selected."
exit 1
fi
rm -rf "$anonymous_config"
docker tag "$CANDIDATE_IMAGE" "$REGISTRY_IMAGE:$key"
docker push "$REGISTRY_IMAGE:$key"
digest=$(docker image inspect "$version" --format '{{index .RepoDigests 0}}')
printf '### Builder image (%s)\n\nInput key: `%s`\n\nImage: `%s`\n' \
"${{ matrix.arch }}" "$key" "$digest" >> "$GITHUB_STEP_SUMMARY"
+455
View File
@@ -0,0 +1,455 @@
name: Publish merged packages
# On every push to master: for each package directory the push touched and
# each architecture it supports, find the PR build artifact for exactly that
# tree (label = <pkg>-<arch>-<treehash>), or build it now when there is
# none, then publish that one artifact into every channel the package ships
# to. One build, one file, several databases: a filename means one set of
# bytes everywhere, and channels are views over a shared pool.
#
# Secrets live in the "publish" environment, restricted to master:
# GPG_PRIVATE_KEY, GPG_PASSPHRASE the channel signing key
# R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_ENDPOINT
# OMARCHY_PUBLISH_PREFIX (environment variable, not secret) points a proof
# run at a scratch prefix inside the live bucket; empty means the real
# channel paths.
on:
push:
branches: [master]
paths: ["pkgbuilds/**"]
workflow_dispatch:
inputs:
packages:
description: "Space-separated package directories to publish from master"
required: true
# Only the publish job serializes (see its concurrency group): two publishes
# into one channel at once would race on the database. Builds run outside the
# lock, so a merge waits behind another merge's signing and upload, seconds,
# never behind its kernel build.
jobs:
changes:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.list.outputs.matrix }}
count: ${{ steps.list.outputs.count }}
rebuild: ${{ steps.list.outputs.rebuild }}
rebuild_count: ${{ steps.list.outputs.rebuild_count }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- id: list
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [[ -n "${{ github.event.inputs.packages }}" ]]; then
names="${{ github.event.inputs.packages }}"
else
names=$(git diff --name-only "${{ github.event.before }}..${{ github.sha }}" -- pkgbuilds \
| awk -F/ '$1=="pkgbuilds" && NF>2 {print $2}' | sort -u)
fi
matrix=$(printf '%s\n' $names | bin/build-matrix)
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
echo "count=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT"
jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix"
# Reuse or rebuild, decided per entry and said out loud. A tree with
# no build artifact (PR artifacts last 7 days; a dispatch may name
# any package) goes to the rebuild job: aarch64 natively on GitHub's
# arm64 runner, x86_64 on a builder droplet, one runner per entry.
rebuild=()
echo "| package | arch | tree | decision |" >> "$GITHUB_STEP_SUMMARY"
echo "| --- | --- | --- | --- |" >> "$GITHUB_STEP_SUMMARY"
while read -r entry; do
package=$(jq -r .package <<<"$entry"); arch=$(jq -r .arch <<<"$entry")
hash=$(git rev-parse "HEAD:pkgbuilds/$package")
label="$package-$arch-$hash"
found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \
| jq -r '[.artifacts[] | select(.expired|not)] | sort_by(.created_at) | last | select(. != null) | "run \(.workflow_run.id), expires \(.expires_at)"')
# The plan the publish job makes, made here before a runner is
# asked for: a tree the channel already holds at master's version
# (a re-run, or a dispatch naming a package that is fine) needs
# no build, and an x86 rebuild would wait minutes for a droplet
# to find that out in seconds.
plan=""
[[ -n "$found" ]] || plan=$(bin/build --dry-run --mirror edge --arch "$arch" --package "$package" 2>&1 | grep -E '^==> Plan complete' || true)
if [[ -n "$found" ]]; then
decision="reuse the build artifact ($found)"
elif [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then
decision="already published at master's version, nothing to build"
elif [[ $arch == aarch64 ]]; then
decision="no build artifact: rebuild natively on ubuntu-24.04-arm"
rebuild+=("$(jq -c '. + {runner: "[\"ubuntu-24.04-arm\"]"}' <<<"$entry")")
else
decision="no build artifact: rebuild on a builder droplet"
rebuild+=("$(jq -c '. + {runner: "[\"self-hosted\",\"omarchy-builder\"]"}' <<<"$entry")")
fi
echo "==> $label: $decision"
echo "| $package | $arch | ${hash:0:12} | $decision |" >> "$GITHUB_STEP_SUMMARY"
done < <(jq -c '.include[]' <<<"$matrix")
echo "rebuild=$(printf '%s\n' "${rebuild[@]}" | jq -sc '{include: .}')" >> "$GITHUB_OUTPUT"
echo "rebuild_count=${#rebuild[@]}" >> "$GITHUB_OUTPUT"
# "Build it now when there is none". Each entry builds exactly as
# build-pr.yml builds it (same runner kind, same builder image, same
# bin/build call) and uploads under the same label, so the publish job
# collects this run's artifact the way it collects a PR's. No secret
# reaches these runners, and they hold no lock: entries build in parallel,
# and other merges publish while they do.
rebuild:
needs: changes
if: needs.changes.outputs.rebuild_count != '0'
runs-on: ${{ fromJSON(matrix.runner) }}
# The droplets are x86, so aarch64 never builds there. omarchy-mac-boot
# under QEMU took 2h47m; native arm64 and x86 kernels fit easily.
timeout-minutes: 240
permissions:
contents: read
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.changes.outputs.rebuild) }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# The same check the publish job makes before building: a re-run for a
# package the channel already holds at master's version builds
# nothing, and uploads nothing that could shadow the published file.
- name: Build ${{ matrix.package }} (${{ matrix.arch }})
id: build
env:
CONTAINER_ENGINE: docker
run: |
set -euo pipefail
plan=$(bin/build --dry-run --mirror edge --arch "${{ matrix.arch }}" --package "${{ matrix.package }}" 2>&1 | grep -E '^==> Plan complete' || true)
if [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then
echo "==> ${{ matrix.package }} (${{ matrix.arch }}): already published at master's version, nothing to build"
echo "built=false" >> "$GITHUB_OUTPUT"
exit 0
fi
bin/build --mirror edge --arch "${{ matrix.arch }}" --package "${{ matrix.package }}"
echo "built=true" >> "$GITHUB_OUTPUT"
- name: Pack artifact
if: steps.build.outputs.built == 'true'
id: pack
run: |
source helpers/artifact-helpers.sh
pack_packages build-output/edge/${{ matrix.arch }} packages.tar
tar -tvf packages.tar
echo "label=${{ matrix.package }}-${{ matrix.arch }}-$(git rev-parse "HEAD:pkgbuilds/${{ matrix.package }}")" >> "$GITHUB_OUTPUT"
- name: Upload artifact
if: steps.build.outputs.built == 'true'
uses: actions/upload-artifact@v4
with:
name: ${{ steps.pack.outputs.label }}
path: packages.tar
if-no-files-found: error
retention-days: 7
# One job for the whole merge. It collects every artifact for the merged
# tree (PR builds, or the rebuild job above), then walks each channel and
# architecture slot exactly once: pull that database, add every package
# that belongs in it, upload. Six slots, six round trips, however many
# packages the merge carried. One process is the only writer, so there
# is no race between packages; the concurrency group keeps one merge's
# publish from overlapping the next. It builds nothing, so it runs on a
# hosted runner in about a minute instead of waiting for a droplet.
# It waits for the rebuild job and runs whatever that job's result: a
# failed rebuild leaves its package without an artifact, and the collect
# step below records that and stops before any publish.
publish:
needs: [changes, rebuild]
if: ${{ !cancelled() && needs.changes.result == 'success' && needs.changes.outputs.count != '0' }}
runs-on: ubuntu-latest
environment: publish
timeout-minutes: 30
concurrency:
group: publish
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# Every matrix entry, as a file the shell steps can loop over:
# package arch channels publish_arches
- name: Plan
run: |
jq -r '.include[] | "\(.package) \(.arch) \(.channels|gsub(" ";",")) \(.publish_arches|gsub(" ";","))"' \
<<'EOF_MATRIX' > plan.txt
${{ needs.changes.outputs.matrix }}
EOF_MATRIX
cat plan.txt
# Fetch each package's artifact into build-output/edge/<arch>/: the PR's,
# or the rebuild job's when the PR's had expired. An artifact
# carries its package files inside packages.tar (see build-pr.yml and
# helpers/artifact-helpers.sh: the upload action rejects the colon in
# an epoch filename).
- name: Collect artifacts
env:
GH_TOKEN: ${{ github.token }}
CONTAINER_ENGINE: docker
run: |
set -uo pipefail
source helpers/artifact-helpers.sh
# sources.jsonl: where each package's files came from, or that the
# build failed. A failed build ends the run before any publish, and
# the record says so instead of the report job finding nothing.
: > sources.jsonl
failed=0
while read -r package arch channels publish_arches; do
hash=$(git rev-parse "HEAD:pkgbuilds/$package")
label="$package-$arch-$hash"
found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \
| jq -r '[.artifacts[] | select(.expired|not)] | sort_by(.created_at) | last | select(. != null) | "\(.archive_download_url) \(.workflow_run.id)"')
read -r found from_run <<<"$found" || true
mkdir -p "build-output/edge/$arch"
if [[ -n "$found" ]]; then
if [[ $from_run == "${{ github.run_id }}" ]]; then
kind=rebuild
echo "==> $label: artifact from this run's $arch rebuild"
else
kind=pr-artifact
echo "==> $label: reusing the build artifact from run $from_run"
fi
rm -rf /tmp/artifact; mkdir -p /tmp/artifact
if curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -o /tmp/artifact.zip "$found" \
&& unzip -oq /tmp/artifact.zip -d /tmp/artifact \
&& unpack_packages /tmp/artifact "build-output/edge/$arch"; then
jq -nc --arg p "$package" --arg a "$arch" --arg s "$kind" '{package:$p, arch:$a, source:$s}' >> sources.jsonl
else
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"artifact-download-failed"}' >> sources.jsonl; failed=1; break
fi
else
# bin/build plans against the public channel first. If the
# channel already holds master's version there is nothing to
# build and nothing to publish: a re-run for a package that
# turned out to be fine. Record it and move on.
plan=$(CONTAINER_ENGINE=docker bin/build --dry-run --mirror edge --arch "$arch" --package "$package" 2>&1 | grep -E '^==> Plan complete' || true)
# "Packages that would build:" followed by nothing means none.
if [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then
echo "==> $label: already published at master's version, nothing to do"
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"already-published"}' >> sources.jsonl
continue
fi
# Nothing builds here. No artifact means the rebuild failed (see
# the rebuild job), or an artifact expired between planning and
# now (re-run all jobs).
echo "::error::$label: no artifact from the rebuild job"
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"build-failed"}' >> sources.jsonl; failed=1; break
fi
done < plan.txt
ls -1 build-output/edge/*/*.pkg.tar.zst 2>/dev/null || true
if (( failed )); then
# Write the record now; the publish step will not run.
jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \
--slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \
'{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:[]}' \
> publish-record.json
cat publish-record.json
exit 1
fi
- name: Publish
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
# The token is scoped to the bucket; it may not CreateBucket, and
# rclone's existence check is a CreateBucket in disguise.
RCLONE_CONFIG_R2_NO_CHECK_BUCKET: "true"
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
OMARCHY_PUBLISH_PREFIX: ${{ vars.OMARCHY_PUBLISH_PREFIX }}
# repo-add, gpg and bsdtar are Arch tools; run the publish inside the
# builder image (host-native, edge) with the workspace mounted.
run: |
set -euo pipefail
if ! compgen -G "build-output/edge/*/*.pkg.tar.zst" >/dev/null; then
echo "Nothing to publish: every requested package is already published at master's version."
jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \
--slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \
'{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:[]}' \
> publish-record.json
cat publish-record.json
exit 0
fi
# A fresh runner has no images, and building this one here cost
# every publish about 100 s (and 20 s more to start a container
# from it) for the 14 s of signing and upload it is needed for.
# builder-images.yml already publishes the tested image for exactly
# these build inputs under their key; pull that. Build only when no
# image carries the key: a merge that changed build/ publishes
# before the refresh it triggered has finished.
builder=omarchy-pkg-builder:latest-x86_64-edge
if ! docker image inspect "$builder" >/dev/null 2>&1; then
key=$(bin/builder-image key --arch x86_64 --mirror edge)
published="ghcr.io/omacom/omarchy-pkg-builder:$key"
if docker pull --quiet "$published" &&
[[ $(docker image inspect "$published" --format '{{index .Config.Labels "org.omarchy.builder.key"}}') == "$key" ]]; then
docker tag "$published" "$builder"
echo "==> Builder image: pulled $published"
else
echo "==> Builder image: none published for $key, building it"
docker buildx build --load -t "$builder" --build-arg MIRROR=edge build
fi
fi
# Group the merge's files by the (channel, architecture) slot each
# belongs to. A package's files live under build-output/edge/<built
# arch>/ and are named <pkgname>-<ver>-<arch|any>.pkg.tar.zst; a
# split package's outputs share the pkgbase's directory, so match
# on the artifact list rather than the name.
# pkgbase is read inside the builder image: the Ubuntu host has no
# bsdtar. One container call maps every file to its pkgbase.
docker run --rm -v "$PWD:/w:ro" -w /w omarchy-pkg-builder:latest-x86_64-edge bash -c '
for f in build-output/edge/*/*.pkg.tar.zst; do
printf "%s %s\n" "$f" "$(bsdtar -xOf "$f" .PKGINFO | awk -F" = " "\$1==\"pkgbase\"{print \$2}")"
done' > pkgbase.txt
declare -A slot_files=()
while read -r package arch channels publish_arches; do
for f in build-output/edge/"$arch"/*.pkg.tar.zst; do
# Only files this package produced (its PKGINFO pkgbase).
[[ $(awk -v f="$f" '$1==f{print $2}' pkgbase.txt) == "$package" ]] || continue
for mirror in ${channels//,/ }; do
for parch in ${publish_arches//,/ }; do
slot_files["$mirror/$parch"]+="$f "
done
done
done
done < plan.txt
# Deterministic slot order: edge before rc before stable, x86_64
# before aarch64, so a failure leaves the earlier rings consistent.
# Every slot's outcome goes into publish-record.json for the report
# job: what was published, where, from which artifact, and whether
# the slot succeeded. A failing slot stops the loop (set -e) but the
# record still shows everything before it landed.
: > slots.jsonl
record_slot() { jq -nc --arg m "$1" --arg a "$2" --arg st "$3" --arg files "$4" \
'{mirror:$m, arch:$a, status:$st, packages:[$files | split(" ") | .[] | select(length>0) | sub(".*/";"") | sub("\\.pkg\\.tar\\.zst$";"")]}' >> slots.jsonl; }
status=0
for mirror in edge rc stable; do
for parch in x86_64 aarch64; do
files=${slot_files["$mirror/$parch"]:-}
[[ -n "$files" ]] || continue
echo "==> $mirror/$parch: $files"
if docker run --rm \
-e GPG_PRIVATE_KEY -e GPG_PASSPHRASE -e OMARCHY_PUBLISH_PREFIX \
-e RCLONE_CONFIG_R2_TYPE -e RCLONE_CONFIG_R2_PROVIDER -e RCLONE_CONFIG_R2_ENDPOINT -e RCLONE_CONFIG_R2_NO_CHECK_BUCKET \
-e RCLONE_CONFIG_R2_ACCESS_KEY_ID -e RCLONE_CONFIG_R2_SECRET_ACCESS_KEY \
-v "$PWD:/w:ro" -w /w \
omarchy-pkg-builder:latest-x86_64-edge \
bin/publish-artifact --remote R2:omarchy-pkgs --mirror "$mirror" --arch "$parch" $files; then
record_slot "$mirror" "$parch" published "$files"
else
record_slot "$mirror" "$parch" failed "$files"
status=1
break 2
fi
done
done
jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \
--slurpfile slots slots.jsonl --slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \
'{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:$slots}' \
> publish-record.json
cat publish-record.json
exit $status
- name: Keep the publish record
if: always()
uses: actions/upload-artifact@v4
with:
name: publish-record-${{ github.run_id }}
path: publish-record.json
retention-days: 90
# Tell people what happened. A comment on the merged PR (found by the
# merge commit, so squash and rebase merges work too) and a line appended
# to a running JSON log in the bucket, next to the packages it describes,
# so the history is public and can be rendered later.
report:
needs: [changes, publish]
if: always() && needs.publish.result != 'skipped'
runs-on: ubuntu-latest
environment: publish
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/download-artifact@v4
with:
name: publish-record-${{ github.run_id }}
- name: Render
id: render
run: |
jq -r --arg outcome "${{ needs.publish.result }}" '
def pkgs: [.packages[] | select(test("-debug-")|not)] | join(", ");
def src: if .source=="pr-artifact" then "PR artifact" elif .source=="rebuild" or .source=="native-rebuild" then "rebuilt at merge" elif .source=="built" then "built here" elif .source=="already-published" then "already published, skipped" else "**" + .source + "**" end;
"### Publish " + (if $outcome=="success" then "succeeded" else "FAILED" end) +
" → **" + .target + "**" + (if .target!="live" then " (proof prefix, not live)" else "" end),
"",
"Packages: " + ([.sources[] | .package + " (" + .arch + ", " + src + ")"] | join("; ")),
"",
(if (.slots|length) > 0 then (.slots[] | "- `" + .mirror + "/" + .arch + "`: " + (if .status=="published" then "" else "**" + .status + "** " end) + pkgs)
elif ([.sources[] | select(.source=="already-published")] | length) == (.sources|length) then "_Nothing to publish: already at master'"'"'s version everywhere._"
else "_Nothing was published._" end),
"",
(if ([.sources[] | select(.source|test("failed"))] | length) > 0 then "_A build failed, so no channel was touched._\n"
elif (.plan|length) > (.slots|length) then "_Some planned slots did not run because an earlier slot failed._\n" else "" end),
"Commit " + .commit[0:7] + " · [run](" + .run + ")"
' publish-record.json > comment.md
cat comment.md
- name: Append to the publish log in the bucket
env:
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
RCLONE_CONFIG_R2_NO_CHECK_BUCKET: "true"
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
run: |
curl -fsSL https://downloads.rclone.org/rclone-current-linux-amd64.zip -o rclone.zip && unzip -jq rclone.zip '*/rclone' && chmod +x rclone
# One JSON object per line, newest last. Served at
# https://pkgs.omarchy.org/publish-log.jsonl
./rclone copy R2:omarchy-pkgs/publish-log.jsonl . --s3-no-head 2>/dev/null || : > publish-log.jsonl
jq -c . publish-record.json >> publish-log.jsonl
./rclone copyto publish-log.jsonl R2:omarchy-pkgs/publish-log.jsonl --s3-no-head
echo "log now has $(wc -l < publish-log.jsonl) entries"
- name: Comment on the merged PR
# Only for a push: the merge commit names its PR. A dispatch runs
# from master's head, whose PR merged something else entirely, so
# commenting there would attach this run's report to the wrong PR.
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
run: |
pr=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" --jq '.[0].number // empty')
if [[ -n "$pr" ]]; then
gh pr comment "$pr" -R "${{ github.repository }}" --body-file comment.md
echo "commented on #$pr"
else
echo "no PR for ${{ github.sha }} (manual dispatch?); skipping PR comment"
fi
result:
needs: [changes, publish]
if: always()
runs-on: ubuntu-latest
steps:
- run: |
echo "changes result: ${{ needs.changes.result }}, publish result: ${{ needs.publish.result }}"
[[ "${{ needs.changes.result }}" == "success" ]]
[[ "${{ needs.publish.result }}" == "success" || "${{ needs.publish.result }}" == "skipped" ]]
-91
View File
@@ -1,91 +0,0 @@
name: Sync AUR Packages
on:
schedule:
# Every 6 hours
- cron: '0 */6 * * *'
workflow_dispatch:
inputs:
packages:
description: 'Specific packages to sync (space-separated, leave empty for all)'
required: false
default: ''
jobs:
sync:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Sync AUR packages
run: |
docker run --rm \
-e PACKAGES="$PACKAGES" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
-v "$PWD/bin:/workspace/bin:ro" \
-v "$PWD/helpers:/workspace/helpers:ro" \
-v "$PWD/pkgbuilds:/workspace/pkgbuilds" \
-w /workspace \
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm git jq
groupadd -g "$HOST_GID" runner
useradd -m -u "$HOST_UID" -g "$HOST_GID" runner
chown -R runner:runner /workspace/pkgbuilds
if [[ -n "${PACKAGES:-}" ]]; then
read -r -a package_args <<< "$PACKAGES"
runuser -u runner -- ./bin/sync-aur "${package_args[@]}"
else
runuser -u runner -- ./bin/sync-aur
fi
'
env:
PACKAGES: ${{ github.event.inputs.packages }}
- name: Check for changes
id: changes
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Create Pull Request
if: steps.changes.outputs.has_changes == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: sync AUR packages'
title: 'chore: sync AUR packages'
body: |
Automated AUR package sync.
Package sync behavior is controlled by `.omarchy/package.json`.
branch: auto/sync-aur
delete-branch: true
labels: automated
reviewers: ryanrhughes
- name: Notify Basecamp on failure
if: failure() && env.BASECAMP_CHATBOT_URL != ''
env:
BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }}
run: |
curl -s -o /dev/null \
-H "Content-Type: application/json" \
-d "$(jq -n --arg content \
"🔴 <strong>AUR sync failed</strong><br><a href=\"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\">View run</a>" \
'{content: $content}')" \
"$BASECAMP_CHATBOT_URL"
+51 -4
View File
@@ -1,5 +1,14 @@
name: Sync Rebuild Triggers
# Rebuilds ride the unattended lane, like track-branches.yml: the pkgrel bump
# PR builds on the droplets, auto-merge lands it once `result`, `self-tests`
# and `build-isolation` are green, and the merge publishes. A rebuild that
# fails stays an unmerged red PR for a maintainer.
#
# The PR and auto-merge use the PAT in PKGS_BOT_TOKEN, because a merge made
# with the built-in GITHUB_TOKEN does not start publish.yml, and its pushes
# are held for approval instead of building.
on:
schedule:
# Every 6 hours, off the hour to dodge the scheduling backlog at :00
@@ -19,11 +28,31 @@ jobs:
pull-requests: write
steps:
- name: Require the bot token
env:
PKGS_BOT_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
run: |
if [[ -z "$PKGS_BOT_TOKEN" ]]; then
echo "::error::Set PKGS_BOT_TOKEN to a PAT with Contents: write and Pull requests: write on this repository, owned by an account trusted to trigger builds."
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
# A scoped dispatch regenerates only the named packages. Pushed to the
# shared branch, that would replace every other pending update in its
# PR, so it gets a branch and PR of its own.
- name: Choose the PR branch
id: branch
env:
PACKAGES: ${{ github.event.inputs.packages }}
run: |
read -r -a package_args <<< "${PACKAGES:-}"
.github/scripts/sync-pr-branch.sh auto/sync-rebuilds "${package_args[@]}" | tee -a "$GITHUB_OUTPUT"
# Runs in an Arch container against the mirror the x86_64 builder itself
# uses, because the question being asked is what that builder will link
# against and a different mirror can be hours ahead of it. Recording a
@@ -70,11 +99,12 @@ jobs:
- name: Create Pull Request
if: steps.changes.outputs.has_changes == 'true'
id: cpr
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.PKGS_BOT_TOKEN }}
commit-message: 'chore: rebuild against updated dependencies'
title: 'chore: rebuild against updated dependencies'
title: "chore: rebuild against updated dependencies${{ steps.branch.outputs.scope && format(' ({0})', steps.branch.outputs.scope) || '' }}"
body: |
Automated pkgrel bump for packages that link against a dependency
which has moved in the official repositories.
@@ -84,10 +114,27 @@ jobs:
bump is what makes the rebuilt package an upgrade pacman will offer;
without it the build produces the version already published and no
one receives it.
branch: auto/sync-rebuilds
This PR auto-merges once the build checks pass. A failing rebuild
leaves it open for a maintainer.
branch: ${{ steps.branch.outputs.branch }}
delete-branch: true
labels: automated
reviewers: ryanrhughes
# Auto-merge, not a direct merge: branch protection still has to see
# the build checks green before the rebuild lands.
- name: Enable auto-merge
if: steps.cpr.outputs.pull-request-number != ''
env:
GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
PR: ${{ steps.cpr.outputs.pull-request-number }}
run: |
# Idempotent across re-runs of an updated PR: enabling twice errors.
if [[ "$(gh pr view "$PR" -R "${{ github.repository }}" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; then
echo "auto-merge already enabled on #$PR"
exit 0
fi
gh pr merge --auto --merge "$PR" -R "${{ github.repository }}"
- name: Notify Basecamp on failure
if: failure() && env.BASECAMP_CHATBOT_URL != ''
+80 -9
View File
@@ -17,6 +17,12 @@ jobs:
permissions:
contents: write
pull-requests: write
outputs:
branch: ${{ steps.branch.outputs.branch }}
pushed_at: ${{ steps.pushed.outputs.at }}
number: ${{ steps.cpr.outputs.pull-request-number }}
operation: ${{ steps.cpr.outputs.pull-request-operation }}
head_sha: ${{ steps.cpr.outputs.pull-request-head-sha }}
steps:
- name: Checkout repository
@@ -24,12 +30,25 @@ jobs:
with:
persist-credentials: false
# A scoped dispatch regenerates only the named packages. Pushed to the
# shared branch, that would replace every other pending update in its
# PR, so it gets a branch and PR of its own.
- name: Choose the PR branch
id: branch
env:
PACKAGES: ${{ github.event.inputs.packages }}
run: |
read -r -a package_args <<< "${PACKAGES:-}"
.github/scripts/sync-pr-branch.sh auto/sync-upstream "${package_args[@]}" | tee -a "$GITHUB_OUTPUT"
# Runs in an Arch container for vercmp: whether a release is an upgrade has
# to be decided by the same comparator pacman will use on users' machines.
- name: Update packages from upstream release feeds
id: sync
run: |
docker run --rm \
-e PACKAGES="$PACKAGES" \
-e UPSTREAM_GITHUB_TOKEN="$UPSTREAM_GITHUB_TOKEN" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
-v "$PWD/bin:/workspace/bin:ro" \
@@ -39,23 +58,29 @@ jobs:
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm jq
pacman -Syu --noconfirm git jq python libarchive
groupadd -g "$HOST_GID" runner
useradd -m -u "$HOST_UID" -g "$HOST_GID" runner
chown -R runner:runner /workspace/pkgbuilds
# The reviewed lane only: packages marked auto_merge ride
# track-branches.yml, which merges without a human.
if [[ -n "${PACKAGES:-}" ]]; then
read -r -a package_args <<< "$PACKAGES"
runuser -u runner -- ./bin/sync-upstream "${package_args[@]}"
runuser -u runner -- ./bin/sync-upstream --lane reviewed "${package_args[@]}"
else
runuser -u runner -- ./bin/sync-upstream
runuser -u runner -- ./bin/sync-upstream --lane reviewed
fi
'
env:
PACKAGES: ${{ github.event.inputs.packages }}
UPSTREAM_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Failed feeds leave their recipes untouched; completed updates still
# reach review. The failed sync step keeps the workflow red and notifies.
- name: Check for changes
if: ${{ !cancelled() && steps.sync.outcome != 'skipped' }}
id: changes
run: |
if [ -z "$(git status --porcelain)" ]; then
@@ -64,22 +89,35 @@ jobs:
echo "has_changes=true" >> "$GITHUB_OUTPUT"
fi
# Runs created by this push are newer than this; the approve job
# waits for them. A minute's slack absorbs runner clock skew.
- name: Record push time
if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }}
id: pushed
run: echo "at=$(date -u -d '-1 minute' +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
- name: Create Pull Request
if: steps.changes.outputs.has_changes == 'true'
if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }}
id: cpr
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: sync upstream releases'
title: 'chore: sync upstream releases'
title: "chore: sync upstream releases${{ steps.branch.outputs.scope && format(' ({0})', steps.branch.outputs.scope) || '' }}"
body: |
Automated update of packages that track an upstream vendor release
feed rather than the AUR.
Each package reports its newest release through
`.omarchy/upstream.sh`.
branch: auto/sync-upstream
Release watches and providers are declared in `.omarchy/package.json`;
exceptional feeds use `.omarchy/upstream.sh`. Failed package updates
are left untouched; check the workflow result for outstanding failures.
branch: ${{ steps.branch.outputs.branch }}
delete-branch: true
labels: automated
# The bot is trusted; build-approved lets the approve job below
# release GitHub's hold on its pushes without a maintainer.
labels: |
automated
build-approved
reviewers: ryanrhughes
- name: Notify Basecamp on failure
@@ -93,3 +131,36 @@ jobs:
"🔴 <strong>Upstream sync failed</strong><br><a href=\"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\">View run</a>" \
'{content: $content}')" \
"$BASECAMP_CHATBOT_URL"
# GitHub holds pull_request runs from a GITHUB_TOKEN push for approval and
# creates no pull_request_target run for it, so approve-pr.yml never sees
# the sync's own pushes. The sync labels its PR build-approved, so release
# the held runs for the commit just pushed, whether it opened the PR or
# updated it. A separate job, so the sync container's token never holds
# actions: write.
approve:
needs: sync
if: ${{ !cancelled() && (needs.sync.outputs.operation == 'created' || needs.sync.outputs.operation == 'updated') }}
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
pull-requests: read
actions: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Release held build and test runs
uses: actions/github-script@v7
env:
NUMBER: ${{ needs.sync.outputs.number }}
BRANCH: ${{ needs.sync.outputs.branch }}
HEAD_SHA: ${{ needs.sync.outputs.head_sha }}
SINCE: ${{ needs.sync.outputs.pushed_at }}
with:
script: |
const approve = require('./.github/scripts/approve-sync-push.cjs');
const { NUMBER, BRANCH, HEAD_SHA, SINCE } = process.env;
await approve({ github, context, core, number: Number(NUMBER),
branch: BRANCH, headSha: HEAD_SHA, since: SINCE });
+44 -3
View File
@@ -1,12 +1,28 @@
name: Tests
# PR-only. Publishing on push has its own workflow and is what verifies the
# merged tree: it resolves every package against the live channel and refuses
# a filename that already exists with different bytes, so two PRs cannot land
# the same version twice. A post-merge test run would only repeat the PR's.
on:
pull_request:
push:
branches: [master]
workflow_dispatch:
jobs:
build-isolation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Prepare fixture builder
run: docker build -t omarchy-build-isolation-test -f tests/build-isolation.Dockerfile tests
- name: Verify isolated builds with real pacman transactions
env:
CONTAINER_ENGINE: docker
TEST_BUILDER_IMAGE: omarchy-build-isolation-test
run: tests/build-isolation.sh
self-tests:
runs-on: ubuntu-latest
@@ -16,6 +32,12 @@ jobs:
with:
persist-credentials: false
- name: Test PR workflow approval
run: node --test tests/pr-workflow-approval.cjs
- name: Test builder images
run: node --test tests/builder-image.cjs
# An Arch container for vercmp: version ordering has to be decided by
# the same comparator pacman uses on users' machines.
- name: Run self-tests
@@ -25,8 +47,27 @@ jobs:
-w /workspace \
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm jq
pacman -Syu --noconfirm git jq python libarchive neovim tmux
python tests/oma-service-removal.py
python tests/upstream-watch.py
bash tests/ipu7-install.sh
bash tests/ipu7-headers.sh
./bin/sync-upstream self-test
./bin/sync-rebuilds --self-test
./bin/omarchy-pkgs self-test
./bin/omarchy-release self-test
./tests/neovim-remote-clipboard.sh
python tests/neovim-clipboard-tmux.py
./tests/partial-release.sh
./tests/published-build-plan.sh
./tests/settings-boot-config.sh
./tests/settings-runtime-profile.sh
./tests/pinned-sources.sh
./tests/controller.sh
./tests/artifact-helpers.sh
./tests/limine-mkinitcpio-hook.sh
./tests/voxtype-bin-install.sh
./tests/superwhisper-bin-install.sh
pacman -S --noconfirm --quiet rclone >/dev/null
./tests/publish-artifact.sh
'
+161
View File
@@ -0,0 +1,161 @@
name: Track upstream branches
# The unattended lane. Packages marked "auto_merge": true follow a moving
# upstream branch (omarchy-dev and omarchy-settings-dev on quattro, omasnap-git
# on main) rather than tagged releases, so nothing in this repository changes
# when their source does. This workflow makes each new branch tip a commit pin
# in the recipe, which publish.yml then treats like any other version bump:
# the PR builds on the droplets, auto-merge lands it when `result` is green,
# and the merge publishes the artifacts. A tip that fails to build stays an
# unmerged red PR that the next tick supersedes.
#
# The PR and auto-merge use the PAT in PKGS_BOT_TOKEN so they trigger the
# build and publish workflows. The built-in GITHUB_TOKEN cannot drive this
# unattended chain. The PAT needs Contents: write and Pull requests: write
# on this repository, and its owner must be trusted by the build workflow.
on:
schedule:
# Every 2 hours, off the hour to dodge the scheduling backlog at :00
- cron: '35 */2 * * *'
workflow_dispatch:
inputs:
packages:
description: 'Specific packages to track (space-separated, leave empty for every auto_merge package)'
required: false
default: ''
# One tracker at a time: two runs racing on auto/track-branches would each
# force-push their own pin over the other's.
concurrency:
group: track-branches
cancel-in-progress: false
jobs:
track:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Require the tracking token
env:
PKGS_BOT_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
run: |
if [[ -z "$PKGS_BOT_TOKEN" ]]; then
echo "::error::Set PKGS_BOT_TOKEN to a PAT with Contents: write and Pull requests: write on this repository, owned by an account trusted to trigger builds."
exit 1
fi
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
# Same container as the reviewed sync: vercmp decides whether a pin is
# an upgrade with the comparator pacman uses on users' machines.
- name: Pin tracked branches to their current tips
id: sync
run: |
docker run --rm \
-e PACKAGES="$PACKAGES" \
-e UPSTREAM_GITHUB_TOKEN="$UPSTREAM_GITHUB_TOKEN" \
-e HOST_UID="$(id -u)" \
-e HOST_GID="$(id -g)" \
-v "$PWD/bin:/workspace/bin:ro" \
-v "$PWD/helpers:/workspace/helpers:ro" \
-v "$PWD/pkgbuilds:/workspace/pkgbuilds" \
-w /workspace \
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm git jq python libarchive
groupadd -g "$HOST_GID" runner
useradd -m -u "$HOST_UID" -g "$HOST_GID" runner
chown -R runner:runner /workspace/pkgbuilds
if [[ -n "${PACKAGES:-}" ]]; then
read -r -a package_args <<< "$PACKAGES"
runuser -u runner -- ./bin/sync-upstream --lane auto-merge "${package_args[@]}"
else
runuser -u runner -- ./bin/sync-upstream --lane auto-merge
fi
'
env:
PACKAGES: ${{ github.event.inputs.packages }}
UPSTREAM_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check for changes
if: ${{ !cancelled() && steps.sync.outcome != 'skipped' }}
id: changes
run: |
if [ -z "$(git status --porcelain)" ]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
git status --porcelain
{
echo "### Pinned"
git diff --unified=0 -- 'pkgbuilds/*/PKGBUILD' | grep -E '^\+(pkgver|_commit)=' | sed 's/^+/- /'
} >> "$GITHUB_STEP_SUMMARY"
fi
# The PR title names what moved, so the merged history reads like a
# changelog: "Track upstream branches: omarchy-dev 4.0.0.r6520.g1a2b3c4, ...".
- name: Describe the pins
if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }}
id: describe
run: |
title=$(git diff --unified=0 -- 'pkgbuilds/*/PKGBUILD' \
| awk '/^\+\+\+ b\/pkgbuilds\//{split($2,p,"/"); pkg=p[3]} /^\+pkgver=/{sub(/^\+pkgver=/,""); printf "%s %s, ", pkg, $0}' \
| sed 's/, $//')
echo "title=Track upstream branches: ${title}" >> "$GITHUB_OUTPUT"
- name: Open or update the tracking PR
if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }}
id: pr
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.PKGS_BOT_TOKEN }}
commit-message: ${{ steps.describe.outputs.title }}
title: ${{ steps.describe.outputs.title }}
body: |
Automated pin of packages that follow a moving upstream branch
(`"auto_merge": true` in `.omarchy/package.json`). Each package's
`_commit` now points at the branch tip. Fresh tips wait until
their commit timestamp is at least `min_release_age` old.
This PR auto-merges once the build checks pass. A failing build
leaves it open; the next tracker run replaces it with the newer tip.
branch: auto/track-branches
delete-branch: true
labels: automated
# Auto-merge, not a direct merge: branch protection still has to see
# `result`, `self-tests` and `build-isolation` green, and this lane
# inherits every rule the reviewed lane has except the human.
- name: Enable auto-merge
if: ${{ !cancelled() && steps.pr.outputs.pull-request-number != '' }}
env:
GH_TOKEN: ${{ secrets.PKGS_BOT_TOKEN }}
PR: ${{ steps.pr.outputs.pull-request-number }}
run: |
# Idempotent across re-runs of an updated PR: enabling twice errors.
if [[ "$(gh pr view "$PR" -R "${{ github.repository }}" --json autoMergeRequest --jq '.autoMergeRequest != null')" == true ]]; then
echo "auto-merge already enabled on #$PR"
exit 0
fi
gh pr merge --auto --merge "$PR" -R "${{ github.repository }}"
- name: Notify Basecamp on failure
if: failure() && env.BASECAMP_CHATBOT_URL != ''
env:
BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }}
run: |
curl -s -o /dev/null \
-H "Content-Type: application/json" \
-d "$(jq -n --arg content \
"🔴 <strong>Branch tracking failed</strong><br><a href=\"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\">View run</a>" \
'{content: $content}')" \
"$BASECAMP_CHATBOT_URL"
+79
View File
@@ -0,0 +1,79 @@
name: Unpublish retired packages
# Takes packages out of the channel databases after their recipes are gone
# from master. Deleting a recipe stops it building; this stops pacman
# offering it. Files stay in the bucket (see bin/unpublish-packages).
#
# Only packages with no recipe on master: one that still has a recipe would
# come back on its next publish, and refusing it keeps a typo from pulling a
# live package out of every channel.
on:
workflow_dispatch:
inputs:
packages:
description: "Space-separated pkgbases whose recipes were removed from master"
required: true
channels:
description: "Channels to remove them from"
required: true
default: "edge rc stable"
jobs:
unpublish:
runs-on: ubuntu-latest
environment: publish
timeout-minutes: 15
# The publish job's group: one writer per channel database at a time.
concurrency:
group: publish
cancel-in-progress: false
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Refuse packages that still have a recipe
env:
PACKAGES: ${{ github.event.inputs.packages }}
CHANNELS: ${{ github.event.inputs.channels }}
run: |
set -euo pipefail
for c in $CHANNELS; do
[[ $c == edge || $c == rc || $c == stable ]] || { echo "::error::Unknown channel: $c"; exit 1; }
done
for p in $PACKAGES; do
[[ $p =~ ^[a-z0-9@._+-]+$ ]] || { echo "::error::Not a package name: $p"; exit 1; }
if [[ -e pkgbuilds/$p ]]; then
echo "::error::pkgbuilds/$p still exists on master; remove the recipe first"
exit 1
fi
done
- name: Unpublish
env:
PACKAGES: ${{ github.event.inputs.packages }}
CHANNELS: ${{ github.event.inputs.channels }}
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
RCLONE_CONFIG_R2_NO_CHECK_BUCKET: "true"
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
OMARCHY_PUBLISH_PREFIX: ${{ vars.OMARCHY_PUBLISH_PREFIX }}
run: |
set -euo pipefail
# repo-remove and bsdtar are Arch tools: run in the tested builder
# image, as the publish job does.
builder=ghcr.io/omacom/omarchy-pkg-builder:$(bin/builder-image key --arch x86_64 --mirror edge)
docker pull --quiet "$builder"
for mirror in $CHANNELS; do
for arch in x86_64 aarch64; do
docker run --rm \
-e OMARCHY_PUBLISH_PREFIX \
-e RCLONE_CONFIG_R2_TYPE -e RCLONE_CONFIG_R2_PROVIDER -e RCLONE_CONFIG_R2_ENDPOINT -e RCLONE_CONFIG_R2_NO_CHECK_BUCKET \
-e RCLONE_CONFIG_R2_ACCESS_KEY_ID -e RCLONE_CONFIG_R2_SECRET_ACCESS_KEY \
-v "$PWD:/w:ro" -w /w "$builder" \
bin/unpublish-packages --remote R2:omarchy-pkgs --mirror "$mirror" --arch "$arch" $PACKAGES
done
done
+6
View File
@@ -1,6 +1,8 @@
.firecrawl/
src/
logs/
build-output/
cache/
pkgs.omarchy.org/
pkgbuilds/*/.SRCINFO
pkgbuilds/*/.gitignore
@@ -35,3 +37,7 @@ pkgbuilds/yay/yay/
.srcdest/
.repo-host
.worktrees/
# Python helpers and offline tests
__pycache__/
.release-verification/
+365 -137
View File
@@ -23,25 +23,72 @@ The filesystem no longer encodes release policy. Instead:
(`OMARCHY_RC_PINS=1`, which `omarchy-release rc` sets) may build it for rc — master's
shipped pins can never overwrite an in-flight RC. The dev pair
(`omarchy-dev`, `omarchy-settings-dev`) is pinned to `edge`
- AUR sync behavior is controlled by `source`, `sync`, `aur`, patches, and hooks in `.omarchy/`
- packages that follow a moving upstream branch (the dev pair on `quattro`,
`omasnap-git` on `main`) still pin an exact commit in their PKGBUILD. A
`git_branch` upstream watch moves that pin, and `"auto_merge": true` puts the
package on the unattended lane: `track-branches.yml` opens the bump PR every
two hours and auto-merges it once the build checks pass, so a branch tip
reaches the edge channel without anyone clicking. No PKGBUILD may carry an
unpinned git source (`tests/pinned-sources.sh`); a branch that has to be
followed gets a watch, not a `#branch=` fragment
- Omarchy owns every checked-in recipe; upstream watches update release metadata without replacing packaging or architecture support
- packages can opt out of unscoped builds with `skip_build`; explicit `--package` builds remain available
- packages that follow a vendor release feed instead of the AUR carry an `.omarchy/upstream.sh` hook
- packages follow direct upstream watches/providers in `.omarchy/package.json`, or a custom `.omarchy/upstream.sh` hook
## Prerequisites
### aarch64 Builds (Optional)
To build ARM64 packages on x86_64, enable QEMU emulation:
The repository host builds every architecture it publishes on the same
machine. A foreign architecture runs under QEMU user emulation, which
`bin/build` checks by actually running a container for the target platform.
Rootful Docker registers QEMU on first use. Rootless Podman uses the host's
registration and prints the one-time Arch setup commands when it is missing or
lacks the credential flag required by `sudo` inside the builder:
```bash
# Run after each reboot
docker run --privileged --rm tonistiigi/binfmt --install arm64
# Verify
docker run --rm --platform linux/arm64 alpine:latest uname -m
podman run --rm --platform linux/arm64 docker.io/library/alpine:latest uname -m
# Should output: aarch64
```
**Note**: aarch64 builds use QEMU and slower than native x86_64 builds.
**Note**: emulated builds are much slower than native ones.
### Published architectures
`helpers/paths.sh` names the architectures this repository publishes:
```bash
PUBLISHED_ARCHES="${OMARCHY_ARCHES:-x86_64}"
```
That list drives the whole scheduled pipeline. `check-versions` compares
PKGBUILDs against each architecture's channel databases and writes one queue
file per channel and architecture (`.sync-needed-<channel>-<arch>`);
`auto-release <channel>` works through the queues one architecture at a
time, each with its own backoff (`.build-failed-<channel>-<arch>`), so a
failing build on one architecture never holds up the other; and the release
train advances channels with `--arch all`: it takes one host-wide lock and
verifies every architecture's source database before moving any of them. The
first entry is the reference architecture the release train observes channels
through. A remote sync failure can still leave a promotion temporarily partial;
rerunning the same advance completes it safely.
Adding an architecture to the scheduled pipeline is therefore one checked-in
change to that list: the next `check-versions` tick queues everything the new
architecture lacks, and the next `auto-release` tick starts building it. A
checked-in list also means the rebuild workflow and release host cannot drift
onto different architecture sets. For a one-off run, override it directly:
```bash
OMARCHY_ARCHES=x86_64 bin/check-versions
OMARCHY_ARCHES=aarch64 bin/check-versions
OMARCHY_ARCHES="x86_64 aarch64" bin/check-versions
```
The builder image bootstraps
`omarchy-keyring` from the x86_64 tree for every architecture, so the first
build of a new architecture does not depend on a repository that only it can
create.
## Quick Start
@@ -86,6 +133,14 @@ bin/repo advance --from edge --to rc
The release command is smart and **incremental** - it only builds packages that have changed or are missing. You generally don't need to specify a package manually unless you are debugging a specific failure.
When a package fails, a completed build run still signs and publishes the packages
that succeeded. Failed packages and their blocked dependents remain queued with
failure backoff; retries compare against the updated repository and skip the
published versions. Only artifacts recorded by fully completed package builds
are eligible for a partial release. An interrupted build, a failed publication
step, or an incomplete pair using deferred runtime dependencies still stops the
release. Reports distinguish partial publication from complete success.
```bash
# Build changed/new packages, sign, promote, clean, update, and sync
bin/repo release
@@ -282,14 +337,16 @@ push uploaded, so `push` stops when it finds packages already staged there —
usually leftovers from a failed run. Remove them on the host, or pass
`--include-staged` to publish them too.
### Sync AUR PKGBUILDs
### Import an initial AUR recipe
```bash
bin/sync-aur # Sync all AUR packages with sync enabled
bin/sync-aur yay v4l2-relayd # Sync specific packages
bin/add-package package-name --source aur
```
AUR sync is metadata-driven. It preserves `.omarchy/`, replaces the package root with AUR contents, applies `.omarchy/patches/*.patch`, runs `.omarchy/post-sync.sh` when present, applies pkgrel metadata, removes AUR-only `.SRCINFO` and `.gitignore` files, and records `upstream_commit`.
AUR is an optional source for an initial recipe. Imported packages become
Omarchy-owned immediately; subsequent updates use direct upstream releases.
There is no scheduled AUR sync. Edit the checked-in PKGBUILD to maintain
architecture support and packaging behavior.
### Sync Upstream Releases
@@ -300,10 +357,11 @@ bin/sync-upstream openai-codex-desktop # Update specific packages
Some vendors publish a release feed of their own that is faster and more precise
than the AUR packaging of it. Those packages are `source: local` — Omarchy owns
the PKGBUILD — and declare where releases come from in one of two ways.
the PKGBUILD — and declare where releases come from either as data or, for an
unusual feed, a small hook.
A vendor shipping tagged GitHub releases with a checksum manifest asset is pure
data, declared as `upstream` in `.omarchy/package.json` with no code at all:
A vendor shipping tagged GitHub releases is pure data, declared as `upstream`
in `.omarchy/package.json` with no code at all:
```json
"upstream": {
@@ -316,16 +374,107 @@ data, declared as `upstream` in `.omarchy/package.json` with no code at all:
}
```
`checksums` names the manifest asset the vendor publishes. A vendor publishing
none sets `"digests": true` instead, and the checksums come from the SHA-256
digest GitHub's release API reports for every asset — see
`pkgbuilds/schist-bin/.omarchy/package.json`. Either way the artifacts
themselves are never downloaded.
An architecture may map to an ordered array when its PKGBUILD downloads more
than one release asset. Small versioned files outside the release assets can be
listed under `sources` and are downloaded and hashed when a new version appears:
```json
"upstream": {
"github": "owner/project",
"digests": true,
"assets": {
"x86_64": ["tool-{pkgver}-x86_64", "tool-{pkgver}-x86_64.asc"],
"aarch64": ["tool-{pkgver}-aarch64", "tool-{pkgver}-aarch64.asc"]
},
"sources": {
"any": ["https://raw.githubusercontent.com/owner/project/{tag}/LICENSE"]
}
}
```
Asset and source keys must be disjoint because each key maps to one PKGBUILD
checksum array (`any` means the unsuffixed `sha256sums`).
Repositories whose historical releases use incompatible tag schemes may set
`"latest_only": true`. The provider then considers only the newest stable
GitHub release, while retaining all validation for that release. A quarantine
will wait for that release to age instead of falling back to an older one.
`{tag}` and `{pkgver}` interpolate into asset names; a leading `v` on the tag is
stripped for `pkgver`; drafts and prereleases are ignored. Only the 100 most
recent releases are considered. The provider fails closed on anything it cannot
read — an unusable tag, timestamp, or checksum stops the sync rather than being
skipped.
A package may also declare `"min_release_age": "24h"` (`s`/`m`/`h`/`d` suffix or
bare seconds) to quarantine fresh releases until maintainers have had time to
pull a bad or compromised one. The newest release that has cleared the window
ships, so a fast release cadence cannot starve updates. The window is enforced
Projects that publish version tags but no checksum manifest can declare the
tag repository, the exact tag shape, and every source that should be hashed:
```json
"upstream": {
"git_tags": "https://github.com/owner/project.git",
"tag_pattern": "v{pkgver}",
"sources": {
"any": ["https://github.com/owner/project/archive/refs/tags/{tag}.tar.gz"]
}
}
```
The newest matching tag is selected with pacman's `vercmp`; unrelated tags are
ignored. `tag_pattern` must contain exactly one `{pkgver}`. Source templates may
use `{tag}` and `{pkgver}`. Each expanded URL must be HTTPS and is downloaded
only when the discovered version is newer. A checked-in patch or other local
source can be included as `file:patch-name.patch`; it is hashed from the package
directory. Keys such as `any`, `x86_64`, and `aarch64` select the corresponding
`sha256sums` array.
npm packages use the same source mapping, with `{npm_tarball}` available for
the tarball named by the selected dist-tag:
```json
"upstream": {
"npm": "@scope/package",
"dist_tag": "latest",
"sources": {
"any": ["{npm_tarball}", "https://example.com/v{pkgver}/CHANGELOG.md"]
}
}
```
`dist_tag` defaults to `latest`. The registry's publication timestamp is
carried into the provider result, so `min_release_age` works for npm packages.
A vendor with a plain-text Debian `Packages` index can use it to discover the
newest exact package version, then hash immutable source URLs:
```json
"upstream": {
"debian": "https://example.com/debian/dists/stable/main/binary-amd64/Packages",
"package": "example-app",
"sources": {
"x86_64": ["https://example.com/tool-{pkgver}-x64.tar.gz"],
"aarch64": ["https://example.com/tool-{pkgver}-arm64.tar.gz"]
}
}
```
This deliberately accepts only Debian versions that are already valid Arch
`pkgver` values. Feeds needing epoch, revision, or filename translation retain
a hook. Exactly one of `github`, `git_tags`, `npm`, or `debian` may appear in a
declaration.
A timestamped provider may also declare `"min_release_age": "24h"`
(`s`/`m`/`h`/`d` suffix or bare seconds) to quarantine fresh releases until
maintainers have had time to pull a bad or compromised one. GitHub Releases and
npm provide publication times; raw git tags and Debian Packages indexes do not,
so combining either with this policy fails closed. The newest release that has
cleared the window ships, so a fast release cadence cannot starve updates. The
window is enforced
centrally: whatever reports the release must prove its age via `published_at`,
or the sync fails. A maintainer deliberately shipping inside the window runs
`BYPASS_MIN_RELEASE_AGE=1 bin/sync-upstream <package>` locally and merges the
@@ -378,7 +527,13 @@ A package names those dependencies in `.omarchy/package.json`:
{ "source": "aur", "sync": false, "rebuild_on": ["qt6-base", "qt6-declarative", "qt6-wayland"] }
```
`bin/sync-rebuilds` reads each named package's version from the official repositories and compares it to `rebuilt_against`, the record of what the checked-in pkgrel was last bumped for. pkgrel is bumped unless every name in `rebuild_on` is recorded and still matches, so a name the record does not carry reads as changed rather than going unexamined forever. Opting a package in therefore buys one rebuild: what its published build actually linked against is not knowable from here, and a record written without a rebuild would certify a build nobody checked.
`bin/sync-rebuilds` reads each named package's version from the official
repositories for every architecture a merge builds (`CI_ARCHES` in
`helpers/paths.sh`, both by default) that the package supports and compares
it to `rebuilt_against`. Records are kept per architecture because Arch and
Arch Linux ARM can carry different dependency versions. pkgrel is bumped once
when any recorded version moves; that one source revision is then rebuilt by
each architecture's normal queue.
The bump is the point of the command, and it has to land in git rather than in the builder. A rebuild that reuses the published version string produces a package pacman will never offer anyone, so merely unlocking the build gate would ship nothing. Bumping pkgrel needs no other change: `bin/check-versions` and the builder both already rebuild when pkgrel moves.
@@ -386,9 +541,14 @@ For an AUR-synced package the bump is expressed as the dotted Omarchy pkgrel suf
The bumped version is checked against the published one as well as the checked-in one, and refused when pacman would not order it higher. The checked-in version is not the floor; what a user already has is, and a checkout that has fallen behind the repository can otherwise be bumped to something that loses to the package it means to replace. That check is skipped with a warning when the published database cannot be read.
Versions are read from the local pacman database, so this runs on Arch or in an Arch container against a synced database. Only `core`, `extra` and `multilib` count: a Qt release sitting in testing or kde-unstable is not what the builder will link against, and rebuilding for it would ship a package built against the wrong ABI. The workflow points that database at `mirror.omarchy.org`, the mirror the x86_64 builder itself uses, because a mirror running ahead of the builder would record a version the build never linked against and nothing re-fires once the record matches.
x86_64 versions are read from the local pacman database, so the workflow runs
in an Arch container pointed at `mirror.omarchy.org`, the same mirror as the
x86_64 builder. aarch64 versions are read directly from the live Arch Linux ARM
repository database, which is also what the ARM builder uses. Testing and
staging repositories do not count. A legacy flat `rebuilt_against` record is
read as x86_64 and is migrated naturally the next time a rebuild is needed.
aarch64 is not covered. Those builds resolve Qt from Arch Linux ARM, which can lag Arch, so one record cannot describe both architectures. Only x86_64 is published today, so nothing currently ships from the untracked side; if ARM publishing starts, `rebuilt_against` has to become per-architecture before this can be trusted there.
A dependency this repository carries for an architecture (a recipe here that builds for edge on it, such as aquamarine on aarch64) shadows the distribution's, because the builder lists `[omarchy]` first. Its version is the recipe's, and it counts only once edge publishes that version: until then the builder still links against the previous one, so dependents are left alone for that run.
### Other
@@ -403,14 +563,28 @@ bin/omarchy-release # Release front door (start / pick / rc / s
bin/repo list # List package metadata
bin/repo deploy # Build locally, then publish from the host
bin/repo push # Upload local builds to the host and publish
bin/add-package <package> # Add an AUR/local package with metadata
bin/package-worktree <package> # Create upstream/patched/current scratch workspace
bin/repo remove <package> # Remove package
bin/add-package <package> # Add an Omarchy-owned package with metadata
bin/package-worktree <package> # Inspect historical AUR provenance in a scratch workspace
bin/repo remove <package> # Remove package (host workflow; CI uses unpublish.yml)
bin/sync-upstream # Update packages that track a vendor release feed
bin/sync-rebuilds # Bump pkgrel for packages whose dependencies moved
bin/clean-docker # Clear Docker images/cache (forces fresh rebuild)
```
### Retiring a package
Delete its recipe directory in a PR. Once that merges, take it out of the
channel databases with the **Unpublish retired packages** workflow:
```
gh workflow run unpublish.yml -f packages="<pkgbase> ..." -f channels="edge rc stable"
```
It removes every entry built from those pkgbases (split outputs and `-debug`
included) from both architectures' databases, and refuses any package that
still has a recipe on master. Package files stay in the bucket: published
filenames are immutable, and nothing references them once the entries are gone.
### Package Metadata Tools
```bash
@@ -423,7 +597,7 @@ bin/repo list # Table view of source package metadata
bin/repo list --json # Agent/script-friendly JSON
bin/repo list --repo --mirror stable # List packages in a published repo database
bin/package-worktree v4l2-relayd # Create upstream/patched/current scratch workspace
bin/package-worktree yay # Compare with the original imported AUR recipe
```
## Cutting an Omarchy Release
@@ -537,9 +711,7 @@ omarchy-pkgs/
│ ├── PKGBUILD
│ └── .omarchy/
│ ├── package.json # Source/sync/release metadata
│ ├── patches/ # Omarchy patches reapplied after AUR sync
│ ├── post-sync.sh # Optional dynamic post-sync customization hook
│ └── upstream.sh # Optional vendor release feed hook (non-AUR packages)
│ └── upstream.sh # Optional custom vendor release feed hook
├── build/
├── build-output/ # Unsigned packages (temporary)
│ ├── edge/ # (rc/ and stable/ alongside, each x86_64 + aarch64)
@@ -559,45 +731,29 @@ Each source package has Omarchy metadata at `pkgbuilds/<package>/.omarchy/packag
Minimal examples:
```json
{ "source": "aur" }
```
```json
{ "source": "aur", "sync": false }
```
```json
{ "source": "aur", "release_ring": "fast" }
```
```json
{ "source": "local" }
```
```json
{ "source": "local", "release_ring": "fast" }
{ "source": "local", "skip_build": true }
```
```json
{ "source": "aur", "pkgrel": { "suffix": 1 } }
{ "source": "local", "upstream": { "watch": { "github": "abenz1267/walker", "pattern": "v(?P<version>[0-9]+(?:\\.[0-9]+)*)" } } }
```
Fields:
- `source`: `aur` or `local`. A `local` package can still follow an upstream release, either declaratively via `upstream` or with an `.omarchy/upstream.sh` hook.
- `upstream`: optional for `local` packages whose vendor ships tagged GitHub releases with a checksum manifest asset. `{ "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "<arch>": "name-{tag}.tar.xz" } }` — see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`.
- `source`: `local` for maintained packages. The legacy `aur` value is used only during an initial import. A local recipe can follow an upstream watch, provider, or `.omarchy/upstream.sh` hook.
- `upstream`: optional direct release watch (see [Upstream watches](docs/upstream-sources.md)), or an existing GitHub, git-tag, npm, or Debian provider. GitHub architecture assets may be a string or an ordered array, and can be combined with disjoint versioned `sources` — see [Sync Upstream Releases](#sync-upstream-releases). Mutually exclusive with `.omarchy/upstream.sh`.
- `min_release_age`: optional quarantine for upstream releases (`"24h"`, `"2d"`, or bare seconds). The newest release older than the window ships; anything younger waits, and a release whose age cannot be proven fails the sync. Bypass deliberately with `BYPASS_MIN_RELEASE_AGE=1 bin/sync-upstream <package>`.
- `sync`: optional for AUR packages; defaults to `true`. Set `false` for AUR-origin packages that Omarchy maintains manually.
- `aur`: optional AUR package name when it differs from the local package directory, usually for split packages.
- `sync`: `false` records an existing manual maintenance hold. Held packages have no upstream watch/provider/hook and are excluded from automatic updates.
- `auto_merge`: optional boolean; defaults to `false`. `true` moves the package's upstream updates from the reviewed 6-hourly sync PR to the unattended lane: `track-branches.yml` opens its bump PR and auto-merges it when CI is green. Meant for packages that follow a moving branch through a `git_branch` watch, where every tip is a release and there is nothing for a reviewer to read. Requires an upstream watch, provider, or hook.
- `origin`: optional historical import provenance, with `aur` (package name) and `commit`. It does not control updates.
- `release_ring`: optional. `fast` means the package is built directly for stable as well as edge, with the artifacts replicated into rc for parity. Packages without a ring build in edge and reach stable through the pipeline (`bin/repo advance`).
- `channels`: optional array bounding where the package may be built (`edge`, `rc`, `stable`). Without the key a package is a member of every channel and follows the default build rules above; `bin/repo advance` refuses to carry a package anywhere it isn't a member.
- `pinned`: optional boolean. A pinned package's version is set per release by `omarchy-release` on the `rc` branch, so it is never built for stable (promotion only) and is built for rc only from that branch's worktree (`OMARCHY_RC_PINS=1`). Used by `omarchy` and `omarchy-settings`.
- `skip_build`: optional boolean; defaults to `false`. Set `true` to exclude a package from scheduled version checks and unscoped builds. The package can still be built explicitly with `bin/repo release --package <name>`.
- `pkgrel`: optional Omarchy pkgrel suffix for a version-pinned rebuild bump. This emits `<aur pkgrel>.<suffix>` instead of replacing AUR's pkgrel. `offset` can be used only when preserving monotonic upgrades from old absolute pkgrel bumps. The metadata is removed automatically when AUR sync changes `pkgver`; the current package version is read from the checked-in PKGBUILD, so the version is not duplicated in JSON.
- `pkgrel`: legacy import customization metadata. Maintained recipes keep their complete package release directly in PKGBUILD; rebuilds increment it there.
- `rebuild_on`: optional array of package names this package links against closely enough that it must be rebuilt when they change, independent of its own source. Read by `bin/sync-rebuilds`.
- `rebuilt_against`: written by `bin/sync-rebuilds`. Records the version of each `rebuild_on` package that the current pkgrel was bumped for.
- `upstream_commit`: set by `bin/sync-aur` for AUR packages. Used by `bin/package-worktree` to recreate the exact raw AUR package that Omarchy last synced.
- `rebuilt_against`: written by `bin/sync-rebuilds`. Maps each built architecture to the versions of its `rebuild_on` packages that the current pkgrel was bumped for.
- `upstream_commit`: legacy AUR metadata, superseded by `origin.commit`. `bin/package-worktree` can use historical provenance to inspect the original recipe.
### Build Matrix
@@ -609,78 +765,26 @@ Fields:
## Adding Packages
### From AUR
### Start from an existing recipe
```bash
bin/add-package package-name
bin/add-package package-name --source aur --fast
# Review the imported files, own any architecture/packaging changes directly,
# and declare an upstream watch/provider or hook in .omarchy/.
bin/sync-upstream package-name
bin/repo release --package package-name
```
### From AUR, fast release ring
The import records historical provenance in `origin`. It does not opt a package
into future AUR imports. Upstream watches update only release scalars and source
checksums; downstream build behavior stays in the recipe. Ordinary source-code
patches still belong beside PKGBUILD and are applied by `prepare()` as needed.
### Custom package
```bash
bin/add-package package-name --fast
bin/repo release --package package-name
bin/repo release --mirror stable --package package-name
```
### AUR-origin, manually maintained by Omarchy
```bash
bin/add-package package-name --no-sync
```
### Local Customizations for AUR Packages
For static changes, create `pkgbuilds/package-name/.omarchy/patches/*.patch` to maintain modifications across AUR syncs.
The recommended workflow is to use a scratch workspace:
```bash
bin/package-worktree package-name --dir /tmp/package-name-worktree
```
This creates:
```text
upstream/ # raw AUR package at upstream_commit
patched/ # AUR + existing Omarchy .omarchy customizations
current/ # current checked-in package directory
```
Patch-authoring flow:
```bash
# 1. Make the intended change in pkgbuilds/package-name/
# 2. Recreate the scratch workspace
bin/package-worktree package-name --dir /tmp/package-name-worktree
# 3. Inspect drift from patched -> current
# For multi-file changes, inspect this and split into focused patches.
diff -ruN /tmp/package-name-worktree/patched /tmp/package-name-worktree/current
# For a single PKGBUILD change, write a patch like this:
mkdir -p pkgbuilds/package-name/.omarchy/patches
(
cd /tmp/package-name-worktree/patched
diff -u --label a/PKGBUILD --label b/PKGBUILD \
PKGBUILD /tmp/package-name-worktree/current/PKGBUILD || true
) > pkgbuilds/package-name/.omarchy/patches/my-fix.patch
# 4. Verify the package is reproducible from AUR + .omarchy
bin/sync-aur package-name
bin/package-worktree package-name --dir /tmp/package-name-check
diff -ruN /tmp/package-name-check/patched /tmp/package-name-check/current
```
For dynamic changes that depend on the current upstream version, add `pkgbuilds/package-name/.omarchy/post-sync.sh`. The hook runs after the AUR package is copied into a temporary worktree and before the Omarchy pkgrel suffix is applied. After patches/hooks/metadata pkgrel overrides, `bin/sync-aur` removes AUR-only `.SRCINFO` and `.gitignore` files before writing the package back.
### Custom Package
```bash
bin/add-package my-package --local --scaffold
# Fill in PKGBUILD and package files
bin/add-package my-package --scaffold
# Fill in PKGBUILD, package files, and upstream metadata
bin/repo release --package my-package
```
@@ -691,10 +795,15 @@ bin/repo release --package my-package
- Mirrors: mirror.omarchy.org, rackspace, pkgbuild.com
### aarch64
- QEMU emulation required on x86_64 hosts (slower)
- Uses Arch Linux ARM repositories
- Built on the repository host like x86_64; under QEMU when the host is x86_64
- On an ARM host, package builds and the signing/database utility containers
run natively; only an explicitly requested x86_64 package build is emulated
- Uses Arch Linux ARM repositories through the same HTTPS mirror for every
channel (Arch Linux ARM publishes no dated snapshots to pin a channel's base)
- Additional repos: `[alarm]`, `[aur]`
- Same workflow, just add `--arch aarch64`
- Same workflow, just add `--arch aarch64`; the scheduled pipeline runs it
automatically once `aarch64` is in `PUBLISHED_ARCHES`
- Packages whose `arch=()` lacks `aarch64` are skipped, not failed
### Building for Both Architectures
@@ -714,12 +823,73 @@ bin/repo sync --arch aarch64
The build system automatically handles inter-package dependencies:
1. Parses `depends=()` and `makedepends=()` from PKGBUILDs
2. Builds in correct order
3. Makes newly-built packages available via temporary `[omarchy-build]` repo
1. Plans dependency order once, including `depends`, `makedepends`,
`checkdepends`, and their architecture-specific arrays.
2. Builds each package in a fresh container. Installed packages and changes
to the container's system files cannot carry over to the next build.
3. Shares successful artifacts through the temporary `[omarchy-build]` repo,
installing newly built prerequisites in each consumer's container.
4. Blocks consumers of a failed prerequisite while continuing independent
builds. Any failure still prevents the release from publishing.
Example: If `aether` depends on `hyprshade`, `hyprshade` is built first.
Isolation also lets the release and dev Omarchy pairs build in the same run:
Flea can install `omarchy` without preventing `omarchy-dev` from installing
its conflicting settings package in a different container.
Pacman downloads are cached under `cache/pacman/<channel>/<arch>/` across
containers and runs. The installed package database is never shared. Each
container updates its base system before resolving build dependencies, so a
cached builder image cannot cause a partial system upgrade. The existing
`OMARCHY_KEEP_BUILD_WORKSPACE`, `OMARCHY_SKIP_BUILDER_IMAGE`, and
`OMARCHY_DEFER_RUNTIME_DEPS` flags retain their behavior.
`tests/build-isolation.sh` exercises conflicting package pairs, failed
prerequisites, resumed builds, cache replacement, and deferred dependencies
using real containers and pacman transactions. It uses the prepared builder
image, or an image named by `TEST_BUILDER_IMAGE`; CI builds the small fixture
image in `tests/build-isolation.Dockerfile`.
### Daily builder images
`Refresh builder images` builds fresh `edge` environments daily at 04:23 UTC,
when their inputs change on `master`, and on manual dispatch. x86_64 and
aarch64 build on native GitHub-hosted runners, without occupying the DO
package-builder pool. Each candidate must pass `tests/build-isolation.sh`,
including real package builds, before publication to
`ghcr.io/omacom/omarchy-pkg-builder`. Only `master` in this repository can
publish; PR workflows cannot replace the shared images.
PRs that change image inputs also build and test both candidates on native
runners, with a read-only token and no registry publication.
The compatibility tag contains the architecture, mirror, and a hash of the
entire `build/` context, including executable bits and symlink targets but
excluding checkout timestamps and ownership. This deliberately invalidates
images when mounted build scripts change too. `v1` identifies the image build
contract; change it if the invocation or compatibility rules change. Each
successful refresh also gets a run-specific tag for diagnosis and rollback.
A failed build, isolation test, or push leaves the previous compatible image
selected. Scheduled builds use `--pull --no-cache` so unchanged Dockerfiles
still pick up fresh Arch packages.
To build and test a candidate locally:
```bash
bin/builder-image key --arch x86_64 --mirror edge
bin/builder-image build --arch x86_64 --mirror edge --tag builder-candidate:test --fresh
CONTAINER_ENGINE=docker TEST_BUILDER_IMAGE=builder-candidate:test tests/build-isolation.sh
```
The workflow uses its repository `GITHUB_TOKEN` with `packages: write`; no
registry PAT is needed. **First publication needs one package setting:** GHCR
creates the package private. In the `omacom/omarchy-pkg-builder` package
settings, change visibility to **Public**, then rerun the failed refresh job.
The workflow checks anonymous registry access before advancing the compatible
tag, so fork PRs will not be directed to an image they cannot pull. Subsequent
refreshes preserve that package visibility. This change only produces images;
package jobs keep their existing behavior until image consumption is enabled.
## Version Management
Packages are only rebuilt if:
@@ -736,17 +906,70 @@ The repository includes GitHub workflows and systemd services for automated rele
#### GitHub Workflows
1. **sync-aur.yml** (Every 6 hours): Syncs AUR packages according to `.omarchy/package.json` and opens a PR when changes are found.
2. **sync-upstream.yml** (Every 6 hours): Runs `.omarchy/upstream.sh` for packages that track a vendor release feed and opens a PR when a newer version is out.
3. **sync-rebuilds.yml** (Every 6 hours): Bumps pkgrel for packages whose `rebuild_on` dependencies have moved in the official repositories and opens a PR.
1. **sync-upstream.yml** (Every 6 hours): Watches direct upstream feeds and updates owned recipes on the reviewed lane. Successful package updates reach a PR even if another feed fails; failed recipes stay untouched and the workflow remains red.
2. **sync-rebuilds.yml** (Every 6 hours): Bumps pkgrel for packages whose `rebuild_on` dependencies have moved in the official repositories, opens a PR, and enables auto-merge. The PR lands once its build checks pass; a rebuild that fails stays an open red PR for a maintainer.
3. **track-branches.yml** (Every 2 hours): The unattended lane. Pins every `"auto_merge": true` package to the tip of its watched branch once its commit timestamp clears `min_release_age`, opens one PR for all of them, and enables auto-merge. Packages pinned from the same branch move together or not at all, including targeted syncs. The PR builds like any other; a tip that fails to build stays an open red PR until the next tick supersedes it.
The tracking and rebuild PRs and their auto-merge use the PAT stored in `PKGS_BOT_TOKEN`, with
Contents and Pull requests write access to this repository and an owner trusted
to trigger builds. The existing controller PAT can be reused. No GitHub App is
required. The built-in Actions `GITHUB_TOKEN` cannot drive the unattended
build-and-publish chain, so both workflows require this secret before they run.
The reviewed upstream sync continues to use `GITHUB_TOKEN` and requires
maintainer approval as before. See [setup instructions](docs/upstream-sources.md#enable-unattended-branch-updates).
Scheduled runs regenerate one shared PR (`auto/sync-upstream`, `auto/sync-rebuilds`)
from master. A manual run with the `packages` input only regenerates those
packages, so it opens its own PR on `auto/sync-upstream-<packages>` (or
`auto/sync-rebuilds-<packages>`) rather than replacing the shared PR's other
pending updates. The next scheduled run still picks the same update up in the
shared PR if it has not merged by then; identical package trees reuse the same
build artifacts.
Upstream sync PRs are pushed with `GITHUB_TOKEN`, so GitHub holds their build and test
runs for approval on every push and starts no `pull_request_target` workflow
for them. The upstream sync labels its own PRs **`build-approved`**, and
its `approve` job releases the held runs for each commit they push, including
the push that opens the PR. A push to an `auto/sync-*` branch does not cancel
the PR's in-flight build: the new build waits for it and then reuses its
artifacts, so a long aarch64 build is not restarted by every sync.
Package PRs that are trusted to build also merge themselves.
`auto-merge-pr.yml` enables auto-merge (with `PKGS_BOT_TOKEN`, so the merge
publishes) on any open, non-draft PR whose author is a collaborator, vouched,
or a bot, or that carries **`build-approved`**, and that changes only
packages: anything under `pkgbuilds/`, plus the test a package PR brings with
it (a new file under `tests/`, and `test.yml` lines that only run new
`./tests/*.sh`). The PR lands once `result`, `self-tests` and
`build-isolation` pass; a red build stays open. PRs that also touch
workflows, scripts, build tooling or existing tests still need a maintainer, as does
the upstream sync (`auto/sync-upstream`), which labels itself. Removing
`build-approved` withdraws the auto-merge it armed. For a PR opened before
the workflow existed, run it by hand: `gh workflow run auto-merge-pr.yml -f pr=<number>`.
To approve builds for an unvouched contributor's PR, apply **`build-approved`**.
Until approval, the PR shows **Awaiting build approval** and its required
`result` check stays pending, keeping the PR blocked from merging without
reporting a failed build. Actual build failures and denouncements still fail.
Applying the label triggers a package build and automatically releases GitHub's
pending build and test workflows for that PR's current commit. The approval workflow
runs only trusted default-branch code; package builds and tests stay in the
ordinary PR workflows. It may take a few minutes for GitHub to register and
release all the runs.
The label stays effective for that PR while attached, including later commits;
it does not vouch for the author's other PRs. Removing it stops further label
approvals, but does not cancel runs already released. An explicit denouncement
in `.github/VOUCHED.td` still blocks builds. If the approval workflow times out,
remove and reapply the label to retry.
#### Systemd Services
All four units run **every 5 minutes**, staggered by a minute each, so a push
reaches the mirror in minutes rather than hours:
1. **check-versions** (`*:0/5`): Pulls latest from git, compares PKGBUILD versions to published versions, creates state files if builds are needed
2. **auto-release-edge** (`*:1/5`): If a state file exists, builds all edge packages that need updates
1. **check-versions** (`*:0/5`): Pulls latest from git, compares PKGBUILD versions to published versions for every published architecture, creates one state file per channel and architecture if builds are needed
2. **auto-release-edge** (`*:1/5`): For each published architecture with a state file, builds all edge packages that need updates
3. **auto-release-rc** (`*:2/5`): Builds fast-ring packages for rc, from the main checkout like the other two — natively in the rc image, not copied from another channel. The pinned release pair is built separately by `omarchy-release rc` in the `rc` branch worktree
4. **auto-release-stable** (`*:3/5`): If a state file exists, builds `release_ring=fast` packages for stable and replicates them to rc
@@ -760,10 +983,11 @@ That cadence is only safe because of three guards:
an operator expects. `check-versions` takes it too — its `git pull` would
otherwise swap PKGBUILDs out from under a running build.
- **Backoff on failure.** A failed release records the attempt in
`.build-failed-<channel>` and backs off exponentially — 10m, 20m, 40m, up to
`.build-failed-<channel>-<arch>` and backs off exponentially — 10m, 20m, 40m, up to
a 6h ceiling — instead of rebuilding the same broken tree every 5 minutes.
**Any new commit clears the backoff immediately**, since a push is the most
likely fix. Clear it by hand with `rm /root/.state/.build-failed-<channel>`.
likely fix. Clear it by hand with
`rm /root/.state/.build-failed-<channel>-<arch>`.
- **Quiet when idle.** With nothing queued a tick exits without output, so the
journal shows the runs that mattered rather than 288 no-ops a day.
@@ -816,10 +1040,14 @@ bin/repo timers --local # inspect this machine instead
```
State files are stored in `/root/.state/`:
- `.sync-needed-<channel>` — the packages queued for that channel, one per
line; the release run reads them to name what it is building
- `.build-failed-<channel>` — consecutive failure count, timestamp, and the
commit it failed on (drives the backoff; removing it forces a retry)
- `.sync-needed-<channel>-<arch>` — the packages queued for that channel and
architecture, one per line; the release run reads them to name what it is
building
- `.build-failed-<channel>-<arch>` — consecutive failure count, timestamp, and
the commit it failed on (drives the backoff; removing it forces a retry)
Legacy files without the architecture suffix are consumed once as x86_64
state, so upgrading the host does not lose an in-flight build.
### Schedule (America/New_York)
+19 -20
View File
@@ -6,7 +6,7 @@ source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
PACKAGE=""
SOURCE="aur"
SOURCE="local"
SYNC="true"
RELEASE_RING=""
AUR_PACKAGE=""
@@ -17,14 +17,14 @@ usage() {
cat <<EOF
Usage: $0 <package> [OPTIONS]
Create pkgbuilds/<package> with Omarchy metadata. AUR packages are synced
immediately after metadata is written.
Create an Omarchy-owned package. --source aur imports a starting recipe once;
future releases must use an upstream watch, provider, or hook.
Options:
--source <aur|local> Package source (default: aur)
--source <aur|local> Initial recipe source (default: local)
--local Shortcut for --source local
--aur <name> AUR package name when different from local directory
--no-sync For AUR packages, mark sync disabled after initial setup
--no-sync Keep the imported recipe manually maintained
--fast Put package in the fast release ring
--release-ring <ring> Release ring (currently: fast)
--scaffold For local packages, create a starter PKGBUILD
@@ -32,9 +32,9 @@ Options:
-h, --help Show this help message
Examples:
$0 yay
$0 spotify --fast
$0 signal-desktop --no-sync
$0 yay --source aur
$0 spotify --source aur --fast
$0 signal-desktop --source aur --no-sync
$0 omarchy-zsh --local --scaffold
EOF
}
@@ -97,6 +97,10 @@ if [[ -z "$PACKAGE" ]]; then
usage
exit 1
fi
if [[ ! $PACKAGE =~ ^[a-zA-Z0-9@_+][a-zA-Z0-9@._+-]*$ ]]; then
print_error "Invalid package name: $PACKAGE"
exit 1
fi
case "$SOURCE" in
aur|local) ;;
@@ -112,6 +116,11 @@ PACKAGE_DIR="$PKGBUILDS_DIR/$PACKAGE"
OMARCHY_DIR="$PACKAGE_DIR/.omarchy"
METADATA_FILE="$OMARCHY_DIR/package.json"
if [[ "$SOURCE" == aur && -f "$PACKAGE_DIR/PKGBUILD" ]]; then
print_error "Refusing to replace the maintained recipe for $PACKAGE"
exit 1
fi
if [[ -f "$METADATA_FILE" && "$FORCE" != true ]]; then
print_error "Package metadata already exists: $METADATA_FILE"
print_info "Use --force to overwrite it"
@@ -146,18 +155,8 @@ jq -n "${jq_args[@]}" "$jq_filter" > "$METADATA_FILE"
print_success "Wrote $METADATA_FILE"
if [[ "$SOURCE" == "aur" ]]; then
if [[ "$SYNC" == "false" ]]; then
# Temporarily sync once, then restore sync=false so future automated syncs skip it.
tmpfile=$(mktemp)
jq 'del(.sync)' "$METADATA_FILE" > "$tmpfile"
mv "$tmpfile" "$METADATA_FILE"
"$BUILD_ROOT/bin/sync-aur" "$PACKAGE"
jq '. + {sync: false}' "$METADATA_FILE" > "$tmpfile"
mv "$tmpfile" "$METADATA_FILE"
print_info "AUR sync disabled for future runs"
else
"$BUILD_ROOT/bin/sync-aur" "$PACKAGE"
fi
"$BUILD_ROOT/bin/import-aur" "$PACKAGE"
else
if [[ "$SCAFFOLD" == true && ! -f "$PACKAGE_DIR/PKGBUILD" ]]; then
cat > "$PACKAGE_DIR/PKGBUILD" <<EOF
+57 -10
View File
@@ -25,6 +25,22 @@ SKIP_PROD_CHECK=false
FAST_RING_ONLY=false
BOOTSTRAP=false
PACKAGES=""
ALL_ARCHES=false
# Kept for --arch all, which re-invokes this script per architecture with the
# other arguments unchanged (minus the --arch all pair itself).
ORIGINAL_ARGS=()
arch_option=false
for arg in "$@"; do
if [[ "$arch_option" == true ]]; then
[[ "$arg" != "all" ]] && ORIGINAL_ARGS+=("--arch" "$arg")
arch_option=false
elif [[ "$arg" == "--arch" ]]; then
arch_option=true
else
ORIGINAL_ARGS+=("$arg")
fi
done
usage() {
echo "Usage: $0 --from <channel> --to <channel> [OPTIONS]"
@@ -36,7 +52,8 @@ usage() {
echo " or --bootstrap (one-time initial seed)"
echo ""
echo "Options:"
echo " --arch <arch> Target architecture (x86_64 or aarch64, default: x86_64)"
echo " --arch <arch>|all Target architecture (x86_64 or aarch64, default: x86_64);"
echo " all = every published architecture, one after another"
echo " --package <names...> Advance only the named package(s)"
echo " --fast-ring Restrict to fast-ring packages (stable -> rc parity copy)"
echo " --bootstrap One-time stable -> rc seed before forward-only enforcement"
@@ -65,8 +82,14 @@ while [[ $# -gt 0 ]]; do
shift 2
;;
--arch)
ARCH="$2"
update_arch_paths
if [[ "$2" == "all" ]]; then
ALL_ARCHES=true
ARCH=all
else
require_valid_arch "$2"
ARCH="$2"
update_arch_paths
fi
shift 2
;;
--package)
@@ -134,6 +157,31 @@ case "$FROM->$TO" in
;;
esac
if [[ "$ALL_ARCHES" == true ]]; then
# Hold one lock across the whole operation so a timer cannot mutate a
# channel between architectures. Check every source database before moving
# the first one; a failed sync can still require an idempotent retry, but a
# missing architecture never creates a knowingly partial advance.
if [[ "$DRY_RUN" != true ]]; then
acquire_release_lock || exit 1
fi
ARCHES=$(published_arches)
for arch in $ARCHES; do
source_db="$REPO_ROOT/$FROM/$arch/omarchy.db.tar.zst"
if [[ ! -f "$source_db" ]]; then
print_error "Source database not found: $source_db"
echo "Nothing has been published to the $FROM channel for $arch."
exit 1
fi
done
for arch in $ARCHES; do
"$0" --arch "$arch" "${ORIGINAL_ARGS[@]}" || exit $?
done
exit 0
fi
SOURCE_DIR="$REPO_ROOT/$FROM/$ARCH"
TARGET_DIR="$REPO_ROOT/$TO/$ARCH"
SOURCE_DB="$SOURCE_DIR/omarchy.db.tar.zst"
@@ -278,7 +326,7 @@ while IFS=$'\t' read -r name base filename; do
done < <(read_manifest)
echo ""
print_info "Copied: $COPIED | Already present: $PRESENT | Not eligible: $SKIPPED"
print_info "Copied: $COPIED | Already present: $PRESENT | Differing (kept): ${#DIFFERING[@]} | Not eligible: $SKIPPED"
if [[ ${#MISSING_FILES[@]} -gt 0 ]]; then
print_error "${#MISSING_FILES[@]} package(s) named in the $FROM database are missing on disk:"
@@ -297,13 +345,12 @@ if [[ ${#MISSING_SIGS[@]} -gt 0 ]]; then
fi
if [[ ${#DIFFERING[@]} -gt 0 ]]; then
print_error "${#DIFFERING[@]} file(s) already published in $TO with DIFFERENT bytes:"
print_warning "${#DIFFERING[@]} file(s) already published in $TO with different bytes — kept as-is:"
printf ' %s\n' "${DIFFERING[@]}"
echo "Published filenames are never rewritten, and two artifacts fighting over"
echo "one name means something built twice from different inputs. Resolve it"
echo "deliberately: bump pkgrel and rebuild so the new artifact gets a new"
echo "filename, or remove the source copy if the destination is correct."
exit 1
echo "Published filenames are never rewritten, so the $TO copy stays authoritative."
echo "A same-name collision means this version reached $TO through another lineage"
echo "(native build, bootstrap seed, or a same-version rebuild) and the package has"
echo "not moved in $FROM since; it advances under a new filename when it does."
fi
if [[ "$DRY_RUN" == true ]]; then
+104 -57
View File
@@ -1,6 +1,10 @@
#!/bin/bash
# Run the release workflow for a channel when work is queued.
# Usage: auto-release <edge|rc|stable>
# Usage: auto-release <edge|rc|stable> [<arch>|all]
#
# With no architecture (what the timers pass), every published architecture
# is processed in turn, each against its own queue and its own backoff, so a
# failing aarch64 build never holds up x86_64 or the other way round.
#
# Safe to run on a tight schedule. Three guards make that true:
#
@@ -21,7 +25,7 @@ source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/lock-helpers.sh"
MIRROR="${1:-}"
STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}"
ARCH_ARG="${2:-all}"
# Backoff schedule: 10m, 20m, 40m, 80m, 160m, 320m, then hourly-ish forever
# (capped at 6h, the cadence this system ran at before frequent timers).
@@ -29,8 +33,9 @@ BACKOFF_BASE_SECONDS="${OMARCHY_BACKOFF_BASE:-600}"
BACKOFF_MAX_SECONDS="${OMARCHY_BACKOFF_MAX:-21600}"
if [[ -z "$MIRROR" ]]; then
print_error "Usage: $0 <mirror>"
print_error "Usage: $0 <mirror> [<arch>|all]"
echo " mirror: edge, rc, or stable"
echo " arch: one of $VALID_ARCHES, or all (default) for every published architecture"
exit 1
fi
@@ -39,17 +44,13 @@ if [[ "$MIRROR" != "edge" && "$MIRROR" != "rc" && "$MIRROR" != "stable" ]]; then
exit 1
fi
STATE_FILE="$STATE_DIR/.sync-needed-$MIRROR"
FAIL_FILE="$STATE_DIR/.build-failed-$MIRROR"
# Nothing queued: stay quiet. At a 5-minute cadence this is most invocations,
# and a header for each would bury the runs that matter in the journal.
if [[ ! -f "$STATE_FILE" ]]; then
exit 0
if [[ "$ARCH_ARG" == "all" ]]; then
ARCHES=$(published_arches)
else
require_valid_arch "$ARCH_ARG"
ARCHES="$ARCH_ARG"
fi
print_header "Processing Sync for $MIRROR"
# The build inputs are this checkout's contents; its HEAD identifies them.
current_fingerprint() {
git -C "$BUILD_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown"
@@ -65,57 +66,103 @@ backoff_seconds() {
echo "$delay"
}
FAIL_COUNT=0
if [[ -f "$FAIL_FILE" ]]; then
# shellcheck disable=SC1090
source "$FAIL_FILE" 2>/dev/null || true
FAIL_COUNT="${FAILURE_COUNT:-0}"
failed_at="${FAILURE_AT:-0}"
failed_fingerprint="${FAILURE_FINGERPRINT:-}"
# Returns 0 when this architecture's queue was processed (or was empty), 1 when
# the release failed. Backoff and "someone else holds the lock" are not
# failures: they are this tick deciding to do nothing.
release_arch() {
local arch="$1"
local state_file fail_file
state_file=$(sync_queue_file "$MIRROR" "$arch")
fail_file=$(sync_fail_file "$MIRROR" "$arch")
if [[ "$failed_fingerprint" != "$(current_fingerprint)" ]]; then
print_info "Repository changed since the last failure — clearing backoff and retrying"
rm -f "$FAIL_FILE"
FAIL_COUNT=0
else
delay=$(backoff_seconds "$FAIL_COUNT")
now=$(date +%s)
retry_at=$((failed_at + delay))
if ((now < retry_at)); then
print_warning "$MIRROR has failed $FAIL_COUNT time(s) on this tree — not retrying until $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")"
echo " Push a fix (any new commit clears this), or: rm $FAIL_FILE"
exit 0
fi
print_info "Backoff elapsed — retrying $MIRROR (failure #$((FAIL_COUNT + 1)) if this fails)"
# A queue written under the pre-architecture name belongs to x86_64.
if [[ "$arch" == "x86_64" && ! -f "$state_file" && -f "$(legacy_sync_queue_file "$MIRROR")" ]]; then
state_file=$(legacy_sync_queue_file "$MIRROR")
fi
if [[ "$arch" == "x86_64" && ! -f "$fail_file" && -f "$(legacy_sync_fail_file "$MIRROR")" ]]; then
fail_file=$(legacy_sync_fail_file "$MIRROR")
fi
fi
# Non-blocking: a build in progress means this tick has nothing to do.
if ! try_release_lock; then
holder=$(release_lock_holder)
print_info "A release is already running (${holder:-holder unknown}) — skipping this tick"
exit 0
fi
# Nothing queued: stay quiet. At a 5-minute cadence this is most
# invocations, and a header for each would bury the runs that matter in
# the journal.
[[ -f "$state_file" ]] || return 0
print_info "State file found: $STATE_FILE"
print_info "Starting release workflow for $MIRROR..."
print_header "Processing Sync for $MIRROR ($arch)"
if "$BUILD_ROOT/bin/repo" release --mirror "$MIRROR" --skip-prod-check; then
print_success "Release completed successfully for $MIRROR"
rm -f "$STATE_FILE"
rm -f "$FAIL_FILE"
print_success "State file removed: $STATE_FILE"
else
status=$?
FAIL_COUNT=$((FAIL_COUNT + 1))
cat >"$FAIL_FILE" <<EOF
FAILURE_COUNT=$FAIL_COUNT
local fail_count=0 failed_at failed_fingerprint delay now retry_at
if [[ -f "$fail_file" ]]; then
FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT=""
# shellcheck disable=SC1090
source "$fail_file" 2>/dev/null || true
fail_count="${FAILURE_COUNT:-0}"
failed_at="${FAILURE_AT:-0}"
failed_fingerprint="${FAILURE_FINGERPRINT:-}"
if [[ "$failed_fingerprint" != "$(current_fingerprint)" ]]; then
print_info "Repository changed since the last failure — clearing backoff and retrying"
rm -f "$fail_file"
fail_count=0
else
delay=$(backoff_seconds "$fail_count")
now=$(date +%s)
retry_at=$((failed_at + delay))
if ((now < retry_at)); then
print_warning "$MIRROR ($arch) has failed $fail_count time(s) on this tree — not retrying until $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")"
echo " Push a fix (any new commit clears this), or: rm $fail_file"
return 0
fi
print_info "Backoff elapsed — retrying $MIRROR ($arch) (failure #$((fail_count + 1)) if this fails)"
fi
fi
# Non-blocking: a build in progress means this tick has nothing to do. The
# lock is reentrant, so once this run holds it the remaining architectures
# run under the same acquisition.
if ! try_release_lock; then
local holder
holder=$(release_lock_holder)
print_info "A release is already running (${holder:-holder unknown}) — skipping this tick"
return 0
fi
print_info "State file found: $state_file"
print_info "Starting release workflow for $MIRROR ($arch)..."
if "$BUILD_ROOT/bin/repo" release --mirror "$MIRROR" --arch "$arch" --skip-prod-check; then
print_success "Release completed successfully for $MIRROR ($arch)"
local completed_state=("$state_file" "$fail_file")
if [[ "$arch" == "x86_64" ]]; then
completed_state+=("$(legacy_sync_queue_file "$MIRROR")" "$(legacy_sync_fail_file "$MIRROR")")
fi
if ! rm -f "${completed_state[@]}"; then
print_error "Release succeeded, but its queue state could not be cleared"
return 1
fi
print_success "State file removed: $state_file"
return 0
fi
fail_count=$((fail_count + 1))
if ! cat >"$fail_file" <<EOF
FAILURE_COUNT=$fail_count
FAILURE_AT=$(date +%s)
FAILURE_FINGERPRINT=$(current_fingerprint)
EOF
next=$(backoff_seconds "$FAIL_COUNT")
print_error "Release failed for $MIRROR (attempt $FAIL_COUNT)"
print_warning "State file retained for retry: $STATE_FILE"
then
print_error "Could not record failure state: $fail_file"
return 1
fi
local next
next=$(backoff_seconds "$fail_count")
print_error "Release failed for $MIRROR ($arch) (attempt $fail_count)"
print_warning "State file retained for retry: $state_file"
print_warning "Backing off $((next / 60))m before the next attempt; a new commit retries sooner"
exit "$status"
fi
return 1
}
status=0
for arch in $ARCHES; do
release_arch "$arch" || status=1
done
exit "$status"
+219 -30
View File
@@ -13,6 +13,27 @@ print_header "Omarchy Package Builder"
DRY_RUN=false
# Knobs for builds driven from CI or resumed by hand. Each defaults to the
# historical behaviour, so an unadorned `bin/build` is unchanged.
#
# OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output/$MIRROR/$ARCH instead of
# wiping it, so packages built by an earlier
# job (or a previous, interrupted run) seed
# the build database and resolve as
# dependencies of what builds now.
# OMARCHY_SKIP_BUILDER_IMAGE=1 use the omarchy-pkg-builder image already
# present instead of building it; a workflow
# that builds the image once with an external
# BuildKit cache can then fan out over many
# package jobs without each one rebuilding it.
# OMARCHY_DEFER_RUNTIME_DEPS=true build the omarchy/omarchy-settings pair
# with --nodeps (see build/build.sh); only
# for a pipeline that verifies the install
# transaction afterwards.
KEEP_BUILD_WORKSPACE=${OMARCHY_KEEP_BUILD_WORKSPACE:-0}
SKIP_BUILDER_IMAGE=${OMARCHY_SKIP_BUILDER_IMAGE:-0}
DEFER_RUNTIME_DEPS=${OMARCHY_DEFER_RUNTIME_DEPS:-false}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
@@ -65,7 +86,14 @@ while [[ $# -gt 0 ]]; do
echo " $0 --arch aarch64"
echo " $0 --mirror stable"
echo " $0 --package yay"
echo " $0 --package yay elephant cursor-bin"
echo " $0 --package yay walker cursor-bin"
echo ""
echo "Environment (for CI and resumed builds; defaults keep today's behaviour):"
echo " OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output and reuse packages already there"
echo " OMARCHY_SKIP_BUILDER_IMAGE=1 use the existing builder image instead of building it"
echo " OMARCHY_DEFER_RUNTIME_DEPS=true build the omarchy pair with --nodeps (transaction verified later)"
echo " OMARCHY_PUBLISHED_REPO_URL=<url> channel to plan and resolve against when no local tree exists"
echo " (default https://pkgs.omarchy.org; empty disables the fallback)"
echo ""
exit 0
;;
@@ -76,18 +104,55 @@ while [[ $# -gt 0 ]]; do
esac
done
require_valid_arch "$ARCH"
if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then
print_error "OMARCHY_DEFER_RUNTIME_DEPS must be true or false"
exit 1
fi
# Deferring runtime dependencies is only sound for the omarchy pair, and only
# when both halves are built together: the pair depends on each other and on
# packages that a sharded pipeline builds in other jobs, and the consumer of
# this mode installs the assembled set in one verified transaction. Check the
# request here so a misuse fails before Docker starts.
if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then
deferred_runtime=0
deferred_settings=0
deferred_count=0
for package in $PACKAGES; do
((deferred_count += 1))
case $package in
omarchy|omarchy-dev) deferred_runtime=1 ;;
omarchy-settings|omarchy-settings-dev) deferred_settings=1 ;;
*)
print_error "OMARCHY_DEFER_RUNTIME_DEPS only applies to the omarchy/omarchy-settings pair, not $package"
exit 1
;;
esac
done
if (( deferred_runtime != 1 || deferred_settings != 1 || deferred_count != 2 )); then
print_error "OMARCHY_DEFER_RUNTIME_DEPS requires --package with exactly the omarchy pair"
exit 1
fi
fi
# Show target architecture and mirror after parsing args
print_info "Target architecture: $ARCH"
print_info "Mirror: $MIRROR"
print_info "Build workspace: $BUILD_OUTPUT_DIR"
print_info "Final output: $REPO_DIR"
if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then
print_info "Runtime dependency checks: deferred to the install transaction"
fi
if [[ "$DRY_RUN" == true ]]; then
print_warning "DRY RUN MODE - build plan only; no Docker or makepkg will run"
print_warning "DRY RUN MODE - build plan only; no container or makepkg will run"
ARCH="$ARCH" \
MIRROR="$MIRROR" \
PACKAGES="$PACKAGES" \
DRY_RUN=true \
DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS" \
PKGBUILDS_DIR="$PKGBUILDS_DIR" \
BUILD_OUTPUT_DIR="$BUILD_OUTPUT_DIR" \
FINAL_OUTPUT_DIR="$REPO_DIR" \
@@ -100,21 +165,45 @@ fi
# Create directories if they don't exist
mkdir -p "$BUILD_OUTPUT_DIR" "$REPO_DIR" "$SRC_DIR"
# Check Docker is available
check_docker
# Check the selected container engine is available
check_engine
# Setup QEMU for aarch64 builds on x86_64 hosts
if [[ "$(uname -m)" == "x86_64" && "$ARCH" == "aarch64" ]]; then
# Check if QEMU is already working
if ! docker run --rm --platform linux/arm64 alpine:3.21 /bin/true >/dev/null 2>&1; then
print_info "Setting up QEMU for ARM64 emulation..."
setup_qemu
# A foreign target architecture runs under QEMU user emulation. Probe by
# actually running a container for the target platform: that is the only
# test that covers both "binfmt not registered" and "registered but broken".
HOST_ARCH=$(uname -m)
[[ "$HOST_ARCH" == "arm64" ]] && HOST_ARCH=aarch64
if [[ "$HOST_ARCH" != "$ARCH" ]]; then
PROBE_IMAGE="alpine:3.21"
[[ "$CONTAINER_ENGINE" == "podman" ]] && PROBE_IMAGE="docker.io/library/alpine:3.21"
# Rootless Podman cannot repair host binfmt state itself. Validate the flags
# before the basic probe, because an F-only registration can start an ARM
# container but silently breaks sudo inside it.
if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
setup_qemu "$ARCH"
fi
if ! "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$ARCH")" "$PROBE_IMAGE" /bin/true >/dev/null 2>&1; then
if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
print_error "QEMU $ARCH is registered, but the container probe failed"
print_info "Refresh the registration with: sudo systemctl restart systemd-binfmt"
exit 1
else
print_info "Setting up QEMU for $ARCH emulation on this $HOST_ARCH host..."
setup_qemu "$ARCH"
fi
fi
fi
# Clean build-output directory to start fresh
print_info "Cleaning build workspace..."
rm -rf "$BUILD_OUTPUT_DIR"/*
# Clean build-output directory to start fresh, unless the caller seeded it
# with packages from an earlier job or is resuming an interrupted run.
if [[ $KEEP_BUILD_WORKSPACE == "1" ]]; then
print_info "Keeping existing build workspace..."
else
print_info "Cleaning build workspace..."
rm -rf "${BUILD_OUTPUT_DIR:?}"/*
fi
mkdir -p "$BUILD_OUTPUT_DIR"
# Show package info
@@ -124,18 +213,43 @@ else
print_info "Building unscoped packages for $MIRROR mirror"
fi
# Build/update the Docker image
build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR"
# Build/update the Docker image, unless the caller prepared the exact image
# already (a workflow building it once with an external BuildKit cache). A
# missing image is an error rather than a silent rebuild: the point of the
# flag is that every job runs the same bytes.
IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR"
if [[ $SKIP_BUILDER_IMAGE == "1" ]]; then
if ! "$CONTAINER_ENGINE" image inspect "$IMAGE_TAG" >/dev/null 2>&1; then
print_error "Prepared builder image is unavailable: $IMAGE_TAG"
exit 1
fi
print_info "Using prepared builder image: $IMAGE_TAG"
else
build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR"
fi
print_info "Running package build..."
print_info "Planning isolated package builds..."
# Create output directories if they don't exist
mkdir -p "$BUILD_OUTPUT_DIR"
mkdir -p "$REPO_DIR"
# Ensure output directories are writable by container user
make_dir_writable "$BUILD_OUTPUT_DIR"
make_dir_writable "$REPO_DIR"
# Share downloaded archives, never /var/lib/pacman or an installed root.
# Channel/architecture separation preserves each mirror's dependency set.
PACKAGE_CACHE_DIR="$BUILD_ROOT/cache/pacman/$MIRROR/$ARCH"
mkdir -p "$PACKAGE_CACHE_DIR"
PLAN_DIR=$(mktemp -d "$SRC_DIR/build-plan.XXXXXX")
trap 'rm -rf "$PLAN_DIR"' EXIT
# Keep manifest directories host-owned so cleanup also works when Docker's
# builder uid differs from the caller (as on GitHub runners).
mkdir -p "$PLAN_DIR/artifacts"
# Rootful Docker writes as the image uid, so retain its existing permission
# workaround. Rootless Podman uses keep-id and must leave ownership/modes alone.
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$BUILD_OUTPUT_DIR"
make_dir_writable "$PLAN_DIR"
fi
# Build Docker arguments
DOCKER_ARGS=(
@@ -144,26 +258,101 @@ DOCKER_ARGS=(
-e MIRROR="$MIRROR"
-e PACKAGES="$PACKAGES"
-e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}"
-e OMARCHY_PUBLISHED_REPO_URL="${OMARCHY_PUBLISHED_REPO_URL-https://pkgs.omarchy.org}"
-e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS"
-e BUILD_PLAN_DIR=/build-plan
-v "$PLAN_DIR:/build-plan"
-v "$PACKAGE_CACHE_DIR:/var/cache/pacman/pkg"
-v "$BUILD_ROOT/build-output:/build-output"
-v "$REPO_ROOT:/pkgs.omarchy.org"
-v "$REPO_ROOT:/pkgs.omarchy.org:ro"
-v "$BUILD_DIR:/build:ro"
-v "$BUILD_ROOT/helpers:/helpers:ro"
-v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro"
)
# Run the builder with assembled args
IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR"
# Podman-created images can leave WORKDIR owned by a remapped uid. Mount the
# existing host-user-owned workspace so the builder can write there.
if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
DOCKER_ARGS+=(-v "$SRC_DIR:/src")
fi
# Plan once against the published database, then keep that order throughout
# the run. Each package sees the staged artifacts but starts with a fresh
# pacman database and root filesystem, even after a failed build.
PLATFORM_ARG=$(get_platform_arg "$ARCH")
docker run $PLATFORM_ARG "${DOCKER_ARGS[@]}" "$IMAGE_TAG" /build/build.sh
"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" \
-e DRY_RUN=true "$IMAGE_TAG" /build/build.sh
BUILD_RESULT=$?
mapfile -t ORDERED_PACKAGES < "$PLAN_DIR/packages"
mapfile -t SKIPPED_PACKAGES < "$PLAN_DIR/skipped"
SUCCESSFUL_PACKAGES=()
FAILED_PACKAGES=()
BLOCKED_PACKAGES=()
declare -A BUILD_STATUS=()
for package in "${ORDERED_PACKAGES[@]}"; do
blocked_by=""
while read -r consumer dependency; do
if [[ "$consumer" == "$package" && "${BUILD_STATUS[$dependency]:-}" != success ]]; then
blocked_by="$dependency"
break
fi
done < "$PLAN_DIR/dependencies"
if [[ -n "$blocked_by" ]]; then
print_warning "$package blocked by unsuccessful dependency: $blocked_by"
BUILD_STATUS[$package]=blocked
BLOCKED_PACKAGES+=("$package")
continue
fi
print_info "Building $package in a fresh container..."
if "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" "$PLATFORM_ARG" "${DOCKER_ARGS[@]}" \
-e BUILD_PACKAGE="$package" "$IMAGE_TAG" /build/build.sh; then
BUILD_STATUS[$package]=success
SUCCESSFUL_PACKAGES+=("$package")
else
BUILD_STATUS[$package]=failed
FAILED_PACKAGES+=("$package")
fi
done
# Summary
echo ""
if [[ $BUILD_RESULT -eq 0 ]]; then
print_success "Build completed successfully!"
else
print_warning "Some packages failed (see details above)"
exit $BUILD_RESULT
print_header "Build Summary"
echo " Total packages: ${#ORDERED_PACKAGES[@]}"
echo " Built: ${#SUCCESSFUL_PACKAGES[@]}"
echo " Skipped: ${#SKIPPED_PACKAGES[@]} (up-to-date or excluded)"
echo " Failed: ${#FAILED_PACKAGES[@]}"
echo " Blocked: ${#BLOCKED_PACKAGES[@]}"
# The release caller supplies a fresh directory. A completed result
# distinguishes package failures from an interrupted/failed orchestrator;
# only artifacts belonging to fully successful builds may be published.
if [[ -n "${OMARCHY_BUILD_RESULT_DIR:-}" ]]; then
mkdir -p "$OMARCHY_BUILD_RESULT_DIR"
: > "$OMARCHY_BUILD_RESULT_DIR/artifacts"
for package in "${SUCCESSFUL_PACKAGES[@]}"; do
cat "$PLAN_DIR/artifacts/$package" >> "$OMARCHY_BUILD_RESULT_DIR/artifacts"
done
printf '%s\n' "${FAILED_PACKAGES[@]}" | sed '/^$/d' > "$OMARCHY_BUILD_RESULT_DIR/failed"
printf '%s\n' "${BLOCKED_PACKAGES[@]}" | sed '/^$/d' > "$OMARCHY_BUILD_RESULT_DIR/blocked"
touch "$OMARCHY_BUILD_RESULT_DIR/complete"
fi
if (( ${#FAILED_PACKAGES[@]} + ${#BLOCKED_PACKAGES[@]} )); then
if (( ${#FAILED_PACKAGES[@]} )); then
echo "Failed packages:"
printf ' - %s\n' "${FAILED_PACKAGES[@]}"
fi
if (( ${#BLOCKED_PACKAGES[@]} )); then
echo "Packages blocked by failed dependencies:"
printf ' - %s\n' "${BLOCKED_PACKAGES[@]}"
fi
print_warning "Some packages failed (see details above)"
# Reserved for a completed run with unsuccessful packages. Other failures
# must not let release publish arbitrary files left in the workspace.
exit 2
fi
print_success "Build completed successfully!"
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Print the PR build matrix for a set of package directories as JSON: one
# entry per package per supported architecture. Every package builds exactly
# once, against edge, and that one artifact is what every channel ships:
# channels are databases over a shared pool of files, and a filename must
# mean one set of bytes. "channels" lists where the artifact is published on
# merge: edge for everything, plus rc and stable immediately for the fast
# ring. Eligibility comes from package_builds_for_mirror, the rule the
# release host uses, so CI and the host cannot disagree.
#
# Usage: build-matrix [--arch <arch>|all] <package>...
# Reads package names on stdin when none are given. With no --arch, every
# architecture in CI_ARCHES (default "x86_64 aarch64") the package supports.
# Output: {"include":[{"package":"x","arch":"x86_64","channels":"edge rc stable","publish_arches":"x86_64"},...]}
# arch is where it builds; publish_arches lists every architecture
# database the file goes into (all of them for arch=any).
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/package-metadata.sh"
ARCHES=$CI_ARCHES
if [[ "${1:-}" == --arch ]]; then [[ $2 == all ]] || ARCHES=$2; shift 2; fi
for a in $ARCHES; do require_valid_arch "$a"; done
if (( $# )); then names=("$@"); else mapfile -t names; fi
entries=()
for name in "${names[@]}"; do
[[ -n "$name" ]] || continue
pkgdir="$PKGBUILDS_DIR/$name"
[[ -d "$pkgdir" ]] || continue
# skip_build packages still build on their own PR (explicit --package
# semantics); the host's unscoped runs are what skip them.
channels=""
for mirror in $VALID_MIRRORS; do
package_builds_for_mirror "$pkgdir" "$mirror" && channels="$channels $mirror"
done
channels=${channels# }
[[ -n "$channels" ]] || continue
# An arch=any package produces one architecture-independent file, so it
# builds once, on the first architecture, and that file serves every
# channel database of every architecture.
if [[ " $(package_arches "$pkgdir" "${ARCHES%% *}") " == *" any "* ]]; then
entries+=("$(jq -nc --arg p "$name" --arg a "${ARCHES%% *}" --arg c "$channels" --arg pa "$ARCHES" '{package:$p, arch:$a, channels:$c, publish_arches:$pa}')")
continue
fi
for arch in $ARCHES; do
package_supports_arch "$pkgdir" "$arch" || continue
entries+=("$(jq -nc --arg p "$name" --arg a "$arch" --arg c "$channels" '{package:$p, arch:$a, channels:$c, publish_arches:$a}')")
done
done
printf '%s\n' "${entries[@]}" | jq -sc '{include: .}'
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
# Build a reusable package environment from this checkout's own inputs.
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/paths.sh"
usage() {
echo "Usage: bin/builder-image {key|build} [--arch x86_64|aarch64] [--mirror edge|rc|stable] [--tag IMAGE] [--fresh]"
}
command=${1:-}
[[ $# -eq 0 ]] || shift
tag=""
fresh=false
while (( $# )); do
case "$1" in
--arch) ARCH=${2:?Missing architecture}; shift 2 ;;
--mirror) MIRROR=${2:?Missing mirror}; shift 2 ;;
--tag) tag=${2:?Missing image tag}; shift 2 ;;
--fresh) fresh=true; shift ;;
*) usage >&2; exit 1 ;;
esac
done
require_valid_arch "$ARCH"
validate_mirror "$MIRROR" || { echo "Invalid mirror: $MIRROR" >&2; exit 1; }
case "$command" in key|build) ;; *) usage >&2; exit 1 ;; esac
if [[ $command == key && ( -n $tag || $fresh == true ) ]]; then
usage >&2
exit 1
fi
# Include the whole build context, conservatively including mounted build
# scripts too. Normalize timestamps and ownership so fresh checkouts agree;
# retain file contents, names, executable bits and symlink targets. Bump v1
# if the image build invocation or this compatibility contract changes.
hash=$(tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner \
--format=gnu -cf - -C "$BUILD_DIR" . | sha256sum | cut -d' ' -f1)
key="v1-$ARCH-$MIRROR-$hash"
if [[ $command == key ]]; then
echo "$key"
exit 0
fi
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
check_engine
platform=$(get_platform_arg "$ARCH")
tag=${tag:-omarchy-pkg-builder:latest-$ARCH-$MIRROR}
revision=$(git -C "$BUILD_ROOT" rev-parse HEAD 2>/dev/null || echo unknown)
args=("$platform" --build-arg "MIRROR=$MIRROR"
--label "org.omarchy.builder.key=$key"
--label "org.opencontainers.image.source=https://github.com/omacom/omarchy-pkgs"
--label "org.opencontainers.image.revision=$revision"
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
--tag "$tag" --file "$BUILD_DIR/Dockerfile")
if [[ $fresh == true ]]; then
# A daily build must refresh Arch even when its Dockerfile has not changed.
args+=(--no-cache)
if [[ $CONTAINER_ENGINE == docker ]]; then args+=(--pull); else args+=(--pull=always); fi
fi
if [[ $CONTAINER_ENGINE == docker ]]; then
docker buildx build --load "${args[@]}" "$BUILD_DIR"
else
podman build "${args[@]}" "$BUILD_DIR"
fi
+63 -22
View File
@@ -1,6 +1,10 @@
#!/bin/bash
# Check PKGBUILD versions against published repo versions
# Creates state files for edge and/or stable if any packages need building
# Creates a state file per channel and architecture when packages need building
#
# Usage: check-versions [--pull] [--arch <arch>]
# --pull pull the repository first (the scheduled run does this)
# --arch <arch> check one architecture; default: every published one
set -e
@@ -10,11 +14,27 @@ source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/package-metadata.sh"
source "$BUILD_ROOT/helpers/lock-helpers.sh"
STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}"
ARCH="${ARCH:-x86_64}"
PULL=false
ARCHES=""
[[ "${1:-}" == "--pull" ]] && PULL=true
while [[ $# -gt 0 ]]; do
case $1 in
--pull)
PULL=true
shift
;;
--arch)
require_valid_arch "$2"
ARCHES="$2"
shift 2
;;
*)
print_error "Unknown option: $1"
exit 1
;;
esac
done
[[ -n "$ARCHES" ]] || ARCHES=$(published_arches)
mkdir -p "$STATE_DIR"
@@ -59,10 +79,26 @@ get_repo_version() {
'
}
# The PKGBUILD's full version (epoch:pkgver-pkgrel) for the architecture being
# checked. Prints nothing when pkgver or pkgrel cannot be read: a partial
# version like "-" would compare unequal to everything published and queue a
# rebuild on every tick, so an unreadable PKGBUILD must not queue at all.
get_pkgbuild_version() {
local pkgdir="$1"
if [[ -f "$pkgdir/PKGBUILD" ]]; then
(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi')
local epoch pkgver pkgrel
[[ -f "$pkgdir/PKGBUILD" ]] || return 1
epoch=$(package_pkgbuild_var "$pkgdir" epoch "$ARCH")
pkgver=$(package_pkgbuild_var "$pkgdir" pkgver "$ARCH")
pkgrel=$(package_pkgbuild_var "$pkgdir" pkgrel "$ARCH")
[[ -n "$pkgver" && -n "$pkgrel" ]] || return 1
if [[ -n "$epoch" ]]; then
echo "${epoch}:${pkgver}-${pkgrel}"
else
echo "${pkgver}-${pkgrel}"
fi
}
@@ -77,8 +113,8 @@ check_vcs_unchanged() {
# If epoch or pkgrel changed in PKGBUILD, release even if upstream is unchanged.
local pkgbuild_epoch pkgbuild_pkgrel
pkgbuild_epoch=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${epoch:-}"')
pkgbuild_pkgrel=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgrel}"')
pkgbuild_epoch=$(package_pkgbuild_var "$pkgdir" epoch "$ARCH")
pkgbuild_pkgrel=$(package_pkgbuild_var "$pkgdir" pkgrel "$ARCH")
local repo_pkgrel="${repo_version##*-}"
local repo_no_pkgrel="${repo_version%-*}"
@@ -106,12 +142,11 @@ check_package() {
local mirror="$3"
local pkgbuild_version repo_version
pkgbuild_version=$(get_pkgbuild_version "$pkgdir")
repo_version=$(get_repo_version "$pkg" "$mirror")
if [[ -z "$pkgbuild_version" ]]; then
if ! pkgbuild_version=$(get_pkgbuild_version "$pkgdir") || [[ -z "$pkgbuild_version" ]]; then
print_warning "$pkg: could not read pkgver/pkgrel from its PKGBUILD for $ARCH — not queueing"
return 1
fi
repo_version=$(get_repo_version "$pkg" "$mirror")
if grep -qE '^pkgver[[:space:]]*\(\)' "$pkgdir/PKGBUILD"; then
if [[ -n "$repo_version" && -n "$(package_extract_vcs_hash_from_version "$repo_version")" ]]; then
@@ -137,8 +172,8 @@ check_package() {
# it because that exact filename is already published with different bytes.
# If the artifact for this version already exists in the channel, there is
# nothing to build regardless of which direction the versions differ.
if compgen -G "$REPO_ROOT/$mirror/$ARCH/${pkg}-${pkgbuild_version}-*.pkg.tar."[!s]* >/dev/null 2>&1; then
print_warning "$pkg $pkgbuild_version is already published — this checkout is behind the channel; not queueing"
if package_version_is_published "$REPO_ROOT/$mirror/$ARCH" "$pkg" "$pkgbuild_version" "$ARCH"; then
print_warning "$pkg $pkgbuild_version is already published; not queueing"
return 1
fi
@@ -147,11 +182,12 @@ check_package() {
check_mirror() {
local mirror="$1"
local state_file="$STATE_DIR/.sync-needed-$mirror"
local state_file
state_file=$(sync_queue_file "$mirror" "$ARCH")
local needs_build=false
local packages=()
print_info "Checking $mirror packages..."
print_info "Checking $mirror packages for $ARCH..."
while IFS= read -r pkg; do
local pkgdir="$PKGBUILDS_DIR/$pkg"
@@ -159,7 +195,7 @@ check_mirror() {
needs_build=true
packages+=("$pkg")
fi
done < <(packages_for_unscoped_build "$mirror")
done < <(packages_for_unscoped_build "$mirror" "$ARCH")
echo ""
@@ -168,18 +204,23 @@ check_mirror() {
# this to name the packages in its start report, which is the difference
# between "a build is running" and "your package is in this build".
printf '%s\n' "${packages[@]}" >"$state_file"
print_success "${mirror^} needs building (${#packages[@]} packages)"
print_success "${mirror^} ($ARCH) needs building (${#packages[@]} packages)"
print_info "Packages: ${packages[*]}"
print_info "State file created: $state_file"
else
print_info "${mirror^} is up to date"
print_info "${mirror^} ($ARCH) is up to date"
fi
echo ""
}
check_mirror edge
check_mirror rc
check_mirror stable
# One pass per published architecture: the version comparison reads that
# architecture's channel databases, and each queue is its own file.
for ARCH in $ARCHES; do
update_arch_paths
check_mirror edge
check_mirror rc
check_mirror stable
done
print_success "Version check complete!"
+13 -7
View File
@@ -1,21 +1,27 @@
#!/bin/bash
# Clean Docker builder images and cache
# Clean builder images and cache for the selected container engine
# Forces a fresh image build on next run
set -e
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
print_header "Cleaning Docker Builder Images"
check_engine
print_header "Cleaning Container Builder Images"
# Remove all omarchy-pkg-builder images
print_info "Removing omarchy-pkg-builder images..."
docker images omarchy-pkg-builder -q | xargs -r docker rmi -f 2>/dev/null || true
"$CONTAINER_ENGINE" images omarchy-pkg-builder -q | xargs -r "$CONTAINER_ENGINE" rmi -f 2>/dev/null || true
# Clear buildx cache for these builds
print_info "Clearing buildx cache..."
docker buildx prune -a -f
# Clear this engine's build cache.
print_info "Clearing build cache..."
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
"$CONTAINER_ENGINE" buildx prune -a -f
else
"$CONTAINER_ENGINE" image prune --build-cache -f
fi
print_success "Docker builder cache cleared"
print_success "Container builder cache cleared"
print_info "Next build will create fresh images"
+7 -3
View File
@@ -31,9 +31,13 @@ clean_packages() {
# Skip signature files
[[ "$pkg" == *.sig ]] && continue
# Extract package name (remove version and architecture)
# Format: name-version-release-arch.pkg.tar.*
local pkgname=$(echo "$pkg" | sed -E 's/-[0-9]+.*-(any|x86_64|i686)\.pkg\.tar\..*//')
# Format: name-version-release-arch.pkg.tar.*. Work from the right because
# package names can themselves contain version-like pieces (qt6-5compat,
# nvidia-580xx-utils), while pkgver and pkgrel cannot contain hyphens.
local stem="${pkg%%.pkg.tar.*}"
stem="${stem%-*}" # architecture
stem="${stem%-*}" # pkgrel
local pkgname="${stem%-*}" # pkgver
# Add to array
if [[ -n "${packages[$pkgname]}" ]]; then
Executable
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# Internal initial-recipe import used by add-package. Maintained recipes are
# never replaced; subsequent releases go through sync-upstream.
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
if [[ ${1:-} == --help || ${1:-} == -h ]]; then
echo "Usage: bin/add-package <package> --source aur [--aur <upstream-name>]"
exit 0
fi
if [[ $# != 1 || ! $1 =~ ^[a-zA-Z0-9@_+][a-zA-Z0-9@._+-]*$ ]]; then
print_error "Use bin/add-package <package> --source aur for an initial import"
exit 1
fi
package=$1
package_dir="$PKGBUILDS_DIR/$package"
metadata="$package_dir/.omarchy/package.json"
if [[ -f "$package_dir/PKGBUILD" ]]; then
print_error "Refusing to replace the maintained recipe for $package"
exit 1
fi
if [[ ! -f "$metadata" ]] || [[ $(jq -r .source "$metadata") != aur ]]; then
print_error "Initial import requires metadata created by bin/add-package --source aur"
exit 1
fi
aur_package=$(jq -r --arg name "$package" '.aur // $name' "$metadata")
if [[ ! $aur_package =~ ^[a-zA-Z0-9@_+][a-zA-Z0-9@._+-]*$ ]]; then
print_error "Invalid AUR package name"
exit 1
fi
# Refuse unrelated package files: an import only starts from .omarchy metadata.
shopt -s dotglob nullglob
for item in "$package_dir"/*; do
if [[ ${item##*/} != .omarchy ]]; then
print_error "Initial import needs an empty package directory: $package_dir"
exit 1
fi
done
work=$(mktemp -d "$PKGBUILDS_DIR/.import-aur.XXXXXX")
trap 'rm -rf "$work"' EXIT
print_info "Importing $package from AUR package $aur_package..."
git clone --quiet "https://aur.archlinux.org/${aur_package}.git" "$work/recipe"
[[ -f "$work/recipe/PKGBUILD" ]] || { print_error "AUR package has no PKGBUILD"; exit 1; }
commit=$(git -C "$work/recipe" rev-parse HEAD)
rm -rf "$work/recipe/.git" "$work/recipe/.omarchy"
rm -f "$work/recipe/.SRCINFO" "$work/recipe/.gitignore"
cp -a "$package_dir/.omarchy" "$work/recipe/.omarchy"
jq --arg name "$aur_package" --arg commit "$commit" '
.source = "local" | .origin = {aur: $name, commit: $commit}
| del(.aur, .upstream_commit)
' "$metadata" > "$work/recipe/.omarchy/package.json"
# Stage on the same filesystem, restoring the metadata directory on failure.
mv "$package_dir" "$work/original"
if ! mv "$work/recipe" "$package_dir"; then
mv "$work/original" "$package_dir"
exit 1
fi
print_success "Imported $package; review the recipe and configure its direct upstream watch"
+14 -7
View File
@@ -21,9 +21,10 @@ BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/host-helpers.sh"
source "$BUILD_ROOT/helpers/package-metadata.sh"
UPSTREAM_URL="${OMARCHY_UPSTREAM_URL:-https://github.com/basecamp/omarchy.git}"
EDGE_DB_URL="${OMARCHY_EDGE_DB_URL:-https://pkgs.omarchy.org/edge/x86_64/omarchy.db.tar.zst}"
EDGE_DB_URL="${OMARCHY_EDGE_DB_URL:-https://pkgs.omarchy.org/edge/$(reference_arch)/omarchy.db.tar.zst}"
RELEASE_PACKAGES=(omarchy omarchy-settings)
DEFAULT_RC_REF="quattro"
SRCDEST_DIR="$BUILD_ROOT/.srcdest"
@@ -92,7 +93,7 @@ version_is_rc() { version_is_prerelease "$1"; }
pkgbuild_var() {
local pkg="$1" var="$2"
(cd "$BUILD_ROOT/pkgbuilds/$pkg" && bash -c "source PKGBUILD 2>/dev/null; echo \"\${$var}\"")
package_pkgbuild_var "$BUILD_ROOT/pkgbuilds/$pkg" "$var"
}
# Prints the published version of $pkg from the edge DB. Distinguishes
@@ -327,13 +328,19 @@ regenerate_checksums() {
trigger_build_host() {
local host
queue_edge_builds() {
mkdir -p "$STATE_DIR" || return 1
local arch
for arch in $(published_arches); do
touch "$(sync_queue_file edge "$arch")" || return 1
done
}
# Explicit host configuration outranks the local-host inference (a
# workstation that ran a full local release carries the db marker too).
if ! resolve_repo_host "${REPO_HOST_OVERRIDE:-}" >/dev/null && on_repo_host; then
print_info "Triggering edge build locally (this is the build host)..."
if mkdir -p "${OMARCHY_STATE_DIR:-/root/.state}" &&
touch "${OMARCHY_STATE_DIR:-/root/.state}/.sync-needed-edge" &&
systemctl start --no-block omarchy-auto-release-edge.service; then
if queue_edge_builds && systemctl start --no-block omarchy-auto-release-edge.service; then
print_success "Edge build triggered"
else
print_warning "Could not start the edge release service — the 6-hourly timer will pick it up"
@@ -343,11 +350,11 @@ trigger_build_host() {
if ! host=$(resolve_repo_host "${REPO_HOST_OVERRIDE:-}"); then
print_info "No repository host configured (set OMARCHY_REPO_HOST or $BUILD_ROOT/.repo-host — any ssh destination, e.g. root@<host> or an ssh-config alias)."
print_info "The 6-hourly auto-release timer will pick this up, or trigger manually:"
echo " ssh <build-host> 'git -C /root/omarchy-pkgs pull --ff-only && touch /root/.state/.sync-needed-edge && systemctl start omarchy-auto-release-edge.service'"
echo " ssh <build-host> 'git -C /root/omarchy-pkgs pull --ff-only && systemctl start omarchy-check-versions.service omarchy-auto-release-edge.service'"
return 0
fi
print_info "Triggering edge build on $host..."
if ssh "$host" 'git -C /root/omarchy-pkgs pull --ff-only && mkdir -p /root/.state && touch /root/.state/.sync-needed-edge && systemctl start --no-block omarchy-auto-release-edge.service'; then
if ssh "$host" 'git -C /root/omarchy-pkgs pull --ff-only && cd /root/omarchy-pkgs && export BUILD_ROOT=/root/omarchy-pkgs && source helpers/paths.sh && mkdir -p "$STATE_DIR" && for arch in $(published_arches); do touch "$(sync_queue_file edge "$arch")"; done && systemctl start --no-block omarchy-auto-release-edge.service'; then
print_success "Edge build triggered on $host"
else
print_warning "Could not trigger $host — the 6-hourly timer will pick it up"
+93 -55
View File
@@ -18,6 +18,7 @@ set -e
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/host-helpers.sh"
@@ -28,7 +29,11 @@ ISO_REPO="${OMARCHY_ISO_REPO:-omacom-io/omarchy-iso}"
DEV_BRANCH="${OMARCHY_DEV_BRANCH:-quattro}"
PKGS_DB_BASE="${OMARCHY_PKGS_DB_BASE:-https://pkgs.omarchy.org}"
RC_DB_URL="$PKGS_DB_BASE/rc/x86_64/omarchy.db.tar.zst"
# The first published architecture supplies the version-ordering floor when
# cutting pins. Readiness checks below still verify every published
# architecture before an RC or final release can move forward.
OBSERVED_ARCH=$(reference_arch)
RC_DB_URL="$PKGS_DB_BASE/rc/$OBSERVED_ARCH/omarchy.db.tar.zst"
SRCDEST_DIR="$BUILD_ROOT/.srcdest"
MIRROR_CLONE="$SRCDEST_DIR/omarchy" # bare mirror (shared with bin/omarchy-pkgs)
@@ -181,12 +186,12 @@ ensure_work_clone() {
# Prints omarchy's published version in a channel; empty when absent, rc 2 when
# the database cannot be read (callers must not mistake an outage for absence).
published_version() {
local channel="$1" tmp descs
local channel="$1" arch="${2:-$OBSERVED_ARCH}" tmp descs
tmp=$(mktemp) || return 2
# A unique query string busts the CDN cache: right after a sync the plain
# URL can keep serving the previous db for a while, which reads as "not
# published yet" to status, the wait loop, and ship's pre-checks.
if ! curl -sf "$PKGS_DB_BASE/$channel/x86_64/omarchy.db.tar.zst?$(date +%s%N)" -o "$tmp"; then
if ! curl -sf "$PKGS_DB_BASE/$channel/$arch/omarchy.db.tar.zst?$(date +%s%N)" -o "$tmp"; then
rm -f "$tmp"
return 2
fi
@@ -207,6 +212,14 @@ published_version() {
' <<<"$descs"
}
all_arches_at_version() { # all_arches_at_version <channel> <pkgver>
local channel="$1" want="$2" arch got
for arch in $(published_arches); do
got=$(published_version "$channel" "$arch" 2>/dev/null) || return 1
[[ "${got%-*}" == "$want" ]] || return 1
done
}
# The rc branch of THIS repo carries the current pins. Read them without
# touching the working tree.
rc_branch_pin() { # prints "pkgver commit", empty when no rc branch
@@ -251,8 +264,12 @@ fi
git -C /root/omarchy-pkgs-rc fetch origin rc
git -C /root/omarchy-pkgs-rc reset --hard origin/rc
cd /root/omarchy-pkgs-rc
OMARCHY_RC_PINS=1 OMARCHY_REPO_ROOT=/root/omarchy-pkgs/pkgs.omarchy.org \
bin/repo release --mirror rc --package omarchy omarchy-settings --skip-prod-check
# The pair is built once per published architecture (helpers/paths.sh on the
# host decides which), so every architecture'"'"'s rc channel carries the pins.
for arch in $(BUILD_ROOT=/root/omarchy-pkgs-rc bash -c "source helpers/paths.sh; published_arches"); do
OMARCHY_RC_PINS=1 OMARCHY_REPO_ROOT=/root/omarchy-pkgs/pkgs.omarchy.org \
bin/repo release --mirror rc --arch "$arch" --package omarchy omarchy-settings --skip-prod-check
done
'
trigger_rc_build() {
@@ -278,19 +295,25 @@ host_advance() { # host_advance <from> <to> [extra args...]
# against this machine's (likely stale) local tree would be wrong.
if ! resolve_repo_host "$REPO_HOST_OVERRIDE" >/dev/null && ! on_repo_host; then
print_no_host_help "advance $from -> $to" \
" cd /root/omarchy-pkgs && bin/repo advance --from $from --to $to --skip-prod-check $*"
" cd /root/omarchy-pkgs && bin/repo advance --from $from --to $to --arch all --skip-prod-check $*"
return 1
fi
"$BUILD_ROOT/bin/repo" advance --from "$from" --to "$to" --skip-prod-check "$@"
# --arch all: the host advances every architecture it publishes, so a train
# never moves a channel for one architecture and not another.
"$BUILD_ROOT/bin/repo" advance --from "$from" --to "$to" --arch all --skip-prod-check "$@"
}
wait_for_published() { # wait_for_published <channel> <version> [timeout-seconds]
local channel="$1" want="$2" timeout="${3:-3600}" waited=0 got
print_info "Waiting for $want to appear in the $channel channel (up to $((timeout / 60))m)..."
local channel="$1" want="$2" timeout="${3:-3600}" waited=0 arch got pending
print_info "Waiting for $want in $channel for: $(published_arches | tr '\n' ' ')"
while ((waited < timeout)); do
got=$(published_version "$channel" 2>/dev/null) || got=""
if [[ "${got%-*}" == "$want" ]]; then
print_success "$channel now serves omarchy $got"
pending=""
for arch in $(published_arches); do
got=$(published_version "$channel" "$arch" 2>/dev/null) || got=""
[[ "${got%-*}" == "$want" ]] || pending+=" $arch=${got:-unreachable}"
done
if [[ -z "$pending" ]]; then
print_success "$channel now serves omarchy $want on every published architecture"
return 0
fi
sleep 60
@@ -298,7 +321,7 @@ wait_for_published() { # wait_for_published <channel> <version> [timeout-seconds
printf '.' >&2
done
echo "" >&2
print_warning "Timed out waiting for $want in $channel (currently: ${got:-unknown})"
print_warning "Timed out waiting for $want in $channel (pending:$pending)"
print_info "The build may still be running — re-run this command to resume."
return 1
}
@@ -352,8 +375,8 @@ iso_checkout() { # prints a usable omarchy-iso checkout, cloning to tmp if neede
build_iso() { # build_iso <version> [--rc]
local version="$1" rc_flag="${2:-}" dir
dir=$(iso_checkout) || return 1
if ! command -v docker >/dev/null || ! docker info >/dev/null 2>&1; then
print_warning "Docker unavailable — cannot build the ISO here. Run on a Docker machine:"
if [[ -z "$CONTAINER_ENGINE" ]] || ! "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_warning "No container engine is available — cannot build the ISO here. Run on a Docker or Podman machine:"
echo " cd $dir && bin/omarchy-iso-release ${rc_flag:+$rc_flag }$version"
return 1
fi
@@ -662,15 +685,13 @@ cmd_rc() {
if [[ -n "$pin" ]]; then
local pin_ver="${pin%% *}" pin_commit="${pin##* }"
if [[ "$pin_commit" == "$head" && "$pin_ver" =~ ^${version//./\\.}rc[0-9]+$ ]]; then
local pub
pub=$(published_version rc 2>/dev/null) || pub=""
if [[ "${pub%-*}" == "$pin_ver" ]]; then
print_success "$pin_ver is already cut from this head and published to rc"
if all_arches_at_version rc "$pin_ver"; then
print_success "$pin_ver is already cut from this head and published to rc on every architecture"
maybe_iso "$pin_ver" rc "$iso_mode"
return 0
fi
print_info "$pin_ver is pinned from this head but not published yet — re-triggering the build"
trigger_rc_build || true
trigger_rc_build || return 1
[[ "$wait" == true ]] && wait_for_published rc "$pin_ver"
maybe_iso "$pin_ver" rc "$iso_mode"
return 0
@@ -686,7 +707,7 @@ cmd_rc() {
new_ver="${new_pin%% *}"
print_success "Pinned $new_ver (rc branch pushed)"
trigger_rc_build || true
trigger_rc_build || return 1
if [[ "$wait" == true ]]; then
wait_for_published rc "$new_ver" || return 1
fi
@@ -708,9 +729,7 @@ cmd_ship() {
exit 1
fi
version=$(branch_to_version "$branch")
local stable_now
stable_now=$(published_version stable 2>/dev/null) || stable_now=""
if [[ "${stable_now%-*}" == "$version" ]] &&
if all_arches_at_version stable "$version" &&
gh release view "v$version" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; then
print_success "Nothing to ship — $version is tagged, released, and live on stable"
exit 0
@@ -752,10 +771,8 @@ cmd_ship() {
echo " omarchy-release rc"
exit 1
fi
local pub
pub=$(published_version rc 2>/dev/null) || pub=""
if [[ "${pub%-*}" != "$pin_ver" ]]; then
print_error "$pin_ver is pinned but rc serves '${pub:-nothing}' — the candidate build hasn't published"
if ! all_arches_at_version rc "$pin_ver"; then
print_error "$pin_ver is pinned but is not published for every architecture"
echo "Wait for it (or re-run: omarchy-release rc), then ship."
exit 1
fi
@@ -799,10 +816,9 @@ cmd_ship() {
# 2. Final pins into rc. Resolve the tag we just established so the final
# PKGBUILDs record both its provenance and its exact commit.
local rc_pub stable_pub
rc_pub=$(published_version rc 2>/dev/null) || rc_pub=""
if [[ "${rc_pub%-*}" == "$version" ]]; then
print_success "2/7 Final $version already published to rc"
local stable_pub
if all_arches_at_version rc "$version"; then
print_success "2/7 Final $version already published to rc on every architecture"
else
if [[ "$pin_ver" != "$version" ]]; then
print_info "2/7 Pinning final $version from tag v$version..."
@@ -810,22 +826,21 @@ cmd_ship() {
else
print_info "2/7 Final $version pinned — re-triggering build"
fi
trigger_rc_build || true
trigger_rc_build || exit 1
wait_for_published rc "$version" || exit 1
fi
# 3. Promote rc -> stable
stable_pub=$(published_version stable 2>/dev/null) || stable_pub=""
if [[ "${stable_pub%-*}" == "$version" ]]; then
print_success "3/7 Stable already serves $version"
if all_arches_at_version stable "$version"; then
print_success "3/7 Stable already serves $version on every architecture"
else
host_advance rc stable || exit 1
stable_pub=$(published_version stable 2>/dev/null) || stable_pub=""
if [[ "${stable_pub%-*}" != "$version" ]]; then
print_error "Promotion ran but stable serves '${stable_pub:-nothing}' — investigate before continuing"
if ! all_arches_at_version stable "$version"; then
print_error "Promotion ran but stable does not serve $version on every architecture"
exit 1
fi
print_success "3/7 Promoted to stable: omarchy $stable_pub"
stable_pub=$(published_version stable 2>/dev/null) || stable_pub="$version"
print_success "3/7 Promoted to stable: omarchy $stable_pub on every architecture"
fi
# 4. Final pins onto master (keeps edge overlap publishing and the repo record)
@@ -908,7 +923,7 @@ next_step() { # prints "<command>|<description>"
last=$(newest_release_branch 2>/dev/null) || last=""
if [[ -n "$last" && "$STABLE_VER" != "<unreachable>" ]]; then
last_ver=$(branch_to_version "$last")
if [[ "${STABLE_VER%-*}" != "$last_ver" ]] ||
if ! all_arches_at_version stable "$last_ver" ||
{ command -v gh >/dev/null && ! gh release view "v$last_ver" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; }; then
echo "ship|$last_ver is tagged but not fully shipped — resume ship"
return
@@ -924,7 +939,7 @@ next_step() { # prints "<command>|<description>"
echo "ship|Final $TRAIN_VER is pinned — finish shipping (re-runs are safe)"
elif [[ "$pin_commit" != "$TRAIN_HEAD" ]]; then
echo "rc|$TRAIN has commits newer than $pin_ver — cut the next candidate"
elif [[ "${RC_VER%-*}" != "$pin_ver" ]]; then
elif ! all_arches_at_version rc "$pin_ver"; then
echo "rc|$pin_ver is pinned but not published — re-run rc to re-trigger/wait"
else
echo "ship|$pin_ver is published to rc — test it, then ship"
@@ -994,20 +1009,24 @@ cmd_doctor() {
check "makepkg available (checksums)" command -v makepkg
check "curl available" command -v curl
check "upstream reachable ($UPSTREAM_URL)" git ls-remote "$UPSTREAM_URL" HEAD
local ch
local ch arch
for ch in edge stable; do
if published_version "$ch" >/dev/null 2>&1; then
print_success "$ch channel db readable"
for arch in $(published_arches); do
if published_version "$ch" "$arch" >/dev/null 2>&1; then
print_success "$ch/$arch channel db readable"
else
print_error "$ch/$arch channel db readable"
failures=$((failures + 1))
fi
done
done
for arch in $(published_arches); do
if published_version rc "$arch" >/dev/null 2>&1; then
print_success "rc/$arch channel db readable"
else
print_error "$ch channel db readable"
failures=$((failures + 1))
print_warning "rc/$arch channel db not readable — bootstrap it first: bin/repo bootstrap-rc (on the build host)"
fi
done
if published_version rc >/dev/null 2>&1; then
print_success "rc channel db readable"
else
print_warning "rc channel db not readable — bootstrap it first: bin/repo bootstrap-rc (on the build host)"
fi
local host
if host=$(repo_host); then
check "build host ssh ($host)" ssh -o ConnectTimeout=10 "$host" true
@@ -1016,10 +1035,10 @@ cmd_doctor() {
else
print_warning "no build host configured — set OMARCHY_REPO_HOST, --host, or write an ssh destination (root@<host> or an ssh-config alias) to $BUILD_ROOT/.repo-host; until then builds trigger on the 6h timer only"
fi
if command -v docker >/dev/null && docker info >/dev/null 2>&1; then
print_success "docker available (ISO builds possible here)"
if [[ -n "$CONTAINER_ENGINE" ]] && "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "$CONTAINER_ENGINE available (ISO builds possible here)"
else
print_warning "docker unavailable — ISO builds will print instructions instead"
print_warning "container engine unavailable — ISO builds will print instructions instead"
fi
echo ""
if ((failures == 0)); then
@@ -1054,6 +1073,25 @@ cmd_self_test() {
expect "version_is_patch 5.0.0" "$(version_is_patch 5.0.0 && echo yes || echo no)" "no"
expect "previous_patch_tag 4.0.2" "$(previous_patch_tag 4.0.2)" "v4.0.1"
expect "previous_patch_tag 4.0.10" "$(previous_patch_tag 4.0.10)" "v4.0.9"
# Keep release readiness fail-closed when only one architecture has reached
# the requested version. This replaces the network reader for this process;
# self-test exits immediately afterwards.
published_version() {
case "$2" in
x86_64) echo "${TEST_X86_VERSION:-4.0.2-1}" ;;
aarch64) echo "${TEST_ARM_VERSION:-4.0.2-1}" ;;
esac
}
PUBLISHED_ARCHES=x86_64
expect "x86-only readiness" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "ready"
PUBLISHED_ARCHES=aarch64
expect "ARM-only readiness" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "ready"
PUBLISHED_ARCHES="x86_64 aarch64"
TEST_ARM_VERSION=4.0.1-1
expect "mixed versions block release" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "blocked"
TEST_ARM_VERSION=4.0.2-1
expect "both architectures ready" "$(all_arches_at_version rc 4.0.2 && echo ready || echo blocked)" "ready"
echo ""
if ((failures == 0)); then
print_success "Self-test passed"
+6 -6
View File
@@ -17,13 +17,13 @@ Usage: $0 <package> [OPTIONS]
Create a scratch workspace for inspecting an AUR-backed package.
The workspace contains:
upstream/ Raw AUR package at .omarchy/package.json upstream_commit, or HEAD
upstream/ Raw AUR package at the recorded origin.commit, or HEAD
patched/ Raw AUR package with Omarchy .omarchy patches/hooks/pkgrel applied
current/ Current checked-in package directory
Options:
--dir <path> Workspace directory (default: mktemp under /tmp)
--commit <sha> Use a specific AUR commit instead of upstream_commit
--commit <sha> Use a specific AUR commit instead of origin.commit
-h, --help Show this help message
Examples:
@@ -75,13 +75,13 @@ if [[ ! -f "$METADATA" ]]; then
exit 1
fi
if [[ "$(jq -r '.source // ""' "$METADATA")" != "aur" ]]; then
if [[ "$(jq -r '.origin.aur // .aur // (if .source == "aur" then "legacy" else "" end)' "$METADATA")" == "" ]]; then
print_error "package-worktree only supports AUR-backed packages"
exit 1
fi
AUR_PACKAGE=$(jq -r --arg package "$PACKAGE" '.aur // $package' "$METADATA")
UPSTREAM_COMMIT=${COMMIT_OVERRIDE:-$(jq -r '.upstream_commit // ""' "$METADATA")}
AUR_PACKAGE=$(jq -r --arg package "$PACKAGE" '.origin.aur // .aur // $package' "$METADATA")
UPSTREAM_COMMIT=${COMMIT_OVERRIDE:-$(jq -r '.origin.commit // .upstream_commit // ""' "$METADATA")}
if [[ -z "$DEST_DIR" ]]; then
DEST_DIR=$(mktemp -d "${TMPDIR:-/tmp}/omarchy-${PACKAGE}.XXXXXX")
@@ -224,7 +224,7 @@ print_info "AUR package: $AUR_PACKAGE"
if [[ -n "$UPSTREAM_COMMIT" ]]; then
print_info "Upstream commit: $UPSTREAM_COMMIT"
else
print_warning "No upstream_commit recorded; using AUR HEAD"
print_warning "No origin.commit recorded; using AUR HEAD"
fi
rm -rf "$UPSTREAM_DIR" "$PATCHED_DIR" "$CURRENT_DIR"
+118
View File
@@ -0,0 +1,118 @@
#!/bin/bash
# Publish built packages into one channel of the remote repository,
# incrementally and immutably.
#
# publish-artifact --mirror <edge|rc|stable> --arch <arch> <pkg files...>
#
# What it does, in order:
# 1. pull the channel's current database from the remote
# 2. refuse if any package filename already exists on the remote
# 3. sign each package (GPG_PRIVATE_KEY / GPG_PASSPHRASE)
# 4. repo-add the packages into the pulled database (replaces the entry
# for that name; nothing else in the channel is touched)
# 5. upload packages, then signatures, then the database last
#
# Never overwrites: uploads use --ignore-existing for packages and the
# pre-check in step 2 makes a same-name collision a hard failure rather than
# a silent skip. The database is the only object rewritten, and it is
# uploaded only after every file it references is present.
#
# The remote is an rclone remote (REMOTE, default the production one);
# OMARCHY_PUBLISH_PREFIX can point a proof run at a scratch prefix.
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
REMOTE=${REMOTE:-pkgs.omarchy.org:omarchy-pkgs}
PREFIX=${OMARCHY_PUBLISH_PREFIX:-}
FILES=()
while [[ $# -gt 0 ]]; do
case $1 in
--mirror) MIRROR=$2; require_valid_mirror "$MIRROR"; shift 2 ;;
--arch) ARCH=$2; require_valid_arch "$ARCH"; shift 2 ;;
--remote) REMOTE=$2; shift 2 ;;
-h|--help) sed -n '2,22p' "$0"; exit 0 ;;
-*) print_error "Unknown option: $1"; exit 1 ;;
*) FILES+=("$1"); shift ;;
esac
done
(( ${#FILES[@]} )) || { print_error "No package files given"; exit 1; }
: "${GPG_PRIVATE_KEY:?}"; GPG_PASSPHRASE=${GPG_PASSPHRASE-}
DEST="$REMOTE/${PREFIX:+$PREFIX/}$MIRROR/$ARCH"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
print_header "Publish to $DEST"
# --- 0. sanity: every file is a package, named as makepkg names it ---------
for f in "${FILES[@]}"; do
[[ -f "$f" && "$f" == *.pkg.tar.* && "$f" != *.sig ]] || { print_error "Not a package file: $f"; exit 1; }
name=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="pkgname"{print $2}')
ver=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="pkgver"{print $2}')
pkgarch=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="arch"{print $2}')
[[ "$(basename "$f")" == "$name-$ver-$pkgarch.pkg.tar."* ]] || {
print_error "Filename does not match PKGINFO ($name-$ver-$pkgarch): $(basename "$f")"; exit 1; }
[[ "$pkgarch" == any || "$pkgarch" == "$ARCH" ]] || { print_error "$f is $pkgarch, publishing to $ARCH"; exit 1; }
done
# --- 1. pull the current database -----------------------------------------
mkdir -p "$WORK/repo"
listing=$(rclone lsf "$DEST/" --s3-no-head 2>/dev/null || true)
if grep -q '^omarchy.db.tar.zst$' <<<"$listing"; then
rclone copy "$DEST/omarchy.db.tar.zst" "$WORK/repo" --s3-no-head
rclone copy "$DEST/omarchy.files.tar.zst" "$WORK/repo" --s3-no-head 2>/dev/null || true
print_info "Pulled current database ($(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') entries)"
else
print_warning "No database at $DEST — creating a new one"
fi
# --- 2. same-name collisions ----------------------------------------------
# A filename must mean one set of bytes across every channel. The same file
# reaching a channel that already holds it (a fast-ring publish after edge,
# a re-run, a later promotion) is fine: it is skipped on upload and only the
# database entry is added. Different bytes under a name the channel already
# has is the one thing this must never do.
for f in "${FILES[@]}"; do
b=$(basename "$f")
grep -qxF "$b" <<<"$listing" || continue
remote_sum=$(rclone hashsum md5 "$DEST/$b" --s3-no-head 2>/dev/null | awk '{print $1}')
local_sum=$(md5sum "$f" | awk '{print $1}')
if [[ -n "$remote_sum" && "$remote_sum" == "$local_sum" ]]; then
print_info "Already published with identical bytes, adding to the database only: $b"
else
print_error "Already published with DIFFERENT bytes, refusing to overwrite: $b"
echo " Bump pkgrel; published filenames are immutable."
exit 1
fi
done
# --- 3. sign ---------------------------------------------------------------
export GNUPGHOME="$WORK/gnupg"; mkdir -m 700 "$GNUPGHOME"
echo "$GPG_PRIVATE_KEY" | gpg --batch --quiet --import
KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '$1=="sec"{print $5; exit}')
[[ -n "$KEY_ID" ]] || { print_error "No secret key imported"; exit 1; }
for f in "${FILES[@]}"; do
cp "$f" "$WORK/repo/"
gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \
--detach-sign --no-armor --local-user "$KEY_ID" "$WORK/repo/$(basename "$f")"
print_step "signed $(basename "$f")"
done
# --- 4. repo-add (replaces the entry for each pkgname) ---------------------
( cd "$WORK/repo" && repo-add --quiet omarchy.db.tar.zst "${FILES[@]/#*\//}" )
ln -sf omarchy.db.tar.zst "$WORK/repo/omarchy.db"
ln -sf omarchy.files.tar.zst "$WORK/repo/omarchy.files"
print_info "Database now has $(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') entries"
# --- 5. upload: packages, signatures, database last -----------------------
rclone copy "$WORK/repo" "$DEST" --s3-no-head --ignore-existing --filter '- *.sig' --filter '+ *.pkg.tar.*' --filter '- *'
rclone copy "$WORK/repo" "$DEST" --s3-no-head --ignore-existing --filter '+ *.pkg.tar.*.sig' --filter '- *'
# Re-verify every referenced file is really there before the db goes up.
listing=$(rclone lsf "$DEST/" --s3-no-head)
for f in "${FILES[@]}"; do
b=$(basename "$f")
grep -qxF "$b" <<<"$listing" && grep -qxF "$b.sig" <<<"$listing" || { print_error "Upload incomplete: $b"; exit 1; }
done
rclone copy "$WORK/repo" "$DEST" --s3-no-head --checksum --copy-links --filter '+ omarchy.db*' --filter '+ omarchy.files*' --filter '- *'
print_success "Published ${#FILES[@]} package(s) to $DEST"
+19 -8
View File
@@ -149,11 +149,16 @@ fi
# leave nvidia-580xx-dkms and opencl-nvidia-580xx behind. An output's own name
# still matches, for pushing just one of them on purpose.
#
# pkgbase comes from .PKGINFO rather than the PKGBUILD: it is what makepkg
# actually recorded, and it needs no guessing about which directory built what.
pkgbase_of() {
# Package identity comes from .PKGINFO rather than the filename or PKGBUILD: it
# is what makepkg actually recorded, and it handles epochs and split packages.
package_identity_of() {
bsdtar -xOf "$1" .PKGINFO 2>/dev/null |
awk -F ' = ' '$1 == "pkgbase" { print $2; exit }'
awk -F ' = ' '
$1 == "pkgname" { name = $2 }
$1 == "pkgbase" { base = $2 }
$1 == "pkgver" { version = $2 }
END { print name "\t" base "\t" version }
'
}
FILES=()
@@ -161,14 +166,18 @@ if [[ -z "$PACKAGES" ]]; then
FILES=("${ALL_FILES[@]}")
else
declare -A MATCHED=()
declare -A LATEST_FILE=()
declare -A LATEST_VERSION=()
for file in "${ALL_FILES[@]}"; do
# name-version-release-arch.pkg.tar.zst -> name
pkgname="${file%-*-*-*.pkg.tar.*}"
pkgbase=$(pkgbase_of "$BUILD_OUTPUT_DIR/$file")
IFS=$'\t' read -r pkgname pkgbase pkgver < <(package_identity_of "$BUILD_OUTPUT_DIR/$file")
for wanted in $PACKAGES; do
if [[ "$pkgname" == "$wanted" || "$pkgbase" == "$wanted" ]]; then
FILES+=("$file")
MATCHED["$wanted"]=1
if [[ -z "${LATEST_FILE[$pkgname]:-}" ]] ||
[[ $(vercmp "$pkgver" "${LATEST_VERSION[$pkgname]}") -gt 0 ]]; then
LATEST_FILE["$pkgname"]="$file"
LATEST_VERSION["$pkgname"]="$pkgver"
fi
break
fi
done
@@ -181,6 +190,8 @@ else
exit 1
fi
done
mapfile -t FILES < <(printf '%s\n' "${LATEST_FILE[@]}" | sort)
fi
if [[ ${#FILES[@]} -eq 0 ]]; then
+61 -4
View File
@@ -114,7 +114,11 @@ BUILT_FILES=""
# What the scheduled version check queued, when it was the one that asked for
# this run. Absent for a manual run, which is fine — the report just omits it.
QUEUED_PACKAGES=""
QUEUE_FILE="${OMARCHY_STATE_DIR:-/root/.state}/.sync-needed-$MIRROR"
QUEUE_FILE=$(sync_queue_file "$MIRROR" "$ARCH")
# A queue written under the pre-architecture name belongs to x86_64.
if [[ "$ARCH" == "x86_64" && ! -s "$QUEUE_FILE" && -s "$(legacy_sync_queue_file "$MIRROR")" ]]; then
QUEUE_FILE=$(legacy_sync_queue_file "$MIRROR")
fi
[[ -s "$QUEUE_FILE" ]] && QUEUED_PACKAGES=$(grep -c '' "$QUEUE_FILE")
if [[ "$DRY_RUN" != true ]]; then
@@ -134,11 +138,43 @@ if [[ "$DRY_RUN" == true ]]; then
else
print_info "Step 1/6: Building packages..."
fi
"$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || {
BUILD_RESULT_DIR=$(mktemp -d)
trap 'rm -rf "$BUILD_RESULT_DIR"' EXIT
build_status=0
OMARCHY_BUILD_RESULT_DIR="$BUILD_RESULT_DIR" "$BUILD_ROOT/bin/build" "${BUILD_ARGS[@]}" || build_status=$?
partial=false
if [[ "$build_status" == 2 && -f "$BUILD_RESULT_DIR/complete" && "$DRY_RUN" != true ]]; then
if [[ "${OMARCHY_DEFER_RUNTIME_DEPS:-false}" == true ]]; then
print_error "The deferred release pair must succeed together; nothing will be published"
notify_error "Release failed: Incomplete release pair" "$RELEASE_CONTEXT"
exit 1
fi
partial=true
while IFS= read -r file; do
[[ -f "$BUILD_OUTPUT_DIR/$file" ]] || {
print_error "Completed build artifact is missing: $file"
notify_error "Release failed: Missing completed artifact" "$RELEASE_CONTEXT"
exit 1
}
done < "$BUILD_RESULT_DIR/artifacts"
# Exclude partial split outputs or leftovers from failed builds. Moving
# them out of the flat publication directory keeps them available for
# diagnosis without handing them to sign/promote.
unpublished=""
for path in "$BUILD_OUTPUT_DIR"/*.pkg.tar.*; do
[[ -f "$path" ]] || continue
file=${path##*/}
if ! grep -Fxq -- "${file%.sig}" "$BUILD_RESULT_DIR/artifacts"; then
[[ -n "$unpublished" ]] || unpublished=$(mktemp -d "$BUILD_OUTPUT_DIR/.unpublished.XXXXXX")
mv -- "$path" "$unpublished/"
fi
done
print_warning "Some packages failed; publishing the completed packages before retrying the failures"
elif [[ "$build_status" != 0 ]]; then
print_error "Build failed"
notify_error "Release failed: Build step failed" "$RELEASE_CONTEXT"
exit 1
}
fi
if [[ "$DRY_RUN" == true ]]; then
echo ""
@@ -151,6 +187,12 @@ BUILT_COUNT=$(grep -c '' <<<"$BUILT_FILES")
[[ -z "$BUILT_FILES" ]] && BUILT_COUNT=0
print_info "Built $BUILT_COUNT package(s) this run"
if [[ "$partial" == true && "$BUILT_COUNT" == 0 ]]; then
print_error "No completed packages to publish"
notify_error "Release failed: No completed packages" "$RELEASE_CONTEXT"
exit 1
fi
# Step 2: Sign
echo ""
print_info "Step 2/6: Signing packages..."
@@ -209,7 +251,16 @@ if ((BUILT_COUNT > 0)); then
summary+="<br><br><strong>$BUILT_COUNT package(s) published:</strong>"
summary+="$(format_package_list_html "$BUILT_FILES")"
summary+="<br><br>Live at https://pkgs.omarchy.org/$MIRROR/$ARCH/"
notify_success "Release published: $MIRROR" "$summary"
if [[ "$partial" == true ]]; then
failed=$(tr '\n' ' ' < "$BUILD_RESULT_DIR/failed" | basecamp_html_escape)
blocked=$(tr '\n' ' ' < "$BUILD_RESULT_DIR/blocked" | basecamp_html_escape)
[[ -z "$failed" ]] || summary+="<br><br>Failed: $failed"
[[ -z "$blocked" ]] || summary+="<br>Blocked by failed dependencies: $blocked"
summary+="<br>Published packages will be skipped on retry; failed packages remain queued."
notify_info "Partial release published: $MIRROR" "$summary"
else
notify_success "Release published: $MIRROR" "$summary"
fi
else
# Rare by construction: a release only runs when the version check queued
# work, so publishing nothing means the check and the builder disagreed
@@ -224,4 +275,10 @@ else
fi
echo ""
if [[ "$partial" == true ]]; then
print_warning "Completed packages published; unsuccessful packages remain for retry"
# Preserve the scheduled queue and failure backoff. The next version
# check/build compares against the updated repository and skips successes.
exit 1
fi
print_success "Release workflow completed successfully!"
+15 -10
View File
@@ -62,8 +62,8 @@ if [[ ! -d "$REPO_DIR" ]]; then
exit 1
fi
# Check Docker is available
check_docker
# Check the selected container engine is available
check_engine
# Find package files to confirm before running Docker
cd "$REPO_DIR"
@@ -88,24 +88,29 @@ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 0
fi
# Build/update the Docker image (always use x86_64 for removal - it's architecture independent)
# repo-remove is mirror-independent — always use the edge x86_64 image
build_docker_image "$BUILD_DIR" "x86_64" "edge"
# repo-remove is architecture-independent, so use a host-native edge image.
TOOL_ARCH=$(docker_native_arch) || {
print_error "Unsupported host architecture: $(uname -m)"
exit 1
}
build_docker_image "$BUILD_DIR" "$TOOL_ARCH" "edge"
acquire_release_lock || exit 1
print_info "Removing package..."
# Ensure directory is writable by container user
make_dir_writable "$REPO_DIR"
# Rootless Podman uses keep-id and leaves host ownership/modes intact.
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$REPO_DIR"
fi
# Run the removal script in Docker (always use x86_64 image)
docker run --rm --platform linux/amd64 \
# Run the removal script in the host-native image.
"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$TOOL_ARCH")" \
-e ARCH="$ARCH" \
-e MIRROR="$MIRROR" \
-v "$REPO_ROOT:/pkgs.omarchy.org" \
-v "$BUILD_DIR:/build:ro" \
omarchy-pkg-builder:latest-x86_64-edge /build/remove-package.sh "$PACKAGE_NAME"
"omarchy-pkg-builder:latest-$TOOL_ARCH-edge" /build/remove-package.sh "$PACKAGE_NAME"
RESULT=$?
+59 -22
View File
@@ -15,6 +15,8 @@ set -e
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
CHECK_ONLY=false
SKIP_TIMERS=false
@@ -61,10 +63,12 @@ if command -v apt-get >/dev/null 2>&1; then
DISTRO="debian"
PKG_BSDTAR="libarchive-tools"
PKG_DOCKER="docker.io"
PKG_PODMAN="podman"
elif command -v pacman >/dev/null 2>&1; then
DISTRO="arch"
PKG_BSDTAR="libarchive"
PKG_DOCKER="docker"
PKG_PODMAN="podman"
else
print_error "Unsupported distribution — need apt-get or pacman"
exit 1
@@ -72,18 +76,36 @@ fi
print_info "Distribution: $DISTRO"
# A fresh repository host still defaults to Docker. An explicit Podman choice,
# or a working Podman selected by the shared helper, is left alone.
if [[ -z "$CONTAINER_ENGINE" ]]; then
CONTAINER_ENGINE=docker
export CONTAINER_ENGINE
fi
if ! container_engine_supported; then
print_error "Unsupported CONTAINER_ENGINE: $CONTAINER_ENGINE (use docker or podman)"
exit 1
fi
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
PKG_ENGINE="$PKG_DOCKER"
ENGINE_NAME="Docker"
else
PKG_ENGINE="$PKG_PODMAN"
ENGINE_NAME="Podman"
fi
if [[ "$CHECK_ONLY" != true && $EUID -ne 0 ]]; then
print_error "Run as root (installing packages and systemd units)"
exit 1
fi
# Docker and the release timers are both systemd units. Say so plainly rather
# than failing later on a missing command — a container is the usual way to end
# up here, and it cannot be a repository host.
# The release timers are systemd units. Say so plainly rather than failing
# later on a missing command — a container is the usual way to end up here,
# and it cannot be a repository host.
if [[ "$CHECK_ONLY" != true ]] && ! command -v systemctl >/dev/null 2>&1; then
print_error "systemctl not found — the repository host must run systemd"
echo ""
echo "Docker and the release timers are systemd units. This looks like a"
echo "The release timers are systemd units. This looks like a"
echo "container; run setup on the host itself."
exit 1
fi
@@ -139,24 +161,27 @@ else
fi
echo ""
# --- docker ------------------------------------------------------------------
# --- container engine --------------------------------------------------------
# Docker is left alone when it already works. A host may well be running a
# The selected engine is left alone when it already works. A host may be running a
# version from Docker's own repository rather than the distribution's, and
# replacing that underneath a working builder would be a poor trade for
# tidiness.
print_info "Checking Docker..."
print_info "Checking $ENGINE_NAME..."
if command -v docker >/dev/null 2>&1; then
print_step "docker present: $(docker --version 2>/dev/null | head -1)"
if docker info >/dev/null 2>&1; then
print_success "Docker is installed and running — leaving it alone"
if command -v "$CONTAINER_ENGINE" >/dev/null 2>&1; then
print_step "$CONTAINER_ENGINE present: $("$CONTAINER_ENGINE" --version 2>/dev/null | head -1)"
if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "$ENGINE_NAME is installed and available — leaving it alone"
elif [[ "$CHECK_ONLY" == true ]]; then
print_warning "Docker is installed but not running; would start it"
print_warning "$ENGINE_NAME is installed but unavailable"
elif [[ "$CONTAINER_ENGINE" == "podman" ]]; then
print_error "Podman is installed but unavailable to the current user"
exit 1
else
print_info "Docker is installed but not running — starting it"
systemctl enable --now docker.service
if docker info >/dev/null 2>&1; then
if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "Docker started"
else
print_error "Docker is installed but still not responding"
@@ -165,16 +190,24 @@ if command -v docker >/dev/null 2>&1; then
fi
fi
elif [[ "$CHECK_ONLY" == true ]]; then
print_warning "Would install $PKG_DOCKER and enable it"
else
print_info "Installing $PKG_DOCKER..."
install_packages "$PKG_DOCKER"
systemctl enable --now docker.service
if docker info >/dev/null 2>&1; then
print_success "Docker installed and running"
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
print_warning "Would install $PKG_ENGINE and enable it"
else
print_error "Docker installed but not responding"
echo " Check 'systemctl status docker' — builds cannot run without it."
print_warning "Would install $PKG_ENGINE"
fi
else
print_info "Installing $PKG_ENGINE..."
install_packages "$PKG_ENGINE"
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
systemctl enable --now docker.service
fi
if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "$ENGINE_NAME installed and available"
else
print_error "$ENGINE_NAME installed but not responding"
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
echo " Check 'systemctl status docker' — builds cannot run without it."
fi
exit 1
fi
fi
@@ -237,6 +270,10 @@ echo ""
# --- release timers ----------------------------------------------------------
print_info "Published architectures: $(published_arches | tr '\n' ' ')"
echo " (PUBLISHED_ARCHES in helpers/paths.sh; OMARCHY_ARCHES overrides a one-off command)"
echo ""
TIMERS=(omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-rc omarchy-auto-release-stable)
if [[ "$SKIP_TIMERS" == true ]]; then
+16 -9
View File
@@ -52,8 +52,8 @@ if [[ ! -d "$BUILD_OUTPUT_DIR" ]]; then
exit 1
fi
# Check Docker is available
check_docker
# Check the selected container engine is available
check_engine
# Check GPG credentials are in environment
if [[ -z "$GPG_PRIVATE_KEY" ]]; then
@@ -66,23 +66,30 @@ if [[ -z "$GPG_PASSPHRASE" ]]; then
exit 1
fi
# Build/update the Docker image (always use x86_64 for signing - it's architecture independent)
build_docker_image "$BUILD_DIR" "x86_64" "$MIRROR"
# Signing is architecture-independent, so run its utility container natively
# on either an x86_64 or ARM host.
TOOL_ARCH=$(docker_native_arch) || {
print_error "Unsupported host architecture: $(uname -m)"
exit 1
}
build_docker_image "$BUILD_DIR" "$TOOL_ARCH" "$MIRROR"
print_info "Running package signing..."
# Ensure output directory is writable by container user
make_dir_writable "$BUILD_OUTPUT_DIR"
# Rootless Podman uses keep-id and leaves host ownership/modes intact.
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$BUILD_OUTPUT_DIR"
fi
# Run the signing script in Docker (always use x86_64 image)
docker run --rm --platform linux/amd64 \
# Run the signing script in the host-native image.
"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$TOOL_ARCH")" \
-e ARCH="$ARCH" \
-e MIRROR="$MIRROR" \
-e GPG_PRIVATE_KEY="$GPG_PRIVATE_KEY" \
-e GPG_PASSPHRASE="$GPG_PASSPHRASE" \
-v "$BUILD_ROOT/build-output:/build-output" \
-v "$BUILD_DIR:/build:ro" \
omarchy-pkg-builder:latest-x86_64-$MIRROR /build/sign.sh
"omarchy-pkg-builder:latest-$TOOL_ARCH-$MIRROR" /build/sign.sh
SIGN_RESULT=$?
-434
View File
@@ -1,434 +0,0 @@
#!/bin/bash
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/package-metadata.sh"
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
SPECIFIC_PACKAGES=()
usage() {
cat <<EOF
Usage: $0 [PACKAGE...]
Sync AUR-backed packages into pkgbuilds/<package>/.
Package selection is driven by pkgbuilds/<package>/.omarchy/package.json:
{ "source": "aur" } # synced from matching AUR package name
{ "source": "aur", "aur": "yaru" } # synced from different AUR package name
{ "source": "aur", "sync": false } # AUR-origin, but not auto-synced
Arguments:
PACKAGE One or more package names to sync (optional)
Examples:
$0 # Sync all AUR packages with sync enabled
$0 yay cursor-bin # Sync specific packages
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
usage
exit 0
;;
--tier)
print_error "--tier is no longer supported; package metadata controls sync behavior"
exit 1
;;
--*)
print_error "Unknown option: $1"
exit 1
;;
*)
SPECIFIC_PACKAGES+=("$1")
shift
;;
esac
done
print_header "AUR Package Sync"
mkdir -p "$PKGBUILDS_DIR"
SYNCED=0
SKIPPED=0
FAILED=0
SYNCED_PACKAGES=()
SPECIFIC_MODE=false
get_pkgbuild_field() {
local package_dir="$1"
local field="$2"
local value=""
if [[ -f "$package_dir/PKGBUILD" ]]; then
value=$(grep -m1 "^${field}=" "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'") || true
if [[ -n "$value" ]]; then
echo "$value"
return
fi
fi
if [[ -f "$package_dir/.SRCINFO" ]]; then
awk -F' = ' -v field="$field" '$1 ~ "^[[:space:]]*" field "$" { print $2; exit }' "$package_dir/.SRCINFO"
fi
}
set_pkgrel() {
local package_dir="$1"
local pkgrel="$2"
local pkgbuild="$package_dir/PKGBUILD"
if [[ -f "$pkgbuild" ]]; then
sed -i "s/^pkgrel=.*/pkgrel=$pkgrel/" "$pkgbuild"
fi
}
display_package_name() {
local package_dir="$1"
local name
name=$(basename "$package_dir")
echo "${name%.work}"
}
remove_aur_only_files() {
local package_dir="$1"
rm -f "$package_dir/.SRCINFO" "$package_dir/.gitignore"
}
copy_aur_contents() {
local aur_dir="$1"
local target_dir="$2"
mkdir -p "$target_dir"
shopt -s dotglob nullglob
local item base
for item in "$aur_dir"/*; do
base=$(basename "$item")
[[ "$base" == ".git" ]] && continue
cp -a "$item" "$target_dir/"
done
shopt -u dotglob nullglob
}
commit_synced_worktree() {
local work_dir="$1"
local target_dir="$2"
local parent base staged_dir backup_root backup_dir
parent=$(dirname "$target_dir")
base=$(basename "$target_dir")
staged_dir=$(mktemp -d "$parent/.${base}.staged.XXXXXX")
backup_root=$(mktemp -d "$parent/.${base}.backup.XXXXXX")
backup_dir="$backup_root/$base"
# Copy to the package filesystem before swapping. This keeps the original
# package directory intact if copying from /tmp fails or is interrupted.
if ! cp -a "$work_dir/." "$staged_dir/"; then
print_error "Failed to stage synced package for $base"
rm -rf "$staged_dir" "$backup_root"
return 1
fi
if [[ -e "$target_dir" ]]; then
if ! mv "$target_dir" "$backup_dir"; then
print_error "Failed to back up existing package directory: $target_dir"
rm -rf "$staged_dir" "$backup_root"
return 1
fi
fi
if ! mv "$staged_dir" "$target_dir"; then
print_error "Failed to install synced package directory: $target_dir"
if [[ -e "$backup_dir" ]]; then
mv "$backup_dir" "$target_dir" || true
fi
rm -rf "$staged_dir" "$backup_root"
return 1
fi
rm -rf "$backup_root" "$work_dir"
}
set_upstream_commit() {
local package_dir="$1"
local commit="$2"
local metadata="$package_dir/.omarchy/package.json"
local tmpfile
tmpfile=$(mktemp)
jq --arg commit "$commit" '.upstream_commit = $commit' "$metadata" > "$tmpfile"
mv "$tmpfile" "$metadata"
}
apply_omarchy_patches() {
local package_dir="$1"
local patches_dir="$package_dir/.omarchy/patches"
local applied=false
[[ -d "$patches_dir" ]] || return 1
shopt -s nullglob
local patch_files=("$patches_dir"/*.patch)
local patch_dir_files=("$patches_dir"/*)
shopt -u nullglob
if [[ ${#patch_files[@]} -eq 0 ]]; then
if [[ ${#patch_dir_files[@]} -gt 0 ]]; then
print_warning "No .patch files found in $patches_dir"
fi
return 1
fi
print_info "Applying Omarchy patches for $(display_package_name "$package_dir")..."
local patch_file
for patch_file in "${patch_files[@]}"; do
print_info " $(basename "$patch_file")"
if ! (cd "$package_dir" && patch -p1 --forward --batch --no-backup-if-mismatch < "$patch_file"); then
print_error "Failed to apply patch: $patch_file"
return 2
fi
applied=true
done
[[ "$applied" == true ]]
}
run_omarchy_post_sync_hook() {
local package_dir="$1"
local package="$2"
local aur_package="$3"
local aur_pkgrel="$4"
local hook="$package_dir/.omarchy/post-sync.sh"
[[ -f "$hook" ]] || return 1
print_info "Running Omarchy post-sync hook for $(display_package_name "$package_dir")..."
if ! (
cd "$package_dir"
PACKAGE_NAME="$package" \
AUR_PACKAGE_NAME="$aur_package" \
AUR_PKGREL="$aur_pkgrel" \
bash ".omarchy/post-sync.sh"
); then
print_error "Failed to run post-sync hook: $hook"
return 2
fi
return 0
}
apply_pkgrel_suffix_if_customized() {
local package_dir="$1"
local aur_pkgrel="$2"
[[ -n "$aur_pkgrel" ]] || return 0
print_info "Applying Omarchy pkgrel suffix for $(display_package_name "$package_dir"): pkgrel=$aur_pkgrel.1"
set_pkgrel "$package_dir" "$aur_pkgrel.1"
}
apply_pkgrel_override() {
local package_dir="$1"
local aur_pkgrel="$2"
local previous_pkgver="$3"
local metadata="$package_dir/.omarchy/package.json"
local pkgbuild="$package_dir/PKGBUILD"
[[ -f "$metadata" ]] || return 1
jq -e 'has("pkgrel")' "$metadata" >/dev/null || return 1
local current_pkgver suffix offset base rel tmpfile
# Read through the same accessor that produced previous_pkgver. Parsing it a
# second time here let a quoted pkgver= compare unequal to itself, which threw
# away the pkgrel metadata of an unchanged package on every sync.
current_pkgver=$(get_pkgbuild_field "$package_dir" pkgver)
if [[ -n "$previous_pkgver" && "$current_pkgver" != "$previous_pkgver" ]]; then
print_info "Removing stale pkgrel metadata for $(display_package_name "$package_dir") (pkgver changed: $previous_pkgver -> $current_pkgver)"
tmpfile=$(mktemp)
jq 'del(.pkgrel)' "$metadata" > "$tmpfile"
mv "$tmpfile" "$metadata"
return 1
fi
suffix=$(jq -r '.pkgrel.suffix // 1' "$metadata")
offset=$(jq -r '.pkgrel.offset // 0' "$metadata")
if [[ ! "$offset" =~ ^[0-9]+$ || ! "$aur_pkgrel" =~ ^[0-9]+$ ]]; then
print_error "pkgrel offset requires numeric AUR pkgrel for $(display_package_name "$package_dir")"
return 2
fi
base=$((aur_pkgrel + offset))
rel="$base.$suffix"
print_info "Applying pkgrel suffix for $(display_package_name "$package_dir"): AUR pkgrel=$aur_pkgrel, offset=$offset, suffix=$suffix -> pkgrel=$rel"
set_pkgrel "$package_dir" "$rel"
return 0
}
clone_aur_package() {
local aur_package="$1"
local dest="$2"
local clone_log="$TEMP_DIR/clone.log"
local attempt
for attempt in 1 2 3; do
rm -rf "$dest"
if git clone "https://aur.archlinux.org/${aur_package}.git" "$dest" >"$clone_log" 2>&1; then
return 0
fi
if [[ $attempt -lt 3 ]]; then
print_warning "Clone of $aur_package failed (attempt $attempt/3), retrying in 10s..."
sleep 10
fi
done
print_warning "Failed to clone $aur_package after 3 attempts: $(tail -n 1 "$clone_log")"
return 1
}
sync_package() {
local package="$1"
local package_dir="$PKGBUILDS_DIR/$package"
local metadata="$package_dir/.omarchy/package.json"
if [[ ! -f "$metadata" ]]; then
if [[ "$SPECIFIC_MODE" == true ]]; then
print_error "Package $package is missing .omarchy/package.json"
((++FAILED))
else
print_warning "Skipping $package: missing .omarchy/package.json"
((++SKIPPED))
fi
return 0
fi
if [[ "$(jq -r '.source // ""' "$metadata")" != "aur" ]]; then
print_info "Skipping $package: source is not AUR"
((++SKIPPED))
return 0
fi
if [[ "$(jq -r 'if has("sync") then .sync else true end' "$metadata")" == "false" ]]; then
print_info "Skipping $package: AUR sync disabled"
((++SKIPPED))
return 0
fi
local aur_package
aur_package=$(jq -r --arg package "$package" '.aur // $package' "$metadata")
if [[ "$aur_package" == "$package" ]]; then
print_info "Syncing $package from AUR..."
else
print_info "Syncing $package from AUR package $aur_package..."
fi
cd "$TEMP_DIR"
if ! clone_aur_package "$aur_package" "$TEMP_DIR/$aur_package"; then
((++FAILED))
return 0
fi
if [[ ! -f "$TEMP_DIR/$aur_package/PKGBUILD" ]]; then
print_warning "AUR repository for $aur_package is empty (package does not exist in AUR)"
((++FAILED))
return 0
fi
local aur_dir="$TEMP_DIR/$aur_package"
local work_dir="$TEMP_DIR/${package}.work"
local aur_pkgrel previous_pkgver upstream_commit
aur_pkgrel=$(get_pkgbuild_field "$aur_dir" pkgrel)
previous_pkgver=$(get_pkgbuild_field "$package_dir" pkgver || true)
upstream_commit=$(git -C "$aur_dir" rev-parse HEAD)
rm -rf "$work_dir"
copy_aur_contents "$aur_dir" "$work_dir"
rm -rf "$work_dir/.omarchy"
cp -a "$package_dir/.omarchy" "$work_dir/.omarchy"
local customized=false
if apply_omarchy_patches "$work_dir"; then
customized=true
else
local patch_status=$?
if [[ $patch_status -eq 2 ]]; then
((++FAILED))
return 0
fi
fi
if run_omarchy_post_sync_hook "$work_dir" "$package" "$aur_package" "$aur_pkgrel"; then
customized=true
else
local hook_status=$?
if [[ $hook_status -eq 2 ]]; then
((++FAILED))
return 0
fi
fi
local pkgrel_overridden=false
set +e
apply_pkgrel_override "$work_dir" "$aur_pkgrel" "$previous_pkgver"
local pkgrel_status=$?
set -e
case "$pkgrel_status" in
0) pkgrel_overridden=true ;;
1) ;;
*) ((++FAILED)); return 0 ;;
esac
if [[ "$customized" == true && "$pkgrel_overridden" == false ]]; then
apply_pkgrel_suffix_if_customized "$work_dir" "$aur_pkgrel"
fi
remove_aur_only_files "$work_dir"
set_upstream_commit "$work_dir" "$upstream_commit"
if ! commit_synced_worktree "$work_dir" "$package_dir"; then
((++FAILED))
return 0
fi
SYNCED_PACKAGES+=("$package")
((++SYNCED))
}
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then
SPECIFIC_MODE=true
for package in "${SPECIFIC_PACKAGES[@]}"; do
sync_package "$package"
done
else
while IFS= read -r package; do
sync_package "$package"
done < <(packages_for_aur_sync)
fi
echo ""
if [[ $FAILED -gt 0 ]]; then
print_error "Sync completed with failures"
else
print_success "Sync complete!"
fi
echo " Target: $PKGBUILDS_DIR"
echo " Synced: $SYNCED"
echo " Skipped: $SKIPPED"
echo " Failed: $FAILED"
if [[ $FAILED -gt 0 ]]; then
exit 1
fi
+416 -75
View File
@@ -17,11 +17,14 @@ SELF_TEST=false
# rebuilding for it would ship a package built against the wrong ABI.
OFFICIAL_REPOS=" core extra multilib core-debug extra-debug "
# The published repository, used as the floor a bumped pkgrel has to clear.
# Only x86_64 is published today; aarch64 has no repository to compare against.
# The published repositories are the floor a bumped pkgrel has to clear.
PUBLISHED_BASE_URL="${OMARCHY_PUBLISHED_BASE_URL:-https://pkgs.omarchy.org}"
PUBLISHED_MIRRORS=(edge stable)
PUBLISHED_ARCH=x86_64
# Arch Linux ARM has no dated snapshots. This is the same live repository the
# aarch64 builder resolves; override it only when the builder mirror changes.
ALARM_BASE_URL="${OMARCHY_ALARM_BASE_URL:-https://fl.us.mirror.archlinuxarm.org/aarch64}"
ALARM_REPOS=(core extra alarm aur)
usage() {
cat <<EOF
@@ -34,10 +37,13 @@ A package opts in by naming those dependencies in .omarchy/package.json:
{ "source": "aur", "sync": false, "rebuild_on": ["qt6-base"] }
The versions the current pkgrel was bumped for are recorded alongside, in
rebuilt_against, and written by this command:
The versions the current pkgrel was bumped for are recorded per published
architecture in rebuilt_against, and written by this command:
{ "rebuild_on": ["qt6-base"], "rebuilt_against": { "qt6-base": "6.11.2-2" } }
{ "rebuild_on": ["qt6-base"], "rebuilt_against": {
"x86_64": { "qt6-base": "6.11.2-3" },
"aarch64": { "qt6-base": "6.11.2-2" }
} }
pkgrel is bumped unless every package named in rebuild_on is recorded and still
matches. A name that is missing from the record counts as changed, so opting a
@@ -60,8 +66,11 @@ Examples:
$0 # Update every package that declares rebuild_on
$0 quickshell-git # Update specific packages
Trigger versions are read from the local pacman database, so sync it first
(pacman -Sy) or this reports whatever that database last saw.
x86_64 trigger versions come from the local pacman database; aarch64 versions
come from the live Arch Linux ARM repository database. A trigger this
repository carries for an architecture shadows both, so its version is the
checked-in recipe's once edge publishes it. Every architecture in CI_ARCHES
(what a merge builds) that the package supports is considered.
EOF
}
@@ -94,7 +103,7 @@ SPECIFIC_MODE=false
# The version of a trigger package as the build container would resolve it.
# A name pacman does not know reports nothing rather than failing, so the caller
# gets to say which package was left alone instead of the run dying here.
repo_version() {
native_repo_version() {
local package="$1"
local info
@@ -108,9 +117,61 @@ repo_version() {
' <<<"$info"
}
load_alarm_repo() {
local repo="$1" db index
index="$TEMP_DIR/alarm-$repo.index"
[[ -f "$index" ]] && return 0
db="$TEMP_DIR/alarm-$repo.db"
if ! curl -fsSL --max-time 120 -o "$db" "$ALARM_BASE_URL/$repo/$repo.db" 2>/dev/null; then
print_error "Could not read the aarch64 $repo repository database"
return 1
fi
if ! tar -tf "$db" >/dev/null 2>&1; then
print_error "Unreadable aarch64 $repo repository database"
return 1
fi
tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk '
function emit() {
if (name != "" && version != "") {
print name "\t" version
if (base != "" && base != name) print base "\t" version
}
name=""; base=""; version=""
}
$0 == "%FILENAME%" { emit(); next }
$0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next }
$0 == "%BASE%" { getline; base=$0; next }
$0 == "%VERSION%" { getline; version=$0; next }
END { emit() }
' >"$index"
}
alarm_repo_version() {
local package="$1" repo version
for repo in "${ALARM_REPOS[@]}"; do
load_alarm_repo "$repo" || return 1
version=$(awk -F '\t' -v package="$package" '$1 == package { print $2; exit }' "$TEMP_DIR/alarm-$repo.index")
if [[ -n "$version" ]]; then
echo "$version"
return 0
fi
done
}
repo_version() { # repo_version <arch> <package>
case "$1" in
x86_64) native_repo_version "$2" ;;
aarch64) alarm_repo_version "$2" ;;
*) return 1 ;;
esac
}
declare -A PUBLISHED_VERSION=()
declare -A EDGE_VERSION=()
declare -A EDGE_READ=()
PUBLISHED_LOADED=false
PUBLISHED_AVAILABLE=true
remember_published() {
local name="$1"
@@ -131,34 +192,44 @@ load_published_versions() {
[[ "$PUBLISHED_LOADED" == true ]] && return 0
PUBLISHED_LOADED=true
local mirror db name base version
local arch mirror db name base version
for mirror in "${PUBLISHED_MIRRORS[@]}"; do
db="$TEMP_DIR/published-$mirror.db.tar.zst"
for arch in $(ci_arches); do
for mirror in "${PUBLISHED_MIRRORS[@]}"; do
db="$TEMP_DIR/published-$mirror-$arch.db.tar.zst"
if ! curl -fsSL --max-time 120 -o "$db" \
"$PUBLISHED_BASE_URL/$mirror/$PUBLISHED_ARCH/omarchy.db.tar.zst" 2>/dev/null; then
print_warning "Could not read the published $mirror database; bumps are not checked against it this run"
PUBLISHED_AVAILABLE=false
continue
fi
if ! curl -fsSL --max-time 120 -o "$db" \
"$PUBLISHED_BASE_URL/$mirror/$arch/omarchy.db.tar.zst" 2>/dev/null; then
print_warning "Could not read the published $mirror/$arch database; bumps are not checked against it this run"
continue
fi
if ! tar -tf "$db" >/dev/null 2>&1; then
print_warning "Unreadable published $mirror/$arch database; bumps are not checked against it this run"
continue
fi
[[ "$mirror" == edge ]] && EDGE_READ["$arch"]=1
while IFS=$'\t' read -r name base version; do
[[ -n "$name" && -n "$version" ]] && remember_published "$name" "$version"
[[ -n "$base" && -n "$version" ]] && remember_published "$base" "$version"
done < <(
tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk '
function emit() {
if (name != "" && version != "") print name "\t" base "\t" version
name=""; base=""; version=""
}
$0 == "%FILENAME%" { emit(); next }
$0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next }
$0 == "%BASE%" { getline; base=$0; next }
$0 == "%VERSION%" { getline; version=$0; next }
END { emit() }
'
)
while IFS=$'\t' read -r name base version; do
[[ -n "$name" && -n "$version" ]] && remember_published "$name" "$version"
[[ -n "$base" && -n "$version" ]] && remember_published "$base" "$version"
if [[ "$mirror" == edge && -n "$version" ]]; then
[[ -z "$name" ]] || EDGE_VERSION["$arch/$name"]="$version"
[[ -z "$base" ]] || EDGE_VERSION["$arch/$base"]="$version"
fi
done < <(
tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk '
function emit() {
if (name != "" && version != "") print name "\t" base "\t" version
name=""; base=""; version=""
}
$0 == "%FILENAME%" { emit(); next }
$0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next }
$0 == "%BASE%" { getline; base=$0; next }
$0 == "%VERSION%" { getline; version=$0; next }
END { emit() }
'
)
done
done
}
@@ -169,6 +240,29 @@ published_version() {
echo "${PUBLISHED_VERSION[$package]:-}"
}
# A trigger this repository builds for the architecture. The builder lists
# [omarchy] ahead of the distribution repositories, so this recipe, not Arch or
# Arch Linux ARM, decides what a dependent links against.
carried_trigger_dir() { # carried_trigger_dir <arch> <trigger>
local pkgdir
pkgdir=$(package_dir_for_name "$2") || return 1
package_has_metadata "$pkgdir" || return 1
package_builds_for_mirror "$pkgdir" edge || return 1
package_supports_arch "$pkgdir" "$1" || return 1
echo "$pkgdir"
}
carried_version() { # carried_version <arch> <pkgdir>
local epoch pkgver pkgrel
epoch=$(package_pkgbuild_var "$2" epoch "$1") || return 1
pkgver=$(package_pkgbuild_var "$2" pkgver "$1") || return 1
pkgrel=$(package_pkgbuild_var "$2" pkgrel "$1") || return 1
[[ -n "$pkgver" && -n "$pkgrel" ]] || return 1
# makepkg leaves a zero epoch out of the published version
[[ "$epoch" != 0 ]] || epoch=""
echo "${epoch:+$epoch:}$pkgver-$pkgrel"
}
# The pkgver of a full version string, with any epoch and pkgrel removed.
version_pkgver() {
local version="${1#*:}"
@@ -179,7 +273,7 @@ pkgbuild_field() {
local package_dir="$1"
local field="$2"
(cd "$package_dir" && env -u OMARCHY_SRC bash -c "source PKGBUILD 2>/dev/null; echo \"\${$field:-}\"")
package_pkgbuild_var "$package_dir" "$field"
}
# 2 -> 3, and 1.1 -> 1.2. Anything else is a pkgrel this command has no business
@@ -252,7 +346,17 @@ record_triggers() {
local package_dir="$1"
local current="$2"
write_metadata "$package_dir" '.rebuilt_against = $current' --argjson current "$current"
# A flat record is the legacy x86_64 shape. Preserve records for
# architectures outside this run, then replace the ones just rebuilt.
write_metadata "$package_dir" '
(.rebuilt_against // {}) as $old |
(if ($old | length) == 0 then {}
elif ($old | to_entries | all(.value | type == "string"))
then {x86_64: $old}
else $old
end) as $by_arch |
.rebuilt_against = ($by_arch * $current)
' --argjson current "$current"
}
# Metadata that does not parse would otherwise drop its package out of the run
@@ -300,21 +404,61 @@ sync_package() {
print_info "Checking $package against ${triggers[*]}..."
local current="{}" trigger version
for trigger in "${triggers[@]}"; do
version=$(repo_version "$trigger")
if [[ -z "$version" ]]; then
print_error " $trigger is in no official repository; leaving $package alone"
((++FAILED))
return 0
fi
if ! current=$(jq -c --arg name "$trigger" --arg version "$version" '.[$name] = $version' <<<"$current"); then
print_error " Could not record $trigger $version for $package"
local current="{}" arch arch_current trigger version carried considered=0
for arch in $(ci_arches); do
package_supports_arch "$package_dir" "$arch" || continue
considered=$((considered + 1))
arch_current="{}"
for trigger in "${triggers[@]}"; do
if carried=$(carried_trigger_dir "$arch" "$trigger"); then
if ! version=$(carried_version "$arch" "$carried"); then
print_error " Could not read the $arch version of $trigger from its recipe; leaving $package alone"
((++FAILED))
return 0
fi
load_published_versions
if [[ -z "${EDGE_READ[$arch]:-}" ]]; then
print_error " Could not read the published edge/$arch database for $trigger; leaving $package alone"
((++FAILED))
return 0
fi
# Until edge publishes the recipe's version the builder still links
# against the previous one, and recording the new version now would
# certify a build that never saw it.
if [[ "${EDGE_VERSION[$arch/$trigger]:-}" != "$version" ]]; then
print_warning " $trigger $version is not published on edge/$arch yet; leaving $package alone until it is"
((++SKIPPED))
return 0
fi
elif ! version=$(repo_version "$arch" "$trigger"); then
print_error " Could not read $arch repository versions; leaving $package alone"
((++FAILED))
return 0
fi
if [[ -z "$version" ]]; then
print_error " $trigger is in no $arch repository; leaving $package alone"
((++FAILED))
return 0
fi
if ! arch_current=$(jq -c --arg name "$trigger" --arg version "$version" '.[$name] = $version' <<<"$arch_current"); then
print_error " Could not record $arch/$trigger $version for $package"
((++FAILED))
return 0
fi
done
if ! current=$(jq -c --arg arch "$arch" --argjson versions "$arch_current" '.[$arch] = $versions' <<<"$current"); then
print_error " Could not record $arch trigger versions for $package"
((++FAILED))
return 0
fi
done
if ((considered == 0)); then
print_info " Skipping: not built for any published architecture"
((++SKIPPED))
return 0
fi
local recorded
if ! recorded=$(package_metadata_value "$package_dir" '.rebuilt_against' ""); then
print_error " Could not read .omarchy/package.json for $package"
@@ -323,13 +467,20 @@ sync_package() {
fi
[[ -n "$recorded" && "$recorded" != "null" ]] || recorded="{}"
# Before architecture-specific records existed, rebuilt_against described
# x86_64. Read it that way without forcing a metadata-only migration.
if jq -e 'to_entries | all(.value | type == "string")' >/dev/null <<<"$recorded"; then
recorded=$(jq -c '{x86_64: .}' <<<"$recorded")
fi
# Walk the declared triggers rather than the record, so a name the record does
# not carry reads as changed instead of going unexamined forever.
local moved
if ! moved=$(jq -r --argjson recorded "$recorded" '
to_entries
| map(select($recorded[.key] != .value)
| "\(.key) \($recorded[.key] // "unrecorded") -> \(.value)")
[to_entries[] as $arch
| $arch.value | to_entries[] as $trigger
| select($recorded[$arch.key][$trigger.key] != $trigger.value)
| "\($arch.key)/\($trigger.key) \($recorded[$arch.key][$trigger.key] // "unrecorded") -> \($trigger.value)"]
| join(", ")
' <<<"$current"); then
print_error " Could not compare recorded trigger versions for $package"
@@ -338,7 +489,7 @@ sync_package() {
fi
if [[ -z "$moved" ]]; then
print_info " Already rebuilt against $(jq -r 'to_entries | map("\(.key) \(.value)") | join(", ")' <<<"$current")"
print_info " Already rebuilt against every published architecture"
((++SKIPPED))
return 0
fi
@@ -384,6 +535,7 @@ sync_package() {
# checkout that has fallen behind the repository can otherwise be bumped to
# something pacman orders below what it would replace.
local floor
load_published_versions
floor=$(published_version "$package")
if [[ -n "$floor" && "$(version_pkgver "$floor")" == "$pkgver" ]]; then
if [[ "$(vercmp "$new_version" "$floor")" -le 0 ]]; then
@@ -466,11 +618,11 @@ selftest_root() {
}
selftest_package() {
local root="$1" name="$2" pkgrel="$3" metadata="$4" pkgver="${5:-1.0}"
local root="$1" name="$2" pkgrel="$3" metadata="$4" pkgver="${5:-1.0}" arches="${6:-x86_64}"
local dir="$root/pkgbuilds/$name"
mkdir -p "$dir/.omarchy"
printf 'pkgname=%s\npkgver=%s\npkgrel=%s\narch=(x86_64)\n' "$name" "$pkgver" "$pkgrel" > "$dir/PKGBUILD"
printf 'pkgname=%s\npkgver=%s\npkgrel=%s\narch=(%s)\n' "$name" "$pkgver" "$pkgrel" "$arches" > "$dir/PKGBUILD"
printf '%s\n' "$metadata" > "$dir/.omarchy/package.json"
}
@@ -489,44 +641,97 @@ STUB
chmod +x "$root/stub/pacman"
}
# Serves a repository database assembled by hand from name=version pairs. With
# none given the stub fails, which is how the unreachable-repository path is
# A repository database assembled by hand from name=version pairs.
selftest_db() {
local out="$1"
shift
local staging="$out.d" entry name version
rm -rf "$staging"
mkdir -p "$staging"
for entry in "$@"; do
name="${entry%=*}"
version="${entry#*=}"
mkdir -p "$staging/$name-$version"
printf '%%FILENAME%%\n%s-%s-x86_64.pkg.tar.zst\n\n%%NAME%%\n%s\n\n%%BASE%%\n%s\n\n%%VERSION%%\n%s\n' \
"$name" "$version" "$name" "$name" "$version" > "$staging/$name-$version/desc"
done
tar --zstd -cf "$out" -C "$staging" .
}
# Serves one published database for every channel and architecture. With no
# pairs given the stub fails, which is how the unreachable-repository path is
# exercised.
selftest_published() {
local root="$1"
shift
local staging="$root/stub/db"
local entry name version
if [[ $# -gt 0 ]]; then
rm -rf "$staging"
mkdir -p "$staging"
for entry in "$@"; do
name="${entry%=*}"
version="${entry#*=}"
mkdir -p "$staging/$name-$version"
printf '%%FILENAME%%\n%s-%s-x86_64.pkg.tar.zst\n\n%%NAME%%\n%s\n\n%%BASE%%\n%s\n\n%%VERSION%%\n%s\n' \
"$name" "$version" "$name" "$name" "$version" > "$staging/$name-$version/desc"
done
tar --zstd -cf "$root/stub/omarchy.db.tar.zst" -C "$staging" .
selftest_db "$root/stub/omarchy.db.tar.zst" "$@"
fi
cat > "$root/stub/curl" <<'STUB'
#!/bin/bash
out=""
url=""
while [[ $# -gt 0 ]]; do
case "$1" in
-o) out="$2"; shift 2 ;;
*) shift ;;
*) url="$1"; shift ;;
esac
done
db="$(dirname "$0")/omarchy.db.tar.zst"
case "$url" in
*/omarchy.db.tar.zst)
channel="${url%/omarchy.db.tar.zst}"
arch="${channel##*/}"
channel="${channel%/*}"
db="$(dirname "$0")/omarchy-${channel##*/}-$arch.db.tar.zst"
[[ -f "$db" ]] || db="$(dirname "$0")/omarchy.db.tar.zst"
;;
*/aarch64/*)
repo="${url%/*}"
db="$(dirname "$0")/alarm-${repo##*/}.db"
;;
*) db="" ;;
esac
[[ -f "$db" && -n "$out" ]] || exit 22
cp "$db" "$out"
STUB
chmod +x "$root/stub/curl"
}
# Serves one channel and architecture its own published database.
selftest_channel() { # selftest_channel <root> <channel> <arch> [name=version...]
local root="$1" channel="$2" arch="$3"
shift 3
selftest_db "$root/stub/omarchy-$channel-$arch.db.tar.zst" "$@"
}
selftest_alarm() {
local root="$1"
shift
local repo staging="$root/stub/alarm-db" entry name version
for repo in core extra; do
rm -rf "$staging"
mkdir -p "$staging"
if [[ "$repo" == "core" ]]; then
mkdir -p "$staging/unrelated-1-1"
printf '%%FILENAME%%\nunrelated-1-1-aarch64.pkg.tar.zst\n\n%%NAME%%\nunrelated\n\n%%BASE%%\nunrelated\n\n%%VERSION%%\n1-1\n' \
> "$staging/unrelated-1-1/desc"
else
for entry in "$@"; do
name="${entry%=*}"
version="${entry#*=}"
mkdir -p "$staging/$name-$version"
printf '%%FILENAME%%\n%s-%s-aarch64.pkg.tar.zst\n\n%%NAME%%\n%s\n\n%%BASE%%\n%s\n\n%%VERSION%%\n%s\n' \
"$name" "$version" "$name" "$name" "$version" > "$staging/$name-$version/desc"
done
fi
tar -czf "$root/stub/alarm-$repo.db" -C "$staging" .
done
}
cmd_self_test() {
local failures=0
local root
@@ -541,11 +746,15 @@ cmd_self_test() {
fi
}
# SELFTEST_ARCHES stands in for CI_ARCHES; "default" leaves it unset, as the
# scheduled workflow does.
run_case() {
local root="$1"
shift
local status=0
PATH="$root/stub:$PATH" "$root/bin/sync-rebuilds" "$@" > "$root/output" 2>&1 || status=$?
local status=0 arches=(CI_ARCHES="${SELFTEST_ARCHES:-x86_64}")
[[ "${SELFTEST_ARCHES:-}" != default ]] || arches=(-u CI_ARCHES)
env "${arches[@]}" PATH="$root/stub:$PATH" \
"$root/bin/sync-rebuilds" "$@" > "$root/output" 2>&1 || status=$?
echo "$status"
}
@@ -563,7 +772,7 @@ cmd_self_test() {
check "run succeeds" 0 "$(run_case "$root")"
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-partial")"
check "unrecorded trigger now recorded" "2-2" \
"$(jq -r '.rebuilt_against["dep-b"]' "$root/pkgbuilds/t-partial/.omarchy/package.json")"
"$(jq -r '.rebuilt_against.x86_64["dep-b"]' "$root/pkgbuilds/t-partial/.omarchy/package.json")"
echo "Opting a package in buys a rebuild rather than a bare record:"
root=$(selftest_root fresh)
@@ -573,7 +782,7 @@ cmd_self_test() {
check "run succeeds" 0 "$(run_case "$root")"
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-fresh")"
check "trigger recorded" "1-1" \
"$(jq -r '.rebuilt_against["dep-a"]' "$root/pkgbuilds/t-fresh/.omarchy/package.json")"
"$(jq -r '.rebuilt_against.x86_64["dep-a"]' "$root/pkgbuilds/t-fresh/.omarchy/package.json")"
echo "An unchanged package is left alone:"
root=$(selftest_root current)
@@ -612,6 +821,138 @@ cmd_self_test() {
check "suffix recorded for the next AUR sync" 1 \
"$(jq -r '.pkgrel.suffix' "$root/pkgbuilds/t-aur/.omarchy/package.json")"
echo "A dependency is tracked independently for both published architectures:"
root=$(selftest_root multiarch)
selftest_package "$root" t-multi 1 '{"source":"local","rebuild_on":["dep-a"],"rebuilt_against":{"dep-a":"1-1"}}' 1.0 'x86_64 aarch64'
selftest_pacman "$root" dep-a=1-1
selftest_published "$root"
selftest_alarm "$root" dep-a=2-1
SELFTEST_ARCHES="x86_64 aarch64"
check "run succeeds" 0 "$(run_case "$root")"
unset SELFTEST_ARCHES
check "pkgrel bumped once" 2 "$(pkgrel_of "$root/pkgbuilds/t-multi")"
check "x86_64 trigger recorded" "1-1" \
"$(jq -r '.rebuilt_against.x86_64["dep-a"]' "$root/pkgbuilds/t-multi/.omarchy/package.json")"
check "aarch64 trigger recorded" "2-1" \
"$(jq -r '.rebuilt_against.aarch64["dep-a"]' "$root/pkgbuilds/t-multi/.omarchy/package.json")"
echo "An ARM-only run records only the ARM dependency state:"
root=$(selftest_root arm-only)
selftest_package "$root" t-arm 1 '{"source":"local","rebuild_on":["dep-a"]}' 1.0 'x86_64 aarch64'
selftest_pacman "$root"
selftest_published "$root"
selftest_alarm "$root" dep-a=2-1
SELFTEST_ARCHES=aarch64
check "run succeeds" 0 "$(run_case "$root")"
unset SELFTEST_ARCHES
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-arm")"
check "ARM trigger recorded" "2-1" \
"$(jq -r '.rebuilt_against.aarch64["dep-a"]' "$root/pkgbuilds/t-arm/.omarchy/package.json")"
check "x86_64 was not consulted" "false" \
"$(jq -r '.rebuilt_against | has("x86_64")' "$root/pkgbuilds/t-arm/.omarchy/package.json")"
echo "An x86-only package ignores ARM during a dual-architecture run:"
root=$(selftest_root x86-package)
selftest_package "$root" t-x86 1 '{"source":"local","rebuild_on":["dep-a"],"rebuilt_against":{"dep-a":"1-1"}}'
selftest_pacman "$root" dep-a=1-1
selftest_published "$root"
selftest_alarm "$root" dep-a=2-1
SELFTEST_ARCHES="x86_64 aarch64"
check "run succeeds" 0 "$(run_case "$root")"
unset SELFTEST_ARCHES
check "pkgrel untouched" 1 "$(pkgrel_of "$root/pkgbuilds/t-x86")"
echo "An ARM-only package is checked when no architecture list is given:"
root=$(selftest_root arm-default)
selftest_package "$root" t-arm-default 1 '{"source":"local","rebuild_on":["dep-a"],"rebuilt_against":{"aarch64":{"dep-a":"1-1"}}}' 1.0 aarch64
selftest_pacman "$root"
selftest_published "$root"
selftest_alarm "$root" dep-a=2-1
SELFTEST_ARCHES=default
check "run succeeds" 0 "$(run_case "$root")"
unset SELFTEST_ARCHES
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-arm-default")"
check "ARM trigger recorded" "2-1" \
"$(jq -r '.rebuilt_against.aarch64["dep-a"]' "$root/pkgbuilds/t-arm-default/.omarchy/package.json")"
echo "A dependency carried here for ARM is read from its recipe once edge publishes it:"
root=$(selftest_root carried)
selftest_package "$root" t-carried 1.1 '{"source":"local","channels":["edge"]}' 0.15.1 aarch64
selftest_package "$root" t-linked 3 '{"source":"local","rebuild_on":["t-carried"],"rebuilt_against":{"x86_64":{"t-carried":"0.15.1-1"},"aarch64":{"t-carried":"0.15.0-2"}}}' 1.0 'x86_64 aarch64'
selftest_pacman "$root" t-carried=0.15.1-1
selftest_published "$root"
selftest_alarm "$root" t-carried=0.15.1-1
selftest_channel "$root" edge aarch64 t-carried=0.15.1-1.1
SELFTEST_ARCHES=default
check "run succeeds" 0 "$(run_case "$root")"
check "pkgrel bumped" 4 "$(pkgrel_of "$root/pkgbuilds/t-linked")"
check "the carried version is recorded, not Arch Linux ARM's" "0.15.1-1.1" \
"$(jq -r '.rebuilt_against.aarch64["t-carried"]' "$root/pkgbuilds/t-linked/.omarchy/package.json")"
check "x86_64, where nothing is carried, still reads the distribution" "0.15.1-1" \
"$(jq -r '.rebuilt_against.x86_64["t-carried"]' "$root/pkgbuilds/t-linked/.omarchy/package.json")"
check "a second run succeeds" 0 "$(run_case "$root")"
unset SELFTEST_ARCHES
check "a second run leaves it alone" 4 "$(pkgrel_of "$root/pkgbuilds/t-linked")"
echo "A carried dependency that edge does not publish yet leaves its dependents alone:"
root=$(selftest_root carried-in-flight)
selftest_package "$root" t-carried 1.2 '{"source":"local","channels":["edge"]}' 0.15.1 aarch64
selftest_package "$root" t-linked 4 '{"source":"local","rebuild_on":["t-carried"],"rebuilt_against":{"aarch64":{"t-carried":"0.15.1-1.1"}}}' 1.0 aarch64
selftest_pacman "$root"
selftest_published "$root"
selftest_alarm "$root" t-carried=0.15.1-1
selftest_channel "$root" edge aarch64 t-carried=0.15.1-1.1
SELFTEST_ARCHES=aarch64
check "run succeeds" 0 "$(run_case "$root")"
unset SELFTEST_ARCHES
check "pkgrel untouched" 4 "$(pkgrel_of "$root/pkgbuilds/t-linked")"
check "record untouched" "0.15.1-1.1" \
"$(jq -r '.rebuilt_against.aarch64["t-carried"]' "$root/pkgbuilds/t-linked/.omarchy/package.json")"
echo "A carried dependency whose edge database cannot be read fails the run:"
root=$(selftest_root carried-unreadable)
selftest_package "$root" t-carried 1.1 '{"source":"local","channels":["edge"]}' 0.15.1 aarch64
selftest_package "$root" t-linked 3 '{"source":"local","rebuild_on":["t-carried"],"rebuilt_against":{"aarch64":{"t-carried":"0.15.0-2"}}}' 1.0 aarch64
selftest_pacman "$root"
selftest_published "$root"
selftest_alarm "$root" t-carried=0.15.1-1
SELFTEST_ARCHES=aarch64
check "run fails" 1 "$(run_case "$root")"
check "pkgrel untouched" 3 "$(pkgrel_of "$root/pkgbuilds/t-linked")"
printf 'not a database\n' > "$root/stub/omarchy-edge-aarch64.db.tar.zst"
check "a corrupt download fails the run too" 1 "$(run_case "$root")"
unset SELFTEST_ARCHES
check "pkgrel still untouched" 3 "$(pkgrel_of "$root/pkgbuilds/t-linked")"
echo "A PKGBUILD that branches on CARCH at file scope still reads its version:"
root=$(selftest_root carch-branch)
mkdir -p "$root/pkgbuilds/t-carch/.omarchy"
cat > "$root/pkgbuilds/t-carch/PKGBUILD" <<'PKG'
pkgname=t-carch
case "$CARCH" in
x86_64) _suffix=x64 ;;
aarch64) _suffix=arm64 ;;
*) return 1 ;;
esac
pkgver=1.0
pkgrel=3
arch=(x86_64 aarch64)
PKG
echo '{"source":"local"}' > "$root/pkgbuilds/t-carch/.omarchy/package.json"
check "pkgver read for x86_64" "1.0" "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" pkgver x86_64)"
check "pkgrel read for x86_64" "3" "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" pkgrel x86_64)"
check "arch-specific value follows CARCH" "arm64" "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" _suffix aarch64)"
check "unsupported arch reports failure" 1 "$(package_pkgbuild_var "$root/pkgbuilds/t-carch" pkgver armv7h >/dev/null; echo $?)"
echo "A carried recipe's version is spelled the way makepkg publishes it:"
root=$(selftest_root carried-epoch)
selftest_package "$root" t-epoch 1.1 '{"source":"local"}' 0.15.1 aarch64
check "no epoch" "0.15.1-1.1" "$(carried_version aarch64 "$root/pkgbuilds/t-epoch")"
echo 'epoch=0' >> "$root/pkgbuilds/t-epoch/PKGBUILD"
check "a zero epoch is left out" "0.15.1-1.1" "$(carried_version aarch64 "$root/pkgbuilds/t-epoch")"
echo 'epoch=2' >> "$root/pkgbuilds/t-epoch/PKGBUILD"
check "a real epoch is kept" "2:0.15.1-1.1" "$(carried_version aarch64 "$root/pkgbuilds/t-epoch")"
echo ""
if [[ "$failures" -eq 0 ]]; then
print_success "Self-test passed"
@@ -626,9 +967,9 @@ if [[ "$SELF_TEST" == true ]]; then
exit $?
fi
for tool in pacman vercmp jq curl; do
for tool in pacman vercmp jq curl tar; do
if ! command -v "$tool" >/dev/null 2>&1; then
print_error "$tool not found: reading trigger versions and ordering pkgrels both need pacman"
print_error "$tool not found: rebuild trigger sync cannot run"
exit 1
fi
done
+486 -23
View File
@@ -9,8 +9,10 @@ source "$BUILD_ROOT/helpers/upstream-github.sh"
TEMP_DIR=$(mktemp -d)
trap 'rm -rf "$TEMP_DIR"' EXIT
export UPSTREAM_CACHE_DIR="$TEMP_DIR/watch-cache"
SPECIFIC_PACKAGES=()
LANE=all
usage() {
cat <<EOF
@@ -18,11 +20,11 @@ Usage: $0 [PACKAGE...]
Update packages that track an upstream vendor release feed instead of the AUR.
A package whose upstream ships tagged GitHub releases with a checksum manifest
opts in declaratively, via "upstream" in .omarchy/package.json (see
helpers/upstream-github.sh for the schema); no code needed. Anything with a
bespoke feed provides pkgbuilds/<package>/.omarchy/upstream.sh instead, a hook
that reports the newest upstream release as JSON on stdout:
Packages opt in declaratively through "upstream" in .omarchy/package.json.
Providers cover GitHub Releases with checksum manifests or API asset digests,
semver-shaped git tags, npm dist-tags, and plain Debian Packages indexes. See
README.md for the schemas. Anything outside those conventions may provide
pkgbuilds/<package>/.omarchy/upstream.sh, a hook that reports JSON on stdout:
{
"pkgver": "1.2.3",
@@ -48,6 +50,14 @@ the bypass, so the resulting change still goes through a reviewed PR.
Arguments:
PACKAGE One or more package names to update (optional)
Options:
--lane <reviewed|auto-merge|all>
Which delivery lane to sync (default: all). Packages marked
"auto_merge": true ride the unattended lane (the branch tracker
opens and auto-merges their PR); everything else is reviewed.
The scheduled workflows each pass their own lane so a moving
branch tip never waits on a vendor release, or the reverse.
Commands:
self-test Run the offline fixture tests for release selection, the
quarantine backstop, and metadata parsing
@@ -64,6 +74,14 @@ while [[ $# -gt 0 ]]; do
usage
exit 0
;;
--lane)
LANE="${2:-}"
case "$LANE" in
reviewed|auto-merge|all) ;;
*) print_error "Invalid --lane '$LANE' (expected reviewed, auto-merge, or all)"; exit 1 ;;
esac
shift 2
;;
--*)
print_error "Unknown option: $1"
exit 1
@@ -140,17 +158,22 @@ set_pkgbuild_array() {
local rewritten="$TEMP_DIR/pkgbuild-rewritten"
if ! awk -v prefix="${name}=(" -v block="$block" '
# The code on a line, minus any comment. Checksum arrays hold quoted hex
# (or SKIP), so a "#" can only ever start a comment here.
function code(s) { sub(/#.*/, "", s); return s }
!replaced && index($0, prefix) == 1 {
while ((getline line < block) > 0) print line
close(block)
replaced = 1
# A ")" anywhere past the opening closes the array; testing for one at end
# of line instead would treat a trailing comment as a continuation and eat
# every line up to the next ")".
if (index(substr($0, length(prefix) + 1), ")") == 0) skipping = 1
# every line up to the next ")". A ")" inside a comment -- "# sidecar
# (new in v0.7.5)" -- closes nothing, and ending the skip there would
# leave the rest of the old array behind a stray ")".
if (index(code(substr($0, length(prefix) + 1)), ")") == 0) skipping = 1
next
}
skipping { if ($0 ~ /\)/) skipping = 0; next }
skipping { if (code($0) ~ /\)/) skipping = 0; next }
{ print }
' "$pkgbuild" > "$rewritten"; then
print_error "Failed to rewrite ${name} in $pkgbuild"
@@ -332,27 +355,33 @@ sync_package() {
return 0
fi
local github_repo has_upstream=false
github_repo=$(package_upstream_github_repo "$package_dir")
if jq -e '.sync == false' "$package_dir/.omarchy/package.json" >/dev/null 2>&1; then
print_info "Skipping $package: upstream updates held by sync=false"
((++SKIPPED))
return 0
fi
local provider has_upstream=false
provider=$(package_upstream_provider "$package_dir")
if package_has_upstream_provider "$package_dir"; then
has_upstream=true
fi
# A present-but-unusable declaration fails loudly; treating it like "no
# upstream source" would silently drop the package from scheduled runs.
if [[ "$has_upstream" == true && -z "$github_repo" ]]; then
print_error "Package $package has an unusable upstream declaration (needs a github owner/repo)"
if [[ "$has_upstream" == true && -z "$provider" ]]; then
print_error "Package $package has an unusable or ambiguous upstream declaration"
((++FAILED))
return 0
fi
if [[ -n "$github_repo" && -f "$hook" ]]; then
print_error "Package $package declares both upstream.github and an upstream.sh hook; keep exactly one"
if [[ -n "$provider" && -f "$hook" ]]; then
print_error "Package $package declares both an upstream provider and an upstream.sh hook; keep exactly one"
((++FAILED))
return 0
fi
if [[ -z "$github_repo" && ! -f "$hook" ]]; then
if [[ -z "$provider" && ! -f "$hook" ]]; then
if [[ "$SPECIFIC_MODE" == true ]]; then
print_error "Package $package has neither an upstream declaration nor .omarchy/upstream.sh"
((++FAILED))
@@ -372,10 +401,31 @@ sync_package() {
print_info "Checking $package for upstream releases..."
local release
if [[ -n "$github_repo" ]]; then
if ! release=$(github_upstream_release "$package_dir" "$min_age"); then
print_error "GitHub release provider failed for $package"
if [[ "$provider" == watch ]]; then
local result
if ! result=$(python3 "$BUILD_ROOT/helpers/upstream-watch.py" sync "$package_dir" --min-age "$min_age"); then
print_error "Upstream watch failed for $package"
((++FAILED))
elif [[ $(jq -r .status <<<"$result") == updated ]]; then
print_success " $(jq -r '.before + " -> " + .after' <<<"$result")"
((++UPDATED))
else
print_info " $(jq -r '.reason' <<<"$result")"
((++SKIPPED))
fi
return 0
fi
local release release_status=0
if [[ -n "$provider" ]]; then
case "$provider" in
github) release=$(github_upstream_release "$package_dir" "$min_age") || release_status=$? ;;
git_tags) release=$(git_tags_upstream_release "$package_dir") || release_status=$? ;;
npm) release=$(npm_upstream_release "$package_dir") || release_status=$? ;;
debian) release=$(debian_upstream_release "$package_dir") || release_status=$? ;;
esac
if [[ ${release_status:-0} -ne 0 ]]; then
print_error "$provider upstream provider failed for $package"
((++FAILED))
return 0
fi
@@ -461,8 +511,8 @@ sync_package() {
# paths. Covers release selection (fallback past quarantined releases,
# draft/prerelease filtering, bypass, unchanged version), failure paths
# (unusable tags/timestamps, missing checksums), checksum template mapping
# for both architectures, the min_release_age backstop, the duration parser,
# and manifest validation.
# for both architectures, release API digests, the min_release_age backstop,
# the duration parser, and manifest validation.
cmd_self_test() {
local failures=0
@@ -539,6 +589,31 @@ EOF
check "bypass lifts the quarantine" "2.0.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
check "x86_64 checksum via * binary-mode prefix" "$sum_x20" "$(jq -r '.sha256sums.x86_64[0] // "<none>"' <<<"$out")"
cp "$pkg/.omarchy/package.json" "$pkg/normal.json"
jq '.upstream.latest_only = true' "$pkg/normal.json" > "$pkg/.omarchy/package.json"
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" '[
{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false},
{tag_name: "v0.4.1-8", published_at: $old, draft: false, prerelease: false}
]')
FIXTURE_CHECKSUMS=$(printf '%s\n' \
"$sum_x19 ./tool-v1.9.0-x64.tar.xz" \
"$sum_a19 ./tool-v1.9.0-arm64.tar.xz")
out=$(github_upstream_release "$pkg" 0 2>/dev/null) || out="<error>"
check "latest_only ignores incompatible historical tags" "1.9.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")"
cp "$pkg/normal.json" "$pkg/.omarchy/package.json"
FIXTURE_RELEASES=$(jq -n --arg young "$young" --arg old2 "$old2d" --arg old3 "$old3d" '[
{tag_name: "v2.0.0", published_at: $young, draft: false, prerelease: false},
{tag_name: "v1.9.9", published_at: $old2, draft: false, prerelease: true},
{tag_name: "v1.9.8", published_at: $old2, draft: true, prerelease: false},
{tag_name: "v1.9.0", published_at: $old2, draft: false, prerelease: false},
{tag_name: "v1.8.0", published_at: $old3, draft: false, prerelease: false}
]')
FIXTURE_CHECKSUMS=$(printf '%s\n' \
"$sum_x19 ./tool-v1.9.0-x64.tar.xz" \
"$sum_a19 tool-v1.9.0-arm64.tar.xz" \
"$sum_x20 *tool-v2.0.0-x64.tar.xz" \
"$sum_a20 tool-v2.0.0-arm64.tar.xz")
out=$(github_upstream_release "$pkg" 8640000 2>/dev/null) || out="<error>"
check "everything quarantined reports no update" "{}" "$(jq -c . <<<"$out")"
@@ -562,6 +637,190 @@ EOF
rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$?
check "missing aarch64 checksum fails the sync" "1" "$rc"
# A vendor publishing no manifest: checksums come from the digests the
# release API reports per asset, with nothing fetched beyond the feed.
echo "Release API digests:"
local digpkg="$TEMP_DIR/selftest-digests"
mkdir -p "$digpkg/.omarchy"
printf 'pkgver=1.0.0\npkgrel=1\n' > "$digpkg/PKGBUILD"
cat > "$digpkg/.omarchy/package.json" <<'EOF'
{
"source": "local",
"upstream": {
"github": "example/tool",
"digests": true,
"assets": {
"x86_64": "tool-{pkgver}-1-x86_64.pkg.tar.zst",
"aarch64": "tool-{pkgver}-1-aarch64.pkg.tar.zst"
}
}
}
EOF
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" --arg a "$sum_a19" '[
{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [
{name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: ("sha256:" + $x)},
{name: "tool-1.9.0-1-aarch64.pkg.tar.zst", digest: ("sha256:" + $a)},
{name: "tool-1.9.0-1-x86_64.rpm", digest: "sha256:0000000000000000000000000000000000000000000000000000000000000000"}
]}
]')
FIXTURE_CHECKSUMS="manifest must not be consulted"
out=$(github_upstream_release "$digpkg" 0 2>/dev/null) || out="<error>"
check "x86_64 checksum via the asset digest" "$sum_x19" "$(jq -r '.sha256sums.x86_64[0] // "<none>"' <<<"$out")"
check "aarch64 checksum via the asset digest" "$sum_a19" "$(jq -r '.sha256sums.aarch64[0] // "<none>"' <<<"$out")"
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" '[
{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [
{name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: ("sha256:" + $x)},
{name: "tool-1.9.0-1-aarch64.pkg.tar.zst"}
]}
]')
rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$?
check "asset without a digest fails the sync" "1" "$rc"
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" '[
{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [
{name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: ("sha256:" + $x)},
{name: "tool-1.9.0-2-aarch64.pkg.tar.zst", digest: ("sha256:" + $x)}
]}
]')
rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$?
check "asset re-cut under another release number fails the sync" "1" "$rc"
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" --arg a "$sum_a19" '[
{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [
{name: "tool-1.9.0-1-x86_64.pkg.tar.zst", digest: $x},
{name: "tool-1.9.0-1-aarch64.pkg.tar.zst", digest: ("sha256:" + $a)}
]}
]')
rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$?
check "digest without the sha256: prefix fails the sync" "1" "$rc"
# The provider enforces the declaration shape itself: scheduled runs reach
# it without validate_package_metadata.
jq '.upstream.checksums = "SHASUMS256.txt"' "$digpkg/.omarchy/package.json" > "$digpkg/both.json"
cp "$digpkg/.omarchy/package.json" "$digpkg/good.json"
cp "$digpkg/both.json" "$digpkg/.omarchy/package.json"
rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$?
check "provider rejects checksums and digests together" "1" "$rc"
jq '.upstream.digests = "true"' "$digpkg/good.json" > "$digpkg/.omarchy/package.json"
rc=0; github_upstream_release "$digpkg" 0 >/dev/null 2>&1 || rc=$?
check "provider rejects a non-boolean digests" "1" "$rc"
cp "$digpkg/good.json" "$digpkg/.omarchy/package.json"
echo "Ordered GitHub assets with supplemental sources:"
local multipkg="$TEMP_DIR/selftest-multi-assets" multi_sum support_sum multi_vst
mkdir -p "$multipkg/.omarchy"
printf 'pkgver=1.0.0\npkgrel=1\n' > "$multipkg/PKGBUILD"
cat > "$multipkg/.omarchy/package.json" <<'EOF'
{
"source": "local",
"upstream": {
"github": "example/tool",
"digests": true,
"assets": {
"x86_64": ["tool-{pkgver}-x86_64", "tool-{pkgver}-x86_64.asc"],
"aarch64": ["tool-{pkgver}-aarch64", "tool-{pkgver}-aarch64.asc"]
},
"sources": {
"any": ["https://example.test/tool/{tag}/support.txt"]
}
}
}
EOF
local sum_x19_sig sum_a19_sig
sum_x19_sig=$(printf '1%.0s' {1..64})
sum_a19_sig=$(printf '2%.0s' {1..64})
FIXTURE_RELEASES=$(jq -n --arg old "$old2d" --arg x "$sum_x19" --arg xs "$sum_x19_sig" \
--arg a "$sum_a19" --arg as "$sum_a19_sig" '[
{tag_name: "v1.9.0", published_at: $old, draft: false, prerelease: false, assets: [
{name: "tool-1.9.0-x86_64", digest: ("sha256:" + $x)},
{name: "tool-1.9.0-x86_64.asc", digest: ("sha256:" + $xs)},
{name: "tool-1.9.0-aarch64", digest: ("sha256:" + $a)},
{name: "tool-1.9.0-aarch64.asc", digest: ("sha256:" + $as)}
]}
]')
upstream_fetch_source() { printf 'remote fixture for %s\n' "$1" > "$2"; }
multi_sum=$(github_upstream_release "$multipkg" 0 2>/dev/null) || multi_sum="<error>"
support_sum=$(printf 'remote fixture for %s\n' 'https://example.test/tool/v1.9.0/support.txt' | sha256sum | cut -d' ' -f1)
check "ordered GitHub assets produce an ordered checksum array" "$sum_x19 $sum_x19_sig" \
"$(jq -r '.sha256sums.x86_64 | join(" ")' <<<"$multi_sum")"
check "GitHub supplemental source is downloaded and hashed" "$support_sum" \
"$(jq -r '.sha256sums.any[0]' <<<"$multi_sum")"
multi_vst=0; validate_package_metadata "$multipkg" >/dev/null || multi_vst=$?
check "GitHub asset lists and disjoint sources validate" "0" "$multi_vst"
jq '.upstream.sources.x86_64 = ["https://example.test/duplicate"]' \
"$multipkg/.omarchy/package.json" > "$multipkg/overlap.json"
cp "$multipkg/.omarchy/package.json" "$multipkg/good.json"
cp "$multipkg/overlap.json" "$multipkg/.omarchy/package.json"
multi_vst=0; validate_package_metadata "$multipkg" >/dev/null || multi_vst=$?
check "GitHub assets and sources cannot target the same checksum array" "1" "$multi_vst"
cp "$multipkg/good.json" "$multipkg/.omarchy/package.json"
echo "Debian Packages provider:"
local debpkg="$TEMP_DIR/selftest-debian" deb_sum deb_x_sum deb_a_sum deb_vst
mkdir -p "$debpkg/.omarchy"
printf 'pkgver=1.0.0\npkgrel=1\nsha256sums_x86_64=("old")\nsha256sums_aarch64=("old")\n' > "$debpkg/PKGBUILD"
cat > "$debpkg/.omarchy/package.json" <<'EOF'
{
"source": "local",
"upstream": {
"debian": "https://packages.example.test/dists/stable/main/binary-amd64/Packages",
"package": "example-app",
"sources": {
"x86_64": ["https://downloads.example.test/app-{pkgver}-x64.tar.gz"],
"aarch64": ["https://downloads.example.test/app-{pkgver}-arm64.tar.gz"]
}
}
}
EOF
debian_fetch_packages() {
cat <<'EOF'
Package: unrelated
Version: 99.0.0
Package: example-app
Version: 1.9.0
Package: example-app
Version: 1.10.0
EOF
}
deb_sum=$(debian_upstream_release "$debpkg")
deb_x_sum=$(printf 'remote fixture for %s\n' 'https://downloads.example.test/app-1.10.0-x64.tar.gz' | sha256sum | cut -d' ' -f1)
deb_a_sum=$(printf 'remote fixture for %s\n' 'https://downloads.example.test/app-1.10.0-arm64.tar.gz' | sha256sum | cut -d' ' -f1)
check "Debian provider selects the newest exact package stanza" "1.10.0" "$(jq -r '.pkgver' <<<"$deb_sum")"
check "Debian x86_64 source is hashed" "$deb_x_sum" "$(jq -r '.sha256sums.x86_64[0]' <<<"$deb_sum")"
check "Debian aarch64 source is hashed" "$deb_a_sum" "$(jq -r '.sha256sums.aarch64[0]' <<<"$deb_sum")"
deb_vst=0; validate_package_metadata "$debpkg" >/dev/null || deb_vst=$?
check "Debian declaration validates" "0" "$deb_vst"
printf 'pkgver=1.10.0\npkgrel=1\nsha256sums_x86_64=("old")\nsha256sums_aarch64=("old")\n' > "$debpkg/PKGBUILD"
check "checked-in Debian version avoids source downloads" "{}" "$(debian_upstream_release "$debpkg" | jq -c .)"
echo "End-to-end Debian sync with the checked-in 1password recipe:"
local one_root="$TEMP_DIR/e2e-1password" one_dir one_version=8.12.35
mkdir -p "$one_root"
cp -a "$BUILD_ROOT/pkgbuilds/1password" "$one_root/1password"
one_dir="$one_root/1password"
# Keep the real recipe shape, but make the fixture's starting version stable.
# Otherwise a routine package update can outrun the mocked release below.
sed -i -e 's/^pkgver=.*/pkgver=8.12.34/' -e 's/^pkgrel=.*/pkgrel=2/' "$one_dir/PKGBUILD"
debian_fetch_packages() {
printf 'Package: 1password\nVersion: %s\n' "$one_version"
}
upstream_fetch_source() { printf 'remote fixture for %s\n' "$1" > "$2"; }
local one_prev_updated=$UPDATED one_prev_failed=$FAILED
PKGBUILDS_DIR="$one_root" sync_package 1password >/dev/null 2>&1 || true
check "1password sync updates without failures" "updated=1 failed=0" \
"updated=$((UPDATED - one_prev_updated)) failed=$((FAILED - one_prev_failed))"
check "1password pkgver is the single download version source" "$one_version" \
"$(grep -m1 '^pkgver=' "$one_dir/PKGBUILD" | cut -d= -f2-)"
check "1password pkgrel resets to 1" "1" \
"$(grep -m1 '^pkgrel=' "$one_dir/PKGBUILD" | cut -d= -f2-)"
check "1password x86 URL follows the rewritten pkgver" "1password-${one_version}.x64.tar.gz" \
"$(CARCH=x86_64 bash -c 'source "$1"; basename "${source_x86_64[0]}"' _ "$one_dir/PKGBUILD")"
check "1password ARM URL follows the rewritten pkgver" "1password-${one_version}.arm64.tar.gz" \
"$(CARCH=aarch64 bash -c 'source "$1"; basename "${source_aarch64[0]}"' _ "$one_dir/PKGBUILD")"
echo "Quarantine backstop:"
local rel st
rel=$(jq -n --arg p "$old2d" '{pkgver: "1.9.0", published_at: $p, sha256sums: {}}')
@@ -614,10 +873,123 @@ EOF
echo '{"source": "local", "upstream": {"github": "example/tool"}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "upstream without checksums/assets is rejected" "1" "$vst"
echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": "SHASUMS256.txt", "digests": true, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "upstream with both checksums and digests is rejected" "1" "$vst"
echo '{"source": "local", "upstream": {"github": "example/tool", "digests": "yes", "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "non-boolean digests is rejected" "1" "$vst"
echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": false, "digests": true, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "checksums: false alongside digests is rejected" "1" "$vst"
echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": "SUMS", "digests": null, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "digests: null is rejected" "1" "$vst"
echo '{"source": "local", "upstream": {"github": "example/tool", "checksums": "SUMS", "digests": false, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "digests: false beside a checksums manifest is accepted" "0" "$vst"
echo '{"source": "local", "upstream": {"github": "example/tool", "digests": false, "assets": {"x86_64": "a"}}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "digests: false alone is rejected" "1" "$vst"
cp "$digpkg/.omarchy/package.json" "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "the digests declaration shape is accepted" "0" "$vst"
echo '{"source":"local","upstream":{"github":"example/tool","git_tags":"https://example/tool.git","checksums":"sums","assets":{"any":"tool"}}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "multiple provider types are rejected" "1" "$vst"
cp "$pkg/.omarchy/package.json" "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "the real declaration shape is accepted" "0" "$vst"
echo "Git-tag provider:"
local tagpkg="$TEMP_DIR/selftest-tags" tag_sum remote_sum local_sum
mkdir -p "$tagpkg/.omarchy"
printf 'pkgver=1.0.0\npkgrel=4\nsha256sums=("old" "old")\n' > "$tagpkg/PKGBUILD"
printf 'local fixture\n' > "$tagpkg/local.patch"
cat > "$tagpkg/.omarchy/package.json" <<'EOF'
{
"source": "local",
"upstream": {
"git_tags": "https://example.test/tool.git",
"tag_pattern": "release/{pkgver}",
"sources": {
"any": ["https://downloads.example.test/tool-{pkgver}.tar.gz", "file:local.patch"]
}
}
}
EOF
git_tags_fetch_refs() {
printf '%s\n' \
'aaaa refs/tags/release/1.9.0' \
'bbbb refs/tags/release/1.10.0' \
'cccc refs/tags/not-a-release'
}
upstream_fetch_source() { printf 'remote fixture for %s\n' "$1" > "$2"; }
tag_sum=$(git_tags_upstream_release "$tagpkg")
remote_sum=$(printf 'remote fixture for %s\n' 'https://downloads.example.test/tool-1.10.0.tar.gz' | sha256sum | cut -d' ' -f1)
local_sum=$(sha256sum "$tagpkg/local.patch" | cut -d' ' -f1)
check "pacman ordering selects 1.10.0 over 1.9.0" "1.10.0" "$(jq -r '.pkgver' <<<"$tag_sum")"
check "remote source template is downloaded and hashed" "$remote_sum" "$(jq -r '.sha256sums.any[0]' <<<"$tag_sum")"
check "local source entry is hashed" "$local_sum" "$(jq -r '.sha256sums.any[1]' <<<"$tag_sum")"
vst=0; validate_package_metadata "$tagpkg" >/dev/null || vst=$?
check "git-tags declaration validates" "0" "$vst"
echo "npm provider:"
local npmpkg="$TEMP_DIR/selftest-npm" npm_sum npm_tar_sum npm_notes_sum
mkdir -p "$npmpkg/.omarchy"
printf 'pkgver=1.0.0\npkgrel=1\nsha256sums=("old" "old")\n' > "$npmpkg/PKGBUILD"
cat > "$npmpkg/.omarchy/package.json" <<'EOF'
{
"source": "local",
"upstream": {
"npm": "@example/tool",
"dist_tag": "latest",
"sources": {
"any": ["{npm_tarball}", "https://example.test/tool/{pkgver}/notes"]
}
}
}
EOF
npm_fetch_metadata() {
jq -n '{
"dist-tags": {latest: "2.0.0"},
versions: {"2.0.0": {dist: {tarball: "https://registry.npmjs.org/@example/tool/-/tool-2.0.0.tgz"}}},
time: {"2.0.0": "2024-01-02T03:04:05.000Z"}
}'
}
npm_sum=$(npm_upstream_release "$npmpkg")
npm_tar_sum=$(printf 'remote fixture for %s\n' 'https://registry.npmjs.org/@example/tool/-/tool-2.0.0.tgz' | sha256sum | cut -d' ' -f1)
npm_notes_sum=$(printf 'remote fixture for %s\n' 'https://example.test/tool/2.0.0/notes' | sha256sum | cut -d' ' -f1)
check "npm dist-tag selects its version" "2.0.0" "$(jq -r '.pkgver' <<<"$npm_sum")"
check "npm tarball placeholder is hashed" "$npm_tar_sum" "$(jq -r '.sha256sums.any[0]' <<<"$npm_sum")"
check "npm pkgver template is hashed" "$npm_notes_sum" "$(jq -r '.sha256sums.any[1]' <<<"$npm_sum")"
check "npm publication time is preserved" "2024-01-02T03:04:05.000Z" "$(jq -r '.published_at' <<<"$npm_sum")"
vst=0; validate_package_metadata "$npmpkg" >/dev/null || vst=$?
check "npm declaration validates" "0" "$vst"
# A ")" in a comment inside a checksum array must not end the rewrite early;
# voxtype-bin annotates its arrays with "(new in v0.7.5)" and the like.
echo "Checksum array rewrite across commented lines:"
local commented="$TEMP_DIR/commented-pkgbuild" sum_c=$(printf 'c%.0s' {1..64})
cat > "$commented" <<'EOF'
sha256sums_x86_64=(
# Binaries
'aaaa' # tool
# Sidecar (new in v0.7.5)
'bbbb' # sidecar (x86_64)
)
sha256sums=( # support files (arch-independent)
'dddd'
)
package() { :; }
EOF
set_pkgbuild_array "$commented" sha256sums_x86_64 "$sum_c" "$sum_c"
set_pkgbuild_array "$commented" sha256sums "$sum_c"
check "commented arrays rewrite to valid shell" "0" \
"$(bash -n "$commented" 2>/dev/null; echo $?)"
check "commented arrays hold only the new checksums" "$sum_c $sum_c|$sum_c|package" \
"$(bash -c 'source "$1"; printf "%s|%s|%s" "${sha256sums_x86_64[*]}" "${sha256sums[*]}" "$(declare -F package)"' _ "$commented")"
# End to end over the real mise-bin package: its checked-in metadata and
# PKGBUILD, the full sync_package path (selection, validation, backstop,
# rewrite, read-back verification), with only the two network fetches
@@ -669,6 +1041,13 @@ EOF
check "malformed upstream fails the sync instead of skipping" "1" "$((FAILED - prev_failed))"
FAILED=0
if ! bash "$BUILD_ROOT/pkgbuilds/cua-driver-bin/.omarchy/upstream-test.sh"; then
failures=$((failures + 1))
fi
if ! bash "$BUILD_ROOT/pkgbuilds/tmog-bin/.omarchy/upstream-test.sh"; then
failures=$((failures + 1))
fi
echo ""
if [[ "$failures" -eq 0 ]]; then
print_success "Self-test passed"
@@ -683,16 +1062,100 @@ if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 && "${SPECIFIC_PACKAGES[0]}" == "self-test"
exit 0
fi
# Packages that pin the same upstream branch move together or not at all.
# The watch reads one shared clone per run, so they only disagree when one
# package's update failed after the tip was chosen (a checksum fetch, say).
# Leaving the other one advanced would ship omarchy-dev and
# omarchy-settings-dev from different commits, which is exactly the skew the
# release pair's lockstep guard exists to prevent. Restore the packages that
# moved and count the group as failed; the next run tries again.
declare -A BEFORE_SYNC=()
snapshot_package() {
local package="$1" pkgbuild="$PKGBUILDS_DIR/$1/PKGBUILD"
[[ -f "$pkgbuild" ]] || return 0
mkdir -p "$TEMP_DIR/before"
cp "$pkgbuild" "$TEMP_DIR/before/$package"
BEFORE_SYNC["$package"]=1
}
branch_watch_key() {
local package_dir="$1"
jq -r '
(.upstream.watch? | objects | select(has("git_branch")))
| "\(.git_branch)#\(.branch)"
' "$package_dir/.omarchy/package.json" 2>/dev/null
}
enforce_branch_lockstep() {
local package key
declare -A groups=()
for package in "${!BEFORE_SYNC[@]}"; do
key=$(branch_watch_key "$PKGBUILDS_DIR/$package")
[[ -n "$key" ]] || continue
groups["$key"]+="$package "
done
for key in "${!groups[@]}"; do
local members commits pin
read -r -a members <<<"${groups[$key]}"
(( ${#members[@]} > 1 )) || continue
commits=$(for package in "${members[@]}"; do
pin=$(grep -m1 -E '^_commit=' "$PKGBUILDS_DIR/$package/PKGBUILD" | cut -d= -f2- | tr -d "\"'" || true)
printf '%s\n' "${pin:-missing:$package}"
done | sort -u | grep -c .)
(( commits > 1 )) || continue
print_error "Lockstep violation on $key: ${members[*]} pin different commits; restoring all of them"
for package in "${members[@]}"; do
if ! cmp -s "$TEMP_DIR/before/$package" "$PKGBUILDS_DIR/$package/PKGBUILD"; then
cp "$TEMP_DIR/before/$package" "$PKGBUILDS_DIR/$package/PKGBUILD"
# Not ((--UPDATED)): an arithmetic command that evaluates to zero
# returns 1, and under errexit that would end the run right here.
UPDATED=$((UPDATED > 0 ? UPDATED - 1 : 0))
fi
done
((++FAILED))
done
}
sync_in_lane() {
local package="$1" package_dir="$PKGBUILDS_DIR/$1"
snapshot_package "$package"
if [[ -d "$package_dir" ]] && ! package_in_lane "$package_dir" "$LANE"; then
print_info "Skipping $package: not in the $LANE lane"
((++SKIPPED))
return 0
fi
sync_package "$package"
}
if [[ ${#SPECIFIC_PACKAGES[@]} -gt 0 ]]; then
SPECIFIC_MODE=true
# A targeted run is still a branch update: include every sibling watching
# the same branch, otherwise the lockstep check never sees the omitted one.
declare -A selected=() selected_branches=()
for package in "${SPECIFIC_PACKAGES[@]}"; do
sync_package "$package"
selected["$package"]=1
key=$(branch_watch_key "$PKGBUILDS_DIR/$package" || true)
[[ -z "$key" ]] || selected_branches["$key"]=1
done
for package_dir in "$PKGBUILDS_DIR"/*; do
[[ -f "$package_dir/PKGBUILD" ]] || continue
package=${package_dir##*/}
[[ -z "${selected[$package]:-}" ]] || continue
key=$(branch_watch_key "$package_dir" || true)
if [[ -n "$key" && -n "${selected_branches[$key]:-}" ]]; then
SPECIFIC_PACKAGES+=("$package")
fi
done
for package in "${SPECIFIC_PACKAGES[@]}"; do
sync_in_lane "$package"
done
else
while IFS= read -r package; do
sync_package "$package"
sync_in_lane "$package"
done < <(packages_for_upstream_sync)
fi
enforce_branch_lockstep
echo ""
if [[ $FAILED -gt 0 ]]; then
+38 -32
View File
@@ -84,15 +84,18 @@ echo ""
# --- queued work -------------------------------------------------------------
print_info "Queued builds (state files in $STATE_DIR)"
print_info "Queued builds (state files in $STATE_DIR; architectures: $(published_arches | tr '\n' ' '))"
queued=false
for channel in edge rc stable; do
state_file="$STATE_DIR/.sync-needed-$channel"
if [[ -f "$state_file" ]]; then
queued=true
since=$(date -r "$state_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown")
printf ' • %-7s queued since %s\n' "$channel" "$since"
fi
for arch in $(published_arches); do
state_file=$(sync_queue_file "$channel" "$arch")
[[ "$arch" == "x86_64" && ! -f "$state_file" ]] && state_file=$(legacy_sync_queue_file "$channel")
if [[ -f "$state_file" ]]; then
queued=true
since=$(date -r "$state_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown")
printf ' • %-7s %-8s queued since %s\n' "$channel" "$arch" "$since"
fi
done
done
[[ "$queued" == false ]] && echo " (nothing queued — all channels up to date)"
echo ""
@@ -101,34 +104,37 @@ echo ""
# say so plainly: it is queued but deliberately not being retried yet.
paused=false
for channel in edge rc stable; do
fail_file="$STATE_DIR/.build-failed-$channel"
[[ -f "$fail_file" ]] || continue
if [[ "$paused" == false ]]; then
print_error "Failing builds (backoff active)"
paused=true
fi
FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT=""
# shellcheck disable=SC1090
source "$fail_file" 2>/dev/null || true
delay=600
for ((i = 1; i < FAILURE_COUNT; i++)); do
delay=$((delay * 2))
((delay >= 21600)) && { delay=21600; break; }
for arch in $(published_arches); do
fail_file=$(sync_fail_file "$channel" "$arch")
[[ "$arch" == "x86_64" && ! -f "$fail_file" ]] && fail_file=$(legacy_sync_fail_file "$channel")
[[ -f "$fail_file" ]] || continue
if [[ "$paused" == false ]]; then
print_error "Failing builds (backoff active)"
paused=true
fi
FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT=""
# shellcheck disable=SC1090
source "$fail_file" 2>/dev/null || true
delay=600
for ((i = 1; i < FAILURE_COUNT; i++)); do
delay=$((delay * 2))
((delay >= 21600)) && { delay=21600; break; }
done
retry_at=$((FAILURE_AT + delay))
now=$(date +%s)
if ((now < retry_at)); then
when="retries at $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")"
else
when="retries on the next tick"
fi
printf ' ✗ %-7s %-8s %s consecutive failure(s), %s\n' "$channel" "$arch" "$FAILURE_COUNT" "$when"
printf ' last attempt %s on commit %s\n' \
"$(date -d "@$FAILURE_AT" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$FAILURE_AT")" \
"${FAILURE_FINGERPRINT:0:12}"
done
retry_at=$((FAILURE_AT + delay))
now=$(date +%s)
if ((now < retry_at)); then
when="retries at $(date -d "@$retry_at" '+%H:%M:%S' 2>/dev/null || echo "+$((retry_at - now))s")"
else
when="retries on the next tick"
fi
printf ' ✗ %-7s %s consecutive failure(s), %s\n' "$channel" "$FAILURE_COUNT" "$when"
printf ' last attempt %s on commit %s\n' \
"$(date -d "@$FAILURE_AT" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "$FAILURE_AT")" \
"${FAILURE_FINGERPRINT:0:12}"
done
if [[ "$paused" == true ]]; then
echo " Any new commit clears the backoff; or: rm $STATE_DIR/.build-failed-<channel>"
echo " Any new commit clears the backoff; or: rm $STATE_DIR/.build-failed-<channel>-<arch>"
echo ""
fi
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
# Retire packages from one channel of the remote repository.
#
# unpublish-packages --mirror <edge|rc|stable> --arch <arch> <pkgbase...>
#
# The counterpart of publish-artifact, with the same steps:
# 1. pull the channel's current database from the remote
# 2. find every entry built from the named pkgbases (so a package's split
# outputs and -debug go with it)
# 3. repo-remove them
# 4. upload the database
#
# Package files stay in the bucket. Published filenames are immutable and the
# R2 cache cannot recover from a rewrite; an unreferenced file costs nothing
# and pacman never sees it. Naming a package the channel does not hold is not
# an error, so a re-run is harmless.
#
# The remote is an rclone remote (REMOTE, default the production one);
# OMARCHY_PUBLISH_PREFIX can point a proof run at a scratch prefix.
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
REMOTE=${REMOTE:-pkgs.omarchy.org:omarchy-pkgs}
PREFIX=${OMARCHY_PUBLISH_PREFIX:-}
BASES=()
while [[ $# -gt 0 ]]; do
case $1 in
--mirror) MIRROR=$2; require_valid_mirror "$MIRROR"; shift 2 ;;
--arch) ARCH=$2; require_valid_arch "$ARCH"; shift 2 ;;
--remote) REMOTE=$2; shift 2 ;;
-h|--help) sed -n '2,19p' "$0"; exit 0 ;;
-*) print_error "Unknown option: $1"; exit 1 ;;
*) BASES+=("$1"); shift ;;
esac
done
(( ${#BASES[@]} )) || { print_error "No packages given"; exit 1; }
DEST="$REMOTE/${PREFIX:+$PREFIX/}$MIRROR/$ARCH"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
print_header "Unpublish from $DEST"
# --- 1. pull the current database -----------------------------------------
mkdir -p "$WORK/repo"
listing=$(rclone lsf "$DEST/" --s3-no-head 2>/dev/null || true)
if ! grep -q '^omarchy.db.tar.zst$' <<<"$listing"; then
print_info "No database at $DEST; nothing to remove"
exit 0
fi
rclone copy "$DEST/omarchy.db.tar.zst" "$WORK/repo" --s3-no-head
rclone copy "$DEST/omarchy.files.tar.zst" "$WORK/repo" --s3-no-head 2>/dev/null || true
before=$(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$')
print_info "Pulled current database ($before entries)"
# --- 2. entries built from the named pkgbases -----------------------------
names=$(bsdtar -xOf "$WORK/repo/omarchy.db.tar.zst" --include '*/desc' |
awk -v bases=" ${BASES[*]} " '
/^%NAME%$/ { getline name }
/^%BASE%$/ { getline base; if (index(bases, " " base " ")) print name }')
if [[ -z "$names" ]]; then
print_info "None of ${BASES[*]} is in $MIRROR/$ARCH; nothing to remove"
exit 0
fi
mapfile -t NAMES <<<"$names"
for n in "${NAMES[@]}"; do print_step "removing $n"; done
# --- 3. repo-remove ---------------------------------------------------------
( cd "$WORK/repo" && repo-remove --quiet omarchy.db.tar.zst "${NAMES[@]}" )
ln -sf omarchy.db.tar.zst "$WORK/repo/omarchy.db"
ln -sf omarchy.files.tar.zst "$WORK/repo/omarchy.files"
after=$(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$')
(( before - after == ${#NAMES[@]} )) || {
print_error "Expected to remove ${#NAMES[@]} entries, removed $((before - after))"; exit 1; }
print_info "Database now has $after entries"
# --- 4. upload the database -------------------------------------------------
rclone copy "$WORK/repo" "$DEST" --s3-no-head --checksum --copy-links --filter '+ omarchy.db*' --filter '+ omarchy.files*' --filter '- *'
print_success "Removed ${#NAMES[@]} package(s) from $DEST"
+16 -12
View File
@@ -11,12 +11,11 @@ source "$BUILD_ROOT/helpers/docker-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/lock-helpers.sh"
# Function to update repository database using Docker
# Function to update repository database using the selected container engine
update_database() {
print_info "Updating repository database in Docker container..."
print_info "Updating repository database in a container..."
# Check Docker is available
check_docker
check_engine
# Ensure output directory exists
if [[ ! -d "$REPO_DIR" ]]; then
@@ -25,20 +24,25 @@ update_database() {
exit 1
fi
# Make output directory writable for container
make_dir_writable "$REPO_DIR"
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$REPO_DIR"
fi
# repo-add is architecture- and mirror-independent, so always use the edge
# x86_64 image. This also lets bootstrap-rc build the rc database before the
# rc channel exists remotely (an rc image can only build after it does).
build_docker_image "$BUILD_DIR" "x86_64" "edge"
# repo-add is architecture- and mirror-independent. Use the host-native edge
# image, which also lets bootstrap-rc run before that channel exists remotely.
local tool_arch
tool_arch=$(docker_native_arch) || {
print_error "Unsupported host architecture: $(uname -m)"
exit 1
}
build_docker_image "$BUILD_DIR" "$tool_arch" "edge"
docker run --rm --platform linux/amd64 \
"$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$tool_arch")" \
-e ARCH="$ARCH" \
-e MIRROR="$MIRROR" \
-v "$REPO_ROOT:/output" \
-v "$BUILD_DIR:/build:ro" \
omarchy-pkg-builder:latest-x86_64-edge /build/update-repo.sh
"omarchy-pkg-builder:latest-$tool_arch-edge" /build/update-repo.sh
}
# Main execution
+16 -6
View File
@@ -45,9 +45,7 @@ RUN if [ "${TARGETARCH}" = "amd64" ]; then \
printf 'Server = https://mirror.omarchy.org/$repo/os/$arch\n' > /etc/pacman.d/mirrorlist; \
fi; \
else \
curl -L "https://raw.githubusercontent.com/archlinuxarm/PKGBUILDs/master/core/pacman-mirrorlist/mirrorlist" 2>/dev/null | \
sed -E 's/^\s*#\s*Server\s*=/Server =/g' > /etc/pacman.d/mirrorlist && \
sed -i 's/\$arch/aarch64/g' /etc/pacman.d/mirrorlist; \
printf 'Server = https://fl.us.mirror.archlinuxarm.org/aarch64/$repo\n' > /etc/pacman.d/mirrorlist; \
fi
# Bootstrap keyrings (required before pacstrap can verify packages)
@@ -108,11 +106,16 @@ RUN ln -sf /usr/lib/os-release /etc/os-release && \
# Setup Omarchy keyring manually before adding repo (avoids keyserver trust issues)
# Note: Repository is removed at the end since build scripts add it dynamically.
# The keyring comes from this image's own channel (the bare /$arch path is a
# stale legacy layout); %s keeps pacman's $arch literal while MIRROR expands.
# stale legacy layout). It is always taken from the x86_64 tree, whatever the
# image's own architecture: omarchy-keyring is an arch=any package, and the
# x86_64 tree is the one that exists before a new architecture has published
# anything. Bootstrapping from the target's own tree would make the first
# aarch64 build depend on an aarch64 repository that only that build can
# create.
ARG MIRROR=edge
RUN pacman-key --recv-keys 40DFB630FF42BCFFB047046CF0134EE680CAC571 --keyserver keys.openpgp.org && \
pacman-key --lsign-key 40DFB630FF42BCFFB047046CF0134EE680CAC571 && \
printf '\n[omarchy]\nSigLevel = Optional\nServer = https://pkgs.omarchy.org/%s/$arch\n' "${MIRROR}" >> /etc/pacman.conf && \
printf '\n[omarchy]\nSigLevel = Optional\nServer = https://pkgs.omarchy.org/%s/x86_64\n' "${MIRROR}" >> /etc/pacman.conf && \
pacman -Sy --noconfirm && \
pacman -S --noconfirm omarchy-keyring && \
pacman-key --populate omarchy && \
@@ -126,6 +129,7 @@ RUN pacman -Syu --noconfirm && \
wget \
curl \
jq \
rclone \
gnupg && \
pacman -Scc --noconfirm && \
rm -rf /var/cache/pacman/pkg/*
@@ -137,8 +141,14 @@ RUN useradd -m -G wheel -s /bin/bash builder && \
chmod 700 /home/builder/.gnupg && \
chown -R builder:builder /home/builder
# Arch Linux ARM's makepkg.conf still defaults PKGEXT to .pkg.tar.xz; every
# tool downstream of the build (sign.sh, push-build, sync-rebuilds, the
# notifier) expects .pkg.tar.zst, so an aarch64 package would build and then
# be skipped at signing. Pin the extension so both architectures match.
RUN sed -i 's/^#MAKEFLAGS=.*/MAKEFLAGS="-j$(nproc)"/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSZST=.*/COMPRESSZST=(zstd -c -z -q --threads=0 -)/' /etc/makepkg.conf
sed -i 's/^COMPRESSZST=.*/COMPRESSZST=(zstd -c -z -q --threads=0 -)/' /etc/makepkg.conf && \
sed -i "s|^PKGEXT=.*|PKGEXT='.pkg.tar.zst'|" /etc/makepkg.conf && \
sed -i 's|^#\?PACKAGER=.*|PACKAGER="Omarchy <pkgs@omarchy.org>"|' /etc/makepkg.conf
# Pacman wrapper that auto-resolves package conflicts (e.g. rustup vs rust).
# makepkg only passes --noconfirm to pacman, which defaults to 'N' on conflict
+250 -149
View File
@@ -1,21 +1,88 @@
#!/bin/bash
# Build script - builds packages based on package metadata
# Plan a run or build one planned package in an isolated container.
# Unscoped edge builds exclude skip_build packages. Stable also requires the fast release ring.
# Explicit --package selections may build packages with skip_build=true.
# Setup directories
ARCH=${ARCH:-x86_64}
# ARCH selects the repository target for this script, but make and Kbuild also
# interpret an exported ARCH themselves (Linux calls this target "arm64").
# Keep the shell variable local to the orchestrator so PKGBUILDs see CARCH only.
export -n ARCH
MIRROR=${MIRROR:-edge}
DRY_RUN=${DRY_RUN:-false}
# bin/build plans the whole run, then invokes this script once per package in
# a fresh container. PACKAGES retains the original request for validation.
BUILD_PACKAGE=${BUILD_PACKAGE:-}
BUILD_PLAN_DIR=${BUILD_PLAN_DIR:-}
PKGBUILDS_DIR=${PKGBUILDS_DIR:-/pkgbuilds}
BUILD_OUTPUT_DIR=${BUILD_OUTPUT_DIR:-/build-output/$MIRROR/$ARCH}
FINAL_OUTPUT_DIR=${FINAL_OUTPUT_DIR:-/pkgs.omarchy.org/$MIRROR/$ARCH}
HELPERS_DIR=${HELPERS_DIR:-/helpers}
SRC_DIR=${SRC_DIR:-/src}
# Set by bin/build from OMARCHY_DEFER_RUNTIME_DEPS after it has checked the
# request; re-checked here so the container never trusts a stray value.
DEFER_RUNTIME_DEPS=${DEFER_RUNTIME_DEPS:-false}
source "$HELPERS_DIR/package-metadata.sh"
# Where the channel's published database is read from for planning. On the
# repository host it is the published tree itself. Anywhere else (a CI runner,
# a fresh clone) that tree is absent, so the database is fetched from the
# public channel and the same URL serves as pacman's dependency repository.
# Set OMARCHY_PUBLISHED_REPO_URL= (empty) to disable the remote fallback.
PUBLISHED_REPO_URL=${OMARCHY_PUBLISHED_REPO_URL-https://pkgs.omarchy.org}
PUBLISHED_DB_DIR="$FINAL_OUTPUT_DIR"
PUBLISHED_REPO_SERVER=""
if [[ ! -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" && ! -f "$FINAL_OUTPUT_DIR/omarchy.db" && -n "$PUBLISHED_REPO_URL" ]]; then
remote_channel="$PUBLISHED_REPO_URL/$MIRROR/$ARCH"
remote_db_dir=$(mktemp -d /tmp/omarchy-published.XXXXXX) || exit 1
# Cache-bust: the channel sits behind a CDN that serves a stale database
# for a while after a sync.
if curl -fsSL "$remote_channel/omarchy.db.tar.zst?$(date +%s)" -o "$remote_db_dir/omarchy.db.tar.zst"; then
PUBLISHED_DB_DIR="$remote_db_dir"
PUBLISHED_REPO_SERVER="$remote_channel"
echo "==> No local published tree; planning against $remote_channel"
else
rm -rf "$remote_db_dir"
echo "==> No local published tree and $remote_channel is unavailable; treating the channel as empty"
fi
fi
if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then
echo "DEFER_RUNTIME_DEPS must be true or false" >&2
exit 1
fi
if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then
deferred_runtime=0
deferred_settings=0
deferred_count=0
for package in $PACKAGES; do
((deferred_count += 1))
case $package in
omarchy|omarchy-dev) deferred_runtime=1 ;;
omarchy-settings|omarchy-settings-dev) deferred_settings=1 ;;
*)
echo "Runtime dependency deferral only applies to the omarchy pair, not $package" >&2
exit 1
;;
esac
done
if (( deferred_runtime != 1 || deferred_settings != 1 || deferred_count != 2 )); then
echo "Runtime dependency deferral requires exactly the omarchy pair" >&2
exit 1
fi
fi
if [[ "$DRY_RUN" != true ]]; then
if [[ -z "$BUILD_PACKAGE" || -z "$BUILD_PLAN_DIR" ]]; then
echo "Use bin/build to plan and run isolated package builds" >&2
exit 1
fi
if ! grep -Fxq -- "$BUILD_PACKAGE" "$BUILD_PLAN_DIR/packages"; then
echo "Package is not in the build plan: $BUILD_PACKAGE" >&2
exit 1
fi
# Import GPG keys
/build/import-gpg-keys.sh || exit 1
@@ -27,49 +94,66 @@ if [[ "$DRY_RUN" != true ]]; then
# that breaks the new packages (imagemagick wanting GLIBC_2.44, etc).
# Done before the Omarchy repos are added so only core/extra participate.
echo "==> Updating build container packages..."
sudo pacman -Syu --noconfirm
sudo pacman -Syu --noconfirm || exit 1
# Configure Omarchy repositories for dependency resolution
echo "==> Configuring Omarchy repositories for dependency resolution..."
# Always add omarchy-build repo (for incremental builds)
# Packages in build-output are unsigned, so use SigLevel = Never
sudo tee -a /etc/pacman.conf > /dev/null <<EOF
[omarchy-build]
SigLevel = Never
Server = file://$BUILD_OUTPUT_DIR
EOF
# Always add omarchy-build repo first (for incremental builds). Repository
# order is pacman's priority order, so this must precede the official repos;
# otherwise pacman can select an older official package with the same name.
# Packages in build-output are unsigned, so use SigLevel = Never.
sudo sed -i "/^\[core\]$/i [omarchy-build]\nSigLevel = Never\nServer = file://$BUILD_OUTPUT_DIR\n" /etc/pacman.conf
echo " -> omarchy-build (priority 1): $BUILD_OUTPUT_DIR"
# Initialize empty build database if it doesn't exist
cd "$BUILD_OUTPUT_DIR"
cd "$BUILD_OUTPUT_DIR" || exit 1
if [[ ! -f "omarchy-build.db.tar.zst" ]]; then
# Create an empty database
repo-add omarchy-build.db.tar.zst >/dev/null 2>&1
ln -sf omarchy-build.db.tar.zst omarchy-build.db
else
# Database exists, check if we need to rebuild it from packages
if ls *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | grep -q .; then
echo "==> Rebuilding build database from existing packages..."
ls *.pkg.tar.* | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | xargs -r repo-add omarchy-build.db.tar.zst >/dev/null 2>&1
ln -sf omarchy-build.db.tar.zst omarchy-build.db
fi
repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 || exit 1
ln -sf omarchy-build.db.tar.zst omarchy-build.db || exit 1
fi
# Fold any packages already in the workspace into the database, whether
# they came with an existing database or were dropped in by an earlier
# workflow job (OMARCHY_KEEP_BUILD_WORKSPACE). Without this a seeded
# workspace with no database would leave those packages invisible to
# dependency resolution.
staged_packages=()
for staged in *.pkg.tar.*; do
[[ -f "$staged" && "$staged" != *.sig ]] || continue
staged_packages+=("$staged")
done
if (( ${#staged_packages[@]} )); then
# Seed once per run. After that, only successful builds update the DB;
# rescanning in every container could reintroduce a failed build's partial
# outputs or overwrite the new version with an older kept artifact.
if [[ ! -e "$BUILD_PLAN_DIR/repository-initialized" ]]; then
echo "==> Rebuilding build database from existing packages..."
repo-add omarchy-build.db.tar.zst "${staged_packages[@]}" >/dev/null 2>&1 || exit 1
ln -sf omarchy-build.db.tar.zst omarchy-build.db || exit 1
fi
# A resumed/repeated build can produce different bytes under the same
# filename. Never let the shared download cache substitute older bytes
# for the staged artifacts described by this run's database.
for staged in "${staged_packages[@]}"; do
sudo rm -f "/var/cache/pacman/pkg/$staged" "/var/cache/pacman/pkg/$staged.sig" || exit 1
done
fi
touch "$BUILD_PLAN_DIR/repository-initialized" || exit 1
# Add omarchy repo if it has a database (stable packages)
# Add omarchy repo if it has a database (stable packages). The local tree
# is trusted as-is; the public channel is verified against the omarchy
# keyring the image already carries.
if [[ -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" ]] || [[ -f "$FINAL_OUTPUT_DIR/omarchy.db" ]]; then
sudo tee -a /etc/pacman.conf > /dev/null <<EOF
[omarchy]
SigLevel = Optional TrustAll
Server = file://$FINAL_OUTPUT_DIR
EOF
sudo sed -i "/^\[core\]$/i [omarchy]\nSigLevel = Optional TrustAll\nServer = file://$FINAL_OUTPUT_DIR\n" /etc/pacman.conf
echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR"
elif [[ -n "$PUBLISHED_REPO_SERVER" ]]; then
sudo sed -i "/^\[core\]$/i [omarchy]\nSigLevel = Required DatabaseOptional\nServer = $PUBLISHED_REPO_SERVER\n" /etc/pacman.conf
echo " -> omarchy (priority 2): $PUBLISHED_REPO_SERVER"
fi
# Sync pacman database
sudo pacman -Sy
sudo pacman -Sy || exit 1
fi
echo "==> Package Builder"
@@ -82,8 +166,6 @@ if [[ "$DRY_RUN" == true ]]; then
echo "==> Dry run: yes (plan only; makepkg will not run)"
fi
FAILED_PACKAGES=""
SUCCESSFUL_PACKAGES=""
SKIPPED_PACKAGES=""
# Find package directory
@@ -105,10 +187,10 @@ LOCAL_VERSION_CACHE_LOADED=false
LOCAL_VERSION_CACHE_DB=""
load_local_versions() {
local db="$FINAL_OUTPUT_DIR/omarchy.db.tar.zst"
local db="$PUBLISHED_DB_DIR/omarchy.db.tar.zst"
if [[ ! -f "$db" ]]; then
db="$FINAL_OUTPUT_DIR/omarchy.db"
db="$PUBLISHED_DB_DIR/omarchy.db"
fi
[[ -f "$db" ]] || return 0
@@ -155,26 +237,9 @@ get_local_version() {
# Returns 0 (success) if should build, 1 if should skip
should_build_for_arch() {
local pkg="$1"
local current_arch="$ARCH"
local pkgdir=$(find_package_dir "$pkg")
local pkgbuild="$pkgdir/PKGBUILD"
[[ ! -f "$pkgbuild" ]] && return 1
# Check PKGBUILD arch=() array
local pkgbuild_archs=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${arch[@]}"')
# If arch=('any'), build for all architectures
if [[ "$pkgbuild_archs" == "any" ]]; then
return 0
fi
# Check if current arch is in PKGBUILD arch=()
if echo "$pkgbuild_archs" | grep -qw "$current_arch"; then
return 0 # Build
else
return 1 # Skip
fi
local pkgdir
pkgdir=$(find_package_dir "$pkg")
[[ -n "$pkgdir" ]] && package_supports_arch "$pkgdir" "$ARCH"
}
# For VCS packages, makepkg recalculates pkgver() before the build. If the
@@ -224,22 +289,62 @@ refresh_vcs_pkgver_preserving_local_pkgrel() {
fi
}
# With runtime dependency checks deferred, makepkg runs --nodeps, so the
# build-time dependencies it would normally install with -s have to be
# installed explicitly: makedepends and checkdepends, including the
# architecture-suffixed variants for the current CARCH.
install_deferred_build_dependencies() {
local pkg="$1"
local -a build_deps=()
mapfile -t build_deps < <(
CARCH="$ARCH" bash -c '
source PKGBUILD
arch_makedepends="makedepends_${CARCH}[@]"
arch_checkdepends="checkdepends_${CARCH}[@]"
printf "%s\n" \
"${makedepends[@]}" "${!arch_makedepends}" \
"${checkdepends[@]}" "${!arch_checkdepends}"
' | awk 'NF && !seen[$0]++'
)
if (( ${#build_deps[@]} )); then
echo " Installing build-only dependencies for $pkg..."
sudo pacman -S --needed --noconfirm -- "${build_deps[@]}"
fi
}
# Build a package
build_package() {
local pkg="$1"
local pkgdir=$(find_package_dir "$pkg")
local pkgdir
pkgdir=$(find_package_dir "$pkg") || return 1
echo ""
echo " -> Processing: $pkg"
# Install this consumer's freshly built prerequisites in its own container.
# Qualifying the repository also upgrades an older dependency baked into
# the base image, even if that version would satisfy makepkg's check.
if [[ "$DEFER_RUNTIME_DEPS" != true ]]; then
local consumer dependency
local -a built_deps=()
while read -r consumer dependency; do
[[ "$consumer" == "$pkg" ]] && built_deps+=("omarchy-build/$dependency")
done < "$BUILD_PLAN_DIR/dependencies"
if (( ${#built_deps[@]} )); then
echo " Installing freshly built dependencies for $pkg..."
sudo /usr/local/bin/pacman-for-makepkg -S --needed --noconfirm -- "${built_deps[@]}" || return 1
fi
fi
# Copy to build directory
cd /src
rm -rf "$pkg"
cp -r "$pkgdir" "$pkg"
cd /src || return 1
rm -rf "$pkg" || return 1
cp -r "$pkgdir" "$pkg" || return 1
cd "/src/$pkg" || return 1
refresh_vcs_pkgver_preserving_local_pkgrel "$pkg" || {
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
return 1
}
@@ -248,7 +353,6 @@ build_package() {
if [[ -z "$pkgbuild_version" ]]; then
echo " Failed to read PKGBUILD version"
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
return 1
fi
@@ -280,37 +384,72 @@ build_package() {
# Build package without signing (signing is done separately)
# PACMAN override uses a wrapper that adds --ask 4 to auto-resolve conflicts
# (e.g. rustup replacing rust) since --noconfirm defaults to 'N' on those prompts
MAKEPKG_FLAGS="-scf --noconfirm"
local -a makepkg_flags=(-scf --noconfirm)
if [[ $DEFER_RUNTIME_DEPS == "true" ]]; then
# The pair's runtime dependencies (each other, and packages other jobs
# of the same pipeline build) are not resolvable here; the assembled set
# is installed in one verified transaction downstream. Only the
# build-time dependencies are installed, then makepkg skips the check.
install_deferred_build_dependencies "$pkg" || {
return 1
}
makepkg_flags=(-cf --noconfirm --nodeps)
fi
if PACMAN=/usr/local/bin/pacman-for-makepkg makepkg $MAKEPKG_FLAGS; then
if PACMAN=/usr/local/bin/pacman-for-makepkg makepkg "${makepkg_flags[@]}"; then
# Ensure output directory exists
mkdir -p "$BUILD_OUTPUT_DIR"
for pkg_file in *.pkg.tar.*; do
[[ -f "$pkg_file" ]] && cp "$pkg_file" "$BUILD_OUTPUT_DIR/"
done
cd "$BUILD_OUTPUT_DIR"
# Copy only the artifacts makepkg declares as outputs. A PKGBUILD may use
# another pacman package as a source (schist-bin does); a *.pkg.tar.* glob
# would mistake that source archive for one of our freshly built packages.
local -a package_files=()
mapfile -t package_files < <(makepkg --packagelist)
# Find ALL package files (handles split packages)
local new_pkgs=($(ls -t ${pkg}-*.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db'))
if [[ ${#new_pkgs[@]} -gt 0 ]]; then
repo-add omarchy-build.db.tar.zst "${new_pkgs[@]}" >/dev/null 2>&1
ln -sf omarchy-build.db.tar.zst omarchy-build.db
sudo pacman -Sy >/dev/null 2>&1
if [[ ${#package_files[@]} -eq 0 ]]; then
echo " Makepkg produced no package files for $pkg"
return 1
fi
cd /src/$pkg
local -a new_pkgs=()
local pkg_path pkg_file
for pkg_path in "${package_files[@]}"; do
pkg_file=${pkg_path##*/}
if [[ ! -f "$pkg_file" ]]; then
# makepkg predicts an automatic -debug output whenever debug is
# enabled, but data-only packages may contain no symbols and therefore
# legitimately produce no debug archive.
if [[ "$pkg_file" == *-debug-*.pkg.tar.* ]]; then
continue
fi
echo " Expected package file was not produced: $pkg_file"
return 1
fi
cp "$pkg_file" "$BUILD_OUTPUT_DIR/" || return 1
new_pkgs+=("$pkg_file")
done
cd "$BUILD_OUTPUT_DIR" || return 1
# Add every output from this build, including split packages.
if [[ ${#new_pkgs[@]} -gt 0 ]]; then
repo-add omarchy-build.db.tar.zst "${new_pkgs[@]}" >/dev/null 2>&1 || return 1
ln -sf omarchy-build.db.tar.zst omarchy-build.db || return 1
fi
# A release may publish successful builds even when a peer fails. Record
# outputs only after this package's entire split build has completed.
mkdir -p "$BUILD_PLAN_DIR/artifacts" || return 1
printf '%s\n' "${new_pkgs[@]}" > "$BUILD_PLAN_DIR/artifacts/$pkg" || return 1
echo " Successfully built $pkg"
SUCCESSFUL_PACKAGES="$SUCCESSFUL_PACKAGES $pkg"
return 0
else
echo " Makepkg failed for $pkg"
echo " DEBUG: Files in build directory:"
ls -lah *.pkg.tar.* 2>&1 | head -20 || echo " No package files found"
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
return 1
fi
}
@@ -325,11 +464,17 @@ get_package_deps() {
return
fi
# Extract depends and makedepends, filter for packages in our pkgbuilds/
# Include test dependencies and target-specific arrays: each container must
# receive its prerequisites through the repository, not a previous build.
(
CARCH="$ARCH"
source "$pkgbuild" 2>/dev/null
echo "${depends[@]} ${makedepends[@]}"
) | tr ' ' '\n' | while read -r dep; do
for kind in depends makedepends checkdepends; do
generic="${kind}[@]"
specific="${kind}_${CARCH}[@]"
printf '%s\n' "${!generic}" "${!specific}"
done
) | awk 'NF && !seen[$0]++' | while read -r dep; do
# Strip version constraints (e.g., 'hyprshade>=1.0' -> 'hyprshade')
dep=$(echo "$dep" | sed 's/[<>=].*$//')
# Check if this dependency exists in our pkgbuilds
@@ -414,27 +559,37 @@ check_needs_build() {
if [[ "$local_version" == "$pkgbuild_version" ]]; then
return 1 # Already up to date
else
return 0 # Needs building
fi
# Match check-versions: a retained archive is already published even when
# the DB now indexes a newer release (for example, 4.0.4rc1 vs 4.0.3).
# Rebuilding it would produce different bytes under an immutable filename.
if package_version_is_published "$FINAL_OUTPUT_DIR" "$pkg" "$pkgbuild_version" "$ARCH"; then
echo " + $pkg $pkgbuild_version - archive already published; skipping rebuild"
return 1
fi
return 0 # Needs building
}
# Collect packages that should be built for the selected mirror
collect_packages() {
packages_for_unscoped_build "$MIRROR"
packages_for_unscoped_build "$MIRROR" "$ARCH"
}
# Main execution
if [[ "$DRY_RUN" != true ]]; then
cd "$SRC_DIR"
cd "$SRC_DIR" || exit 1
build_package "$BUILD_PACKAGE"
exit $?
fi
TOTAL_COUNT=0
echo "==> Checking which packages need building..."
# First pass: determine which packages need building
PACKAGES_TO_BUILD=()
ORDERED_PACKAGES=()
PLANNED_DEPENDENCIES=()
# If PACKAGES is specified, only check those packages
if [[ -n "$PACKAGES" ]]; then
@@ -473,13 +628,6 @@ if [[ -n "$PACKAGES" ]]; then
else
# Build all packages that need updates from the relevant directories
while IFS= read -r pkg; do
# Check if package should be built for this architecture
if ! should_build_for_arch "$pkg"; then
echo " - $pkg - not built for $ARCH"
SKIPPED_PACKAGES="$SKIPPED_PACKAGES $pkg"
continue
fi
if check_needs_build "$pkg"; then
PACKAGES_TO_BUILD+=("$pkg")
else
@@ -492,7 +640,7 @@ fi
if [[ ${#PACKAGES_TO_BUILD[@]} -eq 0 ]]; then
echo "==> All packages are up to date!"
else
echo "==> ${#PACKAGES_TO_BUILD[@]} package(s) need building: ${PACKAGES_TO_BUILD[@]}"
echo "==> ${#PACKAGES_TO_BUILD[@]} package(s) need building: ${PACKAGES_TO_BUILD[*]}"
echo "==> Determining build order based on dependencies..."
# Second pass: order only the packages that need building
@@ -515,6 +663,7 @@ else
((unmet_deps_count[$pkg]++))
# Track that dep blocks pkg from building
blocks_packages[$dep]="${blocks_packages[$dep]} $pkg"
PLANNED_DEPENDENCIES+=("$pkg $dep")
fi
done
done < <(get_package_deps "$pkg")
@@ -529,7 +678,6 @@ else
done
# Build packages as dependencies become available
ORDERED_PACKAGES=()
while [[ ${#ready_to_build[@]} -gt 0 ]]; do
# Take the first ready package
current="${ready_to_build[0]}"
@@ -551,63 +699,16 @@ else
exit 1
fi
echo "==> Build order: ${ORDERED_PACKAGES[@]}"
echo "==> Build order: ${ORDERED_PACKAGES[*]}"
fi
if [[ "$DRY_RUN" == true ]]; then
echo ""
echo "==> Dry run complete. Packages that would build: ${ORDERED_PACKAGES[@]}"
exit 0
fi
# Determine which packages need to be installed for other packages being built
declare -A INSTALL_PACKAGES
for pkg in "${ORDERED_PACKAGES[@]}"; do
while IFS= read -r dep; do
[[ -z "$dep" ]] && continue
# Only install if it's being built in this run
for build_pkg in "${ORDERED_PACKAGES[@]}"; do
[[ "$dep" == "$build_pkg" ]] && INSTALL_PACKAGES["$dep"]=1
done
done < <(get_package_deps "$pkg")
done
if [[ ${#INSTALL_PACKAGES[@]} -gt 0 ]]; then
echo "==> Packages needed as dependencies: ${!INSTALL_PACKAGES[@]}"
fi
# Build packages in dependency order
for pkg in "${ORDERED_PACKAGES[@]}"; do
((TOTAL_COUNT++))
build_package "$pkg"
done
# The host consumes plain data, never shell code or parsed human log output.
if [[ -n "$BUILD_PLAN_DIR" ]]; then
mkdir -p "$BUILD_PLAN_DIR" || exit 1
printf '%s\n' "${ORDERED_PACKAGES[@]}" | sed '/^$/d' > "$BUILD_PLAN_DIR/packages" || exit 1
printf '%s\n' "${PLANNED_DEPENDENCIES[@]}" | sed '/^$/d' > "$BUILD_PLAN_DIR/dependencies" || exit 1
printf '%s\n' $SKIPPED_PACKAGES | sed '/^$/d' > "$BUILD_PLAN_DIR/skipped" || exit 1
fi
echo ""
echo "========================================"
echo "==> Build Summary"
echo "========================================"
# Count results
SUCCESS_COUNT=$(echo $SUCCESSFUL_PACKAGES | wc -w)
SKIPPED_COUNT=$(echo $SKIPPED_PACKAGES | wc -w)
FAILED_COUNT=$(echo $FAILED_PACKAGES | wc -w)
echo " Total packages: $TOTAL_COUNT"
echo " Built: $SUCCESS_COUNT"
echo " Skipped: $SKIPPED_COUNT (already up-to-date)"
echo " Failed: $FAILED_COUNT"
# List failures if any
if [[ -n "$FAILED_PACKAGES" ]]; then
echo ""
echo "Failed packages:"
for pkg in $FAILED_PACKAGES; do
echo " - $pkg"
done
echo ""
echo "==> Some packages failed to build"
exit 1
fi
echo ""
echo "==> All packages processed successfully!"
echo "==> Plan complete. Packages that would build: ${ORDERED_PACKAGES[*]}"
+96
View File
@@ -0,0 +1,96 @@
# CI spike: build PRs on ephemeral DigitalOcean droplets
Status: spike. Nothing here publishes. The repository host keeps building and
signing on merge exactly as before.
## Pieces
- `.github/workflows/build-pr.yml` — on a PR touching `pkgbuilds/**`, one job
per changed package on runners labelled `omarchy-builder`. aarch64 jobs
run on GitHub's native `ubuntu-24.04-arm` runners instead. Uploads the unsigned
`.pkg.tar.zst` as a workflow artifact (7 days).
- `runner-cloud-init.yaml` — Ubuntu 24.04 user-data: docker + buildx, the
GitHub runner registered `--ephemeral`, runs one job, powers off.
- `controller.sh` — systemd timer every minute on a small always-on droplet.
Polls for queued jobs with our label, creates one g5-32vcpu-64gb-50gb droplet per job up
to `MAX_DROPLETS`, deletes droplets that are powered off or older than
`MAX_AGE_MINUTES`, or still provisioning after `MAX_BOOT_MINUTES`. Builders go in any region DigitalOcean lists the size in
stock in (`REGIONS` only sets which to try first); a refused create, logged
with DigitalOcean's message, falls back to the next region, then the next
of `SIZES`. No inbound endpoint. Plain curl against both APIs, no
doctl and no gh: a token in the environment cannot pick the wrong account
the way a saved doctl context can. Needs curl and jq.
`tests/controller.sh` exercises every decision against canned responses.
- `controller-box/` — the always-on droplet: unit, timer, env template,
cloud-init, and `create.sh` to stand it up with one API call.
## Standing up the controller box
DIGITALOCEAN_TOKEN=<omarchy account> GITHUB_TOKEN=<fine-grained PAT> \
REPO=omacom/omarchy-pkgs ci/controller-box/create.sh <branch>
The GitHub PAT is fine-grained, scoped to the one repo: Actions read,
Administration read+write (registration tokens). The DO token is baked into
the box's env file, so it is the account that pays for builder droplets.
Watch it with `journalctl -u omarchy-controller -f` on the box.
Each tick pulls the box's checkout first, so a merged `controller.sh` is live
within a minute. The unit and timer are copies made at creation; after
changing them, on the box:
cp /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.{service,timer} /etc/systemd/system/
systemctl daemon-reload
## What the spike proved (2026-09-17, fork ryanrhughes/omarchy-pkgs)
- `bin/build` works from a bare clone: with no local published tree it
plans against and resolves from `https://pkgs.omarchy.org/<mirror>/<arch>`.
- Droplet create → runner registered: ~70 s. omarchy-fish PR job: 2 min
including the builder image build. Droplet powers off after the job.
- linux-omarchy on a c-32 droplet: 30 min wall clock for the build job
(23:39 → 00:09), 254 MB artifact. Cold start ~90 s before the job began.
- A PR whose PKGBUILD fails to build turns the required check red and GitHub
refuses the merge (`mergeStateStatus=BLOCKED`, `gh pr merge` refuses
without `--admin`).
- Controller: one queued job + one busy droplet ⇒ creates exactly one more;
reaps powered-off droplets on the next tick.
## Not done (required before this touches the real repo)
- Tooling from base: check out master's `bin/ helpers/ build/` and overlay
only the PR's `pkgbuilds/<name>`; today a PR can edit the build script
and it runs on the droplet. The vouch gate limits who can do that, not
what they can do.
- DigitalOcean cloud firewall on the `omarchy-builder` tag: no inbound, no
egress to private ranges or the metadata address.
- A fine-grained GitHub token for the real repository (the one on the
controller box is scoped to the fork), and the publish environment's
secrets set there.
- Disable the host's auto-release timers for any channel CI publishes to,
so two writers never touch one database.
## Done since the spike README was first written
- Controller as a systemd timer on its own droplet, plain curl, self-test.
- Build once against edge; one artifact per package per architecture,
published into every channel it belongs to (fast ring: all three at
once). arch=any builds once for every architecture database.
- Publish is incremental and immutable: pull the channel db, refuse
different bytes under an existing name, accept identical bytes, upload
packages then signatures then the db.
- aarch64 under QEMU with credential-preserving binfmt. PR builds now run
aarch64 natively on `ubuntu-24.04-arm` (QEMU was up to ~15x slower). When a
merged tree has no artifact, publish.yml rebuilds it in its own job, aarch64
there and x86_64 on a droplet, outside the publish lock.
- Publish itself builds nothing: it signs and uploads on `ubuntu-latest` in the
tested builder image pulled from GHCR, and only that job holds the `publish`
concurrency group, so a merge waits for seconds of signing, not for builds.
- Vouch gate: collaborators, `.github/VOUCHED.td`, or the `build-approved`
label; denounced authors cannot be overridden by the label.
- Tests run on PRs only; `result`, `self-tests`, `build-isolation` are the
required checks with strict up-to-date branches.
## Cleanup
doctl compute droplet list --tag-name omarchy-builder
doctl compute droplet delete -f <id>
+45
View File
@@ -0,0 +1,45 @@
#cloud-config
# The always-on controller droplet (smallest size is fine). Clones the repo
# for ci/controller.sh, installs the unit and timer, and starts polling.
#
# Substitute before use:
# __REPO_URL__ https://github.com/omacom/omarchy-pkgs.git
# __BRANCH__ branch carrying ci/ (master once merged)
# __ENV_B64__ base64 of a filled-in controller.env.example
# __SSH_KEYS_JSON__ JSON array of public keys authorized for root
package_update: true
packages: [curl, jq, git]
# Root stays reachable by key so the journal can be read. Two things stand
# in the way on DO images: disable_root rewrites root's keys into a stub, and
# with no account ssh key attached DO expires root's password, which makes
# sshd refuse every non-interactive session with "password change required".
disable_root: false
chpasswd:
expire: false
ssh_authorized_keys: __SSH_KEYS_JSON__
users:
- name: controller
shell: /bin/bash
write_files:
# defer: write after the users module has created the controller group,
# otherwise chown to root:controller fails and the unit cannot read this.
- path: /etc/omarchy-controller.env
permissions: "0640"
owner: root:controller
encoding: b64
defer: true
content: __ENV_B64__
runcmd:
- chage -d "$(date +%F)" -M -1 root
- chown root:controller /etc/omarchy-controller.env && chmod 0640 /etc/omarchy-controller.env
- git clone --depth 1 --branch __BRANCH__ __REPO_URL__ /opt/omarchy-pkgs
- mkdir -p /run/omarchy-controller && chown controller:controller /run/omarchy-controller
- echo "d /run/omarchy-controller 0755 controller controller -" > /etc/tmpfiles.d/omarchy-controller.conf
# runcmd is executed by /bin/sh: no brace expansion.
- cp /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.service /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.timer /etc/systemd/system/
- systemctl daemon-reload
- systemctl enable --now omarchy-controller.timer
+19
View File
@@ -0,0 +1,19 @@
# /etc/omarchy-controller.env — mode 0600, owned by root, read by systemd.
DIGITALOCEAN_TOKEN=dop_v1_...
# Fine-grained PAT scoped to the repo: Actions: read, Administration: read+write
GITHUB_TOKEN=github_pat_...
REPO=omacom/omarchy-pkgs
LABEL=omarchy-builder
TAG=omarchy-builder
# Builder sizes, tried in order in any region that has them in stock.
SIZES="g5-32vcpu-64gb-50gb g5-32vcpu-128gb-50gb"
# Optional: regions to try first, e.g. "ric1". Empty means any.
REGIONS=
MAX_DROPLETS=6
MAX_AGE_MINUTES=200
# Delete a droplet DigitalOcean still reports as provisioning after this long.
MAX_BOOT_MINUTES=10
LOCK=/run/omarchy-controller/lock
# Operator public keys for root on every builder droplet (JSON array).
# create.sh fills this from the operators' GitHub keys.
SSH_KEYS_JSON=[]
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# Create the controller droplet with plain curl. Run from a laptop, once.
#
# DIGITALOCEAN_TOKEN=... GITHUB_TOKEN=... ci/controller-box/create.sh [branch]
#
# The DO token given here is baked into the box's env file, so it must be the
# token for the account that should pay for builder droplets.
set -euo pipefail
here=$(dirname "$0")
: "${DIGITALOCEAN_TOKEN:?}" "${GITHUB_TOKEN:?}"
REPO=${REPO:-omacom/omarchy-pkgs}
BRANCH=${1:-master}
REGION=${REGION:-ric1}
NAME=${NAME:-omarchy-controller}
# Optional DO ssh key ids as a JSON array, e.g. SSH_KEYS='[123]', for reading
# the journal while bringing the box up. Not needed once it works.
SSH_KEYS=${SSH_KEYS:-[]}
# Public keys authorized for root: the operators' GitHub keys, fetched at
# creation so the box never depends on an ssh_key API scope. Override with
# ADMIN_GITHUB_USERS.
ADMIN_GITHUB_USERS=${ADMIN_GITHUB_USERS:-ryanrhughes dhh}
ssh_keys_json=$(for u in $ADMIN_GITHUB_USERS; do curl -fsS "https://github.com/$u.keys"; done | jq -R . | jq -sc .)
[[ $(jq length <<<"$ssh_keys_json") -gt 0 ]] || { echo "no ssh keys fetched for $ADMIN_GITHUB_USERS" >&2; exit 1; }
env_file=$(sed -e "s|^DIGITALOCEAN_TOKEN=.*|DIGITALOCEAN_TOKEN=$DIGITALOCEAN_TOKEN|" \
-e "s|^GITHUB_TOKEN=.*|GITHUB_TOKEN=$GITHUB_TOKEN|" \
-e "s|^REPO=.*|REPO=$REPO|" \
-e "s|^SSH_KEYS_JSON=.*|SSH_KEYS_JSON=$ssh_keys_json|" "$here/controller.env.example")
userdata=$(sed -e "s|__REPO_URL__|https://github.com/$REPO.git|" -e "s|__BRANCH__|$BRANCH|" \
-e "s|__ENV_B64__|$(printf '%s\n' "$env_file" | base64 -w0)|" \
-e "s|__SSH_KEYS_JSON__|$ssh_keys_json|" "$here/cloud-init.yaml")
body=$(jq -n --arg name "$NAME" --arg region "$REGION" --arg ud "$userdata" --argjson keys "$SSH_KEYS" \
'{name:$name, region:$region, size:"s-1vcpu-1gb", image:"ubuntu-24-04-x64", tags:["omarchy-controller"], user_data:$ud, ssh_keys:$keys}')
# Refuse to create a second one.
existing=$(curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
"https://api.digitalocean.com/v2/droplets?tag_name=omarchy-controller" | jq '.droplets | length')
if (( existing > 0 )); then echo "a controller droplet already exists" >&2; exit 1; fi
curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -H "Content-Type: application/json" \
-X POST -d "$body" https://api.digitalocean.com/v2/droplets | jq -r '"created \(.droplet.name) id=\(.droplet.id)"'
@@ -0,0 +1,15 @@
[Unit]
Description=Provision ephemeral omarchy-builder runner droplets for queued jobs
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=controller
EnvironmentFile=/etc/omarchy-controller.env
# Run the branch's current controller, not the one cloned when the box was
# built. As root (the checkout's owner); a failed pull keeps the last one.
ExecStartPre=-+/usr/bin/git -C /opt/omarchy-pkgs pull --ff-only --quiet
ExecStart=/opt/omarchy-pkgs/ci/controller.sh
# The reaper's safety net is time, not state; a hung tick must not hold the lock.
TimeoutStartSec=240
@@ -0,0 +1,10 @@
[Unit]
Description=Run the omarchy-builder controller every minute
[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
AccuracySec=5s
[Install]
WantedBy=timers.target
+195
View File
@@ -0,0 +1,195 @@
#!/bin/bash
# Droplet-per-job controller for the omarchy-builder runner pool.
#
# Run from a systemd timer every minute on a small always-on droplet. No
# inbound endpoint: it polls GitHub for queued jobs wanting our label, creates
# one ephemeral droplet per job (up to MAX_DROPLETS), and deletes droplets
# that have powered off, exceeded MAX_AGE_MINUTES, or are still provisioning
# after MAX_BOOT_MINUTES. The reaper does not
# trust its own bookkeeping: it lists by tag and acts on what DigitalOcean
# reports.
#
# Talks to both APIs with curl. No doctl: its saved contexts silently choose
# an account; a token in the environment cannot. Needs curl and jq.
#
# Environment:
# DIGITALOCEAN_TOKEN DO API token for the account that pays for droplets
# GITHUB_TOKEN fine-grained PAT: Actions read, Administration write
# REPO owner/name
set -euo pipefail
REPO=${REPO:?owner/name}
: "${DIGITALOCEAN_TOKEN:?}" "${GITHUB_TOKEN:?}"
LABEL=${LABEL:-omarchy-builder}
TAG=${TAG:-omarchy-builder}
# Sizes to try, in order, in any region DigitalOcean lists them in stock. A
# size can sell out in a region for hours; the create is then refused with
# 422 and the next region, then the next size, is tried. REGIONS only orders
# the regions tried first. SIZE and REGION, if set, are one-item lists.
SIZES=${SIZES:-${SIZE:-g5-32vcpu-64gb-50gb g5-32vcpu-128gb-50gb}}
REGIONS=${REGIONS:-${REGION:-}}
IMAGE=${IMAGE:-ubuntu-24-04-x64}
MAX_DROPLETS=${MAX_DROPLETS:-4}
MAX_AGE_MINUTES=${MAX_AGE_MINUTES:-200}
# A droplet DigitalOcean still reports as "new" this long after creation is
# stuck provisioning. Left alone it counts as a runner booting, and holds a
# queued job until MAX_AGE_MINUTES.
MAX_BOOT_MINUTES=${MAX_BOOT_MINUTES:-10}
RUNNER_VERSION=${RUNNER_VERSION:-2.337.0}
CLOUD_INIT=${CLOUD_INIT:-$(dirname "$0")/runner-cloud-init.yaml}
# Operator public keys authorized on every builder (JSON array of strings).
# The box's env file carries them; empty means no root login.
SSH_KEYS_JSON=${SSH_KEYS_JSON:-[]}
LOCK=${LOCK:-/tmp/omarchy-controller.lock}
log() { echo "$(date '+%F %T') $*"; }
# The only two places the outside world is touched. The self-test overrides
# both, so every decision below is exercised against canned responses.
do_api() { # do_api <path> [curl args...]
local path=$1; shift
# --fail-with-body: a refused create still prints why.
curl -sS --fail-with-body -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-H "Content-Type: application/json" "https://api.digitalocean.com/v2/$path" "$@"
}
gh_api() { # gh_api <path> [curl args...]
local path=$1; shift
curl -fsS -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" "https://api.github.com/$path" "$@"
}
# --- reap ------------------------------------------------------------------
reap() {
local now id status created age
now=$(date +%s)
while read -r id status created; do
[[ -n "$id" ]] || continue
age=$(( (now - $(date -d "$created" +%s)) / 60 ))
if [[ $status == off ]] || (( age > MAX_AGE_MINUTES )) ||
{ [[ $status == new ]] && (( age > MAX_BOOT_MINUTES )); }; then
log "deleting droplet $id (status=$status age=${age}m)"
do_api "droplets/$id" -X DELETE
fi
done < <(do_api "droplets?tag_name=$TAG&per_page=200" |
jq -r '.droplets[] | "\(.id) \(.status) \(.created_at)"')
}
# --- demand ----------------------------------------------------------------
# Emit every item, including later pages of large build matrices. Keep API
# failures fatal so a failed query cannot look like an empty queue.
gh_items() {
local path=$1 key=$2 page=1 response count separator="?"
[[ $path != *"?"* ]] || separator="&"
while :; do
response=$(gh_api "${path}${separator}per_page=100&page=$page") || return 1
count=$(jq -er --arg key "$key" '.[$key] | arrays | length' <<< "$response") || return 1
jq -c --arg key "$key" '.[$key][]' <<< "$response" || return 1
(( count == 100 )) || break
((page += 1))
done
}
queued_jobs() {
local status runs run
# A workflow can be in progress while most of its matrix is still queued.
runs=$(
for status in queued in_progress; do
gh_items "repos/$REPO/actions/runs?status=$status" workflow_runs || exit 1
done
) || return 1
jq -r '.id' <<< "$runs" | sort -u |
while read -r run; do
gh_items "repos/$REPO/actions/runs/$run/jobs" jobs |
jq -r --arg l "$LABEL" 'select(.status=="queued") | select(.labels | index($l)) | .id' || return 1
done | sort -u | wc -l
}
live_droplets() {
# Not the ones reap() just deleted: DigitalOcean can list them for a while.
do_api "droplets?tag_name=$TAG&per_page=200" | jq --argjson boot "$MAX_BOOT_MINUTES" '
[.droplets[] | select(.status != "off")
| select(.status != "new" or (now - (.created_at | fromdateiso8601)) / 60 <= $boot)] | length'
}
busy_runners() {
gh_api "repos/$REPO/actions/runners?per_page=100" \
| jq --arg l "$LABEL" '[.runners[] | select(.busy) | select(any(.labels[]; .name == $l))] | length'
}
# --- capacity --------------------------------------------------------------
# "size region" lines to try, best first: SIZES order, then REGIONS order,
# then every other region where DigitalOcean lists the size in stock.
candidates() {
local page=1 response count catalog=""
while :; do
response=$(do_api "sizes?per_page=200&page=$page") || return 1
count=$(jq -er '.sizes | arrays | length' <<< "$response") || return 1
catalog+=$(jq -c '.sizes[]' <<< "$response")$'\n'
(( count == 200 )) || break
((page += 1))
done
jq -rs --arg sizes "$SIZES" --arg regions "$REGIONS" '
($regions | split(" ") | map(select(length > 0))) as $pref
| INDEX(.slug) as $by
| $sizes | split(" ") | map(select(length > 0)) | .[]
| . as $size | $by[$size] // {} | select(.available == true)
| .regions as $in
| (($pref | map(select(. as $r | $in | index($r)))) + ($in - $pref))[]
| "\($size) \(.)"' <<< "$catalog"
}
# --- create ----------------------------------------------------------------
# Built once per tick by the first create; a refused pair is dropped from it.
CANDIDATES=""
create_droplet() {
local token userdata name size region body response
[[ -n $CANDIDATES ]] || CANDIDATES=$(candidates) || return 1
token=$(gh_api "repos/$REPO/actions/runners/registration-token" -X POST | jq -r .token)
userdata=$(sed -e "s|__REPO__|$REPO|g" -e "s|__RUNNER_TOKEN__|$token|g" \
-e "s|__RUNNER_LABELS__|$LABEL|g" -e "s|__RUNNER_VERSION__|$RUNNER_VERSION|g" \
-e "s|__SSH_KEYS_JSON__|$SSH_KEYS_JSON|" "$CLOUD_INIT")
name="$TAG-$(date +%s)-$RANDOM"
while read -r size region; do
[[ -n $size ]] || continue
body=$(jq -n --arg name "$name" --arg region "$region" --arg size "$size" --arg image "$IMAGE" \
--arg tag "$TAG" --arg ud "$userdata" \
'{name:$name, region:$region, size:$size, image:$image, tags:[$tag], user_data:$ud, monitoring:false}')
log "creating $name ($size in $region)"
if response=$(do_api droplets -X POST -d "$body"); then
jq -r '"created droplet \(.droplet.id)"' <<< "$response"
return 0
fi
log "$size in $region refused: $(jq -r .message <<< "$response" 2>/dev/null || echo "$response")"
CANDIDATES=$(grep -Fvx "$size $region" <<< "$CANDIDATES" || true)
done <<< "$CANDIDATES"
# Every size refused everywhere: the rest of this tick's creates would be too.
log "no size in '$SIZES' can be created in any region"
return 1
}
controller_tick() {
CANDIDATES=""
reap
local queued live busy available need room
queued=$(queued_jobs)
live=$(live_droplets)
busy=$(busy_runners)
# A live droplet whose runner is busy is spoken for. Only droplets still
# booting or listening can absorb a queued job.
available=$(( live - busy )); (( available < 0 )) && available=0
need=$(( queued - available ))
(( need > 0 )) || return 0
room=$(( MAX_DROPLETS - live ))
(( need > room )) && need=$room
if (( need <= 0 )); then
log "at cap ($live/$MAX_DROPLETS, $busy busy) with $queued queued"
return 0
fi
local i
for (( i = 0; i < need; i++ )); do create_droplet; done
}
if [[ "${CONTROLLER_LIBRARY_ONLY:-}" != 1 ]]; then
exec 9>"$LOCK"; flock -n 9 || exit 0
controller_tick
fi
+83
View File
@@ -0,0 +1,83 @@
#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
# Power off after the one job, and also when the download or the
# registration fails: the controller deletes powered-off droplets, but
# counts a running one as a runner still booting until MAX_AGE_MINUTES.
trap 'sudo poweroff' EXIT
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
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 tonistiigi/binfmt, exactly as
# helpers/docker-helpers.sh setup_qemu does. Ubuntu's qemu-user-static
# registers without C, so sudo inside the emulated container fails with
# "effective uid is not 0"; multiarch/qemu-user-static is abandoned at QEMU
# 7.2, under which qmake's compiler probe returns nothing on current gcc
# ("failed to parse default include paths", PR #517). Pin the emulator
# version: the tag is the only thing that decides what every aarch64 build
# runs under. Best-effort: an x86-only job never needs it.
- docker run --rm --privileged docker.io/tonistiigi/binfmt:qemu-v10.2.3-68 --uninstall qemu-aarch64 --install arm64 || true
- chown -R runner:runner /home/runner
- sudo -u runner /home/runner/start.sh > /home/runner/runner.log 2>&1
+222
View File
@@ -0,0 +1,222 @@
# Direct upstream watches
Omarchy owns the recipes in `pkgbuilds/`. `bin/sync-upstream` discovers new
releases directly from project/vendor feeds and updates versions, declared
release variables, and the source checksums already used by the recipe. It never
imports upstream PKGBUILDs or runs downloaded build scripts. Architecture support,
root install hooks, dependencies, and build functions remain ours to maintain.
`upstream.watch` complements the existing declarative providers and custom hooks:
```json
{
"source": "local",
"upstream": {
"watch": {
"github": "abenz1267/walker",
"pattern": "v(?P<version>[0-9]+(?:\\.[0-9]+)*)"
}
}
}
```
## Watch fields
Choose exactly one provider: `github` (owner/repository), `git_tags` (repository
HTTPS URL), `git_branch` (repository URL plus explicit `branch`), `npm` or `pypi`
(package name), `debian` (Packages index plus exact `package`), `json` (URL plus
version `path`), `regex` (text URL), `redirect` (final HTTPS download URL), or
`archive` (inspect archive metadata without extracting/executing code).
Tag, text, redirect and archive watches use an explicit `pattern` with a named
`version` capture. Tag patterns match the entire tag. `version` optionally formats
those captures into an Arch pkgver; e.g. Sublime uses `4.{version}`. JSON feeds can
expose additional capture values through `fields`, a name-to-JSON-path map.
`variables` maps recipe scalars such as `_commit` or `_build` to capture templates.
Only explicitly declared underscore-prefixed variables can change. GitHub
`{commit}` resolves the selected tag, not a moving target_commitish branch.
`submodules` can map a recipe variable to a gitlink in the selected GitHub tag;
RustDesk uses this for hbb_common. Downloaded repository code is never evaluated.
For upstreams that rebuild a release, declare a numeric `revision` template and
its `revision_variable`. With unchanged pkgver, only an increasing revision can
advance that variable, and the downstream pkgrel increments instead of resetting.
Cursor CLI uses `sequence` to preserve its date/counter/hash version convention
when the vendor publishes a second hash on the same day. A new pkgver resets
pkgrel to 1, but the complete epoch:pkgver-pkgrel must still increase.
GitHub releases exclude drafts and prereleases unless `allow_prerelease` is true.
Existing `min_release_age` policies apply: a feed without a verifiable publication
time cannot bypass a configured hold.
## Branch watches
A `git_branch` watch treats every commit on a branch as a release and writes an
immutable pin (`"_commit": "{commit}"`) so the recipe never carries a moving
`#branch=` source; `tests/pinned-sources.sh` enforces that. The clone is bare,
blobless and single-branch, read only with git, and shared by every package
that watches the same branch in one run, so two recipes pinned from it always
see the same commit. Values available to `version`:
- `{date}` (default), `{count}` (commits on the branch), `{commit}`
(`{commit:.7}` for the short form)
- with `tag_pattern` (a regular expression with a named `version` group,
matched against whole tags): `{tag}`, `{version}` from that tag, and
`{distance}`, the number of commits past it. Only tags in the pinned
commit's own history count, so a release cut on another branch is ignored.
`{version}.r{distance}.g{commit:.7}` gives `1.21.0.r15.gabc1234`, which pacman
orders above the `1.21.0` release it follows and below `1.21.1`; `omasnap-git`
uses it. The Omarchy dev pair uses `{version}.r{count}.g{commit:.7}` instead
because its published history counted every commit and the number must never
go down.
`min_release_age` holds a branch tip until its commit timestamp is old enough.
A fresh tip leaves the existing pin alone; the watch never walks backward to
an older commit. This uses Git's committer date, not the time a commit was
pushed. `BYPASS_MIN_RELEASE_AGE=1` bypasses the hold.
Packages marked `"auto_merge": true` ride the unattended lane
(`track-branches.yml`) instead of the reviewed sync PR: their bump PR is opened
and auto-merged as soon as the build checks pass. `bin/sync-upstream --lane
reviewed|auto-merge|all` selects a lane; the scheduled workflows each pass their
own. Packages that pin the same branch move in lockstep: if one of them fails
to update, the run restores the others and reports the group as failed. A
targeted sync includes the other packages watching that branch, so requesting
only `omarchy-dev` also updates `omarchy-settings-dev`.
### Enable unattended branch updates
The schedule already runs in GitHub Actions; no server cron job is needed.
It uses a personal access token so its PRs trigger builds and its merges trigger
publishing without manual approval. No GitHub App is required.
1. Use a fine-grained PAT with access to **omacom/omarchy-pkgs** and repository
**Contents: Read and write** and **Pull requests: Read and write** permissions.
Its owner must be trusted by the build workflow (for example, a collaborator).
The existing controller PAT can be reused when it has these permissions.
2. In the repository's
[Actions secrets](https://github.com/omacom/omarchy-pkgs/settings/secrets/actions),
save the PAT as `PKGS_BOT_TOKEN`. Update this secret when the token is rotated
or expires. The built-in Actions `GITHUB_TOKEN` cannot run this unattended chain.
3. Keep **Allow auto-merge** enabled and require `result`, `self-tests`, and
`build-isolation` on `master`; the tracker does not request a protection bypass.
4. After merging the tracker, run **Track upstream branches** once from Actions
to verify that its PR builds, auto-merges, and starts **Publish merged packages**.
Subsequent runs happen every two hours.
Checksums retain their algorithms (SHA256, SHA512, BLAKE2, etc.) and source order.
Changed git sources are hashed with makepkg's git-archive convention. Unchanged
sources retain their hashes; `mutable_sources` explicitly names entries such as
`source:0` that must be fetched again for a new version despite a stable URL.
Existing `SKIP` entries remain unchanged (including signed metadata verified by
the recipe); new skips are never introduced. Changed URLs are still fetched.
A matching GitHub release asset SHA256 digest avoids downloading large assets.
Missing architecture artifacts or malformed metadata fail the package atomically.
Every declared architecture must read back the same release and checksum values.
Archive watches use `member` to select a text member, or `filenames: true` to read
versions from archive member names. Debian archives are read through their control
metadata. `unescape_json` handles JSON strings embedded in a vendor's HTML page.
## Maintenance and validation
Running watches locally requires Python 3.11+, Bash, curl, git, jq, Arch's
`vercmp`, and `bsdtar`. CI installs these in its Arch container.
- Edit packaging and architecture changes directly in PKGBUILD. The old AUR
overlays have been folded into these recipes and removed.
- Keep source-code patches and install hooks checked in as ordinary package files.
- Bump pkgrel when changing a recipe at the same version. Removing a dotted AUR
suffix must never lower the complete version.
- Add a watch with each new package. `bin/add-package --source aur` is a one-time
import; it records historical `origin` metadata and leaves an owned recipe.
- `python helpers/upstream-watch.py check pkgbuilds/NAME` checks release discovery
without rewriting the recipe. `bin/sync-upstream NAME` performs the update.
- `python tests/upstream-watch.py` tests update atomicity, architecture coverage,
version ordering, source hashes and hostile metadata using offline fixtures.
The scheduled workflow continues reviewing completed updates if another package
fails. The failing recipe stays unchanged and the run still reports failure.
## Migrated package watches
68 active AUR packages now use direct watches. The nine previously disabled
packages retain manual maintenance holds. Historical AUR provenance is recorded
in `origin` and has no effect on release selection.
| Package | Provider | Upstream |
|---|---|---|
| `1password-beta` | debian | [https://downloads.1password.com/linux/debian/amd64/dists/beta/main/binary-amd64/Packages](https://downloads.1password.com/linux/debian/amd64/dists/beta/main/binary-amd64/Packages) |
| `1password-cli` | json | [https://app-updates.agilebits.com/check/1/0/CLI2/en/0](https://app-updates.agilebits.com/check/1/0/CLI2/en/0) |
| `aether` | github | [omacom/aether](https://github.com/omacom/aether) |
| `basecamp-cli` | github | [basecamp/basecamp-cli](https://github.com/basecamp/basecamp-cli) |
| `bun-bin` | github | [oven-sh/bun](https://github.com/oven-sh/bun) |
| `claude-code` | regex | [https://downloads.claude.ai/claude-code-releases/latest](https://downloads.claude.ai/claude-code-releases/latest) |
| `cliamp` | github | [bjarneo/cliamp](https://github.com/bjarneo/cliamp) |
| `crush-bin` | github | [charmbracelet/crush](https://github.com/charmbracelet/crush) |
| `cursor-bin` | json | [https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable](https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable) |
| `cursor-cli` | regex | [https://cursor.com/install](https://cursor.com/install) |
| `dbxcli-bin` | github | [dropbox/dbxcli](https://github.com/dropbox/dbxcli) |
| `dropbox` | redirect | [https://www.dropbox.com/download?plat=lnx.x86_64](https://www.dropbox.com/download?plat=lnx.x86_64) |
| `dropbox-cli` | regex | [https://linux.dropbox.com/packages/](https://linux.dropbox.com/packages/) |
| `heroic-games-launcher-bin` | github | [Heroic-Games-Launcher/HeroicGamesLauncher](https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher) |
| `hyprshade` | pypi | [hyprshade](https://pypi.org/project/hyprshade/) |
| `lib32-nvidia-580xx-utils` | regex | [https://download.nvidia.com/XFree86/Linux-x86_64/](https://download.nvidia.com/XFree86/Linux-x86_64/) |
| `limine-mkinitcpio-hook` | git_tags | [https://gitlab.com/Zesko/limine-entry-tool.git](https://gitlab.com/Zesko/limine-entry-tool.git) |
| `limine-snapper-sync` | git_tags | [https://gitlab.com/Zesko/limine-snapper-sync.git](https://gitlab.com/Zesko/limine-snapper-sync.git) |
| `lmstudio-bin` | regex | [https://lmstudio.ai/download](https://lmstudio.ai/download) |
| `localsend` | github | [localsend/localsend](https://github.com/localsend/localsend) |
| `localsend-bin` | github | [localsend/localsend](https://github.com/localsend/localsend) |
| `macbook12-spi-driver-dkms` | git_branch | [https://github.com/marc-git/macbook12-spi-driver.git](https://github.com/marc-git/macbook12-spi-driver.git) |
| `omarchy-dev`, `omarchy-settings-dev` | git_branch (auto-merge) | [https://github.com/basecamp/omarchy.git](https://github.com/basecamp/omarchy.git) `quattro` |
| `omasnap-git` | git_branch (auto-merge) | [https://github.com/omacom/omasnap.git](https://github.com/omacom/omasnap.git) `main` |
| `makima-bin` | github | [cyber-sushi/makima](https://github.com/cyber-sushi/makima) |
| `minecraft-launcher` | archive | [https://launcher.mojang.com/download/Minecraft.deb](https://launcher.mojang.com/download/Minecraft.deb) |
| `nautilus-dropbox` | github | [dropbox/nautilus-dropbox](https://github.com/dropbox/nautilus-dropbox) |
| `nautilus-open-any-terminal` | git_tags | [https://github.com/Stunkymonkey/nautilus-open-any-terminal.git](https://github.com/Stunkymonkey/nautilus-open-any-terminal.git) |
| `nordvpn-bin` | debian | [https://repo.nordvpn.com/deb/nordvpn/debian/dists/stable/main/binary-amd64/Packages](https://repo.nordvpn.com/deb/nordvpn/debian/dists/stable/main/binary-amd64/Packages) |
| `nvidia-580xx-utils` | regex | [https://download.nvidia.com/XFree86/Linux-x86_64/](https://download.nvidia.com/XFree86/Linux-x86_64/) |
| `omarchy-chromium-bin` | github | [omacom/omarchy-chromium](https://github.com/omacom/omarchy-chromium) |
| `omarchy-emacs` | git_tags | [https://github.com/scottjones/omarchy-emacs.git](https://github.com/scottjones/omarchy-emacs.git) |
| `omazed` | git_tags | [https://github.com/aps6/omazed.git](https://github.com/aps6/omazed.git) |
| `once-bin` | github | [basecamp/once](https://github.com/basecamp/once) |
| `openai-codex-bin` | github | [openai/codex](https://github.com/openai/codex) |
| `python-mediapipe` | github | [google-ai-edge/mediapipe](https://github.com/google-ai-edge/mediapipe) |
| `python-sounddevice` | pypi | [sounddevice](https://pypi.org/project/sounddevice/) |
| `python-terminaltexteffects` | pypi | [terminaltexteffects](https://pypi.org/project/terminaltexteffects/) |
| `rustdesk` | github | [rustdesk/rustdesk](https://github.com/rustdesk/rustdesk) |
| `spotify` | debian | [https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages](https://repository.spotify.com/dists/testing/non-free/binary-amd64/Packages) |
| `sublime-text-4` | json | [https://www.sublimetext.com/updates/4/stable_update_check](https://www.sublimetext.com/updates/4/stable_update_check) |
| `sunshine` | github | [LizardByte/Sunshine](https://github.com/LizardByte/Sunshine) |
| `ttf-ia-writer` | git_branch | [https://github.com/iaolo/iA-Fonts.git](https://github.com/iaolo/iA-Fonts.git) |
| `tuxedo-drivers-nocompatcheck-dkms` | git_tags | [https://gitlab.com/kronerm/tuxedo-drivers-nocompatcheck.git](https://gitlab.com/kronerm/tuxedo-drivers-nocompatcheck.git) |
| `typora` | debian | [https://downloads.typora.io/linux/Packages](https://downloads.typora.io/linux/Packages) |
| `ufw-docker` | git_tags | [https://github.com/chaifeng/ufw-docker.git](https://github.com/chaifeng/ufw-docker.git) |
| `vi` | regex | [https://sources.archlinux.org/other/vi/](https://sources.archlinux.org/other/vi/) |
| `visual-studio-code-bin` | json | [https://update.code.visualstudio.com/api/update/linux-deb-x64/stable/latest](https://update.code.visualstudio.com/api/update/linux-deb-x64/stable/latest) |
| `walker` | github | [abenz1267/walker](https://github.com/abenz1267/walker) |
| `xpadneo-dkms` | github | [atar-axis/xpadneo](https://github.com/atar-axis/xpadneo) |
| `yaru-icon-theme` | git_tags | [https://github.com/ubuntu/yaru.git](https://github.com/ubuntu/yaru.git) |
| `yay` | github | [Jguer/yay](https://github.com/Jguer/yay) |
| `yt6801-dkms` | archive | [https://www.motor-comm.com/Cn/Skippower/downloadFile.html?id=1817](https://www.motor-comm.com/Cn/Skippower/downloadFile.html?id=1817) |
## Existing manual holds
`libfprint-git`, `libretro-cap32-git`, `libretro-database-git`, `libretro-fbneo-git`, `libretro-uae-git`, `libretro-vice-git`, `quickshell-git`, `supergfxctl`.
These packages were already excluded from automatic AUR updates. The migration preserves that policy.
`m1n1-aurora` and `uboot-asahi` are deliberate holds: Apple Silicon boot code, pinned by hand like `linux-aurora`, and bumped only after a cold boot on the qualification Macs. `m1n1-aurora` pins an aurora-silicon/m1n1 commit plus a local patch. `uboot-asahi` follows asahi-alarm's recipe and patch set (asahi-alarm/PKGBUILDs), which a tag watch on AsahiLinux/u-boot cannot carry.
`cua-driver-bin` is a deliberate hold: Omarchy bumps it by hand, so a Cua release ships only when a maintainer has verified it. It keeps its `.omarchy/upstream.sh` hook and `min_release_age`, so lifting the hold means removing `"sync": false`. `cua-hyprland-plugin` declares no upstream source, so no automation updates it either.
## Package-specific boundaries
- NVIDIA watches remain on the 580 driver branch.
- Hardware-specific packages keep their declared architectures; this migration does not invent ARM binaries for x86-only upstreams.
- iA Duospace was deleted upstream. Its four legacy font files retain their original immutable pin while the other families track the current repository.
- RustDesk reads hbb_common from the release gitlink; its existing build-time dependency/toolchain checks remain in force.
- Spotify uses HTTPS and retains its signed Release/Packages verification.
- Source and build compatibility still need review when upstream code changes. Direct watches remove AUR recipe churn, not the need to maintain packaging.
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
# Package files cross from a PR build to publish.yml as one GitHub Actions
# artifact. actions/upload-artifact rejects any path containing ':', and a
# package with an epoch is named `name-1:ver-rel-arch.pkg.tar.zst` by
# makepkg. So the files ride inside a tar with a plain name and keep their
# own names untouched: pacman clients and bin/publish-artifact both rely on
# the filename matching PKGINFO.
#
# Both functions run under the workflow's `bash -e`: nothing in them may
# return non-zero except the final failure.
# package_files <dir>: the *.pkg.tar.zst directly in <dir>, one per line.
# Signatures and the scratch database next to them are not packages.
package_files() {
local f
for f in "$1"/*.pkg.tar.zst; do
[[ -e "$f" ]] && printf '%s\n' "$f"
done
return 0
}
# pack_packages <dir> <tar>: every package in <dir> into <tar>.
pack_packages() {
local dir=$1 out=$2 files=()
mapfile -t files < <(package_files "$dir")
(( ${#files[@]} )) || { echo "pack_packages: no *.pkg.tar.zst in $dir" >&2; return 1; }
tar -cf "$out" -C "$dir" -- "${files[@]##*/}"
}
# unpack_packages <artifact dir> <dest>: the packages an unzipped artifact
# carried, into <dest>. Packed artifacts hold packages.tar; artifacts from
# builds before packing hold the bare files. The bare form can go once
# those artifacts have expired (7-day retention).
unpack_packages() {
local src=$1 dest=$2 files=()
mkdir -p "$dest"
if [[ -f "$src/packages.tar" ]]; then
tar -xf "$src/packages.tar" -C "$dest"
return 0
fi
mapfile -t files < <(package_files "$src")
(( ${#files[@]} )) || { echo "unpack_packages: nothing to unpack in $src" >&2; return 1; }
cp -- "${files[@]}" "$dest/"
}
+127 -25
View File
@@ -1,25 +1,113 @@
# Docker helper functions for Omarchy package build system
# Container engine helpers for Omarchy package build system
check_docker() {
if ! command -v docker &>/dev/null; then
print_error "Docker is not installed"
container_engine_supported() {
[[ "$CONTAINER_ENGINE" == "docker" || "$CONTAINER_ENGINE" == "podman" ]]
}
if [[ -z "${CONTAINER_ENGINE:-}" ]]; then
for engine in docker podman; do
if command -v "$engine" >/dev/null 2>&1 && "$engine" info >/dev/null 2>&1; then
CONTAINER_ENGINE="$engine"
break
fi
done
fi
export CONTAINER_ENGINE
# Rootless Podman otherwise maps the image's builder uid 1000 to a subordinate
# host uid. keep-id makes files written through bind mounts belong to the user
# who invoked the build.
CONTAINER_RUN_ARGS=()
if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
CONTAINER_RUN_ARGS+=("--userns=keep-id:uid=1000,gid=1000")
fi
check_engine() {
if [[ -n "$CONTAINER_ENGINE" ]] && ! container_engine_supported; then
print_error "Unsupported CONTAINER_ENGINE: $CONTAINER_ENGINE (use docker or podman)"
exit 1
fi
if ! docker info &>/dev/null; then
print_error "Docker daemon is not running"
print_warning "Start Docker with: sudo systemctl start docker"
if [[ -z "$CONTAINER_ENGINE" ]]; then
print_error "No working container engine found (tried Docker, then Podman)"
if command -v docker >/dev/null 2>&1; then
print_warning "Docker is installed but unavailable. Start it with: sudo systemctl start docker"
elif command -v podman >/dev/null 2>&1; then
print_warning "Podman is installed but 'podman info' failed"
else
print_warning "Install Docker or Podman"
fi
exit 1
fi
if ! command -v "$CONTAINER_ENGINE" >/dev/null 2>&1; then
print_error "$CONTAINER_ENGINE is not installed"
exit 1
fi
if ! "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
print_error "Docker daemon is not running or is not accessible"
print_warning "Start Docker with: sudo systemctl start docker"
else
print_error "Podman is not available to the current user"
fi
exit 1
fi
}
docker_native_arch() {
case "$(uname -m)" in
x86_64) echo x86_64 ;;
aarch64 | arm64) echo aarch64 ;;
*) return 1 ;;
esac
}
setup_qemu() {
# Setup QEMU for building ARM64 packages on x86_64 hosts
if ! docker run --rm --privileged multiarch/qemu-user-static --reset -p yes --credential yes >/dev/null 2>&1; then
print_error "Failed to setup QEMU for ARM64 emulation"
local target_arch="${1:-aarch64}"
if [[ "$CONTAINER_ENGINE" == "podman" ]]; then
local registration="/proc/sys/fs/binfmt_misc/qemu-$target_arch"
local packaged_registration="/usr/lib/binfmt.d/qemu-$target_arch-static.conf"
local flags=""
if [[ -r "$registration" ]]; then
flags=$(sed -n 's/^flags: //p' "$registration")
fi
if [[ "$flags" == *F* && "$flags" == *C* ]]; then
print_success "QEMU $target_arch emulation is registered"
return 0
fi
print_error "Rootless Podman requires QEMU binfmt registration with the F and C flags"
print_info "F keeps the emulator available inside containers; C lets container sudo preserve credentials."
print_info "Configure it once with:"
echo " sudo pacman -S --needed qemu-user-static qemu-user-static-binfmt"
echo " sudo mkdir -p /etc/binfmt.d"
echo " sed 's/:FP$/:FPC/' $packaged_registration | sudo tee /etc/binfmt.d/qemu-$target_arch-static.conf >/dev/null"
echo " sudo systemctl restart systemd-binfmt"
exit 1
fi
print_success "QEMU ARM64 emulation enabled"
# Register emulators for builds whose target differs from the host, with
# the F and C flags (tonistiigi/binfmt always sets both). The image tag pins
# the QEMU version every emulated build runs under; multiarch/qemu-user-static
# stopped at QEMU 7.2, which breaks qmake's compiler probe on current gcc.
# Keep ci/runner-cloud-init.yaml on the same tag. Uninstall first: install
# leaves an existing registration (an older emulator) in place and exits 0.
local platform_arch
case "$target_arch" in
aarch64) platform_arch=arm64 ;;
x86_64) platform_arch=amd64 ;;
*) platform_arch="$target_arch" ;;
esac
if ! "$CONTAINER_ENGINE" run --rm --privileged docker.io/tonistiigi/binfmt:qemu-v10.2.3-68 --uninstall "qemu-$target_arch" --install "$platform_arch" >/dev/null 2>&1; then
print_error "Failed to set up QEMU emulation"
exit 1
fi
print_success "QEMU emulation enabled"
}
build_docker_image() {
@@ -39,31 +127,45 @@ build_docker_image() {
;;
esac
print_info "Building Docker image for $arch ($platform) using $mirror mirror..."
docker buildx build \
--platform "$platform" \
--build-arg MIRROR="$mirror" \
--load \
-t "$image_tag" \
-f "$build_dir/Dockerfile" \
"$build_dir"
print_info "Building container image for $arch ($platform) using $mirror mirror..."
if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
"$CONTAINER_ENGINE" buildx build \
--platform "$platform" \
--build-arg MIRROR="$mirror" \
--load \
-t "$image_tag" \
-f "$build_dir/Dockerfile" \
"$build_dir"
else
"$CONTAINER_ENGINE" build \
--platform "$platform" \
--build-arg MIRROR="$mirror" \
-t "$image_tag" \
-f "$build_dir/Dockerfile" \
"$build_dir"
fi
}
get_platform_arg() {
local arch="$1"
case "$arch" in
x86_64) echo "--platform linux/amd64" ;;
aarch64) echo "--platform linux/arm64" ;;
*) echo "" ;;
x86_64) echo "--platform=linux/amd64" ;;
aarch64) echo "--platform=linux/arm64" ;;
*) return 1 ;;
esac
}
make_dir_writable() {
local dir="$1"
if [ "$(id -u)" -eq 0 ]; then
if (( EUID == 0 )); then
chmod -R 777 "$dir"
else
sudo chown -R $(id -u):$(id -g) "$dir" 2>/dev/null || chmod -R 777 "$dir"
# chown can succeed on part of the tree and fail on the rest (files a
# previous container left behind as another uid); the old `|| chmod`
# fallback only ran when chown failed outright, leaving those files
# unwritable. Always follow with chmod so the whole tree is usable.
sudo chown -R "$(id -u):$(id -g)" "$dir" 2>/dev/null || true
chmod -R 777 "$dir"
fi
}
+180 -20
View File
@@ -3,21 +3,24 @@
# Expects package directories in $PKGBUILDS_DIR, each with:
# .omarchy/package.json
#
# Minimal schema:
# { "source": "aur" }
# { "source": "aur", "sync": false }
# { "source": "aur", "aur": "different-aur-name" }
# { "source": "aur", "release_ring": "fast" }
# { "source": "aur", "skip_build": true }
# { "source": "aur", "pkgrel": { "suffix": 1, "offset": 1 } }
# { "source": "aur", "rebuild_on": ["qt6-base"] }
# Minimal schema (legacy source:aur remains readable for initial imports):
# { "source": "local" }
# { "source": "local", "sync": false }
# { "source": "local", "release_ring": "fast" }
# { "source": "local", "skip_build": true }
# { "source": "local", "rebuild_on": ["qt6-base"] }
# { "source": "local", "upstream": { "watch": { "github": "owner/repo", "pattern": "v(?P<version>[0-9.]+)" } } }
# { "source": "local", "channels": ["edge"] }
# { "source": "local", "channels": ["edge", "rc", "stable"] }
# { "source": "local", "min_release_age": "24h" }
# { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } }
# { "source": "local", "auto_merge": true, "upstream": { "watch": { "git_branch": "...", "branch": "main" } } }
# { "source": "local", "upstream": { "github": "owner/repo", "checksums": "SHASUMS256.txt", "assets": { "x86_64": ["name-{tag}-x64.tar.xz"] } } }
# { "source": "local", "upstream": { "github": "owner/repo", "digests": true, "assets": { "x86_64": "name-{tag}-x64.tar.xz" } } }
# { "source": "local", "upstream": { "git_tags": "https://example/repo.git", "tag_pattern": "v{pkgver}", "sources": { "any": ["https://example/archive/{tag}.tar.gz"] } } }
# { "source": "local", "upstream": { "npm": "@scope/package", "sources": { "any": ["{npm_tarball}"] } } }
# { "source": "local", "upstream": { "debian": "https://example/debian/dists/stable/main/binary-amd64/Packages", "package": "example", "sources": { "any": ["https://example/releases/{pkgver}.tar.gz"] } } }
#
# bin/sync-aur also writes upstream_commit for AUR-backed packages, and
# bin/import-aur records historical origin.aur and origin.commit;
# bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on.
if [[ -z "${PKGBUILDS_DIR:-}" ]]; then
@@ -140,6 +143,67 @@ package_has_pkgbuild() {
[[ -f "$pkgdir/PKGBUILD" ]]
}
# Read one variable from a PKGBUILD the way makepkg would see it.
#
# makepkg always exports CARCH, so PKGBUILDs may branch on it at file scope
# (per-architecture sources, tarball suffixes, even `return` for an
# unsupported architecture). Sourcing without CARCH takes the wrong branch or
# aborts partway, which leaves pkgver and pkgrel empty — and an empty version
# never equals the published one, so the package is queued for a rebuild that
# promotion then refuses. Every read of a PKGBUILD goes through here.
#
# Prints the value; exit status is that of `source PKGBUILD` itself, so a
# caller can tell "variable empty" from "PKGBUILD could not be read".
package_pkgbuild_var() {
local pkgdir="$1"
local var="$2"
local arch="${3:-${ARCH:-x86_64}}"
(cd "$pkgdir" && env -u OMARCHY_SRC CARCH="$arch" bash -c '
source PKGBUILD >/dev/null 2>&1
rc=$?
printf "%s\n" "${!1:-}"
exit "$rc"
' _ "$var")
}
# The architectures declared by a PKGBUILD. Set CARCH while reading it so a
# conditional arch=() assignment is evaluated for the architecture we are
# actually checking, even when the repository host is a different one.
package_arches() {
local pkgdir="$1"
local arch="${2:-${ARCH:-x86_64}}"
(cd "$pkgdir" && env -u OMARCHY_SRC CARCH="$arch" bash -c '
source PKGBUILD >/dev/null 2>&1
printf "%s\n" "${arch[*]}"
')
}
package_supports_arch() {
local pkgdir="$1"
local target="${2:-${ARCH:-x86_64}}"
local arches
arches=$(package_arches "$pkgdir" "$target") || return 1
case " $arches " in
*" any "* | *" $target "*) return 0 ;;
*) return 1 ;;
esac
}
# The channel DB indexes only its newest version, but older published archives
# remain immutable. Both the scheduler and build planner must skip an existing
# filename even when the checkout differs from the version currently indexed.
package_version_is_published() {
local repo_dir="$1" package="$2" version="$3" target="$4" path
for path in "$repo_dir/$package-$version-$target.pkg.tar."* \
"$repo_dir/$package-$version-any.pkg.tar."*; do
[[ -f "$path" && "$path" != *.sig ]] && return 0
done
return 1
}
# Channel membership: where a package may be published. Packages without a
# `channels` key are members of every channel (they flow edge -> rc -> stable).
package_has_channels() {
@@ -216,6 +280,11 @@ package_builds_for_mirror() {
package_moves_to_channel() {
local pkgdir="$1" channel="$2"
package_in_channel "$pkgdir" "$channel" || return 1
# Pinned packages build natively in rc from the release pin. That the
# advancing environment lacks OMARCHY_RC_PINS (so *it* may not build them)
# does not make the edge copy movable over the pin's artifact — edge's
# version can be ahead of the in-flight RC.
[[ "$channel" == "rc" ]] && package_is_pinned "$pkgdir" && return 1
! package_builds_for_mirror "$pkgdir" "$channel"
}
@@ -259,6 +328,31 @@ packages_for_upstream_sync() {
done
}
# Upstream updates travel in one of two lanes. The reviewed lane is the
# 6-hourly sync PR a maintainer reads before merging. A package that marks
# "auto_merge": true rides the unattended lane instead: its bump PR is opened
# and auto-merged by the branch tracker as soon as CI is green, which is how a
# package that follows a moving branch (omarchy-dev, omasnap-git) gets rebuilt
# without anyone clicking. The lanes are disjoint so a branch tip can never
# hold up a reviewed vendor release, or the other way round.
package_auto_merge() {
local pkgdir="$1" metadata
metadata=$(metadata_file_for_dir "$pkgdir")
[[ -f "$metadata" ]] || return 1
[[ "$(jq -r 'if has("auto_merge") then .auto_merge else false end' "$metadata")" == "true" ]]
}
# package_in_lane <pkgdir> <reviewed|auto-merge|all>
package_in_lane() {
local pkgdir="$1" lane="$2"
case "$lane" in
all | "") return 0 ;;
auto-merge) package_auto_merge "$pkgdir" ;;
reviewed) ! package_auto_merge "$pkgdir" ;;
*) echo "invalid lane: $lane (expected reviewed, auto-merge, or all)" >&2; return 2 ;;
esac
}
# Packages that must be rebuilt when a dependency they link against changes,
# even though nothing in their own source moved. `rebuild_on` names those
# dependencies; `rebuilt_against` records the versions the checked-in pkgrel was
@@ -298,9 +392,12 @@ packages_for_mirror() {
packages_for_unscoped_build() {
local mirror="$1"
local arch="${2:-${ARCH:-x86_64}}"
package_dirs | while IFS= read -r pkgdir; do
if package_builds_for_mirror "$pkgdir" "$mirror" && ! package_build_skipped "$pkgdir"; then
if package_builds_for_mirror "$pkgdir" "$mirror" &&
! package_build_skipped "$pkgdir" &&
package_supports_arch "$pkgdir" "$arch"; then
basename "$pkgdir"
fi
done
@@ -443,23 +540,70 @@ validate_package_metadata() {
return 1
fi
if ! jq -e 'if has("auto_merge") | not then true else (.auto_merge | type) == "boolean" end' "$metadata" >/dev/null; then
echo "invalid auto_merge for $(basename "$pkgdir"): must be boolean"
return 1
fi
if package_auto_merge "$pkgdir" && ! package_has_upstream_provider "$pkgdir" && ! package_has_upstream_hook "$pkgdir"; then
echo "invalid auto_merge for $(basename "$pkgdir"): only an upstream watch, provider, or hook can be auto-merged"
return 1
fi
# `has` rather than `// {}`: jq's // treats false as absent, which would
# let "upstream": false slip through as an empty declaration.
if ! jq -e '
def valid_sources:
type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z"))
and (.value | type == "array" and length > 0 and all(type == "string" and length > 0))
));
def valid_assets:
type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z"))
and (.value |
(type == "string" and length > 0)
or (type == "array" and length > 0 and all(type == "string" and length > 0) and (unique | length) == length)
)
));
if has("upstream") | not then true
elif (.upstream | type) != "object" then false
else .upstream |
((.github // "") | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z"))
and ((.checksums // "") | type == "string" and length > 0)
and ((.assets // {}) | type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0)
)))
([has("github"), has("git_tags"), has("npm"), has("debian"), has("watch")] | map(select(.)) | length) == 1
and if has("watch") then (.watch | type == "object")
elif has("github") then
(.github | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z"))
and (if has("checksums") then (.checksums | type == "string" and length > 0) else true end)
and (if has("digests") then (.digests | type == "boolean") else true end)
and (if has("latest_only") then (.latest_only | type == "boolean") else true end)
and (has("checksums") != (has("digests") and .digests == true))
and (.assets | valid_assets)
and (if has("sources") then
(.sources | valid_sources)
and ((.assets | keys) as $assets | (.sources | keys) as $sources | ($assets - $sources | length) == ($assets | length))
else true end)
elif has("git_tags") then
(.git_tags | type == "string" and test("\\Ahttps://[^[:space:]]+\\.git\\z"))
and (.tag_pattern | type == "string" and (split("{pkgver}") | length) == 2)
and (.sources | valid_sources)
elif has("npm") then
(.npm | type == "string" and test("\\A(@[a-z0-9_.-]+/)?[a-z0-9_.-]+\\z"))
and ((.dist_tag // "latest") | type == "string" and test("\\A[a-z0-9_.-]+\\z"))
and (.sources | valid_sources)
else
(.debian | type == "string" and test("\\Ahttps://[^[:space:]]+\\z"))
and (.package | type == "string" and test("\\A[a-z0-9][a-z0-9+.-]*\\z"))
and (.sources | valid_sources)
end
end
' "$metadata" >/dev/null; then
echo "invalid upstream for $(basename "$pkgdir"): needs github owner/repo, checksums asset name, and an assets arch->name map"
echo "invalid upstream for $(basename "$pkgdir"): configure exactly one valid github, git_tags, npm, debian, or watch provider"
return 1
fi
if jq -e '.upstream? | objects | has("watch")' "$metadata" >/dev/null; then
python3 "${BASH_SOURCE[0]%/*}/upstream-watch.py" validate "$pkgdir" || return 1
fi
pkgrel_type=$(jq -r 'if has("pkgrel") then .pkgrel | type else "missing" end' "$metadata")
case "$pkgrel_type" in
object|missing) ;;
@@ -481,12 +625,28 @@ validate_package_metadata() {
return 1
fi
if ! jq -e '(.rebuilt_against // {}) | type == "object" and (to_entries | all(.value | type == "string" and length > 0))' "$metadata" >/dev/null; then
echo "invalid rebuilt_against for $(basename "$pkgdir"): must be an object mapping package names to versions"
if ! jq -e '
def version_map:
type == "object" and (to_entries | all(.value | type == "string" and length > 0));
(.rebuilt_against // {}) as $record |
($record | version_map) or
(($record | type) == "object"
and ((($record | keys) - ["x86_64", "aarch64"]) | length == 0)
and ($record | to_entries | all(.value | version_map)))
' "$metadata" >/dev/null; then
echo "invalid rebuilt_against for $(basename "$pkgdir"): must map architectures to package-version maps"
return 1
fi
if ! jq -e '((.rebuilt_against // {}) | keys) - (.rebuild_on // []) | length == 0' "$metadata" >/dev/null; then
if ! jq -e '
(.rebuild_on // []) as $triggers |
(.rebuilt_against // {}) as $record |
if ($record | to_entries | all(.value | type == "string")) then
((($record | keys) - $triggers) | length == 0)
else
($record | to_entries | all((((.value | keys) - $triggers) | length) == 0))
end
' "$metadata" >/dev/null; then
echo "invalid rebuilt_against for $(basename "$pkgdir"): records a package that rebuild_on does not name"
return 1
fi
+70
View File
@@ -5,6 +5,76 @@
ARCH=${ARCH:-x86_64}
MIRROR=${MIRROR:-edge}
# Architectures the tooling knows how to build.
VALID_ARCHES="x86_64 aarch64"
# Architectures this repository PUBLISHES. The scheduled pipeline (version
# check, auto-release, channel advance with --arch all) runs once per entry,
# in this order; the first entry is the reference architecture that the
# release train observes channels through. Adding an architecture here is the
# enablement step: the next check-versions tick queues its packages and the
# next auto-release tick builds them. OMARCHY_ARCHES overrides it for a
# one-off run.
PUBLISHED_ARCHES="${OMARCHY_ARCHES:-x86_64}"
validate_arch() {
case " $VALID_ARCHES " in
*" $1 "*) return 0 ;;
*) return 1 ;;
esac
}
require_valid_arch() {
if ! validate_arch "$1"; then
echo "Invalid architecture: $1 (must be one of: $VALID_ARCHES)" >&2
exit 1
fi
}
published_arches() {
local arch
for arch in $PUBLISHED_ARCHES; do
require_valid_arch "$arch"
echo "$arch"
done
}
reference_arch() {
published_arches | head -1
}
# Architectures a merge to master builds and publishes: bin/build-matrix plans
# a PR build for each one a package supports, and publish.yml ships them. This
# is independent of PUBLISHED_ARCHES, so anything deciding what a merge has to
# rebuild (bin/sync-rebuilds) follows this list. CI_ARCHES overrides it.
CI_ARCHES="${CI_ARCHES:-x86_64 aarch64}"
ci_arches() {
local arch
for arch in $CI_ARCHES; do
require_valid_arch "$arch"
echo "$arch"
done
}
# Scheduled-pipeline state, one file per channel and architecture, so one
# architecture's queue or backoff never gates another's.
STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}"
sync_queue_file() { # sync_queue_file <mirror> <arch>
echo "$STATE_DIR/.sync-needed-$1-$2"
}
sync_fail_file() { # sync_fail_file <mirror> <arch>
echo "$STATE_DIR/.build-failed-$1-$2"
}
# The pre-architecture names, .sync-needed-<mirror> and .build-failed-<mirror>,
# meant x86_64. A host upgraded mid-cycle may still hold one; readers treat
# it as the x86_64 file until it is consumed.
legacy_sync_queue_file() { echo "$STATE_DIR/.sync-needed-$1"; }
legacy_sync_fail_file() { echo "$STATE_DIR/.build-failed-$1"; }
# Valid package channels, in pipeline order: packages move edge -> rc -> stable
VALID_MIRRORS="edge rc stable"
+321 -32
View File
@@ -1,8 +1,7 @@
# GitHub-releases upstream provider for bin/sync-upstream.
# Declarative upstream providers for bin/sync-upstream.
#
# A package whose upstream ships tagged GitHub releases with a checksum
# manifest asset needs no upstream.sh hook: the whole feed is data, declared
# in .omarchy/package.json --
# A package whose upstream ships tagged GitHub releases needs no upstream.sh
# hook: the whole feed is data, declared in .omarchy/package.json --
#
# "upstream": {
# "github": "jdx/mise",
@@ -13,17 +12,28 @@
# }
# }
#
# "checksums" names the vendor's manifest asset. A vendor publishing none can
# set "digests": true instead, which reads the SHA-256 digest GitHub's release
# API reports for every asset, so the sync never downloads the artifacts.
#
# {tag} and {pkgver} interpolate into asset names; tags may carry a leading
# "v", which is stripped for pkgver. Drafts and prereleases are ignored. The
# provider emits the same JSON contract as an upstream.sh hook, so
# bin/sync-upstream's validation and min_release_age backstop apply
# unchanged; a feed that fits no convention keeps a bespoke upstream.sh.
# unchanged. Git-tag, npm, and Debian Packages providers below cover projects
# without GitHub releases; a feed that fits no convention keeps a bespoke hook.
package_upstream_github_repo() {
local pkgdir="$1"
# `objects` drops a non-object upstream value (validation rejects those
# separately) instead of erroring the jq pipeline.
package_metadata_value "$pkgdir" '(.upstream? | objects | .github)' ""
# Return the single declarative provider selected by a package. An empty
# result means either no provider or an invalid/ambiguous declaration; the
# caller distinguishes those through package_has_upstream_provider().
package_upstream_provider() {
local pkgdir="$1" metadata
metadata=$(metadata_file_for_dir "$pkgdir")
jq -r '
(.upstream? | objects) as $u
| [$u | keys[] | select(. == "github" or . == "git_tags" or . == "npm" or . == "debian" or . == "watch")]
| if length == 1 then .[0] else "" end
' "$metadata"
}
# Fetches sit behind functions so the self-test can replace them with fixture
@@ -43,6 +53,221 @@ github_fetch_checksums() {
curl -fsSL "https://github.com/$repo/releases/download/$tag/$asset"
}
git_tags_fetch_refs() {
local repo="$1"
git ls-remote --tags "$repo"
}
npm_fetch_metadata() {
local package="$1" encoded
encoded=$(jq -rn --arg package "$package" '$package | @uri')
curl -fsSL "https://registry.npmjs.org/$encoded"
}
debian_fetch_packages() {
local url="$1"
curl --proto '=https' --proto-redir '=https' -fsSL "$url"
}
upstream_fetch_source() {
local url="$1" output="$2"
curl --proto '=https' --proto-redir '=https' -fsSL -o "$output" "$url"
}
# Hash every URL template in upstream.sources and emit hook-contract JSON.
# Templates may use {pkgver}, {tag}, and (for npm) {npm_tarball}. Downloads
# happen only after discovery reports a version newer than the PKGBUILD.
upstream_hash_sources() {
local package_dir="$1" pkgver="$2" tag="${3:-}" npm_tarball="${4:-}"
local metadata sources work result arch template url file sum sums index=0
metadata=$(metadata_file_for_dir "$package_dir")
sources=$(jq -c '.upstream.sources' "$metadata")
work=$(mktemp -d)
result=$(jq -n --arg pkgver "$pkgver" '{pkgver: $pkgver, sha256sums: {}}')
while IFS= read -r arch; do
sums='[]'
while IFS= read -r template; do
if [[ "$template" == file:* ]]; then
file=${template#file:}
if [[ ! "$file" =~ ^[A-Za-z0-9._+-]+$ || ! -f "$package_dir/$file" ]]; then
echo "upstream source names an unsafe or missing local file: '$file'" >&2
rm -rf "$work"
return 1
fi
sum=$(sha256sum "$package_dir/$file" | cut -d' ' -f1)
sums=$(jq -c --arg sum "$sum" '. + [$sum]' <<<"$sums")
continue
fi
url=${template//\{pkgver\}/$pkgver}
url=${url//\{tag\}/$tag}
url=${url//\{npm_tarball\}/$npm_tarball}
if [[ ! "$url" =~ ^https://[^[:space:]{}]+$ ]]; then
echo "upstream source template produced an unsafe URL: '$url'" >&2
rm -rf "$work"
return 1
fi
file="$work/source-$((index += 1))"
if ! upstream_fetch_source "$url" "$file"; then
echo "could not fetch upstream source: $url" >&2
rm -rf "$work"
return 1
fi
sum=$(sha256sum "$file" | cut -d' ' -f1)
sums=$(jq -c --arg sum "$sum" '. + [$sum]' <<<"$sums")
done < <(jq -r --arg arch "$arch" '.[$arch][]' <<<"$sources")
result=$(jq -c --arg arch "$arch" --argjson sums "$sums" '.sha256sums[$arch] = $sums' <<<"$result")
done < <(jq -r 'keys[]' <<<"$sources")
rm -rf "$work"
printf '%s\n' "$result"
}
git_tags_upstream_release() {
local package_dir="$1" metadata repo pattern prefix suffix refs
metadata=$(metadata_file_for_dir "$package_dir")
repo=$(jq -r '.upstream.git_tags // ""' "$metadata")
pattern=$(jq -r '.upstream.tag_pattern // ""' "$metadata")
prefix=${pattern%%\{pkgver\}*}
suffix=${pattern#*\{pkgver\}}
if [[ ! "$repo" =~ ^https://[^[:space:]]+\.git$ || "$pattern" != *'{pkgver}'* || "$suffix" == *'{pkgver}'* ]]; then
echo "invalid git_tags provider configuration" >&2
return 1
fi
if ! refs=$(git_tags_fetch_refs "$repo"); then
echo "could not fetch tags from $repo" >&2
return 1
fi
local best_pkgver="" best_tag="" tag candidate
declare -A version_tags=()
while read -r tag; do
tag=${tag%\^\{\}}
[[ "$tag" == "$prefix"*"$suffix" ]] || continue
candidate=${tag#"$prefix"}
[[ -z "$suffix" ]] || candidate=${candidate%"$suffix"}
[[ "$candidate" =~ ^[A-Za-z0-9][A-Za-z0-9._+]*$ ]] || continue
if [[ -n "${version_tags[$candidate]:-}" && "${version_tags[$candidate]}" != "$tag" ]]; then
echo "multiple tags map to pkgver $candidate: ${version_tags[$candidate]} and $tag" >&2
return 1
fi
version_tags[$candidate]="$tag"
if [[ -z "$best_pkgver" || $(vercmp "$candidate" "$best_pkgver") -gt 0 ]]; then
best_pkgver="$candidate"
best_tag="$tag"
fi
done < <(sed -n 's#^.*refs/tags/##p' <<<"$refs")
[[ -n "$best_pkgver" ]] || { echo "no usable tags found at $repo" >&2; return 1; }
local current_pkgver
current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'")
if [[ $(vercmp "$best_pkgver" "$current_pkgver") -le 0 ]]; then
echo '{}'
return 0
fi
upstream_hash_sources "$package_dir" "$best_pkgver" "$best_tag"
}
npm_upstream_release() {
local package_dir="$1" metadata package dist_tag npm_metadata pkgver tarball published_at release
metadata=$(metadata_file_for_dir "$package_dir")
package=$(jq -r '.upstream.npm // ""' "$metadata")
dist_tag=$(jq -r '.upstream.dist_tag // "latest"' "$metadata")
if [[ ! "$package" =~ ^(@[a-z0-9_.-]+/)?[a-z0-9_.-]+$ || ! "$dist_tag" =~ ^[a-z0-9_.-]+$ ]]; then
echo "invalid npm provider configuration" >&2
return 1
fi
if ! npm_metadata=$(npm_fetch_metadata "$package"); then
echo "could not fetch npm metadata for $package" >&2
return 1
fi
pkgver=$(jq -r --arg tag "$dist_tag" '."dist-tags"[$tag] // ""' <<<"$npm_metadata")
tarball=$(jq -r --arg version "$pkgver" '.versions[$version].dist.tarball // ""' <<<"$npm_metadata")
published_at=$(jq -r --arg version "$pkgver" '.time[$version] // ""' <<<"$npm_metadata")
if [[ ! "$pkgver" =~ ^[A-Za-z0-9][A-Za-z0-9._+]*$ || ! "$tarball" =~ ^https://registry\.npmjs\.org/ ]]; then
echo "npm returned an unusable $package release" >&2
return 1
fi
local current_pkgver
current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'")
if [[ $(vercmp "$pkgver" "$current_pkgver") -le 0 ]]; then
echo '{}'
return 0
fi
release=$(upstream_hash_sources "$package_dir" "$pkgver" "$pkgver" "$tarball") || return 1
if [[ -n "$published_at" ]]; then
release=$(jq -c --arg published_at "$published_at" '.published_at = $published_at' <<<"$release")
fi
printf '%s\n' "$release"
}
# Discover a package version from a plain-text Debian Packages index, then
# hash the declared immutable sources. This intentionally supports only
# versions that are already valid Arch pkgver values; feeds needing Debian
# epoch/revision translation keep a package-specific hook.
debian_upstream_release() {
local package_dir="$1" metadata index_url package packages
metadata=$(metadata_file_for_dir "$package_dir")
index_url=$(jq -r '.upstream.debian // ""' "$metadata")
package=$(jq -r '.upstream.package // ""' "$metadata")
if [[ ! "$index_url" =~ ^https://[^[:space:]]+$ || ! "$package" =~ ^[a-z0-9][a-z0-9+.-]*$ ]]; then
echo "invalid Debian Packages provider configuration" >&2
return 1
fi
if ! jq -e '
.upstream.sources | type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z"))
and (.value | type == "array" and length > 0 and all(type == "string" and length > 0))
))
' "$metadata" >/dev/null; then
echo "invalid Debian Packages source mapping" >&2
return 1
fi
if ! packages=$(debian_fetch_packages "$index_url"); then
echo "could not fetch Debian Packages index: $index_url" >&2
return 1
fi
packages=${packages//$'\r'/}
local best_pkgver="" candidate
while IFS= read -r candidate; do
if [[ ! "$candidate" =~ ^[A-Za-z0-9][A-Za-z0-9._+]*$ ]]; then
echo "$package has an unusable Debian version: ${candidate:-<empty>}" >&2
return 1
fi
if [[ -z "$best_pkgver" || $(vercmp "$candidate" "$best_pkgver") -gt 0 ]]; then
best_pkgver="$candidate"
fi
done < <(awk -v target="$package" '
BEGIN { RS = ""; FS = "\n" }
{
name = version = ""
for (i = 1; i <= NF; i++) {
if ($i ~ /^Package: /) name = substr($i, 10)
if ($i ~ /^Version: /) version = substr($i, 10)
}
if (name == target) print version
}
' <<<"$packages")
[[ -n "$best_pkgver" ]] || {
echo "no usable $package release found in $index_url" >&2
return 1
}
local current_pkgver
current_pkgver=$(grep -m1 '^pkgver=' "$package_dir/PKGBUILD" | cut -d= -f2- | tr -d "\"'")
if [[ $(vercmp "$best_pkgver" "$current_pkgver") -le 0 ]]; then
echo '{}'
return 0
fi
upstream_hash_sources "$package_dir" "$best_pkgver" "$best_pkgver"
}
# Emits the newest qualifying release as hook-contract JSON. min_release_age
# is honored during selection (newest release older than the window wins,
# even when a younger one exists) and BYPASS_MIN_RELEASE_AGE=1 lifts it.
@@ -51,7 +276,7 @@ github_fetch_checksums() {
# not silently choose from.
github_upstream_release() {
local package_dir="$1" min_age="${2:-0}"
local metadata repo checksums_name
local metadata repo checksums_name use_digests latest_only
metadata=$(metadata_file_for_dir "$package_dir")
repo=$(jq -r '(.upstream? | objects | .github) // ""' "$metadata")
@@ -59,9 +284,46 @@ github_upstream_release() {
echo "invalid upstream.github repository: '${repo:-<empty>}'" >&2
return 1
fi
checksums_name=$(jq -r '(.upstream? | objects | .checksums) // ""' "$metadata")
if [[ -z "$checksums_name" ]]; then
echo "upstream.checksums names the checksum manifest asset and is required" >&2
# Enforced here as well as in validate_package_metadata: the scheduled sync
# reaches this provider without running the validator first.
checksums_name=$(jq -r '(.upstream? | objects | .checksums) | strings' "$metadata")
use_digests=$(jq -r '(.upstream? | objects | .digests) | if . == null then "false" elif type == "boolean" then tostring else "invalid" end' "$metadata")
latest_only=$(jq -r '(.upstream? | objects | .latest_only) | if . == null then "false" elif type == "boolean" then tostring else "invalid" end' "$metadata")
if [[ "$use_digests" == "invalid" ]]; then
echo "upstream.digests must be true or false" >&2
return 1
fi
if [[ "$latest_only" == "invalid" ]]; then
echo "upstream.latest_only must be true or false" >&2
return 1
fi
if [[ -n "$checksums_name" && "$use_digests" == "true" ]]; then
echo "upstream sets both checksums and digests; keep exactly one" >&2
return 1
fi
if [[ -z "$checksums_name" && "$use_digests" != "true" ]]; then
echo "upstream needs either checksums (a manifest asset name) or digests: true" >&2
return 1
fi
if ! jq -e '
def valid_sources:
type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z"))
and (.value | type == "array" and length > 0 and all(type == "string" and length > 0))
));
(.upstream.assets | type == "object" and length > 0 and (to_entries | all(
(.key | test("\\A[a-z0-9_]+\\z"))
and (.value |
(type == "string" and length > 0)
or (type == "array" and length > 0 and all(type == "string" and length > 0) and (unique | length) == length)
)
)))
and (if .upstream | has("sources") then
(.upstream.sources | valid_sources)
and ((.upstream.assets | keys) as $assets | (.upstream.sources | keys) as $sources | ($assets - $sources | length) == ($assets | length))
else true end)
' "$metadata" >/dev/null; then
echo "invalid upstream.assets or upstream.sources mapping" >&2
return 1
fi
local arches
@@ -77,6 +339,9 @@ github_upstream_release() {
echo "could not fetch the release feed for $repo" >&2
return 1
fi
if [[ "$latest_only" == "true" ]]; then
releases=$(jq '[.[] | select((.draft or .prerelease) | not)][0:1]' <<<"$releases")
fi
local candidates=0 best_tag="" best_pkgver="" best_published_at=""
local tag published_at pkgver published_epoch
@@ -129,33 +394,57 @@ github_upstream_release() {
return 0
fi
local checksums
if ! checksums=$(github_fetch_checksums "$repo" "$best_tag" "$checksums_name"); then
local checksums=""
if [[ "$use_digests" != "true" ]] \
&& ! checksums=$(github_fetch_checksums "$repo" "$best_tag" "$checksums_name"); then
echo "could not fetch $checksums_name for $repo $best_tag" >&2
return 1
fi
local jq_args=(--arg pkgver "$best_pkgver" --arg published_at "$best_published_at")
local jq_filter='{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}'
local arch template filename checksum
local result
result=$(jq -n --arg pkgver "$best_pkgver" --arg published_at "$best_published_at" \
'{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}')
local arch template filename checksum checksum_source sums
for arch in "${arches[@]}"; do
if [[ ! "$arch" =~ ^[a-z0-9_]+$ ]]; then
echo "invalid architecture key in upstream.assets: '$arch'" >&2
return 1
fi
template=$(jq -r --arg arch "$arch" '.upstream.assets[$arch]' "$metadata")
filename=${template//\{pkgver\}/$best_pkgver}
filename=${filename//\{tag\}/$best_tag}
# Manifest lines are "<sha256> <name>", with the name sometimes prefixed
# "./" (sha256sum of a local path) or "*" (binary-mode marker).
checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums")
if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then
echo "no valid checksum for $filename in $repo $best_tag $checksums_name" >&2
return 1
fi
jq_args+=(--arg "sum_$arch" "$checksum")
jq_filter+=" | .sha256sums[\"$arch\"] = [\$sum_$arch]"
sums='[]'
while IFS= read -r template; do
filename=${template//\{pkgver\}/$best_pkgver}
filename=${filename//\{tag\}/$best_tag}
if [[ "$use_digests" == "true" ]]; then
# Only a "sha256:<hex>" digest is stripped to its hex; any other shape
# falls through empty and fails the check below.
checksum=$(jq -r --arg tag "$best_tag" --arg name "$filename" '
first(.[] | select(.tag_name == $tag)) | (.assets // [])[]
| select(.name == $name) | (.digest // "")
| if type == "string" and test("\\Asha256:[0-9a-f]{64}\\z") then ltrimstr("sha256:") else "" end
' <<<"$releases")
checksum_source="the release API digest"
else
# Manifest lines are "<sha256> <name>", with the name sometimes prefixed
# "./" (sha256sum of a local path) or "*" (binary-mode marker).
checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums")
checksum_source="$checksums_name"
fi
if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then
echo "no valid checksum for $filename in $repo $best_tag $checksum_source" >&2
return 1
fi
sums=$(jq -c --arg checksum "$checksum" '. + [$checksum]' <<<"$sums")
done < <(jq -r --arg arch "$arch" '
.upstream.assets[$arch] | if type == "array" then .[] else . end
' "$metadata")
result=$(jq -c --arg arch "$arch" --argjson sums "$sums" '.sha256sums[$arch] = $sums' <<<"$result")
done
jq -n "${jq_args[@]}" "$jq_filter"
if jq -e '.upstream | has("sources")' "$metadata" >/dev/null; then
local source_release
source_release=$(upstream_hash_sources "$package_dir" "$best_pkgver" "$best_tag") || return 1
result=$(jq -c --argjson source "$source_release" '.sha256sums += $source.sha256sums' <<<"$result")
fi
printf '%s\n' "$result"
}
+631
View File
@@ -0,0 +1,631 @@
#!/usr/bin/env python3
"""Discover releases and update our own recipes; never import upstream build code.
The watch selects release metadata. Sources, supported architectures, integrity
algorithms and packaging behavior stay in the checked-in PKGBUILD. Only release
scalars and checksum arrays are replaced, atomically, after every source passes.
"""
import argparse
import datetime as dt
import gzip
import hashlib
import io
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import tarfile
import tempfile
from urllib.parse import quote, urlsplit
import zipfile
PROVIDERS = {"github", "git_tags", "git_branch", "npm", "pypi", "debian", "json", "regex", "archive", "redirect"}
VERSION = re.compile(r"[A-Za-z0-9][A-Za-z0-9._+]*\Z")
SCALAR = re.compile(r"[A-Za-z0-9._+/-]+\Z")
SUM = re.compile(r"(md5|sha1|sha224|sha256|sha384|sha512|b2)sums(_[a-z0-9_]+)?\Z")
HASHES = {"b2": "blake2b"}
# How many times a redirect watch probes before it calls the feed unmatched.
REDIRECT_PROBES = 5
def run(args, **kwargs):
return subprocess.check_output(args, **kwargs)
def vercmp(a, b):
return int(run(["vercmp", a, b], text=True).strip())
def https(url):
parts = urlsplit(url)
if parts.scheme != "https" or not parts.hostname or parts.username or parts.password or re.search(r"[\s\x00-\x1f]", url):
raise ValueError(f"expected an HTTPS upstream URL: {url!r}")
return url
class Fetcher:
def __init__(self, cache):
self.cache = Path(cache)
self.cache.mkdir(parents=True, exist_ok=True)
def file(self, url):
https(url)
dest = self.cache / hashlib.sha256(url.encode()).hexdigest()
if not dest.exists():
scratch = dest.with_suffix(f".{os.getpid()}.tmp")
command = ["curl", "--proto", "=https", "--proto-redir", "=https", "-fsSL",
"--connect-timeout", "20", "--max-time", "300", "--retry", "2", "-o", str(scratch), url]
# Credentials only go to GitHub's API, never to release assets or vendors.
token = os.environ.get("UPSTREAM_GITHUB_TOKEN")
if token and urlsplit(url).hostname == "api.github.com":
command[1:1] = ["--config", "-"]
subprocess.run(command, input=f'header = "Authorization: Bearer {token}"\n', text=True, check=True)
else:
subprocess.run(command, check=True)
scratch.replace(dest)
return dest
def text(self, url):
data = self.file(url).read_bytes()
if data.startswith(b"\x1f\x8b"):
data = gzip.decompress(data)
return data.decode()
def json(self, url):
return json.loads(self.text(url))
def validate(watch):
if not isinstance(watch, dict) or len(PROVIDERS & watch.keys()) != 1:
raise ValueError("watch must select exactly one release provider")
provider = next(iter(PROVIDERS & watch.keys()))
allowed = PROVIDERS | {"pattern", "path", "package", "branch", "variables", "fields",
"submodules", "allow_prerelease", "unescape_json", "filenames",
"sequence", "version", "revision", "revision_variable",
"mutable_sources", "member", "dist_tag", "tag_pattern"}
if watch.keys() - allowed:
raise ValueError(f"unknown watch fields: {sorted(watch.keys() - allowed)}")
value = watch[provider]
if not isinstance(value, str) or not value:
raise ValueError(f"invalid watch.{provider}")
if provider == "github":
if not re.fullmatch(r"[\w.-]+/[\w.-]+", value):
raise ValueError("invalid GitHub repository")
elif provider in {"npm", "pypi"}:
if not re.fullmatch(r"(?:@[\w.-]+/)?[\w.-]+", value):
raise ValueError("invalid registry package")
else:
https(value)
if "pattern" in watch or provider in {"github", "git_tags", "regex", "archive", "redirect"}:
if not isinstance(watch.get("pattern"), str):
raise ValueError("watch needs an explicit release pattern")
pattern = re.compile(watch["pattern"])
if "version" not in pattern.groupindex:
raise ValueError("release pattern needs a named version group")
if provider == "json" and (not isinstance(watch.get("path"), str) or not watch["path"]):
raise ValueError("JSON watch needs a version path")
if provider == "debian":
name = watch.get("package", "")
if not isinstance(name, str) or not re.fullmatch(r"[a-z0-9][a-z0-9+.-]*", name):
raise ValueError("Debian watch needs an exact package name")
if provider == "git_branch":
branch = watch.get("branch", "")
if not isinstance(branch, str) or not branch or branch.startswith("-"):
raise ValueError("git branch watch needs an explicit branch")
run(["git", "check-ref-format", "refs/heads/" + branch])
# A branch watch whose version template names a tag needs to know
# which tags count as releases; anything else is an untagged branch.
if "tag_pattern" in watch:
if not isinstance(watch["tag_pattern"], str) or not watch["tag_pattern"]:
raise ValueError("watch.tag_pattern must be a regular expression string")
if "version" not in re.compile(watch["tag_pattern"]).groupindex:
raise ValueError("tag_pattern needs a named version group")
template = watch.get("version", "{version}")
if any(field in template for field in ("{tag", "{distance")) and "tag_pattern" not in watch:
raise ValueError("a version built from {tag}/{distance} needs a tag_pattern")
elif "tag_pattern" in watch:
raise ValueError("tag_pattern only applies to git_branch watches")
for field in ("variables", "submodules", "fields"):
mapping = watch.get(field, {})
if not isinstance(mapping, dict):
raise ValueError(f"watch.{field} must be a string mapping")
for name, value in mapping.items():
pattern = r"[a-z][a-z0-9_]*" if field == "fields" else r"_[a-z][a-z0-9_]*"
if not re.fullmatch(pattern, name) or not isinstance(value, str) or not value:
raise ValueError(f"invalid watch.{field} mapping")
if "submodules" in watch and provider != "github":
raise ValueError("submodules require a GitHub watch")
if watch.get("variables", {}).keys() & watch.get("submodules", {}).keys():
raise ValueError("a release variable cannot also be a submodule")
for path in watch.get("submodules", {}).values():
if path.startswith("/") or any(part in {"", ".", ".."} for part in path.split("/")):
raise ValueError("submodule path must be relative to the release repository")
for field in ("allow_prerelease", "unescape_json", "filenames", "sequence"):
if field in watch and not isinstance(watch[field], bool):
raise ValueError(f"watch.{field} must be boolean")
for field in ("version", "revision", "member", "dist_tag"):
if field in watch and (not isinstance(watch[field], str) or not watch[field]):
raise ValueError(f"watch.{field} must be a string template")
if "revision_variable" in watch:
name = watch["revision_variable"]
if not isinstance(name, str) or name not in watch.get("variables", {}) or not watch.get("revision"):
raise ValueError("revision_variable requires a declared variable and revision template")
for field in ("mutable_sources",):
entries = watch.get(field, [])
if not isinstance(entries, list) or any(not isinstance(v, str) or not re.fullmatch(r"source(?:_[a-z0-9_]+)?:[0-9]+", v) for v in entries):
raise ValueError(f"watch.{field} must name source-array:index entries")
return provider
def json_path(data, path):
for key in path.split("."):
data = data[int(key)] if isinstance(data, list) else data[key]
return data
def candidate(watch, values):
values = {k: str(v) for k, v in values.items() if v is not None}
version = watch.get("version", "{version}").format_map(values)
if not VERSION.fullmatch(version):
raise ValueError(f"unusable upstream version: {version!r}")
revision = watch.get("revision", "").format_map(values)
if revision and not re.fullmatch(r"[0-9]+", revision):
raise ValueError("upstream release revision must be numeric")
return {"pkgver": version, "values": values,
"published_at": values.get("published_at"),
"revision": revision}
def matches(watch, text, extra=None, full=False):
pattern = re.compile(watch["pattern"])
found = [pattern.fullmatch(text)] if full else pattern.finditer(text)
for match in found:
if match:
yield candidate(watch, {**(extra or {}), **match.groupdict()})
def git_branch_tip(url, branch, tag_pattern, cache):
"""Describe the current tip of an upstream branch:
commit, total count, date, and with a tag_pattern
the newest release tag reachable from it plus the distance from that tag,
so a branch build can be versioned <tag>.r<n>.g<sha>, above the release it
follows and below the next one, the way a pkgver() function would.
One blobless single-branch clone per (url, branch) per run, shared by
every package that tracks it, so two recipes pinned from one clone always
see the same commit. The clone is read with git only; nothing in it runs.
select_release applies the age hold to this tip, without walking back
into history (which could select a commit from a merged side branch).
"""
https(url)
key = hashlib.sha256(f"{url}#{branch}".encode()).hexdigest()
work = Path(cache) / f"{key}.branch.git"
if not work.exists():
scratch = work.with_name(f"{work.name}.{os.getpid()}.tmp")
subprocess.run(["git", "clone", "--quiet", "--bare", "--filter=blob:none", "--single-branch", "--branch", branch, url, str(scratch)], check=True)
scratch.replace(work)
git = ["git", "-C", str(work)]
commit = run([*git, "rev-parse", "HEAD"], text=True).strip()
if not re.fullmatch(r"[0-9a-f]{40}", commit):
raise ValueError("branch tip is not a commit")
count = run([*git, "rev-list", "--count", commit], text=True).strip()
date = run([*git, "show", "-s", "--format=%cs", commit], text=True).strip().replace("-", "")
timestamp = run([*git, "show", "-s", "--format=%cI", commit], text=True).strip()
values = {"version": date, "date": date, "count": count, "commit": commit, "published_at": timestamp}
if tag_pattern:
pattern = re.compile(tag_pattern)
best = None
# Only tags in this commit's history count; a release cut on another
# branch is not something this branch is "past".
for tag in run([*git, "tag", "--merged", commit], text=True).split():
match = pattern.fullmatch(tag)
if not match:
continue
version = match.group("version")
if best is None or vercmp(version, best[0]) > 0:
best = (version, tag)
if best is None:
raise ValueError(f"no tag on {branch} matches {tag_pattern}")
distance = run([*git, "rev-list", "--count", f"{best[1]}..{commit}"], text=True).strip()
values.update({"tag": best[1], "version": best[0], "distance": distance})
return values
def discover(watch, fetch):
provider = validate(watch)
feed = watch[provider]
results = []
if provider == "github":
releases = fetch.json(f"https://api.github.com/repos/{feed}/releases?per_page=100")
if not isinstance(releases, list):
raise ValueError("GitHub did not return a release list")
for release in releases:
if release.get("draft") or (release.get("prerelease") and not watch.get("allow_prerelease")):
continue
for item in matches(watch, release["tag_name"], {"tag": release["tag_name"], "published_at": release["published_at"]}, full=True):
item["assets"] = release.get("assets", [])
results.append(item)
elif provider == "git_tags":
refs = run(["git", "ls-remote", "--tags", feed], text=True)
tags = {}
for line in refs.splitlines():
commit, ref = line.split()
tag = ref.removeprefix("refs/tags/")
if tag.endswith("^{}"):
tags[tag[:-3]] = commit
else:
tags.setdefault(tag, commit)
for tag, commit in tags.items():
results.extend(matches(watch, tag, {"tag": tag, "commit": commit}, full=True))
elif provider == "git_branch":
tip = git_branch_tip(feed, watch["branch"], watch.get("tag_pattern"), fetch.cache)
results.append(candidate(watch, tip))
elif provider == "npm":
data = fetch.json("https://registry.npmjs.org/" + quote(feed, safe=""))
version = data["dist-tags"][watch.get("dist_tag", "latest")]
results.append(candidate(watch, {"version": version, "published_at": data.get("time", {}).get(version)}))
elif provider == "pypi":
data = fetch.json(f"https://pypi.org/pypi/{feed}/json")
version = data["info"]["version"]
dates = [r["upload_time_iso_8601"] for r in data["releases"].get(version, []) if not r.get("yanked")]
if not dates:
raise ValueError("PyPI release has no unyanked files")
results.append(candidate(watch, {"version": version, "published_at": max(dates)}))
elif provider == "debian":
for stanza in re.split(r"\n\s*\n", fetch.text(feed).replace("\r", "")):
fields = dict(re.findall(r"^([A-Za-z0-9-]+): (.*)$", stanza, re.M))
if fields.get("Package") != watch["package"]:
continue
if "pattern" in watch:
results.extend(matches(watch, fields["Version"], full=True))
else:
results.append(candidate(watch, {"version": fields["Version"]}))
elif provider == "json":
data = fetch.json(feed)
values = {"version": json_path(data, watch["path"])}
values.update({name: json_path(data, path) for name, path in watch.get("fields", {}).items()})
results.append(candidate(watch, values))
elif provider == "redirect":
# A download redirect can be a staged rollout: some requests land on a
# newer build the pattern deliberately rejects (Dropbox, 2026-10-04:
# 4 of 100 HEADs ended at 274.3.4801 instead of 272.4.3798), so one
# probe that misses is not an answer. Keep the first one that matches.
for _ in range(REDIRECT_PROBES):
final_url = run(["curl", "--proto", "=https", "--proto-redir", "=https", "-fsSLI", "--max-time", "60", "--retry", "2",
"-o", "/dev/null", "-w", "%{url_effective}", feed], text=True)
results.extend(matches(watch, final_url))
if results:
break
elif provider == "regex":
text = fetch.text(feed)
if watch.get("unescape_json"):
text = text.replace('\\"', '"')
results.extend(matches(watch, text))
elif provider == "archive":
file = fetch.file(feed)
if zipfile.is_zipfile(file):
with zipfile.ZipFile(file) as archive:
names = archive.namelist()
if watch.get("filenames"):
results.extend(matches(watch, "\n".join(names)))
for name in ([] if watch.get("filenames") else names):
if re.fullmatch(watch.get("member", ".*"), name):
results.extend(matches(watch, archive.read(name).decode()))
elif file.read_bytes()[:8] == b"!<arch>\n":
names = run(["bsdtar", "-tf", str(file)], text=True).splitlines()
controls = [name for name in names if name.startswith("control.tar")]
if len(controls) != 1:
raise ValueError("deb does not contain exactly one control archive")
data = run(["bsdtar", "-xOf", str(file), controls[0]])
with tarfile.open(fileobj=io.BytesIO(data)) as archive:
members = [m for m in archive if m.name.removeprefix("./") == "control"]
if len(members) != 1:
raise ValueError("deb control file is missing or ambiguous")
results.extend(matches(watch, archive.extractfile(members[0]).read().decode()))
else:
with tarfile.open(file) as archive:
for member in archive:
if member.isfile() and re.fullmatch(watch.get("member", ".*"), member.name):
results.extend(matches(watch, archive.extractfile(member).read().decode()))
if not results:
raise ValueError(f"no matching releases in {feed}")
return results
def select_release(releases, min_age=0, now=None, bypass=False):
now = now or dt.datetime.now(dt.timezone.utc)
best = None
for release in releases:
if min_age and not bypass:
value = release.get("published_at")
if not value or not re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:?\d\d)", value):
raise ValueError("release age cannot be established")
if (now - dt.datetime.fromisoformat(value.replace("Z", "+00:00"))).total_seconds() < min_age:
continue
order = vercmp(release["pkgver"], best["pkgver"]) if best else 1
if best and order == 0:
order = vercmp(release["revision"] or "0", best["revision"] or "0")
if order > 0:
best = release
return best
DUMP = r'''
source "$1" >/dev/null || exit 1
set +u
for __watch_name in pkgver pkgrel epoch arch $(compgen -A variable | LC_ALL=C sort); do
case "$__watch_name" in
pkgver|pkgrel|epoch|arch|source|source_*|md5sums*|sha1sums*|sha224sums*|sha256sums*|sha384sums*|sha512sums*|b2sums*|_*)
[[ $__watch_name == __watch_* ]] && continue
declare -n __watch_value="$__watch_name"
printf '%s\0' "$__watch_name" "${#__watch_value[@]}" "${__watch_value[@]}"
unset -n __watch_value
;;
esac
done
'''
def read_recipe(path, arch="x86_64"):
with tempfile.TemporaryDirectory(prefix="recipe-read-") as work:
env = {**os.environ, "CARCH": arch, "SRCDEST": work, "srcdir": work, "pkgdir": work}
data = run(["bash", "-c", DUMP, "_", str(path.resolve())], cwd=path.parent, env=env).decode().split("\0")
result = {}
index = 0
while index < len(data) - 1:
name, size = data[index:index + 2]
index += 2
size = int(size)
result[name] = data[index:index + size]
index += size
return result
def scalar(recipe, name, default=""):
return recipe.get(name, [default])[0] if recipe.get(name) else default
def replace_scalar(text, name, value):
if not SCALAR.fullmatch(value):
raise ValueError(f"unsafe {name} value")
pattern = re.compile(r"^" + re.escape(name) + r"=.*$", re.M)
if len(pattern.findall(text)) != 1:
raise ValueError(f"expected one top-level {name}= assignment")
return pattern.sub(lambda _: f"{name}={value}", text)
def replace_array(text, name, values):
starts = list(re.finditer(r"^" + re.escape(name) + r"=\(", text, re.M))
if len(starts) != 1:
raise ValueError(f"expected one top-level {name}= array")
start = starts[0]
depth, quote_char, escaped, comment = 1, None, False, False
for index in range(start.end(), len(text)):
char = text[index]
if comment:
if char == "\n": comment = False
elif escaped:
escaped = False
elif char == "\\" and quote_char != "'":
escaped = True
elif quote_char:
if char == quote_char: quote_char = None
elif char in "\"'": quote_char = char
elif char == "#" and (index == 0 or text[index - 1].isspace()): comment = True
elif char == "(": depth += 1
elif char == ")":
depth -= 1
if depth == 0:
replacement = name + "=(" + " ".join("'" + value + "'" for value in values) + ")"
return text[:start.start()] + replacement + text[index + 1:]
raise ValueError(f"unclosed {name} array")
def bump_pkgrel(value):
if not re.fullmatch(r"[0-9]+(?:\.[0-9]+)?", value):
raise ValueError(f"invalid pkgrel: {value}")
components = value.split(".")
components[-1] = str(int(components[-1]) + 1)
return ".".join(components)
def complete_version(recipe):
return f"{scalar(recipe, 'epoch', '0')}:{scalar(recipe, 'pkgver')}-{scalar(recipe, 'pkgrel')}"
def hash_file(path, algorithm):
with path.open("rb") as stream:
return hashlib.file_digest(stream, HASHES.get(algorithm, algorithm)).hexdigest()
def source_url(source):
return source.split("::", 1)[-1]
def git_source_file(url, cache):
base, fragment = url.removeprefix("git+").split("#", 1)
kind, ref = fragment.split("=", 1)
https(base)
if kind not in {"tag", "commit"} or (kind == "commit" and not re.fullmatch(r"[0-9a-f]{40}", ref)):
raise ValueError("VCS sources must name an immutable commit or a checksummed tag")
if kind == "tag":
run(["git", "check-ref-format", "refs/tags/" + ref])
dest = cache / (hashlib.sha256(url.encode()).hexdigest() + ".git.tar")
if not dest.exists():
with tempfile.TemporaryDirectory(prefix="upstream-source-", dir=cache) as work:
subprocess.run(["git", "init", "--quiet", "--bare", work], check=True)
subprocess.run(["git", "-C", work, "fetch", "--quiet", "--depth=1", base, "refs/tags/" + ref if kind == "tag" else ref], check=True)
scratch = Path(work) / "source.tar"
with scratch.open("wb") as output:
subprocess.run(["git", "-c", "core.abbrev=no", "-C", work, "archive", "--format", "tar", "FETCH_HEAD"], stdout=output, check=True)
# The cache must never retain partial archives after a git failure.
scratch.replace(dest)
return dest
def updated_checksums(before, after, package, fetch, release, watch):
arrays = {}
source_names = {key for key in before if key == "source" or key.startswith("source_")}
if source_names != {key for key in after if key == "source" or key.startswith("source_")}:
raise ValueError("release changed the set of source architectures")
for source_name in sorted(source_names):
old_sources, sources = before[source_name], after[source_name]
suffix = source_name.removeprefix("source")
names = [name for name in before if SUM.fullmatch(name) and (SUM.fullmatch(name)[2] or "") == suffix]
if not sources:
continue
if len(sources) != len(old_sources) or not names:
raise ValueError(f"{source_name}: sources changed shape or have no checksums")
for name in names:
if len(before[name]) != len(sources):
raise ValueError(f"{name}: source/checksum count mismatch")
values = []
algorithm = SUM.fullmatch(name)[1]
for index, source in enumerate(sources):
old = before[name][index]
if source == old_sources[index] and f"{source_name}:{index}" not in watch.get("mutable_sources", []):
values.append(old)
continue
url = source_url(source)
# A release API digest can supply SHA256 without downloading a
# large asset, but only when its exact declared URL matches.
assets = [a for a in release.get("assets", []) if a.get("browser_download_url") == url]
if algorithm == "sha256" and len(assets) == 1 and re.fullmatch(r"sha256:[0-9a-f]{64}", assets[0].get("digest") or ""):
values.append("SKIP" if old == "SKIP" else assets[0]["digest"][7:])
continue
if url.startswith("git+https://"):
file = git_source_file(url, fetch.cache)
elif url.startswith("https://"):
file = fetch.file(url)
elif "://" not in url:
file = (package / url).resolve()
if not file.is_relative_to(package.resolve()) or not file.is_file():
raise ValueError(f"unsafe local source: {url}")
else:
raise ValueError(f"unsupported source transport: {url}")
# Preserve existing signature/prepare()-verified sources. Never
# introduce SKIP; still fetch changed URLs to verify availability.
values.append("SKIP" if old == "SKIP" else hash_file(file, algorithm))
if values != before[name]:
arrays[name] = values
return arrays
def resolve_release_fields(watch, release, fetch):
values = release["values"].copy()
if "github" in watch and any("{commit}" in value for value in watch.get("variables", {}).values()):
ref = fetch.json(f"https://api.github.com/repos/{watch['github']}/git/ref/tags/{quote(values['tag'], safe='')}")['object']
if ref['type'] == 'tag':
ref = fetch.json(f"https://api.github.com/repos/{watch['github']}/git/tags/{ref['sha']}")['object']
if ref['type'] != 'commit' or not re.fullmatch(r"[0-9a-f]{40}", ref['sha']):
raise ValueError("release tag does not resolve to a commit")
values['commit'] = ref['sha']
variables = {k: template.format_map(values) for k, template in watch.get('variables', {}).items()}
for name, path in watch.get('submodules', {}).items():
entry = fetch.json(f"https://api.github.com/repos/{watch['github']}/contents/{quote(path, safe='/')}?ref={quote(values['tag'], safe='')}")
if not entry.get('submodule_git_url') or not re.fullmatch(r"[0-9a-f]{40}", entry.get('sha', '')):
raise ValueError(f"release does not contain submodule {path}")
variables[name] = entry['sha']
if any(not SCALAR.fullmatch(value) for value in variables.values()):
raise ValueError("unsafe release variable value")
release['variables'] = variables
return release
def sync(package, fetch, min_age=0, check=False):
metadata = json.loads((package / ".omarchy/package.json").read_text())
if metadata.get("sync") is False:
return {"status": "skipped", "reason": "upstream updates held by sync=false"}
watch = metadata["upstream"]["watch"]
validate(watch)
path = package / "PKGBUILD"
original = path.read_text()
before = read_recipe(path)
bypass = os.environ.get("BYPASS_MIN_RELEASE_AGE") == "1"
release = select_release(discover(watch, fetch), min_age, bypass=bypass)
if release is None:
return {"status": "skipped", "reason": "minimum release age"}
current = scalar(before, "pkgver")
if watch.get('sequence'):
prefix, counter, identity = current.rsplit('.', 2)
new_prefix, new_identity = release['values']['version'], release['values']['hash']
if new_prefix == prefix:
release['pkgver'] = current if new_identity == identity else f"{prefix}.{int(counter) + 1}.{new_identity}"
order = vercmp(release["pkgver"], current)
if order < 0:
return {"status": "skipped", "current": current, "available": release["pkgver"], "reason": "upstream is older"}
if order == 0 and not watch.get("revision_variable"):
return {"status": "skipped", "current": current, "reason": "already current"}
release = resolve_release_fields(watch, release, fetch)
changed_variables = {k: v for k, v in release["variables"].items() if scalar(before, k) != v}
if order == 0 and not changed_variables:
return {"status": "skipped", "current": current, "reason": "already current"}
if order == 0 and changed_variables:
# Only a declared, forward-moving release revision can rebuild the same
# version. A changed hash/commit alone is an immutable-release violation.
revision_field = watch.get("revision_variable")
if revision_field not in changed_variables or vercmp(changed_variables[revision_field], scalar(before, revision_field, "0")) <= 0:
raise ValueError("release metadata changed without a newer version/revision")
new_pkgrel = "1" if order > 0 else bump_pkgrel(scalar(before, "pkgrel"))
text = replace_scalar(original, "pkgver", release["pkgver"])
text = replace_scalar(text, "pkgrel", new_pkgrel)
for name, value in release["variables"].items():
text = replace_scalar(text, name, value)
if check:
return {"status": "available", "current": current, "release": release}
scratch = path.with_name("PKGBUILD.sync-upstream")
try:
scratch.write_text(text)
after = read_recipe(scratch)
if scalar(after, "pkgver") != release["pkgver"] or scalar(after, "pkgrel") != new_pkgrel:
raise ValueError("recipe did not retain the release version")
if vercmp(complete_version(after), complete_version(before)) <= 0:
raise ValueError("complete package version must increase")
if before["arch"] != after["arch"]:
raise ValueError("release changed supported architectures")
arrays = updated_checksums(before, after, package, fetch, release, watch)
for name, values in arrays.items():
text = replace_array(text, name, values)
scratch.write_text(text)
subprocess.run(["bash", "-n", str(scratch)], check=True)
for arch in before["arch"]:
result = read_recipe(scratch, "x86_64" if arch == "any" else arch)
if complete_version(result) != complete_version(after):
raise ValueError(f"{arch}: inconsistent release version")
for name, values in arrays.items():
if result.get(name) != values:
raise ValueError(f"{arch}: rewritten {name} differs from the checked source hashes")
for name in after:
if name == "source" or name.startswith("source_"):
if result.get(name) != after[name]:
raise ValueError(f"{arch}: conditional {name} differs from the checked sources; use source_<arch> arrays")
scratch.chmod(path.stat().st_mode)
scratch.replace(path)
return {"status": "updated", "before": complete_version(before), "after": complete_version(after)}
finally:
scratch.unlink(missing_ok=True)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("command", choices=["sync", "check", "validate"])
parser.add_argument("package", type=Path)
parser.add_argument("--min-age", type=int, default=0)
args = parser.parse_args()
package = args.package.resolve()
if args.command == "validate":
validate(json.loads((package / ".omarchy/package.json").read_text())["upstream"]["watch"])
return
with tempfile.TemporaryDirectory(prefix="upstream-watch-") as cache:
fetch = Fetcher(os.environ.get("UPSTREAM_CACHE_DIR", cache))
print(json.dumps(sync(package, fetch, args.min_age, check=args.command == "check")))
if __name__ == "__main__":
try:
main()
except (ValueError, KeyError, TypeError, IndexError, re.error, OSError, subprocess.CalledProcessError) as error:
print(f"upstream watch failed: {error}", file=sys.stderr)
sys.exit(1)
+16 -2
View File
@@ -1,5 +1,19 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "a0f4262f94eb8a5b604146e71d42a3bb522feedf"
"upstream": {
"watch": {
"debian": "https://downloads.1password.com/linux/debian/amd64/dists/beta/main/binary-amd64/Packages",
"package": "1password",
"pattern": "(?P<version>[0-9]+(?:\\.[0-9]+)*)~(?P<build>[0-9]+)\\.BETA",
"version": "{version}_{build}.BETA",
"variables": {
"_tarver": "{version}-{build}.BETA"
}
}
},
"origin": {
"aur": "1password-beta",
"commit": "18d2de51dc01c9a58c1e8e96262f7afadcbf0648"
}
}
@@ -1,83 +0,0 @@
#!/bin/bash
set -euo pipefail
# Keep the AUR x86_64 checksums and add aarch64 sources. The aarch64
# artifacts are signed by 1Password's validpgpkeys, so we skip checksums there
# instead of baking version-specific hashes into Omarchy metadata.
set +u
CARCH=x86_64 source PKGBUILD
set -u
if declare -p sha256sums >/dev/null 2>&1 && [[ ${#sha256sums[@]} -ge 2 ]]; then
x86_sums=("${sha256sums[@]}")
elif declare -p sha256sums_x86_64 >/dev/null 2>&1 && [[ ${#sha256sums_x86_64[@]} -ge 2 ]]; then
x86_sums=("${sha256sums_x86_64[@]}")
else
echo "Unable to read x86_64 checksums from PKGBUILD" >&2
exit 1
fi
sha256_from_url() {
curl -fsSL "$1" | sha256sum | awk '{ print $1 }'
}
arm_url="https://downloads.1password.com/linux/tar/beta/aarch64/1password-${_tarver}.arm64.tar.gz"
arm_tar_sum=$(sha256_from_url "$arm_url")
arm_sig_sum=$(sha256_from_url "$arm_url.sig")
emit_archdir() {
cat <<'EOF'
case "${CARCH}" in
x86_64)
_archdir="x64"
;;
aarch64)
_archdir="arm64"
;;
esac
EOF
}
emit_sources() {
cat <<EOF
source=()
sha256sums=()
source_x86_64=(https://downloads.1password.com/linux/tar/beta/x86_64/1password-\${_tarver}.x64.tar.gz{,.sig})
source_aarch64=(https://downloads.1password.com/linux/tar/beta/aarch64/1password-\${_tarver}.arm64.tar.gz{,.sig})
sha256sums_x86_64=('${x86_sums[0]}'
'${x86_sums[1]}')
sha256sums_aarch64=('$arm_tar_sum'
'$arm_sig_sum')
EOF
}
tmpfile=$(mktemp)
skip_checksums=false
while IFS= read -r line || [[ -n "$line" ]]; do
if [[ "$skip_checksums" == true ]]; then
[[ "$line" == ")" ]] && skip_checksums=false
continue
fi
case "$line" in
'_tar="1password-${_tarver}.x64.tar.gz"')
emit_archdir >> "$tmpfile"
;;
"arch=('x86_64')")
echo "arch=('x86_64' 'aarch64')" >> "$tmpfile"
;;
source=\(*)
emit_sources >> "$tmpfile"
;;
sha256sums=\(*)
[[ "$line" == *")" ]] || skip_checksums=true
;;
*)
line=${line//1password-\$\{_tarver\}.x64/1password-\$\{_tarver\}.\$\{_archdir\}}
printf '%s\n' "$line" >> "$tmpfile"
;;
esac
done < PKGBUILD
mv "$tmpfile" PKGBUILD
+7 -10
View File
@@ -1,6 +1,6 @@
pkgname=1password-beta
_tarver=8.12.34-29.BETA
_tarver=8.12.40-27.BETA
case "${CARCH}" in
x86_64)
_archdir="x64"
@@ -9,8 +9,8 @@ case "${CARCH}" in
_archdir="arm64"
;;
esac
pkgver=${_tarver//-/_}
pkgrel=29.1
pkgver=8.12.40_27.BETA
pkgrel=2
conflicts=('1password' '1password-beta-bin')
pkgdesc="Password manager and secure wallet"
arch=('x86_64' 'aarch64')
@@ -22,10 +22,8 @@ source=()
sha256sums=()
source_x86_64=(https://downloads.1password.com/linux/tar/beta/x86_64/1password-${_tarver}.x64.tar.gz{,.sig})
source_aarch64=(https://downloads.1password.com/linux/tar/beta/aarch64/1password-${_tarver}.arm64.tar.gz{,.sig})
sha256sums_x86_64=('6894b283a534cf94b07903fb38966a0aab2e37f75ee3848ce340308819aadddb'
'8d4df4d0a80d2be7aad7d91ad964a750c8f32db5007261045003c191690c8246')
sha256sums_aarch64=('c77ce6ddf36dbd6054c64d91b7f644274d3218f465fd60e211d0296f6443124a'
'91c7249a1cf5e7924ef2810cb0cb1b893d8a9a424b373b433f45d7aaa5a8369b')
sha256sums_x86_64=('1327d102255b5c347e6c5e19b1a6581986446e06848015b93b5d7b10b9bc3f52' '2a19fba2b81ade500d9707a20829930d4fca972254fdc653ffa27cee57638098')
sha256sums_aarch64=('54b14cae2a676480f3f2df7b85e42bf389d1f84207b0a4ad1e32382071625146' '3a55dc9cdee5729e4bdf6830e04d9813c11546b328003800a837df83f79e33f9')
validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22')
package() {
@@ -42,7 +40,7 @@ package() {
"${pkgdir}/usr/share/icons/hicolor/${resolution}/apps/1password.png"
done
# Install desktop file
install -Dm0644 resources/1password.desktop -t "${pkgdir}"/usr/share/applications/
install -Dm0644 resources/com.onepassword.OnePassword.desktop -t "${pkgdir}"/usr/share/applications/
# Fill in policy kit file with a list of (the first 10) human users of the system.
export POLICY_OWNERS
@@ -65,8 +63,7 @@ EOF" > ./com.1password.1Password.policy
# Cleanup un-needed files
rm "${pkgdir}"/opt/1Password/com.1password.1Password.policy "${pkgdir}"/opt/1Password/com.1password.1Password.policy.tpl "${pkgdir}"/opt/1Password/install_biometrics_policy.sh
rm -r "${pkgdir}"/opt/1Password/resources/icons/
rm "${pkgdir}"/opt/1Password/resources/1password.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers
rm "${pkgdir}"/opt/1Password/resources/com.onepassword.OnePassword.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers
# Symlink /usr/bin executable to opt
install -dm0755 "${pkgdir}"/usr/bin
ln -s /opt/1Password/1password "${pkgdir}"/usr/bin/1password
+11 -2
View File
@@ -1,5 +1,14 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "b0d208821677a5dbb883a8b92f06a5c92b9e861a"
"upstream": {
"watch": {
"json": "https://app-updates.agilebits.com/check/1/0/CLI2/en/0",
"path": "version"
}
},
"origin": {
"aur": "1password-cli",
"commit": "b0d208821677a5dbb883a8b92f06a5c92b9e861a"
}
}
+7 -7
View File
@@ -2,8 +2,8 @@
# Contributors: Felix Seidel, Claudia Pellegrino, Liu Yuxuan
pkgname=1password-cli
pkgver=2.39.0
pkgrel=1
pkgver=2.40.0
pkgrel=2
pkgdesc="1Password command line tool"
arch=('x86_64' 'i686' 'arm' 'armv6h' 'aarch64')
url="https://app-updates.agilebits.com/product_history/CLI2"
@@ -18,11 +18,11 @@ source_arm=("https://cache.agilebits.com/dist/1P/op2/pkg/v${pkgver}/op_linux_arm
source_armv6h=("${source_arm}")
source_aarch64=("https://cache.agilebits.com/dist/1P/op2/pkg/v${pkgver}/op_linux_arm64_v${pkgver}.zip")
sha256sums_x86_64=('6fba7f376b6c6dec49f41b06408930a43ad064cce103c6a2ce5b3d0413a86434')
sha256sums_i686=('387d95f046ec9334e9de63514f96767fdd5dacbae292eb086fdfa0b0da310ec4')
sha256sums_arm=('673913f24ff57ce43e9d25ee451121d4733d188f45f6ab0468983fad85f8cc42')
sha256sums_armv6h=("${sha256sums_arm}")
sha256sums_aarch64=('829baeff1c07e055cfa132031b1d9f2282ccdf5076258e482caf2fda70aea5d0')
sha256sums_x86_64=('74277219e8da60958c00f9aee9d2023225e98fdda8bfd2156a5d9e85e0edaab3')
sha256sums_i686=('adb1da45c0a40bb97c1e1ad62119d0a1aeb4ed9d979c06a7b1f95a48d9142b3d')
sha256sums_arm=('cb9a2e938b542eac668e847bf555293549ad6ecc014216c99a66f1348728e354')
sha256sums_armv6h=('cb9a2e938b542eac668e847bf555293549ad6ecc014216c99a66f1348728e354')
sha256sums_aarch64=('0e8ac99ee93d661aa725dc24a5ef8bf344741d224064a5dfb469dc689faec86a')
check() {
if (( ! SKIPPGPCHECK )); then
+15 -2
View File
@@ -1,5 +1,18 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "e323d0d1f8dea6b75bb651ce14acc73904cd0326"
"upstream": {
"debian": "https://downloads.1password.com/linux/debian/amd64/dists/stable/main/binary-amd64/Packages",
"package": "1password",
"sources": {
"x86_64": [
"https://downloads.1password.com/linux/tar/stable/x86_64/1password-{pkgver}.x64.tar.gz",
"https://downloads.1password.com/linux/tar/stable/x86_64/1password-{pkgver}.x64.tar.gz.sig"
],
"aarch64": [
"https://downloads.1password.com/linux/tar/stable/aarch64/1password-{pkgver}.arm64.tar.gz",
"https://downloads.1password.com/linux/tar/stable/aarch64/1password-{pkgver}.arm64.tar.gz.sig"
]
}
}
}
+40 -13
View File
@@ -1,27 +1,47 @@
pkgname=1password
_tarver=8.12.34
_tar="1password-${_tarver}.x64.tar.gz"
pkgver=${_tarver//-/_}
pkgrel=34
pkgver=8.12.40
pkgrel=2
conflicts=('1password-beta' '1password-beta-bin')
pkgdesc="Password manager and secure wallet"
arch=('x86_64')
arch=('x86_64' 'aarch64')
url='https://1password.com'
license=('LicenseRef-1Password-Proprietary')
options=(!strip)
install="1password.install"
source=(https://downloads.1password.com/linux/tar/stable/${CARCH}/${_tar}{,.sig})
sha256sums=('297784aa66770b645607a7f04c9ba2c4aebed4f46d21202487f521ba572b7b13'
'ec085bef60de748895d3c51a8208301ba2ac8fb47db99334539ba4bd1d3260d7'
source_x86_64=(
"https://downloads.1password.com/linux/tar/stable/x86_64/1password-${pkgver}.x64.tar.gz"
"https://downloads.1password.com/linux/tar/stable/x86_64/1password-${pkgver}.x64.tar.gz.sig"
)
source_aarch64=(
"https://downloads.1password.com/linux/tar/stable/aarch64/1password-${pkgver}.arm64.tar.gz"
"https://downloads.1password.com/linux/tar/stable/aarch64/1password-${pkgver}.arm64.tar.gz.sig"
)
sha256sums_x86_64=(
'0ae9645d31be78a8fb57d15f49e5fa4f0b67a0b3608797a410e8fd36f0206266'
'2f777820dc2eb6e7b7b38f4557a897f97fb3259443261fdfb2008127bd975817'
)
sha256sums_aarch64=(
'7476c0fc8215338cd9f304b14822688010fd8ed54d5ea4367c0bbbf72845be1e'
'80412176560a3f76180f7c05ae7ebafe21fac764349cfeed71fe498ee25564ee'
)
validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22')
package() {
depends=('hicolor-icon-theme' 'libgtk-3.so=0' 'nss' 'xdg-utils')
# 1Password names its tarballs by a vendor architecture suffix. arch=()
# above already limits which architectures makepkg builds, so no fallback
# branch is needed here; keeping this inside package() means sourcing the
# PKGBUILD without CARCH (as the version check does) still reads the
# version correctly.
local _tararch
case "$CARCH" in
x86_64) _tararch=x64 ;;
aarch64) _tararch=arm64 ;;
esac
# Go to source directory
cd "1password-${_tarver}.x64"
cd "1password-${pkgver}.${_tararch}"
# Install icons
resolutions=(32x32 64x64 256x256 512x512)
@@ -31,7 +51,14 @@ package() {
"${pkgdir}/usr/share/icons/hicolor/${resolution}/apps/1password.png"
done
# Install desktop file
install -Dm0644 resources/1password.desktop -t "${pkgdir}"/usr/share/applications/
install -Dm0644 resources/com.onepassword.OnePassword.desktop \
"${pkgdir}/usr/share/applications/1password.desktop"
# 1Password reads the display scale itself, the way Electron apps do, and
# comes up oversized next to every other window on a scaled monitor. Pin it
# and let the compositor do the scaling.
sed -i 's|^Exec=.*|Exec=/opt/1Password/1password --force-device-scale-factor=1 %U|' \
"${pkgdir}/usr/share/applications/1password.desktop"
# Fill in policy kit file with a list of (the first 10) human users of the system.
export POLICY_OWNERS
@@ -49,12 +76,12 @@ EOF" > ./com.1password.1Password.policy
# Move package contents to /opt/1Password
cd "${srcdir}"
install -dm0755 "${pkgdir}"/opt
mv "1password-${_tarver}.x64" "${pkgdir}/opt/1Password"
mv "1password-${pkgver}.${_tararch}" "${pkgdir}/opt/1Password"
# Cleanup un-needed files
rm "${pkgdir}"/opt/1Password/com.1password.1Password.policy "${pkgdir}"/opt/1Password/com.1password.1Password.policy.tpl "${pkgdir}"/opt/1Password/install_biometrics_policy.sh
rm -r "${pkgdir}"/opt/1Password/resources/icons/
rm "${pkgdir}"/opt/1Password/resources/1password.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers
rm "${pkgdir}"/opt/1Password/resources/com.onepassword.OnePassword.desktop "${pkgdir}"/opt/1Password/resources/custom_allowed_browsers
# Symlink /usr/bin executable to opt
install -dm0755 "${pkgdir}"/usr/bin
+11 -2
View File
@@ -1,5 +1,14 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "04592d5713f9c09ea2d81cf4b8476714d80014bc"
"upstream": {
"watch": {
"github": "omacom/aether",
"pattern": "v(?P<version>[0-9]+(?:\\.[0-9]+)*)"
}
},
"origin": {
"aur": "aether",
"commit": "0f047f40a200121075ca3cedce59ddf226f2a0f1"
}
}
+9 -9
View File
@@ -1,18 +1,18 @@
# Maintainer: Bjarne Øverli <bjarne@oever.li>
pkgname=aether
pkgver=4.29.6
pkgrel=1
pkgver=4.32.0
pkgrel=2
pkgdesc='Desktop theming application - extract colors from wallpapers and apply cohesive themes'
arch=('x86_64' 'aarch64')
url='https://github.com/omacom-io/aether'
url='https://github.com/omacom/aether'
license=('MIT')
depends=('webkit2gtk-4.1' 'gtk3')
source=("aether-${pkgver}.tar.gz::https://github.com/omacom-io/aether/archive/refs/tags/v${pkgver}.tar.gz")
source_x86_64=("aether-linux-amd64-${pkgver}::https://github.com/omacom-io/aether/releases/download/v${pkgver}/aether-linux-amd64")
source_aarch64=("aether-linux-arm64-${pkgver}::https://github.com/omacom-io/aether/releases/download/v${pkgver}/aether-linux-arm64")
sha256sums=('8b2e97283007c9eefda879e17c5fecb9d59fc90bf2160af93eeba57f11fcdb25')
sha256sums_x86_64=('47b5afa4144b3a3cd7755524956ffe6b074ed5f2f90dadda23e424efeaf88790')
sha256sums_aarch64=('52d1ffb201970ddb6205882a8e1c583cae63470d4180f4bc2fc6298dfb71ddd9')
source=("aether-${pkgver}.tar.gz::https://github.com/omacom/aether/archive/refs/tags/v${pkgver}.tar.gz")
source_x86_64=("aether-linux-amd64-${pkgver}::https://github.com/omacom/aether/releases/download/v${pkgver}/aether-linux-amd64")
source_aarch64=("aether-linux-arm64-${pkgver}::https://github.com/omacom/aether/releases/download/v${pkgver}/aether-linux-arm64")
sha256sums=('3234e5138a46699f8f47330ba582244d5063c8356ffaae1458323e4dda02be82')
sha256sums_x86_64=('c705a4abef734b17ae37cdbff58a9a796517e6aa5f93ef18b68e3ee99d547ff9')
sha256sums_aarch64=('f70195deba008204d58661c3ec1442a16e63f3358b4579969facb0e55dbf63c4')
noextract=("aether-linux-amd64-${pkgver}" "aether-linux-arm64-${pkgver}")
package() {
@@ -0,0 +1,6 @@
{
"source": "local",
"channels": [
"edge"
]
}
@@ -0,0 +1,36 @@
From 375e80ec3826b54618fdd5f2db708c0f2bb936ae Mon Sep 17 00:00:00 2001
From: Marcelo Alcantara <maralc@gmail.com>
Date: Wed, 16 Sep 2026 00:39:04 +1000
Subject: [PATCH] drm: re-read possible CRTCs when rescanning connectors
A driver can change a connector's possible CRTCs at runtime. Apple's DCP
driver narrows a Type-C port's encoder to the display pipeline the fabric
routed it to and relies on the following hotplug for userspace to re-read
it. possibleCrtcs was only read when the connector was first created, so a
rerouted port kept a stale mask and was never assigned its now-free CRTC
until the compositor restarted.
---
src/backend/drm/DRM.cpp | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/backend/drm/DRM.cpp b/src/backend/drm/DRM.cpp
index 6618a26..b492dac 100644
--- a/src/backend/drm/DRM.cpp
+++ b/src/backend/drm/DRM.cpp
@@ -1287,6 +1287,13 @@ void Aquamarine::CDRMBackend::scanConnectors() {
} else {
backend->log(AQ_LOG_DEBUG, std::format("drm: Connector id {} already initialized", connectorID));
conn = *it;
+
+ // drivers may narrow or widen these on hotplug, e.g. when a Type-C port is rerouted to another pipeline
+ const auto possibleCrtcs = drmModeConnectorGetPossibleCrtcs(gpu->fd, drmConn);
+ if (possibleCrtcs && possibleCrtcs != conn->possibleCrtcs) {
+ backend->log(AQ_LOG_DEBUG, std::format("drm: Connector {} possible CRTCs changed {:#x} -> {:#x}", conn->szName, conn->possibleCrtcs, possibleCrtcs));
+ conn->possibleCrtcs = possibleCrtcs;
+ }
}
conn->status = drmConn->connection;
--
2.50.1 (Apple Git-155)
+63
View File
@@ -0,0 +1,63 @@
# Maintainer: Caleb Maclennan <caleb@alerque.com>
# Contributor: Aaron Blasko <blaskoazzolaaaron [at] gmail.com>
#
# Arch's aquamarine 0.15.1-1 plus one patch, carried on edge for Apple Silicon:
# re-read a connector's possible CRTCs when rescanning, so a Type-C port the
# DCP fabric reroutes to another display pipeline gets a CRTC without
# restarting the compositor. Drop the carry once hyprwm releases the fix.
pkgname=aquamarine
pkgver=0.15.1
pkgrel=1.1
pkgdesc='a very light linux rendering backend library'
arch=(aarch64)
url="https://github.com/hyprwm/$pkgname"
license=(BSD-3-Clause)
depends=(libgcc
libstdc++
glibc # libc.so libm.so
hyprutils libhyprutils.so
libdisplay-info libdisplay-info.so
libdrm # libdrm.so
libglvnd libEGL.so
libinput # libinput.so
mesa # libgbm.so
opengl-driver
pixman
seatd libseat.so
systemd-libs libudev.so
wayland libwayland-client.so
wayland-protocols)
makedepends=(cmake
hyprwayland-scanner)
provides=("lib$pkgname.so")
_archive="$pkgname-$pkgver"
source=("$url/archive/v$pkgver/$_archive.tar.gz"
0001-drm-re-read-possible-CRTCs-when-rescanning-connectors.patch)
sha256sums=('2f9de98c0bd1b7b1b09c576e390a2fef436449762fb334163c414f0c300296f2'
'50e9e38ff915b18074dc9b5dd1d07d7f24e340e99f254476d6abe578eece7864')
prepare() {
cd "$_archive"
patch -Np1 -i ../0001-drm-re-read-possible-CRTCs-when-rescanning-connectors.patch
}
build() {
cd "$_archive"
# cc1plus needs more than 8 MiB of stack for DRM.cpp. GCC raises its own
# limit natively, but under QEMU user-mode emulation (the aarch64 builder)
# the guest stack is fixed at exec and setrlimit is ignored, so the compiler
# segfaults. QEMU reads this at exec; native builds ignore it. The object
# code is identical either way.
export QEMU_STACK_SIZE=64M
cmake -B build \
-D CMAKE_INSTALL_PREFIX=/usr \
-D CMAKE_BUILD_TYPE=Release
cmake --build build
}
package() {
cd "$_archive"
DESTDIR="$pkgdir" cmake --install build
install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname" LICENSE
}
+1 -1
View File
@@ -4,7 +4,7 @@ epoch=1
pkgver=0.6.0
pkgrel=2
pkgdesc="Control brightness on Apple Displays connected via USB-C"
arch=('x86_64')
arch=('x86_64' 'aarch64')
url="https://github.com/omakasui/asdcontrol"
license=('GPL2')
options=('!debug')
-4
View File
@@ -1,4 +0,0 @@
[asusctl]
source = "git"
git = "https://gitlab.com/asus-linux/asusctl.git"
-4
View File
@@ -1,4 +0,0 @@
{
"source": "aur",
"upstream_commit": "ed336f7dbd5e8bee333aeaa6246da89241f1c8e6"
}
@@ -1,18 +0,0 @@
diff --git a/PKGBUILD b/PKGBUILD
index 55d0c22..8897aec 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -6,7 +6,7 @@ pkgname=(
asusctl
rog-control-center
)
-pkgver=6.3.8
+pkgver=6.4.0
pkgrel=1
pkgdesc="A control daemon, tools, and a collection of crates for interacting with ASUS ROG laptops"
arch=('x86_64')
@@ -28,2 +28,2 @@
-source=("git+https://gitlab.com/asus-linux/asusctl.git#tag=$pkgver")
-b2sums=('01269bc9fe63c1ce37568f9085c25dbcaaaa5cd9f51339d4c6904600d179ec826dace289d6e4e3501988fce5800da98e9cfa18b21c40da152d91c4f7fe821ffe')
+source=("git+https://github.com/opengamingcollective/asusctl.git#tag=$pkgver")
+b2sums=('0cfdc7f792fc6499cde9e4c9c6d82e484605e1b2878bb9f3a0e0696f5910b9ba7898f124fecd5fb3f39a0787f81e5163721606290099d757365dc3d6715a1bcf')
-108
View File
@@ -1,108 +0,0 @@
# Maintainer: Fabian Bornschein <fabiscafe@archlinux.org>
# Contributor: Static_Rocket
pkgbase=asusctl
pkgname=(
asusctl
rog-control-center
)
pkgver=6.4.0
pkgrel=1.1
pkgdesc="A control daemon, tools, and a collection of crates for interacting with ASUS ROG laptops"
arch=('x86_64')
url="https://asus-linux.org"
license=('MPL-2.0')
makedepends=(
clang
cmake
fontconfig
git
hicolor-icon-theme
libayatana-appindicator
libinput
libusb
rust
seatd
systemd
)
source=("git+https://github.com/opengamingcollective/asusctl.git#tag=$pkgver")
b2sums=('0cfdc7f792fc6499cde9e4c9c6d82e484605e1b2878bb9f3a0e0696f5910b9ba7898f124fecd5fb3f39a0787f81e5163721606290099d757365dc3d6715a1bcf')
prepare() {
cd "${pkgbase}"
# Keep rust/cargo build-dependency management inside the build directory
export CARGO_HOME="${srcdir}/cargo"
# Follow Rust package guidelines
## https://wiki.archlinux.org/title/Rust_package_guidelines
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "${pkgbase}"
# Keep rust/cargo build-dependency management inside the build directory
export CARGO_HOME="${srcdir}/cargo"
# Follow Rust package guidelines
## https://wiki.archlinux.org/title/Rust_package_guidelines
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
make build
}
package_asusctl() {
pkgdesc="${pkgdesc/tools/CLI tools}"
depends=(
glibc
libgcc
libusb
systemd
systemd-libs
)
conflicts=(gnome-shell-extension-asusctl-gnome)
install=asusctl.install
optdepends=(
'acpi_call: fan control'
'asusctltray: tray profile switcher'
'rog-control-center: app to control asusctl'
'supergfxctl: hybrid GPU control'
)
cd "${pkgbase}"
export CARGO_HOME="${srcdir}/cargo"
make DESTDIR="${pkgdir}" \
install-asusctl \
install-asusd \
install-asusd_user \
install-asus-shutdown \
install-data-asusd \
install-data-asusd_user
}
package_rog-control-center() {
depends=(
asusctl
fontconfig
freetype2
glibc
hicolor-icon-theme
libayatana-appindicator
libgcc
libinput
libxkbcommon
mesa
seatd
systemd-libs
)
pkgdesc="App to control asusctl"
cd "${pkgbase}"
export CARGO_HOME="${srcdir}/cargo"
make DESTDIR="${pkgdir}" \
install-data-rog_gui \
install-rog_gui
}
-19
View File
@@ -1,19 +0,0 @@
post_install() {
printf ":: asusd provides a service that is activated by an udev rule on\n"
printf ":: startup. Please reboot the system or run\n"
printf ":: # systemctl start asusd.service\n"
printf ":: to make it work.\n"
printf ":: See https://gitlab.com/asus-linux/asusctl#kernel-support.\n"
printf ":: for latest required kernel patches/versions\n"
}
post_upgrade() {
if systemctl is-active asusd.service --quiet
then
printf ":: asusd service will be restarted…\n"
systemctl daemon-reload
systemctl restart asusd.service
fi
printf ":: See https://gitlab.com/asus-linux/asusctl#kernel-support.\n"
printf ":: for latest required kernel patches/versions\n"
}
+9
View File
@@ -0,0 +1,9 @@
{
"source": "local",
"upstream": {
"watch": {
"pattern": "v(?P<version>[0-9]+(?:\\.[0-9]+)*)",
"git_tags": "https://github.com/AsahiLinux/avd-fw.git"
}
}
}
+51
View File
@@ -0,0 +1,51 @@
# Open replacement firmware for the Apple Video Decoder.
# Built from AsahiLinux sources, without extracting proprietary firmware.
pkgname=avd-fw
pkgver=0.1
pkgrel=1
pkgdesc='Open replacement firmware for the Apple Video Decoder (AVD) on Apple Silicon'
# This recipe uses the native ARM linker and serves Apple Silicon systems.
# Restrict both builds and publication to aarch64.
arch=('aarch64')
url='https://github.com/AsahiLinux/avd-fw'
license=('MIT')
# clang cross-compiles to arm-none-eabi out of the box, so no arm-none-eabi
# toolchain is required; llvm supplies llvm-objcopy, which meson.build looks
# up by name to turn each linked ELF into a padded raw image.
makedepends=('meson' 'clang' 'llvm')
# !buildflags is load-bearing: makepkg's CFLAGS/LDFLAGS target the aarch64 host
# and would be injected into a bare-metal Cortex-M3 build. !strip keeps makepkg
# from running the host strip over raw firmware images.
options=('!strip' '!debug' '!buildflags' '!lto')
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('2e131244275cb15c94243e41eec8a2a665ec895cfe3a818181549db991e62c67')
build() {
# The cross file selects clang, pins the host machine to cortex-m3 and sets
# libdir=lib, which lands the images in /usr/lib/firmware/apple.
#
# Optimization is pinned to match upstream's Makefile (-O2). Meson's default
# buildtype is "debug" (-O0 -g), which would ship unoptimized firmware; the
# MMIO accessors in src/util.c go through a volatile typedef, so -O2 cannot
# elide register reads or writes.
meson setup \
--cross-file "$pkgname-$pkgver/llvm.ini" \
--prefix=/usr \
-Doptimization=2 \
-Ddebug=false \
"$pkgname-$pkgver" build
meson compile -C build
}
package() {
meson install -C build --destdir "$pkgdir"
# meson installs custom_target outputs 0755; these are firmware blobs, not
# programs, and every other firmware file on the system is 0644.
chmod 644 "$pkgdir"/usr/lib/firmware/apple/*.bin
install -Dm644 "$pkgname-$pkgver/LICENSE" \
"$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}
@@ -0,0 +1,17 @@
{
"source": "local",
"release_ring": "fast",
"origin": {
"aur": "bambustudio-bin",
"commit": "b962c12d14873f94669e0e256a444f957b1843cd"
},
"upstream": {
"watch": {
"regex": "https://api.github.com/repos/bambulab/BambuStudio/releases/latest",
"pattern": "\"name\"\\s*:\\s*\"BambuStudio_ubuntu24\\.04-v(?P<version>[0-9]+(?:\\.[0-9]+)*)-(?P<build>[0-9]+)\\.AppImage\"",
"variables": {
"_build": "{build}"
}
}
}
}
@@ -0,0 +1,12 @@
[Desktop Entry]
Name=Bambu Studio
GenericName=3D Printing Software
Comment=Slicer for Bambu Lab and other 3D printers
Exec=/usr/bin/bambu-studio %U
Icon=BambuStudio
Terminal=false
Type=Application
Categories=Graphics;3DGraphics;Engineering;
MimeType=x-scheme-handler/bambustudio;x-scheme-handler/bambustudioopen;model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;
Keywords=3D;Printing;Slicer;gcode;stl;3mf;
StartupWMClass=bambu-studio
+48
View File
@@ -0,0 +1,48 @@
# Maintainer: goll <adrian.goll+aur[at]gmail>
# Contributor: George Woodall <georgewoodall82@gmail.com>
pkgname=bambustudio-bin
pkgver=02.08.02.61
pkgrel=1
pkgdesc="PC Software for BambuLab's 3D printers"
arch=("x86_64")
url="https://github.com/bambulab/BambuStudio"
license=('AGPL-3.0-only')
conflicts=('bambustudio' 'bambustudio-git')
depends=('cairo' 'dbus' 'fontconfig' 'gcc-libs' 'glib2' 'glibc'
'gst-libav' 'gst-plugins-base-libs' 'gstreamer' 'gtk3' 'libglvnd'
'libx11' 'mesa' 'pango' 'wayland' 'webkit2gtk-4.1')
makedepends=('7zip')
options=('!strip' '!debug')
# The upstream watch updates the timestamp together with pkgver.
_build=20260820225108
source=("bambustudio-${pkgver}.AppImage::https://github.com/bambulab/BambuStudio/releases/download/v${pkgver}/BambuStudio_ubuntu24.04-v${pkgver}-${_build}.AppImage"
"BambuStudio.desktop"
"bambu-studio")
noextract=("bambustudio-${pkgver}.AppImage")
sha256sums=(
'd501b103fac5424513ec0e8d6bc145fb30719de2c7d94d7320d723740c81a7fd'
'f10718a8b201cad64800746fe8167ccc032c545d05f7ad8caa99eb5fb975f2a1'
'a3a5c8f6a8b287e42b93957e9602621923c766e0b6a3f10c14eca10b023b15f2'
)
prepare() {
# Read the embedded SquashFS without executing or modifying the AppImage.
rm -rf "$srcdir/squashfs-root"
7z x "$srcdir/bambustudio-${pkgver}.AppImage" -o"$srcdir/squashfs-root" >/dev/null
}
package() {
cd "$srcdir/squashfs-root"
install -Dm755 AppRun "$pkgdir/opt/$pkgname/AppRun"
cp -a bin resources "$pkgdir/opt/$pkgname/"
local icon size
for icon in usr/share/icons/hicolor/*/apps/BambuStudio.png; do
size="${icon#usr/share/icons/hicolor/}"
install -Dm644 "$icon" "$pkgdir/usr/share/icons/hicolor/$size"
done
install -Dm755 "$srcdir/bambu-studio" "$pkgdir/usr/bin/bambu-studio"
install -Dm644 "$srcdir/BambuStudio.desktop" \
"$pkgdir/usr/share/applications/BambuStudio.desktop"
}
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
exec "/opt/bambustudio-bin/AppRun" "$@"
+11 -2
View File
@@ -1,5 +1,14 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "fe2a6345e271f618dc3adf31e48557a717866395"
"upstream": {
"watch": {
"github": "basecamp/basecamp-cli",
"pattern": "v(?P<version>[0-9]+(?:\\.[0-9]+)*)"
}
},
"origin": {
"aur": "basecamp-cli",
"commit": "89c4eb5a0ac9ffe98aecd4ea8b789cf1fee42bf1"
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
# Maintainer: Basecamp <support@basecamp.com>
pkgname=basecamp-cli
pkgver=0.9.1
pkgver=0.12.0
pkgrel=1
pkgdesc="CLI for Basecamp project management"
arch=('x86_64' 'aarch64')
@@ -13,10 +13,10 @@ optdepends=(
'zsh: for zsh shell completions'
'fish: for fish shell completions'
)
source_x86_64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.9.1/basecamp_${pkgver}_linux_amd64.tar.gz")
source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.9.1/basecamp_${pkgver}_linux_arm64.tar.gz")
sha256sums_x86_64=('bcb1fa3a03e702bbf81a3004ded4bc7808605e41106cbc768ba4006b89e0db2f')
sha256sums_aarch64=('ff50313024b6ca14e40146e30e1c9c9c00a4e4103fe55615eeee3e7c359d88d1')
source_x86_64=("https://github.com/basecamp/basecamp-cli/releases/download/v${pkgver}/basecamp_${pkgver}_linux_amd64.tar.gz")
source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v${pkgver}/basecamp_${pkgver}_linux_arm64.tar.gz")
sha256sums_x86_64=('25d61c38de5660e97855661a2bf7329264912fcd46e2b3fb893fbc70bb467b5c')
sha256sums_aarch64=('1f692d2a8cc733ef95232eeb107414e32f1dd3228a0dde6c9039538661e0f2ca')
package() {
install -Dm755 "basecamp" "${pkgdir}/usr/bin/basecamp"
+48
View File
@@ -0,0 +1,48 @@
# Maintainer: Ryan Hughes <ryan@omarchy.org>
# Contributor: Bart De Vries <bart at mogwai dot be>
# Contributor: Dan Johansen <strit@manjaro.org>
#
# Runs x86_64-only Linux programs (dropbox) on AArch64. Pinned past v0.4.5-1:
# that release crashes in its glib wrapper as soon as Dropbox starts its tray
# icon. Move to a release tag once one includes the pinned commit.
pkgname=box64
pkgver=0.4.5.1.r309.gd58d619
pkgrel=1
_commit=d58d619e84e03282326e8a26c2cbfe749931b5f8
pkgdesc='Linux userspace x86_64 emulator with native library wrapping'
arch=('aarch64')
url='https://github.com/ptitSeb/box64'
license=('MIT')
depends=('gcc-libs' 'glibc')
makedepends=('cmake' 'python')
backup=('etc/box64.box64rc')
options=('!strip' '!emptydirs')
source=("$pkgname-$_commit.tar.gz::$url/archive/$_commit.tar.gz")
sha256sums=('f7615a3c4ac5412a983a3fd26be79311e6e2702884d8bdfde954b23bad39fff8')
build() {
# ARM64 is box64's generic AArch64 profile, so the binary does not depend
# on the CPU of the machine that built it.
cmake -S "$pkgname-$_commit" -B build \
-DARM64=ON \
-DARM_DYNAREC=ON \
-DNOGIT=ON \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/usr
cmake --build build --parallel
}
package() {
DESTDIR="$pkgdir" cmake --install build
# Packages call box64 explicitly. A binfmt handler would compete with
# qemu-user-static-binfmt for every x86_64 executable on the system.
rm -r "$pkgdir/etc/binfmt.d"
# The Qt rcfile editor needs PySide, and its launcher entry would show up
# on every system that installs box64 only as a dependency.
rm "$pkgdir/usr/bin/box64-configurator" \
"$pkgdir/usr/share/applications/box64-configurator.desktop"
install -Dm644 "$pkgname-$_commit/LICENSE" -t "$pkgdir/usr/share/licenses/$pkgname"
}
+15
View File
@@ -0,0 +1,15 @@
{
"source": "local",
"release_ring": "fast",
"upstream": {
"watch": {
"debian": "https://brave-browser-apt-release.s3.brave.com/dists/stable/main/binary-amd64/Packages",
"package": "brave-browser",
"pattern": "(?P<version>[0-9]+(?:\\.[0-9]+)*)"
}
},
"origin": {
"aur": "brave-bin",
"commit": "d86e5479e163dac19d7ace3de472710e6eb06eea"
}
}
+62
View File
@@ -0,0 +1,62 @@
# Maintainer: brave <aur-release@brave.com>
# Contributor: Caleb Maclennan <caleb@alerque.com>
# Contributor: José Miguel Sarasola <jmsaraur@gmail.com>
# Contributor: Như Bảo Trương <28810481+nhubaotruong@users.noreply.github.com>
# Contributor: Andrés Rodríguez <hello@andres.codes>
# Contributor: Jacob Mischka <jacob@mischka.me>
# Contributor: Manuel Mazzuola <origin.of@gmail.com>
# Contributor: Simón Oroño <simonorono@protonmail.com>
# Contributor: now-im <now im 627 @ gmail . com>
# Contributor: Giusy Digital <kurmikon at libero dot it>
pkgname=brave-bin
pkgver=1.96.61
pkgrel=2
epoch=1
pkgdesc='Web browser that blocks ads and trackers by default (binary release)'
arch=(x86_64 aarch64)
url=https://brave.com
license=(MPL2 BSD custom:chromium)
depends=(alsa-lib
gtk3
libxss
nss
ttf-font)
optdepends=('cups: Printer support'
'libgnome-keyring: Enable GNOME keyring support'
'libnotify: Native notification support')
provides=("${pkgname%-bin}=$pkgver" 'brave-browser')
conflicts=("${pkgname%-bin}")
options=(!strip)
source=($pkgname.sh brave-browser.desktop)
source_x86_64=(${pkgname}-${pkgver}-x86_64.zip::https://github.com/brave/brave-browser/releases/download/v${pkgver}/brave-browser-${pkgver}-linux-amd64.zip)
source_aarch64=(${pkgname}-${pkgver}-aarch64.zip::https://github.com/brave/brave-browser/releases/download/v${pkgver}/brave-browser-${pkgver}-linux-arm64.zip)
noextract=(${pkgname}-${pkgver}-x86_64.zip ${pkgname}-${pkgver}-aarch64.zip)
sha256sums=('75a87dd17b42fcc6f27adfd16c82bed1c08e9251b07d2012f8d49f7412fa1d00'
'c07276b69c7304981525ecb022f92daf7ae125a4fb05ac3442157b50826e257a')
sha256sums_x86_64=('c68cf179603470e8001a949294fe98b593f0749ca95f42687d855081b1c394a4')
sha256sums_aarch64=('33a1513379505642e4df0dda36a1dd78f735ee351631cffd4a4ac90b1cec0cd5')
prepare() {
mkdir -p brave
bsdtar -xf "$pkgname-$pkgver-$CARCH.zip" -C brave
chmod +x brave/brave
}
package() {
install -dm0755 "$pkgdir/opt"
cp -a brave "$pkgdir/opt/$pkgname"
# allow firejail users to get the suid sandbox working
chmod 4755 "$pkgdir/opt/brave-bin/chrome-sandbox"
install -Dm0755 "$pkgname.sh" "$pkgdir/usr/bin/brave"
install -Dm0644 -t "$pkgdir/usr/share/applications/" "brave-browser.desktop"
install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" brave/LICENSE
pushd "$pkgdir/usr/"
for size in 16x16 24x24 32x32 48x48 64x64 128x128 256x256; do
install -Dm0644 "$pkgdir/opt/$pkgname/product_logo_${size/x*/}.png" \
"share/icons/hicolor/$size/apps/brave-desktop.png"
done
}
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-"${HOME}/.config"}"
CONF_FILE="${XDG_CONFIG_HOME}/brave-flags.conf"
if
test -f "${CONF_FILE}"
then
mapfile -t CONF_LIST < "${CONF_FILE}"
fi
for CONF_LINE in "${CONF_LIST[@]}"
do
if ! [[
"${CONF_LINE}" =~ ^[[:space:]]*(#|$)
]]
then
FLAG_LIST+=("${CONF_LINE}")
fi
done
export CHROME_VERSION_EXTRA='stable'
exec /opt/brave-bin/brave "${FLAG_LIST[@]}" "${@}"
+224
View File
@@ -0,0 +1,224 @@
[Desktop Entry]
Version=1.0
Name=Brave
# Only KDE 4 seems to use GenericName, so we reuse the KDE strings.
# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413.
GenericName=Web Browser
GenericName[ar]=متصفح الشبكة
GenericName[bg]=Уеб браузър
GenericName[ca]=Navegador web
GenericName[cs]=WWW prohlížeč
GenericName[da]=Browser
GenericName[de]=Web-Browser
GenericName[el]=Περιηγητής ιστού
GenericName[en_GB]=Web Browser
GenericName[es]=Navegador web
GenericName[et]=Veebibrauser
GenericName[fi]=WWW-selain
GenericName[fr]=Navigateur Web
GenericName[gu]=વેબ બ્રાઉઝર
GenericName[he]=דפדפן אינטרנט
GenericName[hi]=वेब ब्राउज़र
GenericName[hu]=Webböngésző
GenericName[it]=Browser Web
GenericName[ja]=ウェブブラウザ
GenericName[kn]=ಜಾಲ ವೀಕ್ಷಕ
GenericName[ko]=웹 브라우저
GenericName[lt]=Žiniatinklio naršyklė
GenericName[lv]=Tīmekļa pārlūks
GenericName[ml]=വെബ് ബ്രൌസര്‍
GenericName[mr]=वेब ब्राऊजर
GenericName[nb]=Nettleser
GenericName[nl]=Webbrowser
GenericName[pl]=Przeglądarka WWW
GenericName[pt]=Navegador Web
GenericName[pt_BR]=Navegador da Internet
GenericName[ro]=Navigator de Internet
GenericName[ru]=Веб-браузер
GenericName[sl]=Spletni brskalnik
GenericName[sv]=Webbläsare
GenericName[ta]=இணைய உலாவி
GenericName[th]=เว็บเบราว์เซอร์
GenericName[tr]=Web Tarayıcı
GenericName[uk]=Навігатор Тенет
GenericName[zh_CN]=网页浏览器
GenericName[zh_HK]=網頁瀏覽器
GenericName[zh_TW]=網頁瀏覽器
# Not translated in KDE, from Epiphany 2.26.1-0ubuntu1.
GenericName[bn]=ওয়েব ব্রাউজার
GenericName[fil]=Web Browser
GenericName[hr]=Web preglednik
GenericName[id]=Browser Web
GenericName[or]=ଓ୍ବେବ ବ୍ରାଉଜର
GenericName[sk]=WWW prehliadač
GenericName[sr]=Интернет прегледник
GenericName[te]=మహాతల అన్వేషి
GenericName[vi]=Bộ duyệt Web
# Gnome and KDE 3 uses Comment.
Comment=Access the Internet
Comment[ar]=الدخول إلى الإنترنت
Comment[bg]=Достъп до интернет
Comment[bn]=ইন্টারনেটটি অ্যাক্সেস করুন
Comment[ca]=Accedeix a Internet
Comment[cs]=Přístup k internetu
Comment[da]=Få adgang til internettet
Comment[de]=Internetzugriff
Comment[el]=Πρόσβαση στο Διαδίκτυο
Comment[en_GB]=Access the Internet
Comment[es]=Accede a Internet.
Comment[et]=Pääs Internetti
Comment[fi]=Käytä internetiä
Comment[fil]=I-access ang Internet
Comment[fr]=Accéder à Internet
Comment[gu]=ઇંટરનેટ ઍક્સેસ કરો
Comment[he]=גישה אל האינטרנט
Comment[hi]=इंटरनेट तक पहुंच स्थापित करें
Comment[hr]=Pristup Internetu
Comment[hu]=Internetelérés
Comment[id]=Akses Internet
Comment[it]=Accesso a Internet
Comment[ja]=インターネットにアクセス
Comment[kn]=ಇಂಟರ್ನೆಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಿ
Comment[ko]=인터넷 연결
Comment[lt]=Interneto prieiga
Comment[lv]=Piekļūt internetam
Comment[ml]=ഇന്റര്‍‌നെറ്റ് ആക്‌സസ് ചെയ്യുക
Comment[mr]=इंटरनेटमध्ये प्रवेश करा
Comment[nb]=Gå til Internett
Comment[nl]=Verbinding maken met internet
Comment[or]=ଇଣ୍ଟର୍ନେଟ୍ ପ୍ରବେଶ କରନ୍ତୁ
Comment[pl]=Skorzystaj z internetu
Comment[pt]=Aceder à Internet
Comment[pt_BR]=Acessar a internet
Comment[ro]=Accesaţi Internetul
Comment[ru]=Доступ в Интернет
Comment[sk]=Prístup do siete Internet
Comment[sl]=Dostop do interneta
Comment[sr]=Приступите Интернету
Comment[sv]=Gå ut på Internet
Comment[ta]=இணையத்தை அணுகுதல்
Comment[te]=ఇంటర్నెట్‌ను ఆక్సెస్ చెయ్యండి
Comment[th]=เข้าถึงอินเทอร์เน็ต
Comment[tr]=İnternet'e erişin
Comment[uk]=Доступ до Інтернету
Comment[vi]=Truy cập Internet
Comment[zh_CN]=访问互联网
Comment[zh_HK]=連線到網際網路
Comment[zh_TW]=連線到網際網路
StartupNotify=true
StartupWMClass=brave-browser
TryExec=brave
Exec=brave %U
Terminal=false
Icon=brave-desktop
Type=Application
Categories=Network;WebBrowser;
MimeType=application/pdf;application/rdf+xml;application/rss+xml;application/xhtml+xml;application/xhtml_xml;application/xml;image/gif;image/jpeg;image/png;image/webp;text/html;text/xml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ipfs;x-scheme-handler/ipns;
Actions=new-window;new-private-window;
[Desktop Action new-window]
Name=New Window
Name[am]=አዲስ መስኮት
Name[ar]=نافذة جديدة
Name[bg]=Нов прозорец
Name[bn]=নতুন উইন্ডো
Name[ca]=Finestra nova
Name[cs]=Nové okno
Name[da]=Nyt vindue
Name[de]=Neues Fenster
Name[el]=Νέο Παράθυρο
Name[en_GB]=New Window
Name[es]=Nueva ventana
Name[et]=Uus aken
Name[fa]=پنجره جدید
Name[fi]=Uusi ikkuna
Name[fil]=New Window
Name[fr]=Nouvelle fenêtre
Name[gu]=નવી વિંડો
Name[hi]=नई विंडो
Name[hr]=Novi prozor
Name[hu]=Új ablak
Name[id]=Jendela Baru
Name[it]=Nuova finestra
Name[iw]=חלון חדש
Name[ja]=新規ウインドウ
Name[kn]=ಹೊಸ ವಿಂಡೊ
Name[ko]=새 창
Name[lt]=Naujas langas
Name[lv]=Jauns logs
Name[ml]=പുതിയ വിന്‍ഡോ
Name[mr]=नवीन विंडो
Name[nl]=Nieuw venster
Name[no]=Nytt vindu
Name[pl]=Nowe okno
Name[pt]=Nova janela
Name[pt_BR]=Nova janela
Name[ro]=Fereastră nouă
Name[ru]=Новое окно
Name[sk]=Nové okno
Name[sl]=Novo okno
Name[sr]=Нови прозор
Name[sv]=Nytt fönster
Name[sw]=Dirisha Jipya
Name[ta]=புதிய சாளரம்
Name[te]=క్రొత్త విండో
Name[th]=หน้าต่างใหม่
Name[tr]=Yeni Pencere
Name[uk]=Нове вікно
Name[vi]=Cửa sổ Mới
Name[zh_CN]=新建窗口
Name[zh_TW]=開新視窗
Exec=brave
[Desktop Action new-private-window]
Name=New Incognito Window
Name[ar]=نافذة جديدة للتصفح المتخفي
Name[bg]=Нов прозорец „инкогнито“
Name[bn]=নতুন ছদ্মবেশী উইন্ডো
Name[ca]=Finestra d'incògnit nova
Name[cs]=Nové anonymní okno
Name[da]=Nyt inkognitovindue
Name[de]=Neues Inkognito-Fenster
Name[el]=Νέο παράθυρο για ανώνυμη περιήγηση
Name[en_GB]=New Incognito window
Name[es]=Nueva ventana de incógnito
Name[et]=Uus inkognito aken
Name[fa]=پنجره جدید حالت ناشناس
Name[fi]=Uusi incognito-ikkuna
Name[fil]=Bagong Incognito window
Name[fr]=Nouvelle fenêtre de navigation privée
Name[gu]=નવી છુપી વિંડો
Name[hi]=नई गुप्त विंडो
Name[hr]=Novi anoniman prozor
Name[hu]=Új Inkognitóablak
Name[id]=Jendela Penyamaran baru
Name[it]=Nuova finestra di navigazione in incognito
Name[iw]=חלון חדש לגלישה בסתר
Name[ja]=新しいシークレット ウィンドウ
Name[kn]=ಹೊಸ ಅಜ್ಞಾತ ವಿಂಡೋ
Name[ko]=새 시크릿 창
Name[lt]=Naujas inkognito langas
Name[lv]=Jauns inkognito režīma logs
Name[ml]=പുതിയ വേഷ പ്രച്ഛന്ന വിന്‍ഡോ
Name[mr]=नवीन गुप्त विंडो
Name[nl]=Nieuw incognitovenster
Name[no]=Nytt inkognitovindu
Name[pl]=Nowe okno incognito
Name[pt]=Nova janela de navegação anónima
Name[pt_BR]=Nova janela anônima
Name[ro]=Fereastră nouă incognito
Name[ru]=Новое окно в режиме инкогнито
Name[sk]=Nové okno inkognito
Name[sl]=Novo okno brez beleženja zgodovine
Name[sr]=Нови прозор за прегледање без архивирања
Name[sv]=Nytt inkognitofönster
Name[ta]=புதிய மறைநிலைச் சாளரம்
Name[te]=క్రొత్త అజ్ఞాత విండో
Name[th]=หน้าต่างใหม่ที่ไม่ระบุตัวตน
Name[tr]=Yeni Gizli pencere
Name[uk]=Нове вікно в режимі анонімного перегляду
Name[vi]=Cửa sổ ẩn danh mới
Name[zh_CN]=新建隐身窗口
Name[zh_TW]=新增無痕式視窗
Exec=brave --incognito
MimeType=x-scheme-handler/unknown;x-scheme-handler/about;text/html;text/xml;application/xhtml_xml;image/webp;x-scheme-handler/http;x-scheme-handler/https;
@@ -0,0 +1,15 @@
{
"source": "local",
"release_ring": "fast",
"upstream": {
"watch": {
"debian": "https://brave-browser-apt-release.s3.brave.com/dists/stable/main/binary-amd64/Packages",
"package": "brave-origin",
"pattern": "(?P<version>[0-9]+(?:\\.[0-9]+)*)"
}
},
"origin": {
"aur": "brave-origin-bin",
"commit": "f2daecf7b7576cd445094ed09c3c783619ca0eb2"
}
}
+46
View File
@@ -0,0 +1,46 @@
# Maintainer: brave <aur-release@brave.com>
pkgname=brave-origin-bin
pkgver=1.96.61
pkgrel=2
epoch=1
pkgdesc='The minimalist browser from the makers of Brave (binary release).'
arch=(x86_64 aarch64)
url=https://brave.com/origin/download
license=('MPL2')
depends=(alsa-lib gtk3 libxss nss ttf-font)
optdepends=('cups: Printer support'
'libgnome-keyring: Enable GNOME keyring support'
'libnotify: Native notification support')
provides=("$pkgname" "${pkgname%-bin}")
conflicts=("${pkgname%-bin}")
options=(!strip)
source=($pkgname.sh "${pkgname%-bin}.desktop")
source_x86_64=("${pkgname}-${pkgver}-x86_64.zip::https://github.com/brave/brave-browser/releases/download/v${pkgver}/brave-origin-${pkgver}-linux-amd64.zip")
source_aarch64=("${pkgname}-${pkgver}-aarch64.zip::https://github.com/brave/brave-browser/releases/download/v${pkgver}/brave-origin-${pkgver}-linux-arm64.zip")
noextract=("${pkgname}-${pkgver}-x86_64.zip" "${pkgname}-${pkgver}-aarch64.zip")
sha256sums=('5ff70ee473f35c2fc7642c422c8abe20aaac0d7cc30a3292744eb9fbeafba1bd'
'c70bc71c696b6764247070375ae111bd76c8bad9c7bda4d46e03975b95571a8a')
sha256sums_x86_64=('06e4c48b71c65a33bdb94b6909aab505237b5939adf8985b332cb14557792816')
sha256sums_aarch64=('df07224b25284b67b5c7d0e8859d16f3d2533abcf588c81437df59e9fd4f4c62')
prepare() {
mkdir -p brave
bsdtar -xf "$pkgname-$pkgver-$CARCH.zip" -C brave
chmod +x brave/brave
}
package() {
install -dm0755 "$pkgdir/opt"
cp -a brave "$pkgdir/opt/$pkgname"
chmod 4755 "$pkgdir/opt/$pkgname/chrome-sandbox"
install -Dm0755 "$pkgname.sh" "$pkgdir/usr/bin/${pkgname%-bin}"
install -Dm0644 -t "$pkgdir/usr/share/applications/" "${pkgname%-bin}.desktop"
install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" brave/LICENSE
pushd "$pkgdir/usr/"
for size in 16x16 24x24 32x32 48x48 64x64 128x128 256x256; do
install -Dm0644 "$pkgdir/opt/$pkgname/product_logo_${size/x*/}.png" \
"share/icons/hicolor/$size/apps/brave-origin.png"
done
}
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-"${HOME}/.config"}"
CONF_FILE="${XDG_CONFIG_HOME}/brave-origin-flags.conf"
if
test -f "${CONF_FILE}"
then
mapfile -t CONF_LIST < "${CONF_FILE}"
fi
for CONF_LINE in "${CONF_LIST[@]}"
do
if ! [[
"${CONF_LINE}" =~ ^[[:space:]]*(#|$)
]]
then
FLAG_LIST+=("${CONF_LINE}")
fi
done
export CHROME_VERSION_EXTRA='stable'
exec /opt/brave-origin-bin/brave-origin "${FLAG_LIST[@]}" "${@}"
@@ -0,0 +1,222 @@
[Desktop Entry]
Version=1.0
Name=Brave Origin
# Only KDE 4 seems to use GenericName, so we reuse the KDE strings.
# From Ubuntu's language-pack-kde-XX-base packages, version 9.04-20090413.
GenericName=Web Browser
GenericName[ar]=متصفح الشبكة
GenericName[bg]=Уеб браузър
GenericName[ca]=Navegador web
GenericName[cs]=WWW prohlížeč
GenericName[da]=Browser
GenericName[de]=Web-Browser
GenericName[el]=Περιηγητής ιστού
GenericName[en_GB]=Web Browser
GenericName[es]=Navegador web
GenericName[et]=Veebibrauser
GenericName[fi]=WWW-selain
GenericName[fr]=Navigateur Web
GenericName[gu]=વેબ બ્રાઉઝર
GenericName[he]=דפדפן אינטרנט
GenericName[hi]=वेब ब्राउज़र
GenericName[hu]=Webböngésző
GenericName[it]=Browser Web
GenericName[ja]=ウェブブラウザ
GenericName[kn]=ಜಾಲ ವೀಕ್ಷಕ
GenericName[ko]=웹 브라우저
GenericName[lt]=Žiniatinklio naršyklė
GenericName[lv]=Tīmekļa pārlūks
GenericName[ml]=വെബ് ബ്രൌസര്‍
GenericName[mr]=वेब ब्राऊजर
GenericName[nb]=Nettleser
GenericName[nl]=Webbrowser
GenericName[pl]=Przeglądarka WWW
GenericName[pt]=Navegador Web
GenericName[pt_BR]=Navegador da Internet
GenericName[ro]=Navigator de Internet
GenericName[ru]=Веб-браузер
GenericName[sl]=Spletni brskalnik
GenericName[sv]=Webbläsare
GenericName[ta]=இணைய உலாவி
GenericName[th]=เว็บเบราว์เซอร์
GenericName[tr]=Web Tarayıcı
GenericName[uk]=Навігатор Тенет
GenericName[zh_CN]=网页浏览器
GenericName[zh_HK]=網頁瀏覽器
GenericName[zh_TW]=網頁瀏覽器
# Not translated in KDE, from Epiphany 2.26.1-0ubuntu1.
GenericName[bn]=ওয়েব ব্রাউজার
GenericName[fil]=Web Browser
GenericName[hr]=Web preglednik
GenericName[id]=Browser Web
GenericName[or]=ଓ୍ବେବ ବ୍ରାଉଜର
GenericName[sk]=WWW prehliadač
GenericName[sr]=Интернет прегледник
GenericName[te]=మహాతల అన్వేషి
GenericName[vi]=Bộ duyệt Web
# Gnome and KDE 3 uses Comment.
Comment=Access the Internet
Comment[ar]=الدخول إلى الإنترنت
Comment[bg]=Достъп до интернет
Comment[bn]=ইন্টারনেটটি অ্যাক্সেস করুন
Comment[ca]=Accedeix a Internet
Comment[cs]=Přístup k internetu
Comment[da]=Få adgang til internettet
Comment[de]=Internetzugriff
Comment[el]=Πρόσβαση στο Διαδίκτυο
Comment[en_GB]=Access the Internet
Comment[es]=Accede a Internet.
Comment[et]=Pääs Internetti
Comment[fi]=Käytä internetiä
Comment[fil]=I-access ang Internet
Comment[fr]=Accéder à Internet
Comment[gu]=ઇંટરનેટ ઍક્સેસ કરો
Comment[he]=גישה אל האינטרנט
Comment[hi]=इंटरनेट तक पहुंच स्थापित करें
Comment[hr]=Pristup Internetu
Comment[hu]=Internetelérés
Comment[id]=Akses Internet
Comment[it]=Accesso a Internet
Comment[ja]=インターネットにアクセス
Comment[kn]=ಇಂಟರ್ನೆಟ್ ಅನ್ನು ಪ್ರವೇಶಿಸಿ
Comment[ko]=인터넷 연결
Comment[lt]=Interneto prieiga
Comment[lv]=Piekļūt internetam
Comment[ml]=ഇന്റര്‍‌നെറ്റ് ആക്‌സസ് ചെയ്യുക
Comment[mr]=इंटरनेटमध्ये प्रवेश करा
Comment[nb]=Gå til Internett
Comment[nl]=Verbinding maken met internet
Comment[or]=ଇଣ୍ଟର୍ନେଟ୍ ପ୍ରବେଶ କରନ୍ତୁ
Comment[pl]=Skorzystaj z internetu
Comment[pt]=Aceder à Internet
Comment[pt_BR]=Acessar a internet
Comment[ro]=Accesaţi Internetul
Comment[ru]=Доступ в Интернет
Comment[sk]=Prístup do siete Internet
Comment[sl]=Dostop do interneta
Comment[sr]=Приступите Интернету
Comment[sv]=Gå ut på Internet
Comment[ta]=இணையத்தை அணுகுதல்
Comment[te]=ఇంటర్నెట్‌ను ఆక్సెస్ చెయ్యండి
Comment[th]=เข้าถึงอินเทอร์เน็ต
Comment[tr]=İnternet'e erişin
Comment[uk]=Доступ до Інтернету
Comment[vi]=Truy cập Internet
Comment[zh_CN]=访问互联网
Comment[zh_HK]=連線到網際網路
Comment[zh_TW]=連線到網際網路
StartupNotify=true
StartupWMClass=brave-origin
Exec=brave-origin %U
Terminal=false
Icon=brave-origin
Type=Application
Categories=Network;WebBrowser;
MimeType=application/pdf;application/rdf+xml;application/rss+xml;application/xhtml+xml;application/xhtml_xml;application/xml;image/gif;image/jpeg;image/png;image/webp;text/html;text/xml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/chromium;
Actions=new-window;new-private-window;
[Desktop Action new-window]
Name=New Window
Name[am]=አዲስ መስኮት
Name[ar]=نافذة جديدة
Name[bg]=Нов прозорец
Name[bn]=নতুন উইন্ডো
Name[ca]=Finestra nova
Name[cs]=Nové okno
Name[da]=Nyt vindue
Name[de]=Neues Fenster
Name[el]=Νέο Παράθυρο
Name[en_GB]=New Window
Name[es]=Nueva ventana
Name[et]=Uus aken
Name[fa]=پنجره جدید
Name[fi]=Uusi ikkuna
Name[fil]=New Window
Name[fr]=Nouvelle fenêtre
Name[gu]=નવી વિંડો
Name[hi]=नई विंडो
Name[hr]=Novi prozor
Name[hu]=Új ablak
Name[id]=Jendela Baru
Name[it]=Nuova finestra
Name[iw]=חלון חדש
Name[ja]=新規ウインドウ
Name[kn]=ಹೊಸ ವಿಂಡೊ
Name[ko]=새 창
Name[lt]=Naujas langas
Name[lv]=Jauns logs
Name[ml]=പുതിയ വിന്‍ഡോ
Name[mr]=नवीन विंडो
Name[nl]=Nieuw venster
Name[no]=Nytt vindu
Name[pl]=Nowe okno
Name[pt]=Nova janela
Name[pt_BR]=Nova janela
Name[ro]=Fereastră nouă
Name[ru]=Новое окно
Name[sk]=Nové okno
Name[sl]=Novo okno
Name[sr]=Нови прозор
Name[sv]=Nytt fönster
Name[sw]=Dirisha Jipya
Name[ta]=புதிய சாளரம்
Name[te]=క్రొత్త విండో
Name[th]=หน้าต่างใหม่
Name[tr]=Yeni Pencere
Name[uk]=Нове вікно
Name[vi]=Cửa sổ Mới
Name[zh_CN]=新建窗口
Name[zh_TW]=開新視窗
Exec=brave-origin
[Desktop Action new-private-window]
Name=New Incognito Window
Name[ar]=نافذة جديدة للتصفح المتخفي
Name[bg]=Нов прозорец „инкогнито“
Name[bn]=নতুন ছদ্মবেশী উইন্ডো
Name[ca]=Finestra d'incògnit nova
Name[cs]=Nové anonymní okno
Name[da]=Nyt inkognitovindue
Name[de]=Neues Inkognito-Fenster
Name[el]=Νέο παράθυρο για ανώνυμη περιήγηση
Name[en_GB]=New Incognito window
Name[es]=Nueva ventana de incógnito
Name[et]=Uus inkognito aken
Name[fa]=پنجره جدید حالت ناشناس
Name[fi]=Uusi incognito-ikkuna
Name[fil]=Bagong Incognito window
Name[fr]=Nouvelle fenêtre de navigation privée
Name[gu]=નવી છુપી વિંડો
Name[hi]=नई गुप्त विंडो
Name[hr]=Novi anoniman prozor
Name[hu]=Új Inkognitóablak
Name[id]=Jendela Penyamaran baru
Name[it]=Nuova finestra di navigazione in incognito
Name[iw]=חלון חדש לגלישה בסתר
Name[ja]=新しいシークレット ウィンドウ
Name[kn]=ಹೊಸ ಅಜ್ಞಾತ ವಿಂಡೋ
Name[ko]=새 시크릿 창
Name[lt]=Naujas inkognito langas
Name[lv]=Jauns inkognito režīma logs
Name[ml]=പുതിയ വേഷ പ്രച്ഛന്ന വിന്‍ഡോ
Name[mr]=नवीन गुप्त विंडो
Name[nl]=Nieuw incognitovenster
Name[no]=Nytt inkognitovindu
Name[pl]=Nowe okno incognito
Name[pt]=Nova janela de navegação anónima
Name[pt_BR]=Nova janela anônima
Name[ro]=Fereastră nouă incognito
Name[ru]=Новое окно в режиме инкогнито
Name[sk]=Nové okno inkognito
Name[sl]=Novo okno brez beleženja zgodovine
Name[sr]=Нови прозор за прегледање без архивирања
Name[sv]=Nytt inkognitofönster
Name[ta]=புதிய மறைநிலைச் சாளரம்
Name[te]=క్రొత్త అజ్ఞాత విండో
Name[th]=หน้าต่างใหม่ที่ไม่ระบุตัวตน
Name[tr]=Yeni Gizli pencere
Name[uk]=Нове вікно в режимі анонімного перегляду
Name[vi]=Cửa sổ ẩn danh mới
Name[zh_CN]=新建隐身窗口
Name[zh_TW]=新增無痕式視窗
Exec=brave-origin --incognito
+11 -2
View File
@@ -1,5 +1,14 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "195b828d52ce9a181215f753927123e7ef2af252"
"upstream": {
"watch": {
"github": "oven-sh/bun",
"pattern": "bun-v(?P<version>[0-9]+(?:\\.[0-9]+)*)"
}
},
"origin": {
"aur": "bun-bin",
"commit": "195b828d52ce9a181215f753927123e7ef2af252"
}
}
+3 -6
View File
@@ -3,7 +3,7 @@
# Contributor: 37h4n (aarch64 support added by Ethan Reece <aur at ethanreece dot com>)
# Contributor: sh!zeeg (shizeeque@gmail.com) support for non-avx2 CPUs, shell completions.
pkgname=bun-bin
pkgver=1.3.11
pkgver=1.4.2
pkgrel=1
pkgdesc="All-in-one JavaScript runtime built for speed, with bundler, transpiler, test runner, and package manager. Includes bunx, shell completions and support for baseline CPUs"
arch=('x86_64' 'aarch64')
@@ -12,11 +12,8 @@ license=('MIT')
provides=('bun')
conflicts=('bun')
options=('!debug')
sha256sums_x86_64=('8611ba935af886f05a6f38740a15160326c15e5d5d07adef966130b4493607ed'
'abe346f63414547cdf6b35b7a649a490c728b93d006226156923918a84c0e59b'
'9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b')
sha256sums_aarch64=('d13944da12a53ecc74bf6a720bd1d04c4555c038dfe422365356a7be47691fdf'
'9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b')
sha256sums_x86_64=('36368faef7527875d5ffa52e53cd48021741f2a83eb6208a8dd64068d422a913' 'c678040f14fe0440eb839d37cbd0ce4c051a32da72806ac97de6a6aab6bf728f' '9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b')
sha256sums_aarch64=('54328bbc2d9c8e0c9f892c544d66c57a83b84139e34909e5ee81758f1ac8fda7' '9b296bcc20090b5ea079dc1bca15913a32246121169bcf54cbdb7384d6f5b32b')
source_x86_64=(
"bun-x64.zip::https://github.com/oven-sh/bun/releases/download/bun-v${pkgver}/bun-linux-x64.zip"
"bun-x64-baseline.zip::https://github.com/oven-sh/bun/releases/download/bun-v${pkgver}/bun-linux-x64-baseline.zip"
+11 -2
View File
@@ -1,5 +1,14 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "558b3f7387063304f7392f839c6b693508976338"
"upstream": {
"watch": {
"regex": "https://downloads.claude.ai/claude-code-releases/latest",
"pattern": "^(?P<version>[0-9]+(?:\\.[0-9]+)*)\\s*$"
}
},
"origin": {
"aur": "claude-code",
"commit": "27f61daa48ebdca87c9b2c7c83c162100d233ccc"
}
}
+4 -4
View File
@@ -4,8 +4,8 @@
# Automation repository: https://github.com/fabifont/claude-code-aur
pkgname=claude-code
pkgver=2.1.247
pkgrel=1
pkgver=2.1.291
pkgrel=2
pkgdesc="An agentic coding tool that lives in your terminal"
arch=('x86_64' 'aarch64')
url="https://github.com/anthropics/claude-code"
@@ -29,8 +29,8 @@ source_x86_64=("claude-${pkgver}-x86_64::https://downloads.claude.ai/claude-code
source_aarch64=("claude-${pkgver}-aarch64::https://downloads.claude.ai/claude-code-releases/${pkgver}/linux-arm64/claude")
sha256sums=('SKIP')
sha256sums_x86_64=('5fb321bf417ffc5cd4e3f36e7c9c7e029bf47aaa36d5621db979fcc5e6eabe15')
sha256sums_aarch64=('a68bb633f85e4a0e73465952ea624cf18992c3fa4db615feeb942ffad57d082a')
sha256sums_x86_64=('078fad28d0297c9a25d306b635b2d8816c6839347520f29eb54ffea5d56142fb')
sha256sums_aarch64=('c18473a04cc4f077435d5d9081f09ebea46e699eb2825cea64741c4bccb87647')
package() {
install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude"
Loaded 100 of 1560 files, more files were not shown because too many files have changed in this diff. Show more