Merge remote-tracking branch 'refs/remotes/portfolio/master' into codex/portfolio-263-20260907

This commit is contained in:
Afonso Oliveira committed 2026-09-07 22:18:42 +01:00
commit a0f23f35e5
143 files changed
+5156 -1124

No files matched your search

+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm jq
pacman -Syu --noconfirm git jq
groupadd -g "$HOST_GID" runner
useradd -m -u "$HOST_UID" -g "$HOST_GID" runner
+2 -1
View File
@@ -25,8 +25,9 @@ jobs:
-w /workspace \
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm jq
pacman -Syu --noconfirm git jq
./bin/sync-upstream self-test
./bin/sync-rebuilds --self-test
./bin/omarchy-pkgs self-test
./bin/omarchy-release self-test
'
+1
View File
@@ -1,3 +1,4 @@
.firecrawl/
src/
logs/
build-output/
+179 -30
View File
@@ -30,18 +30,57 @@ The filesystem no longer encodes release policy. Instead:
## 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
@@ -300,10 +339,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 +356,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 +509,12 @@ 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 published architecture 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 +522,12 @@ 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.
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.
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.
### Other
@@ -586,7 +725,7 @@ Minimal examples:
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`.
- `upstream`: optional for `local` packages following GitHub releases, git tags, npm dist-tags, or a Debian `Packages` index. 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.
@@ -596,7 +735,7 @@ Fields:
- `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.
- `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.
- `rebuilt_against`: written by `bin/sync-rebuilds`. Maps each published architecture to the versions of its `rebuild_on` packages 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.
### Build Matrix
@@ -691,10 +830,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
@@ -745,8 +889,8 @@ The repository includes GitHub workflows and systemd services for automated rele
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 +904,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 +961,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)
+51 -3
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"
+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"
+127 -19
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
@@ -67,6 +88,11 @@ while [[ $# -gt 0 ]]; do
echo " $0 --package yay"
echo " $0 --package yay elephant 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 ""
exit 0
;;
*)
@@ -76,18 +102,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 +163,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,8 +211,20 @@ 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..."
@@ -133,9 +232,12 @@ print_info "Running package build..."
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"
# 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 "$REPO_DIR"
fi
# Build Docker arguments
DOCKER_ARGS=(
@@ -144,6 +246,7 @@ DOCKER_ARGS=(
-e MIRROR="$MIRROR"
-e PACKAGES="$PACKAGES"
-e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}"
-e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS"
-v "$BUILD_ROOT/build-output:/build-output"
-v "$REPO_ROOT:/pkgs.omarchy.org"
-v "$BUILD_DIR:/build:ro"
@@ -151,11 +254,16 @@ DOCKER_ARGS=(
-v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro"
)
# 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
# Run the builder with assembled args
IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR"
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[@]}" "$IMAGE_TAG" /build/build.sh
BUILD_RESULT=$?
+61 -20
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
@@ -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
+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"
+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
+5 -1
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
+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=$?
+249 -60
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,9 @@ 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. Only architectures in
PUBLISHED_ARCHES that the package supports are considered.
EOF
}
@@ -94,7 +101,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 +115,59 @@ 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=()
PUBLISHED_LOADED=false
PUBLISHED_AVAILABLE=true
remember_published() {
local name="$1"
@@ -131,34 +188,35 @@ 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 $(published_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
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"
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
}
@@ -179,7 +237,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 +310,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 +368,41 @@ 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 considered=0
for arch in $(published_arches); do
package_supports_arch "$package_dir" "$arch" || continue
considered=$((considered + 1))
arch_current="{}"
for trigger in "${triggers[@]}"; do
if ! 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 +411,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 +433,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
@@ -466,11 +561,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"
}
@@ -514,19 +609,51 @@ selftest_published() {
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"
if [[ "$url" == */aarch64/* ]]; then
repo="${url%/*}"
repo="${repo##*/}"
db="$(dirname "$0")/alarm-$repo.db"
else
db="$(dirname "$0")/omarchy.db.tar.zst"
fi
[[ -f "$db" && -n "$out" ]] || exit 22
cp "$db" "$out"
STUB
chmod +x "$root/stub/curl"
}
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
@@ -545,7 +672,8 @@ cmd_self_test() {
local root="$1"
shift
local status=0
PATH="$root/stub:$PATH" "$root/bin/sync-rebuilds" "$@" > "$root/output" 2>&1 || status=$?
OMARCHY_ARCHES="${SELFTEST_ARCHES:-x86_64}" \
PATH="$root/stub:$PATH" "$root/bin/sync-rebuilds" "$@" > "$root/output" 2>&1 || status=$?
echo "$status"
}
@@ -563,7 +691,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 +701,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 +740,67 @@ 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 "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 ""
if [[ "$failures" -eq 0 ]]; then
print_success "Self-test passed"
@@ -626,9 +815,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
+320 -18
View File
@@ -18,11 +18,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",
@@ -332,27 +332,27 @@ sync_package() {
return 0
fi
local github_repo has_upstream=false
github_repo=$(package_upstream_github_repo "$package_dir")
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 +372,16 @@ 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"
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 +467,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 +545,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 +593,187 @@ 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"
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 +826,100 @@ 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"
# 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
+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
+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
+14 -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 && \
@@ -137,8 +140,13 @@ 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
# Pacman wrapper that auto-resolves package conflicts (e.g. rustup vs rust).
# makepkg only passes --noconfirm to pacman, which defaults to 'N' on conflict
+146 -57
View File
@@ -5,6 +5,10 @@
# 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}
PKGBUILDS_DIR=${PKGBUILDS_DIR:-/pkgbuilds}
@@ -12,9 +16,37 @@ 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"
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
# Import GPG keys
/build/import-gpg-keys.sh || exit 1
@@ -32,14 +64,11 @@ if [[ "$DRY_RUN" != true ]]; then
# 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
@@ -48,23 +77,21 @@ EOF
# 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
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.
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
# Add omarchy repo if it has a database (stable packages)
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"
fi
@@ -155,26 +182,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,6 +234,31 @@ 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"
@@ -280,21 +315,64 @@ 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" || {
FAILED_PACKAGES="$FAILED_PACKAGES $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/"
# 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)
if [[ ${#package_files[@]} -eq 0 ]]; then
echo " Makepkg produced no package files for $pkg"
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
return 1
fi
local dependency_pkg_file=""
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"
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
return 1
fi
cp "$pkg_file" "$BUILD_OUTPUT_DIR/"
new_pkgs+=("$pkg_file")
if [[ "$(bsdtar -xOf "$pkg_file" .PKGINFO 2>/dev/null | sed -n 's/^pkgname = //p')" == "$pkg" ]]; then
dependency_pkg_file="$BUILD_OUTPUT_DIR/$pkg_file"
fi
done
cd "$BUILD_OUTPUT_DIR"
# 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'))
# 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
ln -sf omarchy-build.db.tar.zst omarchy-build.db
@@ -303,6 +381,24 @@ build_package() {
cd /src/$pkg
# A lower-priority official repository may contain an older package with
# the same name. Install the exact artifact we just built before building
# its consumers, so pacman cannot select that older provider instead.
if [[ "${INSTALL_PACKAGES[$pkg]:-}" == "1" ]]; then
if [[ -z "$dependency_pkg_file" ]]; then
echo " Could not find the built $pkg package to install as a dependency"
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
return 1
fi
echo " Installing freshly built $pkg for dependent packages..."
if ! sudo /usr/local/bin/pacman-for-makepkg -U --needed --noconfirm "$dependency_pkg_file"; then
echo " Failed to install freshly built dependency $pkg"
FAILED_PACKAGES="$FAILED_PACKAGES $pkg"
return 1
fi
fi
echo " Successfully built $pkg"
SUCCESSFUL_PACKAGES="$SUCCESSFUL_PACKAGES $pkg"
return 0
@@ -421,7 +517,7 @@ check_needs_build() {
# 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
@@ -473,13 +569,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
+116 -25
View File
@@ -1,25 +1,102 @@
# 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.
if ! "$CONTAINER_ENGINE" run --rm --privileged docker.io/multiarch/qemu-user-static --reset -p yes --credential yes >/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 +116,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
}
+116 -11
View File
@@ -15,7 +15,11 @@
# { "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", "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/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on.
@@ -140,6 +144,55 @@ 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
}
# 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() {
@@ -298,9 +351,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
@@ -446,17 +502,50 @@ validate_package_metadata() {
# `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")] | map(select(.)) | length) == 1
and if 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, or debian provider"
return 1
fi
@@ -481,12 +570,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
+56
View File
@@ -5,6 +5,62 @@
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
}
# 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")]
| 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"
}
+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"
]
}
}
}
+35 -11
View File
@@ -1,27 +1,45 @@
pkgname=1password
_tarver=8.12.34
_tar="1password-${_tarver}.x64.tar.gz"
pkgver=${_tarver//-/_}
pkgrel=34
pkgver=8.12.34
pkgrel=36
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=('297784aa66770b645607a7f04c9ba2c4aebed4f46d21202487f521ba572b7b13'
'ec085bef60de748895d3c51a8208301ba2ac8fb47db99334539ba4bd1d3260d7'
)
sha256sums_aarch64=('ea5102363d6cf3442b96a7abd6743da8c1d261f56a628e1a3c183d84fa65fdcb'
'f44db73fa44c3f68c3ab78a9cce140be9355de1dd9be4ce731c9d6597960c907'
)
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)
@@ -33,6 +51,12 @@ package() {
# Install desktop file
install -Dm0644 resources/1password.desktop -t "${pkgdir}"/usr/share/applications/
# 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
POLICY_OWNERS="$(cut -d: -f1,3 /etc/passwd | grep -E ':[0-9]{4}$' | cut -d: -f1 | head -n 10 | sed 's/^/unix-user:/' | tr '\n' ' ')"
@@ -49,7 +73,7 @@ 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
+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')
+1 -2
View File
@@ -1,4 +1,3 @@
[asusctl]
source = "git"
git = "https://gitlab.com/asus-linux/asusctl.git"
git = "https://github.com/OpenGamingCollective/asusctl.git"
+1 -1
View File
@@ -1,4 +1,4 @@
{
"source": "aur",
"upstream_commit": "ed336f7dbd5e8bee333aeaa6246da89241f1c8e6"
"upstream_commit": "b0ec6ca495eb331a684db91b0fe12085a868b632"
}
@@ -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')
+38 -87
View File
@@ -1,108 +1,59 @@
# Maintainer: Fabian Bornschein <fabiscafe@archlinux.org>
# Maintainer: Mahdi Sarikhani <mahdisarikhani@outlook.com>
# Contributor: Fabian Bornschein <fabiscafe@archlinux.org>
# Contributor: Static_Rocket
pkgbase=asusctl
pkgname=(
asusctl
rog-control-center
)
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"
pkgrel=1
pkgdesc="Daemon and tools to control your ASUS ROG laptop"
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')
makedepends=('cargo' 'fontconfig')
source=("${pkgbase}-${pkgver}.tar.gz::https://github.com/OpenGamingCollective/asusctl/archive/${pkgver}.tar.gz")
b2sums=('e90074e904f364386ad661784bd9fc2e929e83ea06ac62fd1d34eb03490cefe8aae3bed2722162f1e8ea5d69248138cb5fd9f90c3a18443d1d8c04cf732b928a')
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')"
cd "${pkgbase}-${pkgver}"
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target host-tuple
}
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
cd "${pkgbase}-${pkgver}"
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'
)
pkgdesc="An utility for Linux to control many aspects of various ASUS laptops"
depends=('glibc' 'libgcc' 'libusb' 'systemd-libs')
optdepends=(
'acpi_call: fan control'
'asusctltray: tray profile switcher'
'rog-control-center: graphical user interface for asusctl'
'supergfxctl: hybrid GPU control'
)
install=asusctl.install
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
cd "${pkgbase}-${pkgver}"
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"
pkgdesc="Graphical user interface for asusctl"
depends=('asusctl' 'fontconfig' 'glibc' 'hicolor-icon-theme' 'libgcc' 'systemd-libs')
cd "${pkgbase}"
export CARGO_HOME="${srcdir}/cargo"
make DESTDIR="${pkgdir}" \
install-data-rog_gui \
install-rog_gui
cd "${pkgbase}-${pkgver}"
make DESTDIR="${pkgdir}" \
install-rog_gui \
install-data-rog_gui
}
+13 -14
View File
@@ -1,19 +1,18 @@
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"
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://github.com/OpenGamingCollective/asusctl#kernel-requirements\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"
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://github.com/OpenGamingCollective/asusctl#kernel-requirements\n"
printf ":: for latest required kernel patches/versions\n"
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"source": "aur",
"release_ring": "fast",
"upstream_commit": "fe2a6345e271f618dc3adf31e48557a717866395"
"upstream_commit": "1e7bd48ef192b3d6a2854916e75e14ea43f6f0b7"
}
+5 -5
View File
@@ -1,6 +1,6 @@
# Maintainer: Basecamp <support@basecamp.com>
pkgname=basecamp-cli
pkgver=0.9.1
pkgver=0.10.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/v0.10.0/basecamp_${pkgver}_linux_amd64.tar.gz")
source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.10.0/basecamp_${pkgver}_linux_arm64.tar.gz")
sha256sums_x86_64=('ca8b3a085f90323c8ab1694b83ff624cf10c459618ca6010ce204534f3f01987')
sha256sums_aarch64=('dd1b5db88b9b309e95ae1ec0d642d23f4955a3abe159bffe00796a0ef6030c1d')
package() {
install -Dm755 "basecamp" "${pkgdir}/usr/bin/basecamp"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"source": "aur",
"release_ring": "fast",
"upstream_commit": "29ca1941149bbf080d7eb6d2bb53cddc76478a1a"
"upstream_commit": "87aa5d5f2771c83dae737fe1bcfbc129110291fb"
}
+3 -3
View File
@@ -4,7 +4,7 @@
# Automation repository: https://github.com/fabifont/claude-code-aur
pkgname=claude-code
pkgver=2.1.252
pkgver=2.1.263
pkgrel=1
pkgdesc="An agentic coding tool that lives in your terminal"
arch=('x86_64' 'aarch64')
@@ -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=('a715a45105e593fc9808d035d77781f88480b9897975a9df41837f0c591bd4b3')
sha256sums_aarch64=('6c0b32eaa936954a0f4d4ad2595f8416af288d9b3cbaf19e3cdd9a95d7c8853e')
sha256sums_x86_64=('26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba')
sha256sums_aarch64=('7d25d7c8ae6c6e009cc7dae4e817f674179fd31fb7761bcd56fee4c2902b4c03')
package() {
install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude"
+1 -1
View File
@@ -1,4 +1,4 @@
{
"source": "aur",
"upstream_commit": "76b9ce02837a49de0fa50cfa3da5d1c0c692ece5"
"upstream_commit": "0e012cbfe1ea6cbbf15e2ada9cf93cca7aaa9ca0"
}
+3 -3
View File
@@ -1,17 +1,17 @@
# Maintainer: bjarneo <https://github.com/bjarneo>
pkgname=cliamp
pkgver=1.63.2
pkgver=2.0.1
pkgrel=1
pkgdesc='A retro terminal music player inspired by Winamp 2.x'
arch=('x86_64' 'aarch64')
url='https://github.com/bjarneo/cliamp'
license=('MIT')
depends=('alsa-lib' 'flac' 'libvorbis' 'libogg' 'ffmpeg' 'yt-dlp')
depends=('alsa-lib' 'flac' 'libvorbis' 'libogg' 'mpg123' 'ffmpeg' 'yt-dlp')
optdepends=('pipewire-alsa: audio output on PipeWire systems'
'pulseaudio-alsa: audio output on PulseAudio systems')
makedepends=('go')
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/bjarneo/cliamp/archive/refs/tags/v${pkgver}.tar.gz")
sha256sums=('968ff98c1e49bae8a0ce63acf5c77a9621ef70756a048e3df5e79454b82a9eef')
sha256sums=('2c5885665dba5ed2e8dc156bce64751199a92efed7f63959c65e985759b73732')
build() {
cd "${pkgname}-${pkgver}"
+1 -1
View File
@@ -1,5 +1,5 @@
{
"source": "aur",
"release_ring": "fast",
"upstream_commit": "00756d82c31b0056800bb98220c83800ebb85e72"
"upstream_commit": "c5073d62f60f3973a14340eb1b40fc5b5a4ea9f0"
}
+4 -4
View File
@@ -1,18 +1,18 @@
# Maintainer: Gunther Schulz <dev@guntherschulz.de>
pkgname=cursor-bin
pkgver=3.18.25
pkgver=3.19.13
pkgrel=1
pkgdesc='AI-first coding environment'
arch=('x86_64')
url="https://www.cursor.com"
license=('LicenseRef-Cursor_EULA')
# upstream uses Electron newer than internal VSCode
_electron=electron40
_electron=electron42
depends=(xdg-utils ripgrep $_electron nodejs
'gcc-libs' 'hicolor-icon-theme' 'libxkbfile')
options=(!strip !debug) # Don't break ext of VSCode
_commit=280eca2911f1774689696e5f1efa5a4f97a87af3
_commit=dd066f332fcea7382764400fde902f61920648d5
source=("https://downloads.cursor.com/production/${_commit}/linux/x64/deb/amd64/deb/cursor_${pkgver}_amd64.deb"
"https://gitlab.archlinux.org/archlinux/packaging/packages/code/-/raw/main/code."{sh,mjs}
rg.sh)
@@ -20,7 +20,7 @@ sha512sums=('SKIP'
'937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37'
'793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033'
'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a')
sha512sums[0]=5f25c9dccf75c764b6266e74136ad5cf6e99330804c7ae3ed22d4210304791b1ac1233efb07b291da490fbfe3d4801cb46680a171ddf6b2815e0267741ec339a
sha512sums[0]=3c952035fa1809caded7059b7addbb01a9143cd248ecff27cdb3cb3af47d4bc824ed60dfd9757e105a342e53280e075d81f775790295a3272818bab5a994b305
noextract=(cursor_${pkgver}_amd64.deb) # avoid double tarball
_app=usr/share/cursor/resources/app
package() {
@@ -0,0 +1,11 @@
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -11,7 +11,7 @@ pkgname=dropbox-cli
pkgver=2024.04.17
pkgrel=2
pkgdesc="Command line interface for Dropbox"
-arch=("any")
+arch=("x86_64")
url="https://www.dropbox.com"
license=("GPL-3.0-or-later")
makedepends=("gdk-pixbuf2")
+2 -2
View File
@@ -7,9 +7,9 @@
pkgname=dropbox-cli
pkgver=2024.04.17
pkgrel=2
pkgrel=2.1
pkgdesc="Command line interface for Dropbox"
arch=("any")
arch=("x86_64")
url="https://www.dropbox.com"
license=("GPL-3.0-or-later")
makedepends=("gdk-pixbuf2")
+5
View File
@@ -0,0 +1,5 @@
{
"source": "local",
"release_ring": "fast",
"min_release_age": "24h"
}
+100
View File
@@ -0,0 +1,100 @@
#!/bin/bash
# Flea publishes signed source tags but no release artifacts or checksum manifest.
# Check the small GitHub source tarball only when a newer stable release exists,
# validate its expected root, then report its hash alongside the fixed hashes of
# the four upstream security patches carried until a release includes them.
set -euo pipefail
REPO='thisisgm/flea'
RELEASES_URL="https://api.github.com/repos/$REPO/releases?per_page=100"
PATCH_2BD7CC2='c610a9f44294b67341940203943c9c567003b65cc436d6013cd36754379183d8'
PATCH_27D19CA='e457ef17e26f70057b1184eeb938fccc5906f48a7c77f1df573d55d13a845425'
PATCH_23290CE='f8381456a3b5f39d341cc6610bf27beb8354892b17a1c8c6d7882db4e40824c3'
PATCH_B4B7EE4='46bdbe43f135a052c893b1987f8b0928736445616a84b0c65d2181ee74b21b25'
current=$(awk -F= '/^pkgver=/ { print $2; exit }' PKGBUILD)
releases=$(curl -fsSL "$RELEASES_URL")
now=$(date +%s)
min_age=${MIN_RELEASE_AGE_SECONDS:-0}
candidates=0
best_version=''
best_tag=''
best_published_at=''
while IFS=$'\t' read -r tag published_at; do
if [[ ! $tag =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
printf '%s has an unusable stable release tag: %s\n' "$REPO" "${tag:-<empty>}" >&2
exit 1
fi
version=${BASH_REMATCH[1]}
if [[ -z $published_at ]] || ! published_epoch=$(date --date="$published_at" +%s 2>/dev/null); then
printf '%s release %s has an invalid publication time\n' "$REPO" "$tag" >&2
exit 1
fi
candidates=$((candidates + 1))
if (( now - published_epoch < min_age )) && [[ ${BYPASS_MIN_RELEASE_AGE:-} != 1 ]]; then
continue
fi
if [[ -z $best_version ]] || (( $(vercmp "$version" "$best_version") > 0 )); then
best_version=$version
best_tag=$tag
best_published_at=$published_at
fi
done < <(jq -r '.[] | select((.draft or .prerelease) | not) | [.tag_name // "", .published_at // ""] | @tsv' <<<"$releases")
if (( candidates == 0 )); then
printf 'No stable releases found for %s\n' "$REPO" >&2
exit 1
fi
if [[ -z $best_version ]] || (( $(vercmp "$best_version" "$current") <= 0 )); then
echo '{}'
exit 0
fi
tarball=$(mktemp)
trap 'rm -f "$tarball"' EXIT
curl -fsSL -o "$tarball" "https://github.com/$REPO/archive/refs/tags/$best_tag.tar.gz"
expected_root="flea-$best_version"
served_roots=$(tar -tzf "$tarball" | cut -d/ -f1 | sort -u)
if [[ $served_roots != "$expected_root" ]]; then
printf 'Release %s contains root %s, expected %s\n' "$best_tag" "$served_roots" "$expected_root" >&2
exit 1
fi
archive_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archive.rs")
archiveops_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archiveops.rs")
run_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/run.rs")
archivereq_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archivereq.rs")
archivework_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/archivework.rs")
mediaprobe_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/mediaprobe.rs")
metareq_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/metareq.rs")
sharelink_qml=$(tar -xOzf "$tarball" "$expected_root/ui/ShareLink.qml")
copyfile_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/copyfile.rs")
if ! grep -Fq 'a.push("--".to_string());' <<<"$archive_rs" ||
! grep -Fq 'let input = std::fs::canonicalize(input)' <<<"$archiveops_rs" ||
! grep -Fq 'if op != "compress" && op != "extract"' <<<"$run_rs$archivereq_rs" ||
! grep -Fq 'the sandbox is unavailable: bwrap or prlimit is not on PATH' <<<"$archivework_rs" ||
! grep -Fq 'if !sandbox::available()' <<<"$mediaprobe_rs" ||
! grep -Fq 'if !sandbox::available()' <<<"$metareq_rs" ||
! grep -Fq 'copyToClipboard.command = ["wl-copy", url]' <<<"$sharelink_qml" ||
! grep -Fq '.custom_flags(O_NOFOLLOW)' <<<"$copyfile_rs"; then
printf 'Release %s does not contain every required upstream security fix\n' "$best_tag" >&2
exit 1
fi
jq -n \
--arg pkgver "$best_version" \
--arg published_at "$best_published_at" \
--arg source "$(sha256sum "$tarball" | cut -d' ' -f1)" \
--arg patch_2bd7cc2 "$PATCH_2BD7CC2" \
--arg patch_27d19ca "$PATCH_27D19CA" \
--arg patch_23290ce "$PATCH_23290CE" \
--arg patch_b4b7ee4 "$PATCH_B4B7EE4" \
'{pkgver: $pkgver, published_at: $published_at, sha256sums: {any: [$source, $patch_2bd7cc2, $patch_27d19ca, $patch_23290ce, $patch_b4b7ee4]}}'
+163
View File
@@ -0,0 +1,163 @@
# Maintainer: GM <gianmarcomorales@icloud.com>
pkgname=flea
pkgver=0.1.3
pkgrel=1
pkgdesc='Fast, keyboard-first file manager for Omarchy'
arch=('x86_64')
url='https://github.com/thisisgm/flea'
license=('MIT')
depends=(
'bubblewrap'
'gcc-libs'
'glib2'
'glibc'
'hicolor-icon-theme'
'omarchy'
'quickshell'
'qt6-multimedia'
'qt6-webengine'
'shared-mime-info'
'util-linux'
'xdg-utils'
)
makedepends=('cargo')
optdepends=(
'7zip: 7z archive support'
'dropbox-cli: Dropbox browsing and share links'
'ffmpeg: media metadata previews'
'imagemagick: image conversion'
'libarchive: archive listing and extraction'
'tailscale: Taildrop sharing'
'wl-clipboard: copy Dropbox share links to the clipboard'
)
conflicts=('flea-git')
options=('!debug')
_security_patches=(
'2bd7cc207c110ae3b3ea61a4d4e42336f1815111'
'27d19ca4a38ef4a0a920fb78441ce662a0101327'
'23290ce1917b0d7f0392dd09d04bf2a7504ec93a'
'b4b7ee47244b52457eec2874e21a8656d8ace647'
)
source=(
"$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz"
"security-2bd7cc2.patch::$url/commit/${_security_patches[0]}.patch"
"security-27d19ca.patch::$url/commit/${_security_patches[1]}.patch"
"security-23290ce.patch::$url/commit/${_security_patches[2]}.patch"
"security-b4b7ee4.patch::$url/commit/${_security_patches[3]}.patch"
)
sha256sums=(
'3599ab76253c444dea027f1ebe85b32612d9a174e60eb8aca33ee21d9e2d6973'
'c610a9f44294b67341940203943c9c567003b65cc436d6013cd36754379183d8'
'e457ef17e26f70057b1184eeb938fccc5906f48a7c77f1df573d55d13a845425'
'f8381456a3b5f39d341cc6610bf27beb8354892b17a1c8c6d7882db4e40824c3'
'46bdbe43f135a052c893b1987f8b0928736445616a84b0c65d2181ee74b21b25'
)
prepare() {
cd "$pkgname-$pkgver"
security_patch_present() {
case "$1" in
2bd7cc207c110ae3b3ea61a4d4e42336f1815111)
grep -Fq 'a.push("--".to_string());' src/backend/archive.rs &&
grep -Fq 'let input = std::fs::canonicalize(input)' src/backend/archiveops.rs &&
grep -Fq 'if op != "compress" && op != "extract"' \
src/backend/run.rs src/backend/archivereq.rs
;;
27d19ca4a38ef4a0a920fb78441ce662a0101327)
grep -Fq 'the sandbox is unavailable: bwrap or prlimit is not on PATH' src/backend/archivework.rs &&
grep -Fq 'if !sandbox::available()' src/backend/mediaprobe.rs &&
grep -Fq 'if !sandbox::available()' src/backend/metareq.rs
;;
23290ce1917b0d7f0392dd09d04bf2a7504ec93a)
grep -Fq 'copyToClipboard.command = ["wl-copy", url]' ui/ShareLink.qml
;;
b4b7ee47244b52457eec2874e21a8656d8ace647)
grep -Fq '.custom_flags(O_NOFOLLOW)' src/backend/copyfile.rs
;;
*)
return 1
;;
esac
}
local commit patch_file
for commit in "${_security_patches[@]}"; do
patch_file="$srcdir/security-${commit:0:7}.patch"
if patch --batch --forward --dry-run -Np1 -i "$patch_file" >/dev/null 2>&1; then
patch --batch --forward -Np1 -i "$patch_file"
elif security_patch_present "$commit"; then
printf 'Security patch %s is already present upstream\n' "$commit"
else
printf 'Security patch %s no longer applies and is not present upstream\n' "$commit" >&2
return 1
fi
done
}
build() {
cd "$pkgname-$pkgver"
export CARGO_TARGET_DIR=target
cargo build --frozen --release
}
check() {
cd "$pkgname-$pkgver"
export CARGO_TARGET_DIR=target
local -a test_args=()
if ! /usr/bin/prlimit --cpu=30 --as=1073741824 \
/usr/bin/bwrap --unshare-all --die-with-parent --new-session --clearenv \
--ro-bind /usr /usr --ro-bind /etc /etc \
--symlink usr/lib /lib --symlink usr/lib /lib64 \
--symlink usr/bin /bin --symlink usr/bin /sbin \
--proc /proc --dev /dev --tmpfs /tmp /usr/bin/true >/dev/null 2>&1; then
printf 'Skipping sandbox integration tests: this builder cannot create an unprivileged namespace\n'
test_args+=(
--skip backend::archiveops::tests::a_tool_that_exits_zero_writing_nothing_is_caught_by_the_predicate
--skip backend::archiveops::tests::an_empty_archive_extracts_to_an_empty_directory_and_that_is_success
--skip backend::archiveops::tests::only_the_archive_root_extracts_to_nothing_while_nested_directories_do_not
--skip backend::archiveops::tests::the_emptiness_check_reads_the_destination_and_the_index_separately
--skip backend::thumbs::tests::a_cancelled_job_never_runs
--skip backend::thumbs::tests::a_real_file_round_trips_to_a_stamped_cache_entry
--skip backend::thumbs::tests::cancel_all_empties_the_queue
--skip backend::thumbs::tests::the_queue_is_bounded_and_drops_the_oldest_rather_than_growing
)
fi
if [[ ! -x /usr/bin/evince-thumbnailer ]] ||
[[ ! -x /usr/bin/ffmpegthumbnailer ]] ||
[[ ! -x /usr/bin/glycin-thumbnailer ]]; then
printf 'Skipping thumbnailer fixture tests: this builder lacks their optional host programs\n'
test_args+=(
--skip backend::thumbspec::tests::a_type_two_files_declare_goes_to_the_first_in_path_order
--skip backend::thumbspec::tests::the_heaviest_shipped_field_parses
)
fi
cargo test --frozen --release -- "${test_args[@]}"
./tests/js.sh
./tests/keymap-gen.sh
}
package() {
cd "$pkgname-$pkgver"
install -Dm755 target/release/flea "$pkgdir/usr/bin/flea"
install -Dm644 packaging/com.thisisgm.flea.desktop \
"$pkgdir/usr/share/applications/com.thisisgm.flea.desktop"
install -Dm644 packaging/com.thisisgm.flea.svg \
"$pkgdir/usr/share/icons/hicolor/scalable/apps/com.thisisgm.flea.svg"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 ui/qmldir ui/*.qml -t "$pkgdir/usr/share/flea/ui"
install -Dm644 ui/js/*.js -t "$pkgdir/usr/share/flea/ui/js"
ln -s /usr/share/omarchy/shell/Commons "$pkgdir/usr/share/flea/ui/Commons"
ln -s /usr/share/omarchy/shell/Ui "$pkgdir/usr/share/flea/ui/Ui"
}
@@ -1,5 +1,13 @@
{
"source": "aur",
"source": "local",
"release_ring": "fast",
"upstream_commit": "fc4db00ecaf6c2669422b32c023a16bf6eb4e6c7"
"upstream": {
"npm": "@github/copilot",
"sources": {
"any": [
"{npm_tarball}",
"https://raw.githubusercontent.com/github/copilot-cli/v{pkgver}/changelog.md"
]
}
}
}
+6 -5
View File
@@ -6,7 +6,7 @@ _npmmodule=@github/copilot
pkgname=github-copilot-cli
_pkgexec=copilot
pkgver=1.0.82
pkgver=1.0.83
pkgrel=1
pkgdesc="GitHub Copilot CLI brings the power of Copilot coding agent directly to your terminal."
@@ -14,7 +14,7 @@ pkgdesc="GitHub Copilot CLI brings the power of Copilot coding agent directly to
url="https://github.com/github/copilot-cli"
_urlraw="https://raw.githubusercontent.com/github/copilot-cli/v${pkgver}"
arch=("x86_64")
arch=("x86_64" "aarch64")
license=("LicenseRef-GitHub-Copilot")
@@ -30,8 +30,10 @@ source=("https://registry.npmjs.org/${_npmmodule}/-/copilot-${pkgver}.tgz"
"CHANGELOG-${pkgver}.md::${_urlraw}/changelog.md")
noextract=("copilot-${pkgver}.tgz")
b2sums=('41c8306b58a142afe162bad9822b150babb0f3903fe7392aa7106fcb0ffce4483c0a2ebda8bb1d2036199deb52dc83d9a0e9408b8e11f7b0bc2a8f57c6841425'
'5d2dfc870356f83b9b890d351b21bc9339100b0bfdce405ee77751e934dbeab0f8656f5eee85119a06aa3b01dcf3c91224ce28c24ff318eb1b13fdb6082037cd')
sha256sums=(
'135506fc2b13163ab55dbf76a06e2fbcbad04ecac76b4e9c6659f0ba309e6a86'
'0c0064a10effac8adf9ad97338bafaa0d7d7d5bf191cc1c0384e05ff4366d36c'
)
# Document: https://wiki.archlinux.org/title/Node.js_package_guidelines
package() {
@@ -124,4 +126,3 @@ package() {
ln -sf "/usr/lib/node_modules/${_npmmodule}/LICENSE.md" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
}
+34 -34
View File
@@ -1,25 +1,11 @@
# Maintainer: David Heinemeier Hansson <david@hey.com>
# Nous builds Hermes Desktop for macOS and Windows only -- their download page
# offers a .dmg and an .exe and tells Linux users to install from a terminal --
# so there is no vendor binary to repackage. Their electron-builder config does
# carry a Linux target, though, and it works; this builds it.
#
# The app only runs against a Hermes runtime built from its own commit, so it
# provisions one itself under ~/.hermes on first launch and the package stays
# on the newest tag. Pairing it with the mise CLI instead was tried and does
# not work: PyPI trails the tags, and the version gap fails the readiness probe
# with a 401. Pinning back to the tag behind PyPI's release does not rescue it
# either -- v2026.7.20's desktop hangs after "backend is ready" without ever
# opening a window, against its own matching runtime.
#
# The app is only a shell: it runs `hermes serve` against a Hermes CLI it does
# not ship, and clones its own copy with the upstream install script when it
# finds none. /usr/bin/hermes-desktop heads that off. See hermes-desktop.sh.
# Build a prebuilt release; Omarchy seeds it into the user's Hermes checkout
# so the upstream updater can rebuild and relaunch it in place.
pkgname=hermes-desktop
pkgver=2026.8.18
pkgrel=2
pkgver=2026.8.31
pkgrel=3
pkgdesc='Native desktop shell for Hermes Agent'
arch=('x86_64')
url='https://github.com/NousResearch/hermes-agent'
@@ -32,7 +18,7 @@ depends=(
'dbus'
'expat'
'curl'
'gcc-libs'
'gcc'
'gdk-pixbuf2'
'git'
'glib2'
@@ -52,19 +38,24 @@ depends=(
'libxfixes'
'libxkbcommon'
'libxrandr'
'make'
'mesa'
'nodejs'
'npm'
'nspr'
'nss'
'pango'
'python'
'systemd-libs'
'util-linux'
'xdg-utils'
)
optdepends=('omarchy: installs the Hermes CLI the app needs on first launch')
optdepends=('omarchy: sets up the user installation for in-app updates')
# The build runs the repo's own npm workspace install, which fetches Electron
# and rebuilds node-pty against it.
makedepends=('git' 'imagemagick' 'nodejs' 'npm' 'python')
makedepends=('imagemagick')
# Electron bundles prebuilt binaries that stripping corrupts.
options=('!strip' '!debug')
@@ -74,23 +65,27 @@ options=('!strip' '!debug')
# before falling back to `git rev-parse`. That fallback is wrong here: makepkg
# builds inside this repository, so git ascends out of srcdir and stamps the
# app with an omarchy-pkgs commit that means nothing upstream.
_commit=e624e9fde561e1add9388384012b295fde669ade
_commit=29112bef099274229cadff79cdff7bf7b99c4b77
_srcdir="hermes-agent-${pkgver}"
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz"
'hermes-desktop.sh'
'hermes-desktop.desktop'
'hermes-desktop.png')
sha256sums=('1e3d39d3638ec15fa9d31af262568a953e9272090deb1c50c44cd401175f5b80'
'f5833b969ce451aadee9f08d92db55cce3c7c9213175080590bf37444854d676'
'hermes-desktop.png'
'runtime.patch'
'runtime-test.py')
sha256sums=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217'
'094d5f3191109a80eea9f23053b78a2e00dbecf90d62d1ca04c8e48866251469'
'3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4'
'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52')
'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52'
'03b67e26c234c797a6b1d4c9f34a57502dba37f462540e47ce6c88f6ea79302a'
'461e1120e7e6779f531c114d9926479e47fab79113d1b4646efea36bc771b3c5')
build() {
cd "${srcdir}/${_srcdir}"
export GITHUB_SHA="${_commit}"
export GITHUB_REF_NAME="v${pkgver}"
export GITHUB_REF_NAME="main"
# The desktop workspace resolves against the repo root, so the install has to
# happen there rather than in apps/desktop.
@@ -100,6 +95,10 @@ build() {
npm run pack
}
check() {
python "${srcdir}/runtime-test.py" "${srcdir}/${_srcdir}" "${srcdir}/runtime.patch" "${srcdir}/hermes-desktop.sh"
}
package() {
cd "${srcdir}/${_srcdir}/apps/desktop/release/linux-unpacked"
@@ -108,6 +107,12 @@ package() {
install -Dm755 "${srcdir}/hermes-desktop.sh" "${pkgdir}/usr/bin/${pkgname}"
install -Dm644 "${srcdir}/${_srcdir}/scripts/install.sh" \
"${pkgdir}/usr/share/${pkgname}/install.sh"
# Let the release's first updater relaunch with the user-namespace sandbox.
install -Dm644 "${srcdir}/runtime.patch" "${pkgdir}/usr/share/${pkgname}/runtime.patch"
install -Dm644 "${srcdir}/hermes-desktop.desktop" \
"${pkgdir}/usr/share/applications/${pkgname}.desktop"
@@ -125,11 +130,6 @@ package() {
install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \
"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt"
# Chromium's setuid sandbox is only needed where unprivileged user namespaces
# are unavailable; where they work, setuid root is the worse of the two.
if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then
chmod 4755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
else
chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
fi
# The launcher requires the namespace sandbox on the target host.
chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
}
+63 -30
View File
@@ -1,41 +1,74 @@
#!/bin/bash
set -euo pipefail
# Hermes Desktop is a shell around a Hermes runtime, and it only works against
# one built from its own commit. A CLI from PyPI is always a different release
# -- PyPI trails the tags -- and the mismatch fails the app's readiness probe
# with 401 Unauthorized. So keep it away from whatever `hermes` is on PATH,
# which on Omarchy is the mise CLI installed for the terminal agent, and let
# the app provision and manage its own runtime under ~/.hermes. That is the
# arrangement upstream ships, and the only one that starts.
unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME
export HERMES_DESKTOP_IGNORE_EXISTING=1
# Chromium cannot reliably infer the Secret Service password-store backend
# from a Hyprland session, even when GNOME Keyring is already providing it.
# Keep an explicit user choice (such as KWallet), otherwise select the
# libsecret backend that this package depends on.
export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}"
# Reconcile every launch rather than trusting whatever installed us. A plain
# `pacman -S hermes-desktop`, or an install interrupted partway, leaves any
# Hermes the terminal agent had built still sitting there, and by then the
# menu entry that would have tidied it up is disabled because we are present.
# Reconcile direct package installs and interrupted Omarchy setup as well.
if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then
omarchy-install-hermes-cli >/dev/null 2>&1 || true
fi
# Chromium's own Ozone detection falls back to XWayland often enough to matter,
# and the result is a blurry window on every scaled display. Ask for Wayland
# directly, unless the user has already picked a platform themselves.
platform_flags=()
if [[ -n "${WAYLAND_DISPLAY:-}" || ${XDG_SESSION_TYPE:-} == wayland ]]; then
platform_flags=(--ozone-platform=wayland)
for flag in "$@"; do
case "$flag" in
--ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;;
esac
done
hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}")
parent=${hermes_home%/*}
if [[ ${parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then
hermes_home=${parent%/*}
hermes_home=${hermes_home:-/}
fi
export HERMES_HOME="$hermes_home"
runtime="$hermes_home/hermes-agent"
native="$runtime/apps/desktop/release/linux-unpacked/Hermes"
if [[ ! -x $native || ! -x $runtime/venv/bin/hermes ]]; then
native=/opt/hermes-desktop/Hermes
fi
exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@"
# Both app locations use namespaces, never a user-writable setuid helper.
if ! timeout 5 unshare --user --map-root-user true 2>/dev/null; then
echo "Hermes Desktop requires working unprivileged user namespaces for its sandbox." >&2
exit 1
fi
python=/usr/bin/python
if [[ -x $runtime/venv/bin/python && -f $runtime/hermes_cli/main.py ]]; then
python="$runtime/venv/bin/python"
else
runtime=""
fi
exec "$python" - "$native" "$runtime" "$@" <<'PY'
import os
from pathlib import Path
import sys
native, runtime, *args = sys.argv[1:]
env = os.environ.copy()
flags, gpu, store, ozone = [], "auto", "auto", "auto"
if runtime:
sys.path.insert(0, runtime)
try:
# Upstream moved the helper out of main after the packaged release.
if Path(runtime, "hermes_cli/main_desktop.py").is_file():
from hermes_cli.main_desktop import _desktop_launch_options
else:
from hermes_cli.main import _desktop_launch_options
from hermes_constants import with_hermes_node_path
flags, gpu, store, ozone = _desktop_launch_options()
env = with_hermes_node_path(env)
except ImportError:
print("Could not load Hermes desktop settings; using launch defaults.", file=sys.stderr)
env["HERMES_DESKTOP_CWD"] = os.getcwd()
if gpu != "auto":
env.setdefault("HERMES_DESKTOP_DISABLE_GPU", gpu)
if ozone != "auto":
env.setdefault("ELECTRON_OZONE_PLATFORM_HINT", ozone)
env.setdefault("HERMES_DESKTOP_PASSWORD_STORE", store if store != "auto" else "gnome-libsecret")
# Explicit config, environment and command-line choices override the Wayland default.
if (env.get("WAYLAND_DISPLAY") or env.get("XDG_SESSION_TYPE") == "wayland") and (
"ELECTRON_OZONE_PLATFORM_HINT" not in env
and not any(arg.startswith(("--ozone-platform=", "--ozone-platform-hint=")) for arg in flags + args)
):
flags.insert(0, "--ozone-platform=wayland")
os.execve(native, [native, "--disable-setuid-sandbox", *flags, *args], env)
PY
+162
View File
@@ -0,0 +1,162 @@
"""Check launch settings and sandbox gates without running Hermes or sudo."""
import ast
import json
import os
from pathlib import Path
import shutil
import subprocess
import sys
import tempfile
source, patch_file, launcher = map(Path, sys.argv[1:])
with tempfile.TemporaryDirectory(prefix="hermes-runtime-check-") as temporary:
root = Path(temporary)
destination = root / "scripts/desktop-update/posix.sh"
destination.parent.mkdir(parents=True)
shutil.copyfile(source / "scripts/desktop-update/posix.sh", destination)
subprocess.run(["git", "apply", str(patch_file.resolve())], cwd=root, check=True)
home = root / "home with spaces"
runtime = home / ".hermes/hermes-agent"
native = runtime / "apps/desktop/release/linux-unpacked"
native.mkdir(parents=True)
executable = native / "Hermes"
executable.write_text(f"#!{sys.executable}\n" + '''import json, os, sys
from pathlib import Path
Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
"args": sys.argv[1:], "home": os.environ["HERMES_HOME"],
"store": os.environ["HERMES_DESKTOP_PASSWORD_STORE"],
"gpu": os.environ.get("HERMES_DESKTOP_DISABLE_GPU"),
"ozone": os.environ.get("ELECTRON_OZONE_PLATFORM_HINT"),
"cwd": os.environ.get("HERMES_DESKTOP_CWD"),
"inherited": [name for name in ("ELECTRON_RUN_AS_NODE", "PYTHONPATH", "PYTHONHOME") if name in os.environ],
}))
''')
executable.chmod(0o755)
sandbox = native / "chrome-sandbox"
sandbox.write_text("fixture")
sandbox.chmod(0o755)
# Execute the release's real option parser, isolating config I/O and avoiding
# unrelated CLI imports, startup hooks and third-party dependencies.
module = runtime / "hermes_cli"
module.mkdir()
(module / "__init__.py").touch()
upstream = ast.parse((source / "hermes_cli/main.py").read_text())
option_parser = next(node for node in upstream.body
if isinstance(node, ast.FunctionDef) and node.name == "_desktop_launch_options")
stores = next(node for node in upstream.body if isinstance(node, ast.Assign)
and any(isinstance(target, ast.Name) and target.id == "_LINUX_PASSWORD_STORES"
for target in node.targets))
helper = "import os, shlex\n" + ast.unparse(stores) + "\n" + ast.unparse(option_parser) + "\n"
(module / "main.py").write_text(helper)
(module / "config.py").write_text('''import json, os
from pathlib import Path
def load_config():
path = Path(os.environ["HERMES_HOME"], "config.yaml")
return json.loads(path.read_text()) if path.exists() else {}
''')
(runtime / "hermes_constants.py").write_text('''import os
def with_hermes_node_path(env=None):
return (os.environ if env is None else env).copy()
''')
mock_bin = root / "bin"
mock_bin.mkdir()
unshare = mock_bin / "unshare"
unshare.write_text('#!/bin/bash\nexit "${TEST_NAMESPACE_RESULT:-0}"\n')
unshare.chmod(0o755)
forbidden = '#!/bin/bash\ntouch "$TEST_FORBIDDEN"\nexit 99\n'
cli = runtime / "venv/bin/hermes"
cli.parent.mkdir(parents=True)
cli.write_text(forbidden)
cli.chmod(0o755)
(cli.parent / "python").symlink_to(sys.executable)
for command in ("sudo", "omarchy-install-hermes-cli"):
target = mock_bin / command
target.write_text(forbidden if command == "sudo" else '#!/bin/bash\nexit 0\n')
target.chmod(0o755)
output = root / "launch.json"
forbidden_output = root / "forbidden"
env = {"HOME": str(home), "PATH": f"{mock_bin}:/usr/bin:/bin",
"TEST_OUTPUT": str(output), "TEST_FORBIDDEN": str(forbidden_output)}
launch = ["bash", str(launcher.resolve())]
def check_launch(args=(), overrides=None, expected_args=(), store="gnome-libsecret", gpu=None, ozone=None):
subprocess.run(launch + list(args), env={**env, **(overrides or {})}, cwd=home, check=True)
result = json.loads(output.read_text())
assert result == {"args": ["--disable-setuid-sandbox", *expected_args],
"home": str(home / ".hermes"), "store": store, "gpu": gpu,
"ozone": ozone, "cwd": str(home), "inherited": []}, result
assert not forbidden_output.exists(), "launcher invoked CLI or sudo"
output.unlink()
wayland = {"WAYLAND_DISPLAY": "wayland-1"}
check_launch(overrides=wayland, expected_args=["--ozone-platform=wayland"])
url = "hermes://open?text=a%20b"
check_launch(["--ozone-platform=x11", url], {**wayland, "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"},
["--ozone-platform=x11", url], store="kwallet6")
check_launch(overrides={"HERMES_HOME": str(home / ".hermes/profiles/work"),
"HERMES_DESKTOP_DISABLE_GPU": "1"}, gpu="1")
check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"})
config = home / ".hermes/config.yaml"
config.write_text(json.dumps({"desktop": {"disable_gpu": True, "password_store": "kwallet5",
"ozone_platform_hint": "x11",
"electron_flags": '--force-device-scale-factor=1.5 "--test=a b"'}}))
config_flags = ["--force-device-scale-factor=1.5", "--test=a b"]
for args in ([], [url]):
check_launch(args, wayland, config_flags + args, store="kwallet5", gpu="1", ozone="x11")
check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0",
"HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "wayland"},
expected_args=config_flags, store="basic", gpu="0", ozone="wayland")
config.write_text(json.dumps({"desktop": {"electron_flags": ["--ozone-platform=x11"], "disable_gpu": False}}))
check_launch(overrides=wayland, expected_args=["--ozone-platform=x11"], gpu="0")
# New upstream revisions expose the same helper from main_desktop instead.
(module / "main_desktop.py").write_text(helper)
(module / "main.py").write_text('raise AssertionError("old helper import after update")\n')
check_launch([url], wayland, ["--ozone-platform=x11", url], gpu="0")
config.unlink()
(module / "main_desktop.py").write_text(helper + '\nimport os\nos.environ.update(' + repr({
"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid",
"HERMES_HOME": "/invalid", "HERMES_DESKTOP_DISABLE_GPU": "1",
"HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "x11",
}) + ')\n')
check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0"},
expected_args=["--ozone-platform=wayland"], gpu="0")
(module / "main_desktop.py").write_text(helper)
def check_namespace_failure():
for code in ("1", "127"):
for args in ([], [url]):
result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code},
capture_output=True, text=True)
assert result.returncode != 0 and "user namespaces" in result.stderr, result
assert not output.exists() and not forbidden_output.exists()
check_namespace_failure()
# Exercise the real fallback branch with only its absolute app path redirected.
fallback = root / "packaged-Hermes"
shutil.copyfile(executable, fallback)
fallback.chmod(0o755)
fallback_launcher = root / "fallback-launcher"
fallback_launcher.write_text(launcher.read_text().replace("/opt/hermes-desktop/Hermes", str(fallback)))
launch = ["bash", str(fallback_launcher)]
executable.unlink()
check_launch([url], wayland, ["--ozone-platform=wayland", url])
(module / "main_desktop.py").write_text('raise ImportError("incomplete Python dependencies")\n')
check_launch([url], wayland, ["--ozone-platform=wayland", url])
shutil.rmtree(runtime / "venv")
check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"})
check_namespace_failure()
gate = ["bash", str(destination), "--self-test-gate", "--install-root", str(runtime),
"--relaunch-target", str(executable)]
executable.touch()
executable.chmod(0o755)
assert subprocess.check_output(gate, env=env, text=True).strip() == "relaunch"
assert subprocess.check_output(gate, env={**env, "TEST_NAMESPACE_RESULT": "1"}, text=True).startswith("manual:")
gate[-1] = "/opt/hermes-desktop/Hermes"
assert subprocess.check_output(gate, env=env, text=True).startswith("skew:")
print("PASS: desktop settings, environment, native/fallback namespace sandbox and updater gate")
+11
View File
@@ -0,0 +1,11 @@
--- a/scripts/desktop-update/posix.sh
+++ b/scripts/desktop-update/posix.sh
@@ -317,6 +317,8 @@
if [ ! -e "$sb" ]; then GATE=relaunch; return; fi
if [ -u "$sb" ] && [ "$(stat -c %u "$sb" 2>/dev/null)" = "0" ]; then GATE=relaunch; return; fi
+ if unshare --user --map-root-user true 2>/dev/null; then GATE=relaunch; return; fi
+
case "${ELECTRON_DISABLE_SANDBOX:-}" in 1|true|TRUE|True) GATE=relaunch; return ;; esac
[ "$SANDBOX_FALLBACK" -eq 1 ] && { GATE=relaunch; return; }
for arg in ${RELAUNCH_ARGS[@]+"${RELAUNCH_ARGS[@]}"}; do
@@ -0,0 +1,21 @@
{
"source": "local",
"release_ring": "fast",
"upstream": {
"git_tags": "https://github.com/hyprwm/hyprland-guiutils.git",
"tag_pattern": "v{pkgver}",
"sources": {
"any": [
"https://github.com/hyprwm/hyprland-guiutils/archive/v{pkgver}/hyprland-guiutils-{pkgver}.tar.gz"
]
}
},
"rebuild_on": [
"aquamarine"
],
"rebuilt_against": {
"aarch64": {
"aquamarine": "0.15.0-2"
}
}
}
+40
View File
@@ -0,0 +1,40 @@
# Maintainer: Caleb Maclennan <caleb@alerque.com>
pkgname=hyprland-guiutils
pkgver=0.2.2
pkgrel=3
pkgdesc='Hyprland GUI utilities'
arch=(aarch64)
url="https://github.com/hyprwm/$pkgname"
license=(BSD-3-Clause)
depends=(
libgcc
libstdc++
glibc # libc.so libm.so
hyprlang
hyprtoolkit libhyprtoolkit.so
hyprutils libhyprutils.so
libdrm
pixman
)
makedepends=(cmake)
replaces=(hyprland-qtutils)
_archive="$pkgname-$pkgver"
source=("$url/archive/v$pkgver/$_archive.tar.gz")
sha256sums=('16f92a6c5a22ac58e1fc313f6b202c188da45e804e1f21ff57dfd0da5c1a01b7')
build() {
cd "$_archive"
local cmake_flags=(
-D CMAKE_BUILD_TYPE=Release
-D CMAKE_INSTALL_PREFIX=/usr
)
cmake -B build ${cmake_flags[@]}
cmake --build build
}
package() {
cd "$_archive"
DESTDIR="$pkgdir" cmake --install build
install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname" LICENSE
}
@@ -4,7 +4,7 @@ pkgname="hyprland-preview-share-picker"
pkgver=0.2.1
pkgrel=1
pkgdesc="An alternative share picker for hyprland with window and monitor previews"
arch=(x86_64)
arch=(x86_64 aarch64)
url="https://github.com/WhySoBad/hyprland-preview-share-picker"
license=(MIT)
depends=('gtk4' 'gtk4-layer-shell' 'xdg-desktop-portal-hyprland' 'hyprland')
+21
View File
@@ -0,0 +1,21 @@
{
"source": "local",
"release_ring": "fast",
"upstream": {
"git_tags": "https://github.com/hyprwm/Hyprland.git",
"tag_pattern": "v{pkgver}",
"sources": {
"any": [
"https://github.com/hyprwm/Hyprland/releases/download/v{pkgver}/source-v{pkgver}.tar.gz"
]
}
},
"rebuild_on": [
"aquamarine"
],
"rebuilt_against": {
"aarch64": {
"aquamarine": "0.15.0-2"
}
}
}
+105
View File
@@ -0,0 +1,105 @@
# Maintainer: Caleb Maclennan <caleb@alerque.com>
# Contributor: ThatOneCalculator <kainoa@t1c.dev>
# Contributor: Brenno Lemos <brenno@syndel.is>
# Contributor: Gabriel Fox <inbox@gabrielfox.dev>
pkgname=(hyprland hyprpm)
pkgver=0.56.2
pkgrel=3
pkgdesc='a highly customizable dynamic tiling Wayland compositor'
arch=(aarch64)
url="https://github.com/hyprwm/${pkgname^}"
license=(BSD-3-Clause)
depends=(cairo # libcairo.so
aquamarine libaquamarine.so
libgcc # libgcc_s.so
'libstdc++' # libstdc++.so
glibc # libc.so libm.so
glib2 libgio-2.0.so libgobject-2.0.so
glslang # libglslang-default-resource-limits.so libglslang.so
hyprcursor libhyprcursor.so
hyprgraphics libhyprgraphics.so
hyprland-guiutils
hyprlang libhyprlang.so
hyprutils libhyprutils.so
hyprwire libhyprwire.so
lcms2 liblcms2.so
libdrm # libdrm.so
libglvnd libEGL.so libGLESv2.so
libinput # libinput.so
libx11
libxcb # libxcb-dri3.so libxcb-present.so libxcb-render.so libxcb-res.so libxcb-shm.so libxcb.so libxcb-xfixes.so libxcb-xinput.so
libxcomposite # libxcb-composite.so
libxcursor # libXcursor.so
libxfixes
libxkbcommon libxkbcommon.so
libxrender
lua # liblua.so
mesa # libgbm.so
muparser # libmuparser.so
opengl-driver
pango libpango-1.0.so libpangocairo-1.0.so
pixman libpixman-1.so
re2 libre2.so
tomlplusplus libtomlplusplus.so
util-linux-libs libuuid.so
wayland libwayland-server.so
wayland-protocols
xcb-proto
xcb-util
xcb-util-errors # libxcb-errors.so
xcb-util-image
xcb-util-keysyms
xcb-util-renderutil # libxcb-render-util.so
xcb-util-wm # libxcb-ewmh.so libxcb-icccm.so
xorg-xwayland)
makedepends=(cmake
glaze
hyprland-protocols
hyprwayland-scanner
meson
ninja
xorgproto)
optdepends=('hyprpm: build and install plugins'
'hyprshutdown: clean logout and shutdown helper'
'xdg-desktop-portal-hyprland: xdg-desktop-portal backend for hyprland')
provides=(wayland-compositor)
_archive="${pkgname^}-$pkgver"
source=("$_archive.tar.gz::$url/releases/download/v$pkgver/source-v$pkgver.tar.gz")
sha256sums=('03ad3f5ef152ff44116ffd56fcf808486211ecabf4f0ba567108ee746ba5cd2e')
prepare() {
ln -sf hyprland-source "$_archive"
cd "$_archive"
sed -i -e '/^release:/{n;s/-D/-DCMAKE_SKIP_RPATH=ON -D/}' Makefile
sed -i -e '/find_package.glaze/s/7...<8 //' {.,hyprpm,start}/CMakeLists.txt
}
build() {
cd "$_archive"
make release PREFIX=/usr
}
package_hyprland() {
cd "$_archive"
make DESTDIR="$pkgdir" install
rm -fv "$pkgdir/usr/include/hyprland/src/version.h.in"
find $pkgdir -name '*hyprpm*' -delete
install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE
}
package_hyprpm() {
pkgdesc='Plugin manager for Hyprland'
depends=(cmake
cpio
glaze
hyprland
hyprland-protocols
hyprwayland-scanner
meson)
cd "$_archive"
make DESTDIR="$pkgdir" install
find $pkgdir -type f,l -not -name '*hyprpm*' -delete
find $pkgdir -type d -empty -delete
install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE
}
@@ -0,0 +1,21 @@
{
"source": "local",
"release_ring": "fast",
"upstream": {
"git_tags": "https://github.com/hyprwm/hyprtoolkit.git",
"tag_pattern": "v{pkgver}",
"sources": {
"any": [
"https://github.com/hyprwm/hyprtoolkit/archive/v{pkgver}/hyprtoolkit-{pkgver}.tar.gz"
]
}
},
"rebuild_on": [
"aquamarine"
],
"rebuilt_against": {
"aarch64": {
"aquamarine": "0.15.0-2"
}
}
}
+50
View File
@@ -0,0 +1,50 @@
# Maintainer: Caleb Maclennan <caleb@alerque.com>
pkgname=hyprtoolkit
pkgver=0.5.4
pkgrel=5.1
pkgdesc='A modern C++ Wayland-native GUI toolkit'
arch=(aarch64)
url="https://github.com/hyprwm/$pkgname"
license=(BSD-3-Clause)
depends=(
libgcc
libstdc++
aquamarine libaquamarine.so
cairo libcairo.so
glib2 libglib-2.0.so
glibc # libc.so libm.so
hyprgraphics libhyprgraphics.so
hyprlang libhyprlang.so
hyprutils libhyprutils.so
iniparser libiniparser.so
libdrm # libdrm.so
libglvnd libEGL.so libOpenGL.so
libxkbcommon libxkbcommon.so
mesa # libgbm.so
pango libpango-1.0.so # libpango.so
pixman libpixman-1.so
wayland libwayland-client.so
)
makedepends=(cmake
hyprwayland-scanner)
provides=(libhyprtoolkit.so)
_archive="$pkgname-$pkgver"
source=("$url/archive/v$pkgver/$_archive.tar.gz")
sha256sums=('2fb59789f231c1c4e9154ceffc1e7524c0cae154807c0d57e6166806255b570f')
build() {
cd "$_archive"
local cmake_options=(
-D CMAKE_BUILD_TYPE=None
-D CMAKE_INSTALL_PREFIX=/usr
)
cmake -B build -W no-dev ${cmake_options[@]}
cmake --build build
}
package() {
cd "$_archive"
DESTDIR="$pkgdir" cmake --install build
install -Dm0644 -t "$pkgdir/usr/share/licenses/$pkgname/" LICENSE
}
+10 -17
View File
@@ -1,22 +1,18 @@
# Maintainer: Mikko Nyman <mikko@nyman.xyz>
# Based on the Arch libfprint package by Jan Alexander Steffens (heftig).
#
# Carried the focaltech_moc driver and the FocalTech FT9349 device ID
# (USB 2808:a97a, ASUS ExpertBook Ultra B9406CAA) while mainline libfprint
# releases (1.94.x) lacked them. libfprint 1.94.100 ships both, so Omarchy
# has moved back to stock libfprint and no longer installs this package.
#
# Kept around pinned to the last known good commit as an escape hatch;
# the fallback patch stays for the conditional in prepare().
# Omarchy's fingerprint setup installs this in place of stock libfprint, so
# readers upstream supports ahead of an Arch release reach users by bumping
# the pin here. Pinned to the commit adding the Synaptics reader USB
# 06cb:010b, missing from stock libfprint 1.94.100; the FocalTech MOC support
# carried by the previous pin is retained.
pkgname=libfprint-git
pkgver=1.94.10.r12.gd79f157
pkgrel=1.1
# The repo already carries an untested 1.94.10.r220.g695663f build that sorts
# above the pinned pkgver; the epoch makes the pinned build win the version
# comparison and gives it a fresh filename that can be promoted.
pkgver=1.94.100.r10.g6f9479c
pkgrel=1
# Retain the epoch used to supersede the previously published untested build.
epoch=1
pkgdesc="Library for fingerprint readers (libfprint master with FocalTech MOC updates)"
pkgdesc="Library for fingerprint readers (pinned upstream snapshot with FocalTech and Synaptics 06cb:010b support)"
url="https://fprint.freedesktop.org/"
arch=(x86_64)
license=(LGPL-2.1-or-later)
@@ -47,7 +43,7 @@ provides=(libfprint libfprint-2.so)
conflicts=(libfprint)
groups=(fprint)
source=(
"git+https://gitlab.freedesktop.org/libfprint/libfprint.git#commit=d79f157282085738ea8ffbe8c2ae96fb8b3ad831"
"git+https://gitlab.freedesktop.org/libfprint/libfprint.git#commit=6f9479c3d55f847c1b3769f28ceb99227f9858cf"
"focaltech_moc-add-pid-0xA97A.patch"
)
b2sums=(
@@ -79,9 +75,6 @@ build() {
)
arch-meson libfprint build "${meson_options[@]}"
meson compile -C build
# Regenerate autosuspend.hwdb from the patched id_table[] so the
# udev-hwdb test in check() doesn't fail on the new PID 0xA97A.
ninja -C build sync-udev-hwdb
}
check() {
@@ -0,0 +1,48 @@
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -3,6 +3,7 @@
pkgname="limine-mkinitcpio-hook"
_pkgver=1.38.0
_extver=""
+_gradle_version=9.7.1
pkgver="${_pkgver}${_extver}"
pkgrel=1
pkgdesc="Install kernels for the Limine bootloader."
@@ -10,7 +11,8 @@
url="https://gitlab.com/Zesko/limine-entry-tool"
source=("${_pkgname}::git+${url}.git#tag=${pkgver}")
source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-x64_bin.tar.gz")
-source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz")
+source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz"
+ "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip")
license=("GPL3")
provides=('limine-entry-tool')
options=(!debug !strip)
@@ -27,12 +29,14 @@
'sbctl: Signs UEFI boot files for Secure Boot when enabled'
'journalctl-desktop-notification: Sends desktop notifications when errors occur'
)
-makedepends=('git' 'gradle')
+makedepends=('git')
+makedepends_x86_64=('gradle')
backup=(etc/limine-entry-tool.conf)
conflicts=('limine-entry-tool')
sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01')
sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e')
-sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7')
+sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7'
+ 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a')
prepare() {
[[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}"
@@ -48,7 +52,9 @@
export GRAALVM_HOME="$srcdir/${_graalvm_version}"
export JAVA_HOME="${GRAALVM_HOME}"
export NATIVE_IMAGE_OPTIONS="-march=compatibility --future-defaults=all"
- /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
+ local gradle=/usr/bin/gradle
+ [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle"
+ "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
}
package() {
+11 -5
View File
@@ -3,14 +3,16 @@ _pkgname="limine-entry-tool"
pkgname="limine-mkinitcpio-hook"
_pkgver=1.38.0
_extver=""
_gradle_version=9.7.1
pkgver="${_pkgver}${_extver}"
pkgrel=1
pkgrel=1.1
pkgdesc="Install kernels for the Limine bootloader."
arch=('x86_64' 'aarch64')
url="https://gitlab.com/Zesko/limine-entry-tool"
source=("${_pkgname}::git+${url}.git#tag=${pkgver}")
source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-x64_bin.tar.gz")
source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz")
source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/graal-25.2.4/graalvm-community-jdk-25i2-25.0.4_linux-aarch64_bin.tar.gz"
"https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip")
license=("GPL3")
provides=('limine-entry-tool')
options=(!debug !strip)
@@ -27,12 +29,14 @@ optdepends=(
'sbctl: Signs UEFI boot files for Secure Boot when enabled'
'journalctl-desktop-notification: Sends desktop notifications when errors occur'
)
makedepends=('git' 'gradle')
makedepends=('git')
makedepends_x86_64=('gradle')
backup=(etc/limine-entry-tool.conf)
conflicts=('limine-entry-tool')
sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01')
sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e')
sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7')
sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7'
'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a')
prepare() {
[[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}"
@@ -48,7 +52,9 @@ build() {
export GRAALVM_HOME="$srcdir/${_graalvm_version}"
export JAVA_HOME="${GRAALVM_HOME}"
export NATIVE_IMAGE_OPTIONS="-march=compatibility --future-defaults=all"
/usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
local gradle=/usr/bin/gradle
[[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle"
"$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
}
package() {
@@ -0,0 +1,46 @@
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -2,12 +2,14 @@
pkgname="limine-snapper-sync"
pkgver=1.31.0
pkgrel=1
+_gradle_version=9.7.1
pkgdesc="Integrates Limine boot entries with Snapper snapshots."
arch=('x86_64' 'aarch64')
url="https://gitlab.com/Zesko/limine-snapper-sync"
source=("${pkgname}::git+${url}.git#tag=${pkgver}")
source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-x64_bin.tar.gz")
-source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz")
+source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz"
+ "https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip")
license=("GPL3")
options=(!debug !strip)
_graalvm_version=graalvm_ce_jdk25
@@ -25,12 +27,14 @@
'b3sum: Fast Blake3 hash function to prevent duplication.'
'xxhash: Fast hashing utility for deduplication with shorter hashes.'
)
-makedepends=('git' 'gradle')
+makedepends=('git')
+makedepends_x86_64=('gradle')
backup=(etc/limine-snapper-sync.conf)
conflicts=('limine-snapper-cli' 'limine-snapper-sync-git')
sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40')
sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0')
-sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71')
+sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71'
+ 'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a')
prepare() {
[[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}"
@@ -46,7 +50,9 @@
export GRAALVM_HOME="$srcdir/${_graalvm_version}"
export JAVA_HOME="${GRAALVM_HOME}"
export NATIVE_IMAGE_OPTIONS="-march=compatibility"
- /usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
+ local gradle=/usr/bin/gradle
+ [[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle"
+ "$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
}
package() {
+11 -5
View File
@@ -1,13 +1,15 @@
# Maintainer: Zesko
pkgname="limine-snapper-sync"
pkgver=1.31.0
pkgrel=1
pkgrel=1.1
_gradle_version=9.7.1
pkgdesc="Integrates Limine boot entries with Snapper snapshots."
arch=('x86_64' 'aarch64')
url="https://gitlab.com/Zesko/limine-snapper-sync"
source=("${pkgname}::git+${url}.git#tag=${pkgver}")
source_x86_64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-x64_bin.tar.gz")
source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz")
source_aarch64=("https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-25.0.2/graalvm-community-jdk-25.0.2_linux-aarch64_bin.tar.gz"
"https://services.gradle.org/distributions/gradle-${_gradle_version}-bin.zip")
license=("GPL3")
options=(!debug !strip)
_graalvm_version=graalvm_ce_jdk25
@@ -25,12 +27,14 @@ optdepends=(
'b3sum: Fast Blake3 hash function to prevent duplication.'
'xxhash: Fast hashing utility for deduplication with shorter hashes.'
)
makedepends=('git' 'gradle')
makedepends=('git')
makedepends_x86_64=('gradle')
backup=(etc/limine-snapper-sync.conf)
conflicts=('limine-snapper-cli' 'limine-snapper-sync-git')
sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40')
sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0')
sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71')
sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71'
'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a')
prepare() {
[[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}"
@@ -46,7 +50,9 @@ build() {
export GRAALVM_HOME="$srcdir/${_graalvm_version}"
export JAVA_HOME="${GRAALVM_HOME}"
export NATIVE_IMAGE_OPTIONS="-march=compatibility"
/usr/bin/gradle clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
local gradle=/usr/bin/gradle
[[ $CARCH == aarch64 ]] && gradle="$srcdir/gradle-${_gradle_version}/bin/gradle"
"$gradle" clean nativeCompile -Dorg.gradle.java.home="${JAVA_HOME}"
}
package() {
+2 -2
View File
@@ -1,6 +1,6 @@
# Maintainer: Jake Weaver <jakeweaver at hey dot com>
pkgname=link-studio
pkgver=1.0.2
pkgver=1.0.4
pkgrel=1
pkgdesc="Native Linux controller for Insta360 Link webcams"
arch=('x86_64')
@@ -28,7 +28,7 @@ optdepends=(
'whisper-cpp: offline meeting transcription'
)
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/jweaver60/link-studio/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
sha256sums=('b0fc912d8b7924661b57e427280c9766cbb0f01b7a2b3f3cdb1885969f8cfdc6')
sha256sums=('f04156d34ecf5e858bd259dca824f970af53ff177185516fafff15766a0880e9')
build() {
cd "link_studio-${pkgver}"
+3 -3
View File
@@ -1,6 +1,6 @@
# Maintainer: Jeff Dickey <releases@mise.jdx.dev>
pkgname=mise-bin
pkgver=2026.8.15
pkgver=2026.9.1
pkgrel=1
pkgdesc="dev tools, env vars, task runner"
arch=('x86_64' 'aarch64')
@@ -14,8 +14,8 @@ provides=('mise')
conflicts=('mise')
source_x86_64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-x64.tar.xz")
source_aarch64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-arm64.tar.xz")
sha256sums_x86_64=('0ed4ee8bcf229718bead0d0aa83de865803f3fe25cc6c71a41e3a8bd57c40a33')
sha256sums_aarch64=('d5f9b7e6b36b5cf5aaab0dc4d7e29ee74224281b1a81a228bdc1cd1f3edf9d49')
sha256sums_x86_64=('bbc21f8fe9b2aa09f74250be15f9bf32e87562e1039d858ad91d7944f057c8e2')
sha256sums_aarch64=('7d0c28d44bd7e1c5444a7d867146afabe43592c728329bc0ed5ec59ca7106c48')
package() {
install -Dm755 "${srcdir}/mise/bin/mise" "${pkgdir}/usr/bin/mise"
+1 -1
View File
@@ -2,7 +2,7 @@
"source": "local",
"release_ring": "fast",
"upstream": {
"github": "tsouth89/omakade",
"github": "btsouth/omakade",
"checksums": "SHA256SUMS",
"assets": {
"any": "omakade-{pkgver}.tar.gz"
+9 -7
View File
@@ -1,15 +1,17 @@
pkgname=omakade
pkgver=1.2.3
pkgver=1.7.0
pkgrel=1
pkgdesc='A beautiful, local-first game library for Omarchy'
arch=('x86_64')
url='https://tsouth89.github.io/omakade/'
arch=('x86_64' 'aarch64')
url='https://btsouth.github.io/omakade/'
license=('GPL-3.0-or-later')
depends=('glib2' 'libsecret' 'qt6-base' 'qt6-declarative' 'qt6-wayland' 'sdl3')
makedepends=('cmake' 'ninja' 'pkgconf')
depends=('glib2' 'hicolor-icon-theme' 'libsecret' 'libzip' 'openssl' 'qt6-base' 'qt6-declarative' 'qt6-imageformats' 'qt6-svg' 'qt6-wayland' 'sdl3' 'wayland' 'zstd')
# wayland-protocols turns the optional idle-inhibit feature on
# deterministically instead of leaving it to what the chroot happens to carry.
makedepends=('cmake' 'ninja' 'pkgconf' 'wayland-protocols')
options=('!debug')
source=("$pkgname-$pkgver.tar.gz::https://github.com/tsouth89/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz")
sha256sums=('dcbb57caf982317ae0fd75a1a97c48b80d2b02f92d65b38030876a172eb25606')
source=("$pkgname-$pkgver.tar.gz::https://github.com/btsouth/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz")
sha256sums=('715cd5e0cb860514276b1af57fe08ced71c1c69a6aeebd93aa51f1ca367eefa8')
build() {
cmake -S "$pkgname-$pkgver" -B build -G Ninja \
+27 -13
View File
@@ -5,7 +5,10 @@ pkgrel=1
_pkgver_base=4.0.0
_pkgver_base_tag=v3.8.2
pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH (quattro branch tip)'
arch=('any')
# The payload is architecture-independent, but the dependency set is not: the
# boot stack differs per architecture (see depends_x86_64 / depends_aarch64),
# and makepkg only honours arch-suffixed arrays on arch-specific packages.
arch=('x86_64' 'aarch64')
url='https://github.com/basecamp/omarchy'
license=('MIT')
provides=('omarchy')
@@ -17,18 +20,6 @@ depends=(
'omarchy-keyring'
'omarchy-settings-dev'
# Bootloader stack. Lives here rather than on omarchy-settings because
# omarchy-settings is also installed into the live ISO env (for plymouth
# / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with
# 'Cannot detect an ESP path' inside the chroot. The omarchy-settings
# config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/,
# and /etc/snapper/config-templates/ only do anything when these are
# installed alongside.
'limine'
'limine-mkinitcpio-hook'
'limine-snapper-sync'
'snapper'
# Wayland / Hyprland core
'hyprland'
'quickshell'
@@ -55,6 +46,29 @@ depends=(
'ttf-jetbrains-mono-nerd-basic'
)
# Bootloader stack. Lives here rather than on omarchy-settings because
# omarchy-settings is also installed into the live ISO env (for plymouth
# / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with
# 'Cannot detect an ESP path' inside the chroot. The omarchy-settings
# config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/,
# and /etc/snapper/config-templates/ only do anything when these are
# installed alongside.
depends_x86_64=(
'limine'
'limine-mkinitcpio-hook'
'limine-snapper-sync'
'snapper'
)
# Apple Silicon boots through m1n1 + GRUB from the Asahi packages and keeps
# Arch Linux ARM's kernel and boot layout, so the Limine/Snapper stack does not
# apply. Wi-Fi on the Broadcom parts only scans reliably through the iwd
# backend, which install/hardware/network.sh selects on Apple Silicon.
depends_aarch64=(
'iwd'
'networkmanager'
)
makedepends=(
'git'
)
+60 -11
View File
@@ -5,7 +5,12 @@ pkgrel=1
_pkgver_base=4.0.0
_pkgver_base_tag=v3.8.2
pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers (quattro branch tip)'
arch=('any')
# Arch-specific because the shipped /etc tree is not the same on every
# architecture: the Limine, mkinitcpio, zram and oomd drop-ins belong to the
# x86_64 boot and memory stack and are left out of the aarch64 package (see
# package()). makepkg only honours the arch-suffixed arrays below on
# arch-specific packages.
arch=('x86_64' 'aarch64')
url='https://github.com/basecamp/omarchy'
license=('MIT')
provides=('omarchy-settings')
@@ -44,15 +49,10 @@ makedepends=(
# scriptlet WILL clobber user edits on each upgrade (documented limitation).
backup=(
'etc/fastfetch/config.jsonc'
'etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'etc/mkinitcpio.conf.d/thunderbolt_module.conf'
'etc/limine-entry-tool.d/omarchy-defaults.conf'
'etc/limine-entry-tool.d/omarchy-uki.conf'
'etc/docker/daemon.json'
'etc/gnupg/dirmngr.conf'
'etc/sysctl.d/99-omarchy-sysctl.conf'
'etc/sysctl.d/90-omarchy-file-watchers.conf'
'etc/modprobe.d/omarchy-usb-autosuspend.conf'
'etc/systemd/logind.conf.d/10-ignore-power-button.conf'
'etc/systemd/logind.conf.d/20-inhibit-delay.conf'
'etc/systemd/resolved.conf.d/10-disable-multicast.conf'
@@ -63,18 +63,31 @@ backup=(
'etc/systemd/system/plocate-updatedb.service.d/ac-only.conf'
'etc/systemd/system.conf.d/20-omarchy-nofile.conf'
'etc/systemd/user.conf.d/20-omarchy-nofile.conf'
'etc/systemd/oomd.conf.d/10-omarchy.conf'
'etc/systemd/zram-generator.conf'
'etc/sddm.conf.d/10-theme.conf'
'etc/sddm.conf.d/10-wayland.conf'
'etc/udev/rules.d/99-omarchy-power-profile.rules'
'etc/udev/rules.d/99-omarchy-wifi-powersave.rules'
)
# backup has no arch-suffixed form, so the x86_64-only drop-ins join it here.
# Each of these paths is removed from the aarch64 package in package().
if [[ $CARCH == x86_64 ]]; then
backup+=(
'etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'etc/mkinitcpio.conf.d/thunderbolt_module.conf'
'etc/limine-entry-tool.d/omarchy-defaults.conf'
'etc/limine-entry-tool.d/omarchy-uki.conf'
'etc/modprobe.d/omarchy-usb-autosuspend.conf'
'etc/systemd/oomd.conf.d/10-omarchy.conf'
'etc/systemd/zram-generator.conf'
)
fi
optdepends=(
'omarchy-dev: Full Omarchy desktop (quattro branch tip)'
'inxi: System information for omarchy-debug'
'fastfetch: System information for omarchy-upload-log'
)
optdepends_x86_64=(
'limine: Bootloader the shipped /etc/mkinitcpio.conf.d/ and /etc/limine-entry-tool.d/ drop-ins are for'
'limine-mkinitcpio-hook: Picks up the shipped /etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'limine-snapper-sync: Boot-entry sync for snapper snapshots'
@@ -147,6 +160,14 @@ package() {
install -d "$pkgdir/usr/share/omarchy/config"
cp -a config/. "$pkgdir/usr/share/omarchy/config/"
# The Limine/Snapper notifier has nothing to notify about without the x86_64
# boot stack; drop it from both seeds so aarch64 users don't autostart a
# helper whose backing tool is not installed.
if [[ $CARCH == aarch64 ]]; then
rm -f "$pkgdir/etc/skel/.config/autostart/limine-snapper-notify.desktop" \
"$pkgdir/usr/share/omarchy/config/autostart/limine-snapper-notify.desktop"
fi
# Package-owned defaults with real system/XDG locations. User config remains
# higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d,
# ~/.config/environment.d, ~/.config/fontconfig, ~/.config/xdg-terminals.list,
@@ -174,9 +195,14 @@ package() {
install -Dm644 default/systemd/user/omarchy-crash-watch.service "$pkgdir/usr/lib/systemd/user/omarchy-crash-watch.service"
# Marks app.slice (and only app.slice) as a systemd-oomd kill candidate, so
# memory pressure costs one app scope instead of the compositor session.
# Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf.
install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf"
install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf"
# Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. Both this and
# the zram vendor drop-in belong to the x86_64 memory stack: the aarch64
# install neither enables systemd-oomd nor configures zram swap.
if [[ $CARCH == x86_64 ]]; then
install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf"
install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf"
fi
install -Dm644 default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf "$pkgdir/usr/lib/systemd/system/plocate-updatedb.service.d/10-omarchy.conf"
# Same for applications/** — used by omarchy-refresh-applications,
# omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share.
@@ -190,6 +216,25 @@ package() {
# stage separately below).
install -d "$pkgdir/etc"
cp -a etc/. "$pkgdir/etc/"
if [[ $CARCH == aarch64 ]]; then
# The x86_64 boot stack's drop-ins must not ship on aarch64: mkinitcpio
# reads every file under /etc/mkinitcpio.conf.d/, so omarchy_hooks.conf
# would inject the Limine hooks into the Asahi kernel's initramfs, and the
# Limine entry-tool config has no consumer without Limine.
rm -rf "$pkgdir/etc/limine-entry-tool.d" "$pkgdir/etc/mkinitcpio.conf.d"
# Memory stack: no zram device or zswap on the aarch64 install, and
# systemd-oomd is not enabled there, so the vm.* reclaim tuning written
# for zram would be wrong for it. Keep only the network tuning.
rm -rf "$pkgdir/etc/systemd/oomd.conf.d"
rm -f "$pkgdir/etc/systemd/zram-generator.conf" "$pkgdir/etc/tmpfiles.d/omarchy-zswap.conf"
cat >"$pkgdir/etc/sysctl.d/99-omarchy-sysctl.conf" <<'EOF'
# Solve common flakiness with SSH (MTU discovery on flaky links).
net.ipv4.tcp_mtu_probing=1
EOF
# USB autosuspend is left at the kernel default on Apple Silicon.
rm -f "$pkgdir/etc/modprobe.d/omarchy-usb-autosuspend.conf"
rmdir "$pkgdir/etc/modprobe.d" "$pkgdir/etc/tmpfiles.d" 2>/dev/null || true
fi
# sudoers.d files must be mode 0440 or sudo will refuse them, and the
# directory must be 0750 to match the Arch sudo package's filesystem
# convention (otherwise pacman warns about dir-mode drift on every install).
@@ -280,6 +325,10 @@ EOF
# live root config.
install -d "$pkgdir/usr/share/omarchy/default"
cp -a default/. "$pkgdir/usr/share/omarchy/default/"
# The Limine template is read by the x86_64 ISO orchestrator only.
if [[ $CARCH == aarch64 ]]; then
rm -rf "$pkgdir/usr/share/omarchy/default/limine"
fi
# Snapper config template used by the install-time `snapper create-config`.
install -Dm644 default/snapper/root \
@@ -1,6 +1,31 @@
_apple_silicon() {
[[ $(uname -m) == aarch64 ]] && grep -aq 'apple,' /proc/device-tree/compatible 2>/dev/null
}
_etc_overrides_apply() {
echo "Applying Omarchy etc-overrides..."
# The hardened CUPS authorization applies on every platform. It waits for the
# file the cups package owns: an ISO bootstrap without cups installed is left
# alone and picks it up once cups lands.
if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then
systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf
install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf
fi
# Apple Silicon installs run on Arch Linux ARM's base with the Asahi
# packages, and keep that system identity: /etc/os-release stays the
# symlink to the distribution's /usr/lib/os-release, and the PAM, NSS,
# plymouth and skel overrides below are not applied there.
if _apple_silicon; then
echo "Preserving Arch Linux ARM system configuration on Apple Silicon..."
if [[ -f /usr/lib/os-release ]]; then
rm -f /etc/os-release
ln -s ../usr/lib/os-release /etc/os-release
fi
return 0
fi
# Ensure parent dirs exist. cups-browsed may not be installed yet during ISO
# bootstrap; do not create /etc/cups/cups-browsed.conf before the package is
# installed or pacman will preserve its packaged file as a noisy .pacnew.
@@ -21,10 +46,6 @@ _etc_overrides_apply() {
install -d /etc/cups
cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf
fi
if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then
systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf
install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf
fi
cp -f /usr/share/omarchy/etc-overrides/plymouth-plymouthd.conf /etc/plymouth/plymouthd.conf
cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc
chmod 0644 /etc/os-release /etc/security/faillock.conf /etc/nsswitch.conf \
+63 -11
View File
@@ -15,7 +15,12 @@ _commit='346e69e1cec6c4e8924531874af6ba010a1bc99e'
pkgver=4.0.2
pkgrel=1
pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers'
arch=('any')
# Arch-specific because the shipped /etc tree is not the same on every
# architecture: the Limine, mkinitcpio, zram and oomd drop-ins belong to the
# x86_64 boot and memory stack and are left out of the aarch64 package (see
# package()). makepkg only honours the arch-suffixed arrays below on
# arch-specific packages.
arch=('x86_64' 'aarch64')
url='https://github.com/basecamp/omarchy'
license=('MIT')
conflicts=('omarchy-settings-dev')
@@ -53,15 +58,10 @@ makedepends=(
# scriptlet WILL clobber user edits on each upgrade (documented limitation).
backup=(
'etc/fastfetch/config.jsonc'
'etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'etc/mkinitcpio.conf.d/thunderbolt_module.conf'
'etc/limine-entry-tool.d/omarchy-defaults.conf'
'etc/limine-entry-tool.d/omarchy-uki.conf'
'etc/docker/daemon.json'
'etc/gnupg/dirmngr.conf'
'etc/sysctl.d/99-omarchy-sysctl.conf'
'etc/sysctl.d/90-omarchy-file-watchers.conf'
'etc/modprobe.d/omarchy-usb-autosuspend.conf'
'etc/systemd/logind.conf.d/10-ignore-power-button.conf'
'etc/systemd/logind.conf.d/20-inhibit-delay.conf'
'etc/systemd/resolved.conf.d/10-disable-multicast.conf'
@@ -72,18 +72,31 @@ backup=(
'etc/systemd/system/plocate-updatedb.service.d/ac-only.conf'
'etc/systemd/system.conf.d/20-omarchy-nofile.conf'
'etc/systemd/user.conf.d/20-omarchy-nofile.conf'
'etc/systemd/oomd.conf.d/10-omarchy.conf'
'etc/systemd/zram-generator.conf'
'etc/sddm.conf.d/10-theme.conf'
'etc/sddm.conf.d/10-wayland.conf'
'etc/udev/rules.d/99-omarchy-power-profile.rules'
'etc/udev/rules.d/99-omarchy-wifi-powersave.rules'
)
# backup has no arch-suffixed form, so the x86_64-only drop-ins join it here.
# Each of these paths is removed from the aarch64 package in package().
if [[ $CARCH == x86_64 ]]; then
backup+=(
'etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'etc/mkinitcpio.conf.d/thunderbolt_module.conf'
'etc/limine-entry-tool.d/omarchy-defaults.conf'
'etc/limine-entry-tool.d/omarchy-uki.conf'
'etc/modprobe.d/omarchy-usb-autosuspend.conf'
'etc/systemd/oomd.conf.d/10-omarchy.conf'
'etc/systemd/zram-generator.conf'
)
fi
optdepends=(
'omarchy: Full Omarchy desktop'
'inxi: System information for omarchy-debug'
'fastfetch: System information for omarchy-upload-log'
)
optdepends_x86_64=(
'limine: Bootloader the shipped /etc/mkinitcpio.conf.d/ and /etc/limine-entry-tool.d/ drop-ins are for'
'limine-mkinitcpio-hook: Picks up the shipped /etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'limine-snapper-sync: Boot-entry sync for snapper snapshots'
@@ -142,6 +155,14 @@ package() {
install -d "$pkgdir/usr/share/omarchy/config"
cp -a config/. "$pkgdir/usr/share/omarchy/config/"
# The Limine/Snapper notifier has nothing to notify about without the x86_64
# boot stack; drop it from both seeds so aarch64 users don't autostart a
# helper whose backing tool is not installed.
if [[ $CARCH == aarch64 ]]; then
rm -f "$pkgdir/etc/skel/.config/autostart/limine-snapper-notify.desktop" \
"$pkgdir/usr/share/omarchy/config/autostart/limine-snapper-notify.desktop"
fi
# Package-owned defaults with real system/XDG locations. User config remains
# higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d,
# ~/.config/environment.d, ~/.config/fontconfig, ~/.config/xdg-terminals.list,
@@ -169,9 +190,17 @@ package() {
install -Dm644 default/systemd/user/omarchy-crash-watch.service "$pkgdir/usr/lib/systemd/user/omarchy-crash-watch.service"
# Marks app.slice (and only app.slice) as a systemd-oomd kill candidate, so
# memory pressure costs one app scope instead of the compositor session.
# Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf.
install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf"
install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf"
# Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. Both this and
# the zram vendor drop-in belong to the x86_64 memory stack: the aarch64
# install neither enables systemd-oomd nor configures zram swap.
if [[ $CARCH == x86_64 ]]; then
install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf"
install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf"
fi
# Older release pins still configure locate through install/config/locate.sh.
if [[ -f default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf ]]; then
install -Dm644 default/systemd/system/plocate-updatedb.service.d/10-omarchy.conf "$pkgdir/usr/lib/systemd/system/plocate-updatedb.service.d/10-omarchy.conf"
fi
# Same for applications/** — used by omarchy-refresh-applications,
# omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share.
@@ -185,6 +214,25 @@ package() {
# stage separately below).
install -d "$pkgdir/etc"
cp -a etc/. "$pkgdir/etc/"
if [[ $CARCH == aarch64 ]]; then
# The x86_64 boot stack's drop-ins must not ship on aarch64: mkinitcpio
# reads every file under /etc/mkinitcpio.conf.d/, so omarchy_hooks.conf
# would inject the Limine hooks into the Asahi kernel's initramfs, and the
# Limine entry-tool config has no consumer without Limine.
rm -rf "$pkgdir/etc/limine-entry-tool.d" "$pkgdir/etc/mkinitcpio.conf.d"
# Memory stack: no zram device or zswap on the aarch64 install, and
# systemd-oomd is not enabled there, so the vm.* reclaim tuning written
# for zram would be wrong for it. Keep only the network tuning.
rm -rf "$pkgdir/etc/systemd/oomd.conf.d"
rm -f "$pkgdir/etc/systemd/zram-generator.conf" "$pkgdir/etc/tmpfiles.d/omarchy-zswap.conf"
cat >"$pkgdir/etc/sysctl.d/99-omarchy-sysctl.conf" <<'EOF'
# Solve common flakiness with SSH (MTU discovery on flaky links).
net.ipv4.tcp_mtu_probing=1
EOF
# USB autosuspend is left at the kernel default on Apple Silicon.
rm -f "$pkgdir/etc/modprobe.d/omarchy-usb-autosuspend.conf"
rmdir "$pkgdir/etc/modprobe.d" "$pkgdir/etc/tmpfiles.d" 2>/dev/null || true
fi
# sudoers.d files must be mode 0440 or sudo will refuse them, and the
# directory must be 0750 to match the Arch sudo package's filesystem
# convention (otherwise pacman warns about dir-mode drift on every install).
@@ -275,6 +323,10 @@ EOF
# live root config.
install -d "$pkgdir/usr/share/omarchy/default"
cp -a default/. "$pkgdir/usr/share/omarchy/default/"
# The Limine template is read by the x86_64 ISO orchestrator only.
if [[ $CARCH == aarch64 ]]; then
rm -rf "$pkgdir/usr/share/omarchy/default/limine"
fi
# Snapper config template used by the install-time `snapper create-config`.
install -Dm644 default/snapper/root \
@@ -1,6 +1,31 @@
_apple_silicon() {
[[ $(uname -m) == aarch64 ]] && grep -aq 'apple,' /proc/device-tree/compatible 2>/dev/null
}
_etc_overrides_apply() {
echo "Applying Omarchy etc-overrides..."
# The hardened CUPS authorization applies on every platform. It waits for the
# file the cups package owns: an ISO bootstrap without cups installed is left
# alone and picks it up once cups lands.
if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then
systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf
install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf
fi
# Apple Silicon installs run on Arch Linux ARM's base with the Asahi
# packages, and keep that system identity: /etc/os-release stays the
# symlink to the distribution's /usr/lib/os-release, and the PAM, NSS,
# plymouth and skel overrides below are not applied there.
if _apple_silicon; then
echo "Preserving Arch Linux ARM system configuration on Apple Silicon..."
if [[ -f /usr/lib/os-release ]]; then
rm -f /etc/os-release
ln -s ../usr/lib/os-release /etc/os-release
fi
return 0
fi
# Ensure parent dirs exist. cups-browsed may not be installed yet during ISO
# bootstrap; do not create /etc/cups/cups-browsed.conf before the package is
# installed or pacman will preserve its packaged file as a noisy .pacnew.
@@ -21,10 +46,6 @@ _etc_overrides_apply() {
install -d /etc/cups
cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf
fi
if [[ -f /etc/cups/cups-files.conf && -f /usr/share/omarchy/etc-overrides/cups-cups-files.conf ]]; then
systemd-sysusers /etc/sysusers.d/omarchy-cups-browsed.conf
install -m 0640 -o root -g cups /usr/share/omarchy/etc-overrides/cups-cups-files.conf /etc/cups/cups-files.conf
fi
cp -f /usr/share/omarchy/etc-overrides/plymouth-plymouthd.conf /etc/plymouth/plymouthd.conf
cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc
chmod 0644 /etc/os-release /etc/security/faillock.conf /etc/nsswitch.conf \
+27 -13
View File
@@ -15,7 +15,10 @@ _commit='346e69e1cec6c4e8924531874af6ba010a1bc99e'
pkgver=4.0.2
pkgrel=1
pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH'
arch=('any')
# The payload is architecture-independent, but the dependency set is not: the
# boot stack differs per architecture (see depends_x86_64 / depends_aarch64),
# and makepkg only honours arch-suffixed arrays on arch-specific packages.
arch=('x86_64' 'aarch64')
url='https://github.com/basecamp/omarchy'
license=('MIT')
conflicts=('omarchy-dev')
@@ -31,18 +34,6 @@ depends=(
# from the same _commit, and forces the pair to upgrade together.
"omarchy-settings=${pkgver}"
# Bootloader stack. Lives here rather than on omarchy-settings because
# omarchy-settings is also installed into the live ISO env (for plymouth
# / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with
# 'Cannot detect an ESP path' inside the chroot. The omarchy-settings
# config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/,
# and /etc/snapper/config-templates/ only do anything when these are
# installed alongside.
'limine'
'limine-mkinitcpio-hook'
'limine-snapper-sync'
'snapper'
# Wayland / Hyprland core
'hyprland'
'quickshell'
@@ -69,6 +60,29 @@ depends=(
'ttf-jetbrains-mono-nerd-basic'
)
# Bootloader stack. Lives here rather than on omarchy-settings because
# omarchy-settings is also installed into the live ISO env (for plymouth
# / /etc/skel seeding), where the limine-mkinitcpio-hook would fail with
# 'Cannot detect an ESP path' inside the chroot. The omarchy-settings
# config drop-ins under /etc/mkinitcpio.conf.d/, /etc/limine-entry-tool.d/,
# and /etc/snapper/config-templates/ only do anything when these are
# installed alongside.
depends_x86_64=(
'limine'
'limine-mkinitcpio-hook'
'limine-snapper-sync'
'snapper'
)
# Apple Silicon boots through m1n1 + GRUB from the Asahi packages and keeps
# Arch Linux ARM's kernel and boot layout, so the Limine/Snapper stack does not
# apply. Wi-Fi on the Broadcom parts only scans reliably through the iwd
# backend, which install/hardware/network.sh selects on Apple Silicon.
depends_aarch64=(
'iwd'
'networkmanager'
)
makedepends=(
'git'
)
+6
View File
@@ -0,0 +1,6 @@
{
"source": "local",
"rebuild_on": [
"hyprland"
]
}
+61
View File
@@ -0,0 +1,61 @@
# Maintainer: Ryan <ryan@heyoodle.com>
pkgname=omareel
pkgver=0.1.0
pkgrel=1
pkgdesc='Screen recorder and editor for Omarchy with a synthetic cursor, auto zooms, and a camera bubble'
arch=('x86_64' 'aarch64')
url='https://github.com/omacom/omareel'
license=('MIT')
install='omareel.install'
options=('!debug')
depends=(
'ffmpeg'
'gpu-screen-recorder'
'hicolor-icon-theme'
'hyprland'
'layer-shell-qt'
'libevdev'
'libjpeg-turbo'
'qt6-base'
'qt6-declarative'
'qt6-multimedia'
'qt6-svg'
'slurp'
'xdg-desktop-portal'
)
makedepends=(
'cmake'
'ninja'
'pkgconf'
'qt6-shadertools'
'wayland'
)
optdepends=('gifsicle: optimize exported GIF files')
conflicts=('omarecord')
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('330d8e2805135af33bf35378e2e961094fc375075d38120c8270bd42fe04ce5b')
build() {
cd "$srcdir/$pkgname-$pkgver"
./bin/build
}
package() {
cd "$srcdir/$pkgname-$pkgver"
install -Dm755 build/omareel "$pkgdir/usr/bin/omareel"
ln -s omareel "$pkgdir/usr/bin/omarecord"
# The capture-exclusion plugin is bound to the Hyprland commit it was built
# against; rebuild_on hyprland keeps this package in step with the compositor.
install -Dm755 build/plugin/omareel-capture-exclude.so "$pkgdir/usr/lib/omareel/omareel-capture-exclude.so"
install -Dm644 build/plugin/omareel-capture-exclude.so.hash "$pkgdir/usr/lib/omareel/omareel-capture-exclude.so.hash"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 LICENSES/lucide.txt "$pkgdir/usr/share/licenses/$pkgname/lucide.txt"
install -Dm644 pkg/omareel.svg "$pkgdir/usr/share/icons/hicolor/scalable/apps/omareel.svg"
local size
for size in 16 32 48 64 128 256 512; do
install -Dm644 "pkg/icons/omareel-$size.png" \
"$pkgdir/usr/share/icons/hicolor/${size}x${size}/apps/omareel.png"
done
install -Dm644 pkg/omareel.desktop "$pkgdir/usr/share/applications/omareel.desktop"
}
+13
View File
@@ -0,0 +1,13 @@
post_install() {
command -v update-desktop-database >/dev/null 2>&1 && update-desktop-database -q
command -v gtk-update-icon-cache >/dev/null 2>&1 && gtk-update-icon-cache -q -t -f usr/share/icons/hicolor
return 0
}
post_upgrade() {
post_install
}
post_remove() {
post_install
}
+1 -1
View File
@@ -4,7 +4,7 @@ pkgname=omasnap
pkgver=1.20.1
pkgrel=1
pkgdesc="Native Wayland screenshot and annotation overlay for Hyprland"
arch=('x86_64')
arch=('x86_64' 'aarch64')
url="https://github.com/tobi/omasnap"
license=('MIT' 'OFL-1.1')
depends=(
+1 -1
View File
@@ -1,4 +1,4 @@
{
"source": "aur",
"upstream_commit": "e2a85e53e2a73cec9170a5fa631805f62b98bb36"
"upstream_commit": "cbc25a60a791ccda07ed5e8300e8acb267d23007"
}
+2 -2
View File
@@ -1,5 +1,5 @@
pkgname=omazed
pkgver=2.1.1
pkgver=2.1.2
pkgrel=1
pkgdesc="Live theme switching for Zed in Omarchy - automatically synchronize your Zed editor theme with your Omarchy system theme"
arch=('any')
@@ -10,7 +10,7 @@ makedepends=('git')
backup=()
install=omazed.install
source=("$pkgname-$pkgver.tar.gz::https://github.com/aps6/$pkgname/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('083757c5385179f241ac0c6e9dde05ba0c7f465c1fed617779d1ebe2a6dfe036')
sha256sums=('ca0708c86969547b14958a307ae3df89f63d92ffa68af3b5c6bf79c036b8cab9')
package() {
cd "$srcdir/$pkgname-$pkgver"
-5
View File
@@ -17,11 +17,6 @@ post_upgrade() {
echo ""
echo "==> Omazed updated to $(pacman -Q omazed | cut -d' ' -f2)"
echo ""
echo " Complete the update!"
echo ""
echo " To complete this update, run:"
echo ""
echo " omazed setup"
echo ""
}
@@ -1,5 +1,5 @@
{
"source": "aur",
"release_ring": "fast",
"upstream_commit": "7298bbbde771f2f33d0942fc429bd27643a9986a"
"upstream_commit": "134a0e93624d39306f94dc4384dbd102b8d37bb2"
}
+5 -5
View File
@@ -2,7 +2,7 @@
# shellcheck disable=SC2034
# Maintainer: Chmouel Boudjnah <chmouel@chmouel.com>
pkgname=openai-codex-bin
pkgver=0.152.0
pkgver=0.153.4
pkgrel=1
pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated"
arch=('x86_64' 'aarch64')
@@ -21,10 +21,10 @@ source_x86_64=(
"codex-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-x86_64-unknown-linux-musl.tar.gz"
"codex-code-mode-host-${pkgver}-x86_64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-code-mode-host-x86_64-unknown-linux-musl.tar.gz"
)
sha256sums_x86_64=('05f942d3d3c5b5acd9edad56ce2797b6fe72dbb1462b24e5c9bf7dcec9a28a11'
'449cefe35b9f347e3fdbf121e816339b37825eb0bfee7de8298a0a61b6687cba')
sha256sums_aarch64=('37da6b486503c8a42cc4604d2a3d80d388df896dd251e9225f4f3d49b08c2e8c'
'134183c7f5bb2245ed4bb15cfcf00d1a64950adbdd089d33c003c8bcf13267e9')
sha256sums_x86_64=('f479424eca092484dc40d87ae28c44f4cc40234a60045d6131e493800d814a30'
'f95830a869590957664bbfc67bccb08773806b693670baf15908176f89b4cd31')
sha256sums_aarch64=('5cda6182bd94c3a30f2eb63a495489ebf7f691fddb14d70f48c6c1a5071b6cde'
'd8047b8d33370d6090e729d27eb76de60a2686baa1c143c138c9b05dc70d813b')
source_aarch64=(
"codex-${pkgver}-aarch64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-aarch64-unknown-linux-musl.tar.gz"
+3 -3
View File
@@ -5,7 +5,7 @@
# the version and checksums below from that repository's package index.
pkgname=openai-codex-desktop
pkgver=26.825.51511
pkgver=26.901.51231
pkgrel=1
pkgdesc="Official ChatGPT desktop app with Codex"
arch=('x86_64' 'aarch64')
@@ -71,8 +71,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64}")
source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}")
noextract=("${_deb_x86_64}" "${_deb_aarch64}")
sha256sums=('b3a4503b5931f102444bc7015c3cf4e40266cf034e0d682bd2a407dc5b3ee58c')
sha256sums_x86_64=('3554b0022c6cfb513326f43fd11f718835a77086ac4d7ca2ff3ebbba2d4c7f45')
sha256sums_aarch64=('125e36522d43c75bd7958477846ba6b1cddf2eb19cefcb57dda80be1742f917f')
sha256sums_x86_64=('62580188d87c3d3a9369dab7c73b42a8a32518d4df8a2d5bae6466ddeac5c05e')
sha256sums_aarch64=('02a2f5c6cb69509c62abcbdd13c76b139cdb2ca9edde7537239ddde024077ea0')
package() {
cd "${srcdir}"
+11
View File
@@ -0,0 +1,11 @@
{
"source": "local",
"release_ring": "fast",
"min_release_age": "24h",
"upstream": {
"npm": "openclaw",
"sources": {
"any": ["{npm_tarball}"]
}
}
}
+72
View File
@@ -0,0 +1,72 @@
# Based on the AUR openclaw package:
# Maintainer: Ralph Torres <mail at ralphptorr dot es>
# Contributor: Evert Vorster <evorster@gmail.com>
# Contributor: Emir-Eins <emir-eins@outlook.com>
# Contributor: Wuxxin <wuxxin@gmail.com>
# Followed here straight from the npm registry (see .omarchy/package.json), since
# upstream's release cadence outruns the AUR.
pkgname=openclaw
pkgver=2026.9.2
pkgrel=1
pkgdesc='Multi-channel AI gateway with extensible messaging integrations'
arch=(x86_64 aarch64)
url=https://github.com/openclaw/openclaw
license=(MIT)
depends=('nodejs>=22')
makedepends=(npm)
optdepends=(
'1password-cli: 1password skill'
'curl: weather, openai-whisper-api skills'
'ffmpeg: video-frames skill'
'github-cli: github, gh-issues skills'
'jq: session-logs, trello skills'
'python-openai-whisper: openai-whisper skill'
'ripgrep: session-logs skill'
'tmux: tmux skill'
'uv: nano-pdf skill'
'go: for installing skill tools not packaged for Arch'
)
source=($pkgname-$pkgver.tgz::https://registry.npmjs.org/$pkgname/-/$pkgname-$pkgver.tgz)
sha256sums=('3431f4cd2d8dbd6b936def2694ac27e19fa0256295cf4ada0f652ecf1c9ee520')
options=(!debug !strip)
install=$pkgname.install
noextract=($pkgname-$pkgver.tgz)
package() {
export SHARP_IGNORE_GLOBAL_LIBVIPS=1
# npm 12 blocks 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, not the package name (arborist matches node.resolved).
# Upstream's postinstall prunes stale dist files and clears the
# .openclaw-lifecycle-pending marker the tarball ships with; without it,
# every openclaw invocation tries to finish that lifecycle itself and dies
# with EACCES on the root-owned install.
# That postinstall also migrates legacy state under whatever home it sees
# (pruning plugin-runtime-deps in ~/.openclaw and friends), so it gets a
# scratch home: a maintainer's own OpenClaw is not the build's to touch.
mkdir -p "$srcdir"/home
env -u OPENCLAW_HOME -u OPENCLAW_STATE_DIR -u OPENCLAW_CONFIG_PATH HOME="$srcdir"/home \
npm install --silent --global --cache "$srcdir"/npm-cache \
--allow-scripts="file:$srcdir/$pkgname-$pkgver.tgz" \
--prefix "$pkgdir"/usr "$srcdir"/$pkgname-$pkgver.tgz
if [[ -e "$pkgdir"/usr/lib/node_modules/$pkgname/.openclaw-lifecycle-pending ]]; then
error "openclaw's postinstall did not run; the package would fail on first use"
return 1
fi
cat > $pkgname <<'EOF'
#!/bin/sh
export SHARP_IGNORE_GLOBAL_LIBVIPS=1
exec node /usr/lib/node_modules/openclaw/openclaw.mjs "$@"
EOF
install -Dm755 -t "$pkgdir"/usr/bin $pkgname
cd "$pkgdir"/usr/lib/node_modules/$pkgname
install -Dm644 -t "$pkgdir"/usr/share/licenses/$pkgname LICENSE
install -Dm644 -t "$pkgdir"/usr/share/doc/$pkgname README.md CHANGELOG.md
for f in docs/*
do ln -s /usr/lib/node_modules/$pkgname/"$f" "$pkgdir"/usr/share/doc/$pkgname/
done
}
+17
View File
@@ -0,0 +1,17 @@
post_install() {
printf "%s\n" \
"==> Install gateway service: openclaw gateway install --force" \
"==> Perform onboarding once: openclaw onboard"
}
post_upgrade() {
# The gateway runs as a systemd user unit (openclaw-gateway.service in
# ~/.config/systemd/user), which a root pacman hook has no session bus
# to reach, so it cannot be restarted from here and keeps running the
# old code until the user restarts it.
printf "%s\n" \
"==> Restart the gateway to apply updates:" \
" openclaw gateway restart" \
"==> Run doctor to migrate configs, check gateway health, etc.:" \
" openclaw doctor"
}
+82
View File
@@ -0,0 +1,82 @@
#!/bin/bash
# Perplexity ships its desktop app from its own Debian repository. The
# per-architecture package index carries the filename and SHA256 of every deb,
# so an update costs two small HTTP requests instead of a 175 MB download. The
# index's Version field drops the build number the pool filename carries, and
# upstream rebuilds under the same marketing version, so the version of record
# here is the full string embedded in the filename.
set -euo pipefail
BASE_URL="https://packages.perplexity.ai/deb"
declare -A DEB_ARCHES=([x86_64]=amd64 [aarch64]=arm64)
# Print "<version> <sha256>" for the newest stanza in a Packages index, version
# meaning the full string from the pool filename. Newest is vercmp's opinion,
# which is the one bin/sync-upstream and pacman both use; sort -V disagrees
# with it over versions like 1.0a.
newest_release() {
local index="$1"
local debarch="$2"
local filename sha256 version best_version="" best_sha256=""
local prefix="pool/main/p/perplexity/perplexity_"
local suffix="_${debarch}.deb"
while read -r filename sha256; do
# The PKGBUILD reconstructs the pool URL from pkgver alone, so a stanza
# naming anything else -- a rename, or a new pool layout -- has to stop
# the sync rather than pin a checksum to a URL nobody will fetch.
if [[ "$filename" != "${prefix}"*"${suffix}" ]]; then
echo "Unexpected pool filename in the upstream index: $filename" >&2
return 1
fi
version="${filename#"$prefix"}"
version="${version%"$suffix"}"
if [[ -z "$best_version" ]] || [[ "$(vercmp "$version" "$best_version")" -gt 0 ]]; then
best_version="$version"
best_sha256="$sha256"
fi
done < <(awk '
{ sub(/\r$/, "") }
/^Filename:/ { filename = $2 }
/^SHA256:/ { sha256 = $2 }
/^$/ { if (filename && sha256) print filename, sha256; filename = sha256 = "" }
END { if (filename && sha256) print filename, sha256 }
' <<<"$index")
[[ -n "$best_version" ]] || return 1
echo "$best_version $best_sha256"
}
versions=()
declare -A checksums=()
for arch in "${!DEB_ARCHES[@]}"; do
index=$(curl -fsSL "$BASE_URL/dists/stable/main/binary-${DEB_ARCHES[$arch]}/Packages")
read -r version sha256 <<<"$(newest_release "$index" "${DEB_ARCHES[$arch]}")"
if [[ -z "${version:-}" || -z "${sha256:-}" ]]; then
echo "No usable release found for $arch in the upstream package index" >&2
exit 1
fi
versions+=("$version")
checksums[$arch]="$sha256"
done
# A release lands one architecture at a time, and a single pkgver has to cover
# both -- including the build number, since that is what the download URLs are
# built from. Report no update until they agree; the next run picks it up.
for version in "${versions[@]}"; do
if [[ "$version" != "${versions[0]}" ]]; then
echo "Upstream architectures are mid-release (${versions[*]}); skipping" >&2
echo '{}'
exit 0
fi
done
jq -n \
--arg pkgver "${versions[0]}" \
--arg x86_64 "${checksums[x86_64]}" \
--arg aarch64 "${checksums[aarch64]}" \
'{pkgver: $pkgver, sha256sums: {x86_64: [$x86_64], aarch64: [$aarch64]}}'
+139
View File
@@ -0,0 +1,139 @@
# Maintainer: Spencer Bull <spencer@omarchy.org>
# Omarchy tracks Perplexity's own Debian repository, the feed the app updates
# itself from. .omarchy/upstream.sh rewrites the version and checksums below
# from that repository's package index.
pkgname=perplexity
pkgver=26.9.1+build61614
pkgrel=2
pkgdesc="Official Perplexity desktop app"
arch=('x86_64' 'aarch64')
url="https://www.perplexity.ai"
# Upstream ships no license text with the app; its terms live at
# https://www.perplexity.ai/hub/legal/terms-of-service
license=('custom')
depends=(
'alsa-lib'
'at-spi2-core'
'bash'
'bubblewrap'
'cairo'
'dbus'
'expat'
'gcc-libs'
'gdk-pixbuf2'
'glib2'
'glibc'
'gtk3'
'hicolor-icon-theme'
'libcups'
'libdrm'
'libglvnd'
'libnotify'
'libsecret'
'libx11'
'libxcb'
'libxcomposite'
'libxdamage'
'libxext'
'libxfixes'
'libxkbcommon'
'libxrandr'
'libxss'
'libxtst'
'mesa'
'nspr'
'nss'
'pango'
'systemd-libs'
'util-linux-libs'
'vulkan-icd-loader'
'xdg-utils'
)
optdepends=(
'apparmor: confine the app with the bundled user-namespace profile'
'libappindicator-gtk3: tray icon support'
'docker: containerized local GPU runtime for on-device models'
'nvidia-container-toolkit: containerized local GPU runtime for on-device models'
)
makedepends=('libarchive')
options=('!debug' '!strip')
# Omarchy: the build image compresses with zstd at its default level. Maximum
# zstd buys roughly a third off this ~600 MB Electron tree for a few extra
# minutes of build time -- worth it for a package this large on the fast ring,
# same reasoning as openai-codex-desktop.
COMPRESSZST=(zstd -c -z -q --ultra -22 --threads=0 -)
# The pool filename carries a build number the index's Version field drops, and
# upstream rebuilds under the same marketing version, so pkgver is the full
# string from the filename. The pool's storage treats a literal '+' in a URL as
# a space and answers 403, hence the %2B in the download URL only.
_deb_x86_64="perplexity_${pkgver}_amd64.deb"
_deb_aarch64="perplexity_${pkgver}_arm64.deb"
_pool="https://packages.perplexity.ai/deb/pool/main/p/perplexity"
source=('perplexity-launcher.sh')
source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64//+/%2B}")
source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64//+/%2B}")
noextract=("${_deb_x86_64}" "${_deb_aarch64}")
sha256sums=('b180ca6fbd268712a277a34d4215f176001c0031a4f6360686458623f47fad65')
sha256sums_x86_64=('2fc95f5500335e661af6901bbad5c95e8805cc9f43d9f0b979787c7249c15c33')
sha256sums_aarch64=('917562bad5ab319612c22fca49773cdee163b2e7c9f0c3dd5569fc4878f10237')
package() {
cd "${srcdir}"
local deb_var="_deb_${CARCH}"
local deb="${!deb_var}"
bsdtar -xOf "${deb}" data.tar.xz |
bsdtar --no-same-owner -xf - -C "${pkgdir}"
# 26.9.1 started shipping a systemd unit under /lib, and owning /lib -- a
# symlink owned by the filesystem package -- makes pacman refuse the whole
# transaction. The unit is also a no-op here: its setup script apt-installs
# Docker and the NVIDIA Container Toolkit and exits on any distro but Ubuntu
# (install those yourself; see optdepends). Delete it, and its parents once
# emptied; then anything else left outside opt/ and usr/ -- whatever its
# type, an empty bin/ or lib64/ included, since those are filesystem-owned
# symlinks too -- stops the build rather than shipping the next conflict.
(
cd "${pkgdir}"
rm -f lib/systemd/system/perplexity-local-runtime-setup.service
rmdir -p lib/systemd/system 2>/dev/null || true
)
local unexpected
unexpected=$(cd "${pkgdir}" && find . -mindepth 1 \
-path ./opt -prune -o -path ./usr -prune -o -print)
if [[ -n "${unexpected}" ]]; then
echo "Unexpected entries outside opt/ and usr/ in the upstream deb:" >&2
echo "${unexpected}" >&2
return 1
fi
# pacman never runs the deb's postinst, so the /usr/bin entry it would have
# symlinked is a launcher here instead, and the menu entry goes through it so
# the flags file applies there too. The scheme handler stays upstream's.
install -Dm755 perplexity-launcher.sh "${pkgdir}/usr/bin/perplexity"
sed -i 's|^Exec=.*|Exec=perplexity %U|' \
"${pkgdir}/usr/share/applications/perplexity.desktop"
# Arch ships Chromium's and Electron's own sandbox helper setuid, which is
# what keeps the sandbox up on a kernel that denies unprivileged user
# namespaces -- linux-hardened, mainly -- instead of aborting the app. The
# deb leaves this bit to its postinst.
chmod 4755 "${pkgdir}/opt/Perplexity/chrome-sandbox"
# The profile upstream's postinst would install. It names /opt/Perplexity
# paths, which this package keeps.
install -Dm644 "${pkgdir}/opt/Perplexity/resources/apparmor-profile" \
"${pkgdir}/etc/apparmor.d/perplexity"
# Debian package-policy files are not used on Arch Linux.
rm -rf "${pkgdir}/usr/share/doc"
}
@@ -4,7 +4,7 @@ set -euo pipefail
user_flags=()
config_home="${XDG_CONFIG_HOME:-}"
[[ -n "$config_home" || -z "${HOME:-}" ]] || config_home="$HOME/.config"
flags_file="${config_home:+$config_home/t3code-flags.conf}"
flags_file="${config_home:+$config_home/perplexity-flags.conf}"
if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do
@@ -16,7 +16,8 @@ if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then
fi
# Chromium's own Ozone detection falls back to XWayland often enough to matter,
# and the result is a blurry window on every scaled display.
# and the result is a blurry window on every scaled display. Ask for Wayland
# directly, unless the user has already picked a platform themselves.
platform_flags=()
if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then
platform_flags=(--ozone-platform=wayland)
@@ -28,4 +29,4 @@ if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then
done
fi
exec /usr/lib/t3code/t3code "${platform_flags[@]}" "${user_flags[@]}" "$@"
exec /opt/Perplexity/perplexity "${platform_flags[@]}" "${user_flags[@]}" "$@"
+8 -2
View File
@@ -1,4 +1,10 @@
{
"source": "aur",
"upstream_commit": "a850f2d07e825f61cb01b87931d2ae5f1802dde1"
"source": "local",
"upstream": {
"git_tags": "https://github.com/FrameworkComputer/qmk_hid.git",
"tag_pattern": "v{pkgver}",
"sources": {
"any": ["https://github.com/FrameworkComputer/qmk_hid/archive/refs/tags/{tag}.tar.gz"]
}
}
}
+4 -4
View File
@@ -2,16 +2,16 @@
pkgname=qmk-hid
_srcname=qmk_hid
pkgver=0.1.11
pkgrel=3
pkgver=0.1.13
pkgrel=1
pkgdesc="Commandline tool for interacting with QMK devices over HID"
arch=('x86_64')
arch=('x86_64' 'aarch64')
url="https://github.com/FrameworkComputer/qmk_hid"
license=('BSD')
depends=('gcc-libs' 'systemd-libs' 'libcap')
makedepends=('cargo')
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('e898d27cfe8350f4a8cd2938a3d066935093daf1292f41fc10b8f1937059f2b59e4ac34a987f26c8258a5d3b15a900059024fe3a18ed793f7e3b524939ab4c71')
sha256sums=('8a058395b8ba56ff54308a681223f11a6032c54df42b4d0223143ccf09d0c858')
options=('!lto')
prepare() {
@@ -8,7 +8,7 @@
"qt6-wayland"
],
"rebuilt_against": {
"qt6-base": "6.11.2-2",
"qt6-base": "6.11.2-3",
"qt6-declarative": "6.11.2-1",
"qt6-wayland": "6.11.2-1"
}
+1 -1
View File
@@ -3,7 +3,7 @@
_pkgname=quickshell
pkgname="$_pkgname-git"
pkgver=0.3.0.r20.g28771c7
pkgrel=2
pkgrel=3
pkgdesc='Flexible toolkit for making desktop shells with QtQuick'
arch=(x86_64 aarch64)
url='https://git.outfoxxed.me/quickshell/quickshell'
@@ -1,4 +1,3 @@
{
"source": "aur",
"upstream_commit": "80c65c27a9d5511d2b4ce2ca7ad2396ad528f0da"
"source": "local"
}
@@ -1,7 +1,10 @@
# Maintainer: Alexandre Bouvier <contact@amb.tf>
_pkgname=retroarch-joypad-autoconfig
# Pinned so every architecture packages the same tree and a rebuild of the
# same pkgver reproduces the same contents; bump both together.
_commit=033151045d378b64e712a92592467800d7924227
pkgname=$_pkgname-git
pkgver=1.22.0.r24.g739cee1
pkgver=1.22.0.r96.g0331510
pkgrel=1
pkgdesc="RetroArch joypad autoconfig files"
arch=('any')
@@ -12,7 +15,7 @@ makedepends=('git')
optdepends=('retroarch')
provides=("$_pkgname")
conflicts=("$_pkgname")
source=("$_pkgname::git+$url.git")
source=("$_pkgname::git+$url.git#commit=$_commit")
b2sums=('SKIP')
pkgver() {
@@ -0,0 +1,13 @@
{
"source": "local",
"release_ring": "fast",
"min_release_age": "24h",
"upstream": {
"github": "Infrawrench/schist",
"digests": true,
"assets": {
"x86_64": "schist-{pkgver}-1-x86_64.pkg.tar.zst",
"aarch64": "schist-{pkgver}-1-aarch64.pkg.tar.zst"
}
}
}
Loaded 100 of 143 files, more files were not shown because too many files have changed in this diff. Show more