bin/repo advance --from/--to moves packages forward through the pipeline
(edge → rc → stable), driven by the source channel's database rather than the
raw directory, copying packages AND their detached signatures (fixing the old
migrate bug that left promoted packages unverifiable), refusing to rewrite any
published filename, and requiring a .sig for everything it moves. stable → rc
is allowed only as --fast-ring parity replication or the one-time
--bootstrap seed (bin/repo bootstrap-rc). 'migrate' stays as a deprecated
alias for the transition.
helpers/lock-helpers.sh adds a host-wide flock shared by bin/release,
advance-channel, and upload-prebuilt (reentrant via OMARCHY_RELEASE_LOCK_HELD)
so timers and operators serialize instead of interleaving partial publishes.
bin/release gains a stable-only step 7: replicate fast-ring artifacts to rc so
rc and stable stay in parity between release trains (skipped until rc is
bootstrapped).
A package's .omarchy/package.json may now pin where it lives with
"channels": [...]. With the key present the package builds in each listed
channel except stable (stable is only fed by promotion); without it, today's
defaults hold (build for edge; fast-ring also builds stable directly).
package_moves_to_channel() is the advance/promote eligibility rule: a member
of the destination channel that is not built there natively.
The release pair (omarchy, omarchy-settings) is edge+rc+stable — edge stays
during the client-migration overlap window and drops later. The dev pair is
pinned to edge only.
- helpers/paths.sh: validate_mirror/require_valid_mirror for the edge|rc|stable
set, and REPO_ROOT (OMARCHY_REPO_ROOT override) so a secondary checkout like
the rc branch worktree publishes into the same channel tree as the primary
- validate --mirror everywhere it previously accepted any string (sync-repo,
promote-build, update-repo, clean-repo, remove-package) and widen the
edge|stable checks in build, deploy, push-build, auto-release
- build/Dockerfile: rc builds compile against rc-mirror.omarchy.org
GNU date accepts relative expressions like '2 days ago', which would let a
buggy hook fabricate a release age; the backstop now insists on an ISO 8601
timestamp before date parses it. The provider header now states, rather than
contradicts, the code's behavior for a feed with no stable releases: that is
a loud failure by design, while quarantined releases report no update.
An empty min_release_age string now maps to unparseable rather than absent,
so "min_release_age": "" fails validation instead of silently running
with a zero-second quarantine. The end-to-end fixtures extend the
checked-in pkgver (.90/.91) so the test keeps working at any future mise
version. A Tests workflow runs bin/sync-upstream self-test and
bin/omarchy-pkgs self-test on every PR in the Arch container, making the
proof machine-checked instead of author-supplied. The README package
metadata field list documents upstream and min_release_age.
The self-test now runs sync_package over the checked-in mise-bin package --
its real metadata and PKGBUILD, the full selection/validation/backstop/
rewrite/read-back path -- with only the two network fetches replaced by
mise-shaped fixtures, asserting the final PKGBUILD holds the quarantine-
cleared version, pkgrel 1, and both architecture checksums.
Review fixes: the release-row builder uses "" fallbacks instead of empty
so a malformed row cannot shift columns past the per-field checks, and
provider discovery now keys on the presence of an upstream declaration
rather than a well-formed one, with sync_package failing loudly on a
declaration it cannot use -- a malformed manifest can no longer silently
drop a package out of scheduled synchronization.
bin/sync-upstream self-test swaps the two network fetches in
helpers/upstream-github.sh for fixture readers and runs the production code
paths: fallback past a quarantined release, draft/prerelease filtering, the
deliberate bypass, unchanged-version and all-quarantined no-update paths,
unusable tags/timestamps and missing checksums failing the sync, {tag} and
{pkgver} asset templates with ./ and * manifest prefixes across both
architectures, the min_release_age backstop verdicts (now a testable
release_age_status function), the duration parser, and manifest validation.
Also fixes from the review: the duration parser forces base-10 arithmetic
(leading zeros no longer parse as octal) and bounds values to nine digits so
no suffix can overflow; jq // treating false as absent can no longer let
"min_release_age": false or "upstream": false slip through as unset; the
release feed page grew to the API maximum of 100 with the bounded search
documented; and the README package-metadata section documents the upstream
block, min_release_age, the bypass, and provider-versus-hook exclusivity.
After the quarantine moved into the manifest, all mise-bin's hook still knew
was data: the repository, the checksum manifest name, and the asset filename
patterns. That now lives in .omarchy/package.json as an upstream block --
"upstream": {
"github": "jdx/mise",
"checksums": "SHASUMS256.txt",
"assets": { "x86_64": "mise-{tag}-linux-x64.tar.xz", ... }
}
-- handled by helpers/upstream-github.sh inside bin/sync-upstream. The
provider walks the release feed (drafts/prereleases excluded), honors
min_release_age and BYPASS_MIN_RELEASE_AGE during selection, reports
published_at so the framework backstop still applies, fails closed on any
unreadable tag or timestamp, and skips the checksum fetch when the newest
qualifying release is already checked in.
upstream.sh remains the escape hatch for feeds that fit no convention
(openai-codex-desktop's Debian index, tmog's version.txt, t3code's
electron-builder manifest); declaring both is an error.
Move the hold from a mise-only hardcode to min_release_age in
.omarchy/package.json ("24h", "2d", or bare seconds), alongside source and
release_ring where package policy already lives. bin/sync-upstream exports
the window to every hook as MIN_RELEASE_AGE_SECONDS so a hook that can walk
its release feed selects the newest release that has cleared it, and
enforces it as a backstop: with a policy set, the hook must report
published_at, and a release younger than the window is treated as no
update. A hook that cannot prove the age fails the sync rather than
shipping unverified. BYPASS_MIN_RELEASE_AGE=1 replaces the package-specific
bypass for deliberate emergency updates; scheduled automation never sets it.
The mise hook keeps its release-list walk but reads the window from the
environment and reports published_at; the other upstream hooks are
untouched and unaffected until they opt in.
A package that links Qt private API has to be rebuilt whenever qt6-base moves, because Qt_6_PRIVATE_API symbols are not covered by the soname and pacman upgrades Qt out from under the installed binary while the dependency stays unversioned. Nothing here noticed. Both version gates ask whether the package's own source moved, and for a VCS package pinned to a commit that answer stays no through every Qt release.
Unlocking the build gate would not have been enough on its own. A rebuild that reuses the published version string produces a package pacman never offers anyone, so the trigger has to edit git and bump pkgrel, which is why it sits beside sync-aur and sync-upstream rather than inside check-versions or the builder. Once pkgrel moves, both existing gates already do the right thing untouched.
Packages opt in with rebuild_on in .omarchy/package.json. bin/sync-rebuilds records what each was last bumped for in rebuilt_against and compares that to core, extra and multilib, ignoring testing and kde-unstable because those are not what the builder links against. A package with no record yet is only recorded, never bumped: what its published build linked against is not knowable from here, so the first run establishes the baseline. For an AUR-synced package the bump is written as the dotted Omarchy pkgrel suffix in the metadata as well, since the next sync replaces the PKGBUILD wholesale and would otherwise drop it.
🤖 Generated by Opus 5 in Claude Code.
Some vendors publish a release feed of their own that is faster and more
precise than anyone's packaging of it. A package opts in with an
.omarchy/upstream.sh hook that reports the newest release as JSON, and the
driver rewrites pkgver, the checksum arrays the hook names, and pkgrel.
Writes are guarded on both ends: every assignment the update will touch is
verified to exist before anything is written, so a hook naming an array the
PKGBUILD lacks fails with the file untouched rather than half rewritten; and
pkgver is held to pacman's character set, because it lands in a file makepkg
sources as shell.
Ordering is vercmp's, not sort -V's -- they disagree about whether 1.0a
precedes 1.0, and pacman is what decides if a published package is an upgrade.
That is also why the workflow runs in an Arch container rather than straight on
the runner.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
bin/build asks the local repository database which packages are already built.
A build machine has no such database, so every package looks out of date: an
unscoped 'bin/repo deploy' on this laptop would have built all 108 packages and
published them. Verified with a dry run.
deploy now refuses to run unscoped when that database is absent, and push
refuses the same combination under --yes, where nobody would see the list it
prints before publishing. Both are allowed on the repository host, which has
the database that makes the comparison meaningful.
Also states the split in the README: build, push and deploy are the three
commands that may run off the repository host; everything else works on the
published tree directly.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The warning dated from when .build-host was the release trigger's own setting
and push was a second consumer of it. One setting now names one machine for all
three commands, so there is nothing to keep separate -- and the advice was
wrong regardless: OMARCHY_REPO_HOST is read by the same resolver, so it arms
the trigger exactly as the file does. Only --host is per-invocation.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OMARCHY_BUILD_HOST and .build-host were carried as fallbacks through the
rename. Nothing in this checkout ever set either one, so they were a second
name for a setting that has only one real name.
Resolution is now --host, OMARCHY_REPO_HOST, .repo-host.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Builds now happen wherever the operator likes, so naming the destination after
building described the old arrangement rather than the current one. What the
push and deploy commands reach is the machine that serves pkgs.omarchy.org and
holds the signing key: the repository host. It also runs the scheduled builds,
which is why the trigger in omarchy-pkgs release points at the same place.
Resolution moves into helpers/host-helpers.sh, which all three commands now
share instead of repeating: --host, then OMARCHY_REPO_HOST, then .repo-host.
OMARCHY_BUILD_HOST and .build-host keep working as fallbacks, so existing
environments and checkouts are unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>