Recipes and tooling for the packages Omarchy ships from
https://pkgs.omarchy.org/<channel>/<arch>.
edge → rc → stable. Packages only move forward.x86_64 and aarch64.pkgbuilds/<package>/, with a
PKGBUILD and .omarchy/package.json.Everything a package PR needs is in pkgbuilds/<package>/. No secrets or
access are required. To work locally you need:
git, bash, jq, curl and a tar that reads zstdsudo to fix the ownership of its output.vercmp, bsdtarQuestions and problems: open an issue on this repository.
master.bash
bin/add-package my-package --local --scaffold # Starter PKGBUILD and metadata
bin/add-package my-package --source aur # Or import an AUR recipe once
An imported recipe is Omarchy's from then on; there is no AUR sync.
3. Write pkgbuilds/my-package/PKGBUILD to Arch packaging standards.
- List every architecture it supports in arch=(): x86_64, aarch64,
or any. CI builds each one.
- Pin every git source with #commit=<sha> or #tag=<tag>. An unpinned
source is rejected; tests/pinned-sources.sh checks it locally.
- Fill the checksums: run updpkgsums (from pacman-contrib) in the
package directory. For a git source it leaves a clone of the source
there; delete it before committing.
4. Declare where new releases come from in
pkgbuilds/my-package/.omarchy/package.json, so updates arrive without
another PR. See docs/upstream-sources.md.
json
{
"source": "local",
"upstream": { "watch": { "github": "owner/project", "pattern": "v(?P<version>[0-9]+(?:\\.[0-9]+)*)" } }
}
Use this watch form for a new package. The pattern must match the whole
tag, so drop the v for a project that tags 1.2.3. A github watch
reads GitHub Releases; for a project that only pushes tags, watch the tags:
json
"upstream": { "watch": { "git_tags": "https://github.com/owner/project.git", "pattern": "(?P<version>[0-9]+(?:\\.[0-9]+)*)" } }
Check that it finds the current release:
bash
python helpers/upstream-watch.py check pkgbuilds/my-package
A working watch on a current recipe prints
{"status": "skipped", ..., "reason": "already current"}.
If the package should not be updated automatically, set "sync": false
and say why in the PR.
5. Build it:
bash
bin/build --package my-package --dry-run # Show the plan
bin/build --package my-package # Build for x86_64
bin/build --package my-package --arch aarch64 # Emulated on an x86_64 machine; slow
--dry-run only shows the plan; it does not validate the recipe. The
package lands unsigned in build-output/edge/<arch>/. Install it with
pacman -U to try it. CI builds aarch64 natively, so a local aarch64
build is optional.
6. Open a PR against master with one package in it. Say what the package
is, and how you tested it.
upstream declaration, a
bot proposes the update on its next scheduled run, or later if the package
sets min_release_age; you don't need to. Otherwise set pkgver, run
updpkgsums, and reset pkgrel to 1.pkgrel. A PR that changes
a recipe without moving its version builds nothing and ships nothing; when
the PKGBUILD itself changed, the PR's build check carries a "Change will
not ship" warning. (A -git package whose version carries the commit hash
is also rebuilt when its pinned commit changes.)epoch without agreeing it in the PR first; it is permanent..github/VOUCHED.td. A maintainer starts the builds by labelling the
PR build-approved. Until then it shows Awaiting build approval, or,
for your first PR here, GitHub's own "workflows awaiting approval".
Builds cost machines, so they run for trusted authors only.result, self-tests and build-isolation.edge within minutes of merging, and reaches
rc and stable when those channels are next
promoted. Say so in the PR if you think the
package should reach stable immediately (the fast ring).Keep a package PR to these files, or it needs a maintainer to merge it by hand:
pkgbuilds/tests/, and the one line in .github/workflows/test.yml
that runs itChanges to bin/, helpers/, build/ or the workflows go in a separate PR,
first: a PR's packages are built with master's tooling, not its own.
PR touching pkgbuilds/<package>/
└─ build-pr.yml one build per package and architecture, unsigned
merge to master
└─ publish.yml collect the builds, sign, add to the channel
databases, upload; then bring rc and stable in line
with edge for the fast ring
by hand, from Actions or bin/omarchy-release
├─ promote.yml Promote channel: edge -> rc, rc -> stable
└─ release-rc.yml Build release candidate: the rc branch's pins -> rc, edge
pkgs.omarchy.org served from the R2 bucket omarchy-pkgs through a CDN
To ship a change: open a PR, wait for green, merge. Nothing else is needed.
build-pr.yml builds every package directory the PR touches,
once, against edge. x86_64 builds on an ephemeral DigitalOcean droplet
(runner label omarchy-builder), aarch64 on GitHub's ubuntu-24.04-arm
(aarch64 kernels on Namespace; see the runner table).
Each build is kept as a workflow artifact for 7 days, named
<package>-<arch>-<git tree hash of the package directory>.result, self-tests and
build-isolation. Don't merge around a red check.publish.yml runs on every push to master that touches
pkgbuilds/**. It finds the artifact for exactly the merged tree, signs
it, and publishes it to every channel and architecture the package ships
to. When no artifact exists it builds one first, in a separate job. Its
last step copies edge's file to rc or stable for any fast-ring
package either is missing. It reports in a comment on the merged PR and
appends to the publish log.Nothing else moves on its own: rc and stable change only when someone
promotes them or cuts a release.
A package is built once and that one file is what every channel serves.
A published filename never changes its bytes. To ship a different build
of the same version, bump pkgrel.
bin/build-matrix decides, from .omarchy/package.json. The release ring is
the only choice a package makes:
| Package | Published on merge to |
|---|---|
| Default (normal ring) | edge; rc and stable by promotion |
"release_ring": "fast" |
edge, rc and stable |
"pinned": true (omarchy, omarchy-settings) |
edge; rc only through a release candidate, stable by promotion |
Every package reaches every channel. Nothing is held to edge: a package that must not reach rc yet is left out of the promotion by naming the ones that should go.
A package is published for each architecture in its arch=(). An arch=any
package builds once and goes into both architectures' databases.
It also decides where each build runs (package_runner in
helpers/package-metadata.sh):
| Build | Runner |
|---|---|
| x86_64 | the droplet pool |
| aarch64 | GitHub's ubuntu-24.04-arm (4 vCPU) |
aarch64 kernel (linux-*) |
namespace-profile-arm64-linux-apple-12x32 (Linux on Apple Silicon, 12 vCPU) |
A build that has to be repeated at merge runs on the same runner.
Everything not on the fast ring reaches rc and stable by promotion: see
Releases.
A fast-ring package is still built once, against edge, and that file lands on
rc and stable straight away, ahead of the Arch snapshot it was built
against. So the fast ring is for packages whose files do not care which
snapshot they run on: vendor binaries, static Go and Rust tools, scripts,
themes and fonts. A package that needs rebuilding when a dependency moves
stays off it and reaches rc and stable with the release, together with
the libraries it was built for.
The three channels serve the same file. A merge that publishes a fast-ring
build writes it to all three, and the same run copies edge's file to rc or
stable when either is behind, so moving a package onto the fast ring brings
its current build along (see Republish a package).
Both halves are enforced. rebuild_on and "release_ring": "fast" cannot be
combined. And after a fast-ring package builds, build-pr.yml runs
bin/check-fast-ring, which installs the package on edge, rc and stable
from each channel's own repositories and fails the build when:
rc or stable;GLIBC_2.43, say) that
rc or stable does not have yet (ldd -r). Edge is the baseline: an
optional plugin whose library is missing on every channel is not the fast
ring's concern;build()) and a binary links Qt or libpython. These break on a
dependency update without a missing symbol to show it: Qt's private API and
plugin version check, Python's versioned module path. A vendor's binary is
the same bytes whatever we rebuild, so for it only the library check
applies.On aarch64 every channel runs the same live Arch Linux ARM repositories, so only the rules are checked there.
A package that fails the check comes off the fast ring; nothing else about it changes.
Use this when a publish run failed or was cancelled, or a package is missing from a channel:
```bash gh workflow run
| 日期 | 榜单 | 排名 | 周期新增 | 总 STAR |
|---|