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 ' archlinux:base-devel bash -lc '
set -euo pipefail set -euo pipefail
pacman -Syu --noconfirm jq pacman -Syu --noconfirm git jq
groupadd -g "$HOST_GID" runner groupadd -g "$HOST_GID" runner
useradd -m -u "$HOST_UID" -g "$HOST_GID" runner useradd -m -u "$HOST_UID" -g "$HOST_GID" runner
+2 -1
View File
@@ -25,8 +25,9 @@ jobs:
-w /workspace \ -w /workspace \
archlinux:base-devel bash -lc ' archlinux:base-devel bash -lc '
set -euo pipefail set -euo pipefail
pacman -Syu --noconfirm jq pacman -Syu --noconfirm git jq
./bin/sync-upstream self-test ./bin/sync-upstream self-test
./bin/sync-rebuilds --self-test
./bin/omarchy-pkgs self-test ./bin/omarchy-pkgs self-test
./bin/omarchy-release self-test ./bin/omarchy-release self-test
' '
+1
View File
@@ -1,3 +1,4 @@
.firecrawl/
src/ src/
logs/ logs/
build-output/ build-output/
+179 -30
View File
@@ -30,18 +30,57 @@ The filesystem no longer encodes release policy. Instead:
## Prerequisites ## Prerequisites
### aarch64 Builds (Optional) ### 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 ```bash
# Run after each reboot
docker run --privileged --rm tonistiigi/binfmt --install arm64
# Verify # 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 # 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 ## 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 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 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 A vendor shipping tagged GitHub releases is pure data, declared as `upstream`
data, declared as `upstream` in `.omarchy/package.json` with no code at all: in `.omarchy/package.json` with no code at all:
```json ```json
"upstream": { "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 `{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 stripped for `pkgver`; drafts and prereleases are ignored. Only the 100 most
recent releases are considered. The provider fails closed on anything it cannot 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 read — an unusable tag, timestamp, or checksum stops the sync rather than being
skipped. skipped.
A package may also declare `"min_release_age": "24h"` (`s`/`m`/`h`/`d` suffix or Projects that publish version tags but no checksum manifest can declare the
bare seconds) to quarantine fresh releases until maintainers have had time to tag repository, the exact tag shape, and every source that should be hashed:
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 ```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`, centrally: whatever reports the release must prove its age via `published_at`,
or the sync fails. A maintainer deliberately shipping inside the window runs 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 `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"] } { "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. 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. The bumped version is checked against the published one as well as the checked-in one, and refused when pacman would not order it higher. The checked-in version is not the floor; what a user already has is, and a checkout that has fallen behind the repository can otherwise be bumped to something that loses to the package it means to replace. That check is skipped with a warning when the published database cannot be read.
Versions are read from the local pacman database, so this runs on Arch or in an Arch container against a synced database. Only `core`, `extra` and `multilib` count: a Qt release sitting in testing or kde-unstable is not what the builder will link against, and rebuilding for it would ship a package built against the wrong ABI. The workflow points that database at `mirror.omarchy.org`, the mirror the x86_64 builder itself uses, because a mirror running ahead of the builder would record a version the build never linked against and nothing re-fires once the record matches. x86_64 versions are read from the local pacman database, so the workflow runs
in an Arch container pointed at `mirror.omarchy.org`, the same mirror as the
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 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 ### Other
@@ -586,7 +725,7 @@ Minimal examples:
Fields: 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. - `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>`. - `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. - `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. - `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>`. - `skip_build`: optional boolean; defaults to `false`. Set `true` to exclude a package from scheduled version checks and unscoped builds. The package can still be built explicitly with `bin/repo release --package <name>`.
- `pkgrel`: optional Omarchy pkgrel suffix for a version-pinned rebuild bump. This emits `<aur pkgrel>.<suffix>` instead of replacing AUR's pkgrel. `offset` can be used only when preserving monotonic upgrades from old absolute pkgrel bumps. The metadata is removed automatically when AUR sync changes `pkgver`; the current package version is read from the checked-in PKGBUILD, so the version is not duplicated in JSON. - `pkgrel`: 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`. - `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. - `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 ### Build Matrix
@@ -691,10 +830,15 @@ bin/repo release --package my-package
- Mirrors: mirror.omarchy.org, rackspace, pkgbuild.com - Mirrors: mirror.omarchy.org, rackspace, pkgbuild.com
### aarch64 ### aarch64
- QEMU emulation required on x86_64 hosts (slower) - Built on the repository host like x86_64; under QEMU when the host is x86_64
- Uses Arch Linux ARM repositories - 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]` - 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 ### 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 All four units run **every 5 minutes**, staggered by a minute each, so a push
reaches the mirror in minutes rather than hours: 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 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`): If a state file exists, builds all edge packages that need updates 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 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 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 an operator expects. `check-versions` takes it too — its `git pull` would
otherwise swap PKGBUILDs out from under a running build. otherwise swap PKGBUILDs out from under a running build.
- **Backoff on failure.** A failed release records the attempt in - **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. 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 **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 - **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. 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/`: State files are stored in `/root/.state/`:
- `.sync-needed-<channel>` — the packages queued for that channel, one per - `.sync-needed-<channel>-<arch>` — the packages queued for that channel and
line; the release run reads them to name what it is building architecture, one per line; the release run reads them to name what it is
- `.build-failed-<channel>` — consecutive failure count, timestamp, and the building
commit it failed on (drives the backoff; removing it forces a retry) - `.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) ### Schedule (America/New_York)
+51 -3
View File
@@ -25,6 +25,22 @@ SKIP_PROD_CHECK=false
FAST_RING_ONLY=false FAST_RING_ONLY=false
BOOTSTRAP=false BOOTSTRAP=false
PACKAGES="" 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() { usage() {
echo "Usage: $0 --from <channel> --to <channel> [OPTIONS]" echo "Usage: $0 --from <channel> --to <channel> [OPTIONS]"
@@ -36,7 +52,8 @@ usage() {
echo " or --bootstrap (one-time initial seed)" echo " or --bootstrap (one-time initial seed)"
echo "" echo ""
echo "Options:" 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 " --package <names...> Advance only the named package(s)"
echo " --fast-ring Restrict to fast-ring packages (stable -> rc parity copy)" echo " --fast-ring Restrict to fast-ring packages (stable -> rc parity copy)"
echo " --bootstrap One-time stable -> rc seed before forward-only enforcement" echo " --bootstrap One-time stable -> rc seed before forward-only enforcement"
@@ -65,8 +82,14 @@ while [[ $# -gt 0 ]]; do
shift 2 shift 2
;; ;;
--arch) --arch)
ARCH="$2" if [[ "$2" == "all" ]]; then
update_arch_paths ALL_ARCHES=true
ARCH=all
else
require_valid_arch "$2"
ARCH="$2"
update_arch_paths
fi
shift 2 shift 2
;; ;;
--package) --package)
@@ -134,6 +157,31 @@ case "$FROM->$TO" in
;; ;;
esac 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" SOURCE_DIR="$REPO_ROOT/$FROM/$ARCH"
TARGET_DIR="$REPO_ROOT/$TO/$ARCH" TARGET_DIR="$REPO_ROOT/$TO/$ARCH"
SOURCE_DB="$SOURCE_DIR/omarchy.db.tar.zst" SOURCE_DB="$SOURCE_DIR/omarchy.db.tar.zst"
+104 -57
View File
@@ -1,6 +1,10 @@
#!/bin/bash #!/bin/bash
# Run the release workflow for a channel when work is queued. # 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: # 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" source "$BUILD_ROOT/helpers/lock-helpers.sh"
MIRROR="${1:-}" 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 # Backoff schedule: 10m, 20m, 40m, 80m, 160m, 320m, then hourly-ish forever
# (capped at 6h, the cadence this system ran at before frequent timers). # (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}" BACKOFF_MAX_SECONDS="${OMARCHY_BACKOFF_MAX:-21600}"
if [[ -z "$MIRROR" ]]; then if [[ -z "$MIRROR" ]]; then
print_error "Usage: $0 <mirror>" print_error "Usage: $0 <mirror> [<arch>|all]"
echo " mirror: edge, rc, or stable" echo " mirror: edge, rc, or stable"
echo " arch: one of $VALID_ARCHES, or all (default) for every published architecture"
exit 1 exit 1
fi fi
@@ -39,17 +44,13 @@ if [[ "$MIRROR" != "edge" && "$MIRROR" != "rc" && "$MIRROR" != "stable" ]]; then
exit 1 exit 1
fi fi
STATE_FILE="$STATE_DIR/.sync-needed-$MIRROR" if [[ "$ARCH_ARG" == "all" ]]; then
FAIL_FILE="$STATE_DIR/.build-failed-$MIRROR" ARCHES=$(published_arches)
else
# Nothing queued: stay quiet. At a 5-minute cadence this is most invocations, require_valid_arch "$ARCH_ARG"
# and a header for each would bury the runs that matter in the journal. ARCHES="$ARCH_ARG"
if [[ ! -f "$STATE_FILE" ]]; then
exit 0
fi fi
print_header "Processing Sync for $MIRROR"
# The build inputs are this checkout's contents; its HEAD identifies them. # The build inputs are this checkout's contents; its HEAD identifies them.
current_fingerprint() { current_fingerprint() {
git -C "$BUILD_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown" git -C "$BUILD_ROOT" rev-parse HEAD 2>/dev/null || echo "unknown"
@@ -65,57 +66,103 @@ backoff_seconds() {
echo "$delay" echo "$delay"
} }
FAIL_COUNT=0 # Returns 0 when this architecture's queue was processed (or was empty), 1 when
if [[ -f "$FAIL_FILE" ]]; then # the release failed. Backoff and "someone else holds the lock" are not
# shellcheck disable=SC1090 # failures: they are this tick deciding to do nothing.
source "$FAIL_FILE" 2>/dev/null || true release_arch() {
FAIL_COUNT="${FAILURE_COUNT:-0}" local arch="$1"
failed_at="${FAILURE_AT:-0}" local state_file fail_file
failed_fingerprint="${FAILURE_FINGERPRINT:-}" state_file=$(sync_queue_file "$MIRROR" "$arch")
fail_file=$(sync_fail_file "$MIRROR" "$arch")
if [[ "$failed_fingerprint" != "$(current_fingerprint)" ]]; then # A queue written under the pre-architecture name belongs to x86_64.
print_info "Repository changed since the last failure — clearing backoff and retrying" if [[ "$arch" == "x86_64" && ! -f "$state_file" && -f "$(legacy_sync_queue_file "$MIRROR")" ]]; then
rm -f "$FAIL_FILE" state_file=$(legacy_sync_queue_file "$MIRROR")
FAIL_COUNT=0 fi
else if [[ "$arch" == "x86_64" && ! -f "$fail_file" && -f "$(legacy_sync_fail_file "$MIRROR")" ]]; then
delay=$(backoff_seconds "$FAIL_COUNT") fail_file=$(legacy_sync_fail_file "$MIRROR")
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)"
fi fi
fi
# Non-blocking: a build in progress means this tick has nothing to do. # Nothing queued: stay quiet. At a 5-minute cadence this is most
if ! try_release_lock; then # invocations, and a header for each would bury the runs that matter in
holder=$(release_lock_holder) # the journal.
print_info "A release is already running (${holder:-holder unknown}) — skipping this tick" [[ -f "$state_file" ]] || return 0
exit 0
fi
print_info "State file found: $STATE_FILE" print_header "Processing Sync for $MIRROR ($arch)"
print_info "Starting release workflow for $MIRROR..."
if "$BUILD_ROOT/bin/repo" release --mirror "$MIRROR" --skip-prod-check; then local fail_count=0 failed_at failed_fingerprint delay now retry_at
print_success "Release completed successfully for $MIRROR" if [[ -f "$fail_file" ]]; then
rm -f "$STATE_FILE" FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT=""
rm -f "$FAIL_FILE" # shellcheck disable=SC1090
print_success "State file removed: $STATE_FILE" source "$fail_file" 2>/dev/null || true
else fail_count="${FAILURE_COUNT:-0}"
status=$? failed_at="${FAILURE_AT:-0}"
FAIL_COUNT=$((FAIL_COUNT + 1)) failed_fingerprint="${FAILURE_FINGERPRINT:-}"
cat >"$FAIL_FILE" <<EOF
FAILURE_COUNT=$FAIL_COUNT 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_AT=$(date +%s)
FAILURE_FINGERPRINT=$(current_fingerprint) FAILURE_FINGERPRINT=$(current_fingerprint)
EOF EOF
next=$(backoff_seconds "$FAIL_COUNT") then
print_error "Release failed for $MIRROR (attempt $FAIL_COUNT)" print_error "Could not record failure state: $fail_file"
print_warning "State file retained for retry: $STATE_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" print_warning "Backing off $((next / 60))m before the next attempt; a new commit retries sooner"
exit "$status" return 1
fi }
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 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 # Parse command line arguments
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case $1 in case $1 in
@@ -67,6 +88,11 @@ while [[ $# -gt 0 ]]; do
echo " $0 --package yay" echo " $0 --package yay"
echo " $0 --package yay elephant cursor-bin" echo " $0 --package yay elephant cursor-bin"
echo "" 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 exit 0
;; ;;
*) *)
@@ -76,18 +102,55 @@ while [[ $# -gt 0 ]]; do
esac esac
done 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 # Show target architecture and mirror after parsing args
print_info "Target architecture: $ARCH" print_info "Target architecture: $ARCH"
print_info "Mirror: $MIRROR" print_info "Mirror: $MIRROR"
print_info "Build workspace: $BUILD_OUTPUT_DIR" print_info "Build workspace: $BUILD_OUTPUT_DIR"
print_info "Final output: $REPO_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 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" \ ARCH="$ARCH" \
MIRROR="$MIRROR" \ MIRROR="$MIRROR" \
PACKAGES="$PACKAGES" \ PACKAGES="$PACKAGES" \
DRY_RUN=true \ DRY_RUN=true \
DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS" \
PKGBUILDS_DIR="$PKGBUILDS_DIR" \ PKGBUILDS_DIR="$PKGBUILDS_DIR" \
BUILD_OUTPUT_DIR="$BUILD_OUTPUT_DIR" \ BUILD_OUTPUT_DIR="$BUILD_OUTPUT_DIR" \
FINAL_OUTPUT_DIR="$REPO_DIR" \ FINAL_OUTPUT_DIR="$REPO_DIR" \
@@ -100,21 +163,45 @@ fi
# Create directories if they don't exist # Create directories if they don't exist
mkdir -p "$BUILD_OUTPUT_DIR" "$REPO_DIR" "$SRC_DIR" mkdir -p "$BUILD_OUTPUT_DIR" "$REPO_DIR" "$SRC_DIR"
# Check Docker is available # Check the selected container engine is available
check_docker check_engine
# Setup QEMU for aarch64 builds on x86_64 hosts # A foreign target architecture runs under QEMU user emulation. Probe by
if [[ "$(uname -m)" == "x86_64" && "$ARCH" == "aarch64" ]]; then # actually running a container for the target platform: that is the only
# Check if QEMU is already working # test that covers both "binfmt not registered" and "registered but broken".
if ! docker run --rm --platform linux/arm64 alpine:3.21 /bin/true >/dev/null 2>&1; then HOST_ARCH=$(uname -m)
print_info "Setting up QEMU for ARM64 emulation..." [[ "$HOST_ARCH" == "arm64" ]] && HOST_ARCH=aarch64
setup_qemu 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
fi fi
# Clean build-output directory to start fresh # Clean build-output directory to start fresh, unless the caller seeded it
print_info "Cleaning build workspace..." # with packages from an earlier job or is resuming an interrupted run.
rm -rf "$BUILD_OUTPUT_DIR"/* 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" mkdir -p "$BUILD_OUTPUT_DIR"
# Show package info # Show package info
@@ -124,8 +211,20 @@ else
print_info "Building unscoped packages for $MIRROR mirror" print_info "Building unscoped packages for $MIRROR mirror"
fi fi
# Build/update the Docker image # Build/update the Docker image, unless the caller prepared the exact image
build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR" # already (a workflow building it once with an external BuildKit cache). A
# missing image is an error rather than a silent rebuild: the point of the
# flag is that every job runs the same bytes.
IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR"
if [[ $SKIP_BUILDER_IMAGE == "1" ]]; then
if ! "$CONTAINER_ENGINE" image inspect "$IMAGE_TAG" >/dev/null 2>&1; then
print_error "Prepared builder image is unavailable: $IMAGE_TAG"
exit 1
fi
print_info "Using prepared builder image: $IMAGE_TAG"
else
build_docker_image "$BUILD_DIR" "$ARCH" "$MIRROR"
fi
print_info "Running package build..." print_info "Running package build..."
@@ -133,9 +232,12 @@ print_info "Running package build..."
mkdir -p "$BUILD_OUTPUT_DIR" mkdir -p "$BUILD_OUTPUT_DIR"
mkdir -p "$REPO_DIR" mkdir -p "$REPO_DIR"
# Ensure output directories are writable by container user # Rootful Docker writes as the image uid, so retain its existing permission
make_dir_writable "$BUILD_OUTPUT_DIR" # workaround. Rootless Podman uses keep-id and must leave ownership/modes alone.
make_dir_writable "$REPO_DIR" if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$BUILD_OUTPUT_DIR"
make_dir_writable "$REPO_DIR"
fi
# Build Docker arguments # Build Docker arguments
DOCKER_ARGS=( DOCKER_ARGS=(
@@ -144,6 +246,7 @@ DOCKER_ARGS=(
-e MIRROR="$MIRROR" -e MIRROR="$MIRROR"
-e PACKAGES="$PACKAGES" -e PACKAGES="$PACKAGES"
-e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}" -e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}"
-e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS"
-v "$BUILD_ROOT/build-output:/build-output" -v "$BUILD_ROOT/build-output:/build-output"
-v "$REPO_ROOT:/pkgs.omarchy.org" -v "$REPO_ROOT:/pkgs.omarchy.org"
-v "$BUILD_DIR:/build:ro" -v "$BUILD_DIR:/build:ro"
@@ -151,11 +254,16 @@ DOCKER_ARGS=(
-v "$BUILD_ROOT/pkgbuilds:/pkgbuilds:ro" -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 # Run the builder with assembled args
IMAGE_TAG="omarchy-pkg-builder:latest-$ARCH-$MIRROR"
PLATFORM_ARG=$(get_platform_arg "$ARCH") 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=$? BUILD_RESULT=$?
+61 -20
View File
@@ -1,6 +1,10 @@
#!/bin/bash #!/bin/bash
# Check PKGBUILD versions against published repo versions # 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 set -e
@@ -10,11 +14,27 @@ source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/package-metadata.sh" source "$BUILD_ROOT/helpers/package-metadata.sh"
source "$BUILD_ROOT/helpers/lock-helpers.sh" source "$BUILD_ROOT/helpers/lock-helpers.sh"
STATE_DIR="${OMARCHY_STATE_DIR:-/root/.state}"
ARCH="${ARCH:-x86_64}"
PULL=false 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" 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() { get_pkgbuild_version() {
local pkgdir="$1" local pkgdir="$1"
if [[ -f "$pkgdir/PKGBUILD" ]]; then local epoch pkgver pkgrel
(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; if [[ -n "$epoch" ]]; then echo "${epoch}:${pkgver}-${pkgrel}"; else echo "${pkgver}-${pkgrel}"; fi')
[[ -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 fi
} }
@@ -77,8 +113,8 @@ check_vcs_unchanged() {
# If epoch or pkgrel changed in PKGBUILD, release even if upstream is unchanged. # If epoch or pkgrel changed in PKGBUILD, release even if upstream is unchanged.
local pkgbuild_epoch pkgbuild_pkgrel local pkgbuild_epoch pkgbuild_pkgrel
pkgbuild_epoch=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${epoch:-}"') pkgbuild_epoch=$(package_pkgbuild_var "$pkgdir" epoch "$ARCH")
pkgbuild_pkgrel=$(cd "$pkgdir" && bash -c 'source PKGBUILD 2>/dev/null; echo "${pkgrel}"') pkgbuild_pkgrel=$(package_pkgbuild_var "$pkgdir" pkgrel "$ARCH")
local repo_pkgrel="${repo_version##*-}" local repo_pkgrel="${repo_version##*-}"
local repo_no_pkgrel="${repo_version%-*}" local repo_no_pkgrel="${repo_version%-*}"
@@ -106,12 +142,11 @@ check_package() {
local mirror="$3" local mirror="$3"
local pkgbuild_version repo_version local pkgbuild_version repo_version
pkgbuild_version=$(get_pkgbuild_version "$pkgdir") if ! pkgbuild_version=$(get_pkgbuild_version "$pkgdir") || [[ -z "$pkgbuild_version" ]]; then
repo_version=$(get_repo_version "$pkg" "$mirror") print_warning "$pkg: could not read pkgver/pkgrel from its PKGBUILD for $ARCH — not queueing"
if [[ -z "$pkgbuild_version" ]]; then
return 1 return 1
fi fi
repo_version=$(get_repo_version "$pkg" "$mirror")
if grep -qE '^pkgver[[:space:]]*\(\)' "$pkgdir/PKGBUILD"; then if grep -qE '^pkgver[[:space:]]*\(\)' "$pkgdir/PKGBUILD"; then
if [[ -n "$repo_version" && -n "$(package_extract_vcs_hash_from_version "$repo_version")" ]]; then if [[ -n "$repo_version" && -n "$(package_extract_vcs_hash_from_version "$repo_version")" ]]; then
@@ -147,11 +182,12 @@ check_package() {
check_mirror() { check_mirror() {
local mirror="$1" 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 needs_build=false
local packages=() local packages=()
print_info "Checking $mirror packages..." print_info "Checking $mirror packages for $ARCH..."
while IFS= read -r pkg; do while IFS= read -r pkg; do
local pkgdir="$PKGBUILDS_DIR/$pkg" local pkgdir="$PKGBUILDS_DIR/$pkg"
@@ -159,7 +195,7 @@ check_mirror() {
needs_build=true needs_build=true
packages+=("$pkg") packages+=("$pkg")
fi fi
done < <(packages_for_unscoped_build "$mirror") done < <(packages_for_unscoped_build "$mirror" "$ARCH")
echo "" echo ""
@@ -168,18 +204,23 @@ check_mirror() {
# this to name the packages in its start report, which is the difference # 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". # between "a build is running" and "your package is in this build".
printf '%s\n' "${packages[@]}" >"$state_file" 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 "Packages: ${packages[*]}"
print_info "State file created: $state_file" print_info "State file created: $state_file"
else else
print_info "${mirror^} is up to date" print_info "${mirror^} ($ARCH) is up to date"
fi fi
echo "" echo ""
} }
check_mirror edge # One pass per published architecture: the version comparison reads that
check_mirror rc # architecture's channel databases, and each queue is its own file.
check_mirror stable for ARCH in $ARCHES; do
update_arch_paths
check_mirror edge
check_mirror rc
check_mirror stable
done
print_success "Version check complete!" print_success "Version check complete!"
+13 -7
View File
@@ -1,21 +1,27 @@
#!/bin/bash #!/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 # Forces a fresh image build on next run
set -e set -e
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh" 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 # Remove all omarchy-pkg-builder images
print_info "Removing 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 # Clear this engine's build cache.
print_info "Clearing buildx cache..." print_info "Clearing build cache..."
docker buildx prune -a -f 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" print_info "Next build will create fresh images"
+7 -3
View File
@@ -31,9 +31,13 @@ clean_packages() {
# Skip signature files # Skip signature files
[[ "$pkg" == *.sig ]] && continue [[ "$pkg" == *.sig ]] && continue
# Extract package name (remove version and architecture) # Format: name-version-release-arch.pkg.tar.*. Work from the right because
# Format: name-version-release-arch.pkg.tar.* # package names can themselves contain version-like pieces (qt6-5compat,
local pkgname=$(echo "$pkg" | sed -E 's/-[0-9]+.*-(any|x86_64|i686)\.pkg\.tar\..*//') # 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 # Add to array
if [[ -n "${packages[$pkgname]}" ]]; then 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/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/host-helpers.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}" 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) RELEASE_PACKAGES=(omarchy omarchy-settings)
DEFAULT_RC_REF="quattro" DEFAULT_RC_REF="quattro"
SRCDEST_DIR="$BUILD_ROOT/.srcdest" SRCDEST_DIR="$BUILD_ROOT/.srcdest"
@@ -92,7 +93,7 @@ version_is_rc() { version_is_prerelease "$1"; }
pkgbuild_var() { pkgbuild_var() {
local pkg="$1" var="$2" 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 # Prints the published version of $pkg from the edge DB. Distinguishes
@@ -327,13 +328,19 @@ regenerate_checksums() {
trigger_build_host() { trigger_build_host() {
local 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 # Explicit host configuration outranks the local-host inference (a
# workstation that ran a full local release carries the db marker too). # 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 if ! resolve_repo_host "${REPO_HOST_OVERRIDE:-}" >/dev/null && on_repo_host; then
print_info "Triggering edge build locally (this is the build host)..." print_info "Triggering edge build locally (this is the build host)..."
if mkdir -p "${OMARCHY_STATE_DIR:-/root/.state}" && if queue_edge_builds && systemctl start --no-block omarchy-auto-release-edge.service; then
touch "${OMARCHY_STATE_DIR:-/root/.state}/.sync-needed-edge" &&
systemctl start --no-block omarchy-auto-release-edge.service; then
print_success "Edge build triggered" print_success "Edge build triggered"
else else
print_warning "Could not start the edge release service — the 6-hourly timer will pick it up" 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 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 "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:" 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 return 0
fi fi
print_info "Triggering edge build on $host..." 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" print_success "Edge build triggered on $host"
else else
print_warning "Could not trigger $host — the 6-hourly timer will pick it up" 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]%/*}/..") BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh" source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/host-helpers.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}" DEV_BRANCH="${OMARCHY_DEV_BRANCH:-quattro}"
PKGS_DB_BASE="${OMARCHY_PKGS_DB_BASE:-https://pkgs.omarchy.org}" 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" SRCDEST_DIR="$BUILD_ROOT/.srcdest"
MIRROR_CLONE="$SRCDEST_DIR/omarchy" # bare mirror (shared with bin/omarchy-pkgs) 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 # 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). # the database cannot be read (callers must not mistake an outage for absence).
published_version() { published_version() {
local channel="$1" tmp descs local channel="$1" arch="${2:-$OBSERVED_ARCH}" tmp descs
tmp=$(mktemp) || return 2 tmp=$(mktemp) || return 2
# A unique query string busts the CDN cache: right after a sync the plain # 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 # 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. # 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" rm -f "$tmp"
return 2 return 2
fi fi
@@ -207,6 +212,14 @@ published_version() {
' <<<"$descs" ' <<<"$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 # The rc branch of THIS repo carries the current pins. Read them without
# touching the working tree. # touching the working tree.
rc_branch_pin() { # prints "pkgver commit", empty when no rc branch 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 fetch origin rc
git -C /root/omarchy-pkgs-rc reset --hard origin/rc git -C /root/omarchy-pkgs-rc reset --hard origin/rc
cd /root/omarchy-pkgs-rc cd /root/omarchy-pkgs-rc
OMARCHY_RC_PINS=1 OMARCHY_REPO_ROOT=/root/omarchy-pkgs/pkgs.omarchy.org \ # The pair is built once per published architecture (helpers/paths.sh on the
bin/repo release --mirror rc --package omarchy omarchy-settings --skip-prod-check # 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() { 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. # against this machine's (likely stale) local tree would be wrong.
if ! resolve_repo_host "$REPO_HOST_OVERRIDE" >/dev/null && ! on_repo_host; then if ! resolve_repo_host "$REPO_HOST_OVERRIDE" >/dev/null && ! on_repo_host; then
print_no_host_help "advance $from -> $to" \ 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 return 1
fi 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] wait_for_published() { # wait_for_published <channel> <version> [timeout-seconds]
local channel="$1" want="$2" timeout="${3:-3600}" waited=0 got local channel="$1" want="$2" timeout="${3:-3600}" waited=0 arch got pending
print_info "Waiting for $want to appear in the $channel channel (up to $((timeout / 60))m)..." print_info "Waiting for $want in $channel for: $(published_arches | tr '\n' ' ')"
while ((waited < timeout)); do while ((waited < timeout)); do
got=$(published_version "$channel" 2>/dev/null) || got="" pending=""
if [[ "${got%-*}" == "$want" ]]; then for arch in $(published_arches); do
print_success "$channel now serves omarchy $got" 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 return 0
fi fi
sleep 60 sleep 60
@@ -298,7 +321,7 @@ wait_for_published() { # wait_for_published <channel> <version> [timeout-seconds
printf '.' >&2 printf '.' >&2
done done
echo "" >&2 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." print_info "The build may still be running — re-run this command to resume."
return 1 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] build_iso() { # build_iso <version> [--rc]
local version="$1" rc_flag="${2:-}" dir local version="$1" rc_flag="${2:-}" dir
dir=$(iso_checkout) || return 1 dir=$(iso_checkout) || return 1
if ! command -v docker >/dev/null || ! docker info >/dev/null 2>&1; then if [[ -z "$CONTAINER_ENGINE" ]] || ! "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_warning "Docker unavailable — cannot build the ISO here. Run on a Docker machine:" 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" echo " cd $dir && bin/omarchy-iso-release ${rc_flag:+$rc_flag }$version"
return 1 return 1
fi fi
@@ -662,15 +685,13 @@ cmd_rc() {
if [[ -n "$pin" ]]; then if [[ -n "$pin" ]]; then
local pin_ver="${pin%% *}" pin_commit="${pin##* }" local pin_ver="${pin%% *}" pin_commit="${pin##* }"
if [[ "$pin_commit" == "$head" && "$pin_ver" =~ ^${version//./\\.}rc[0-9]+$ ]]; then if [[ "$pin_commit" == "$head" && "$pin_ver" =~ ^${version//./\\.}rc[0-9]+$ ]]; then
local pub if all_arches_at_version rc "$pin_ver"; then
pub=$(published_version rc 2>/dev/null) || pub="" print_success "$pin_ver is already cut from this head and published to rc on every architecture"
if [[ "${pub%-*}" == "$pin_ver" ]]; then
print_success "$pin_ver is already cut from this head and published to rc"
maybe_iso "$pin_ver" rc "$iso_mode" maybe_iso "$pin_ver" rc "$iso_mode"
return 0 return 0
fi fi
print_info "$pin_ver is pinned from this head but not published yet — re-triggering the build" 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" [[ "$wait" == true ]] && wait_for_published rc "$pin_ver"
maybe_iso "$pin_ver" rc "$iso_mode" maybe_iso "$pin_ver" rc "$iso_mode"
return 0 return 0
@@ -686,7 +707,7 @@ cmd_rc() {
new_ver="${new_pin%% *}" new_ver="${new_pin%% *}"
print_success "Pinned $new_ver (rc branch pushed)" print_success "Pinned $new_ver (rc branch pushed)"
trigger_rc_build || true trigger_rc_build || return 1
if [[ "$wait" == true ]]; then if [[ "$wait" == true ]]; then
wait_for_published rc "$new_ver" || return 1 wait_for_published rc "$new_ver" || return 1
fi fi
@@ -708,9 +729,7 @@ cmd_ship() {
exit 1 exit 1
fi fi
version=$(branch_to_version "$branch") version=$(branch_to_version "$branch")
local stable_now if all_arches_at_version stable "$version" &&
stable_now=$(published_version stable 2>/dev/null) || stable_now=""
if [[ "${stable_now%-*}" == "$version" ]] &&
gh release view "v$version" --repo "$UPSTREAM_REPO" >/dev/null 2>&1; then 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" print_success "Nothing to ship — $version is tagged, released, and live on stable"
exit 0 exit 0
@@ -752,10 +771,8 @@ cmd_ship() {
echo " omarchy-release rc" echo " omarchy-release rc"
exit 1 exit 1
fi fi
local pub if ! all_arches_at_version rc "$pin_ver"; then
pub=$(published_version rc 2>/dev/null) || pub="" print_error "$pin_ver is pinned but is not published for every architecture"
if [[ "${pub%-*}" != "$pin_ver" ]]; then
print_error "$pin_ver is pinned but rc serves '${pub:-nothing}' — the candidate build hasn't published"
echo "Wait for it (or re-run: omarchy-release rc), then ship." echo "Wait for it (or re-run: omarchy-release rc), then ship."
exit 1 exit 1
fi fi
@@ -799,10 +816,9 @@ cmd_ship() {
# 2. Final pins into rc. Resolve the tag we just established so the final # 2. Final pins into rc. Resolve the tag we just established so the final
# PKGBUILDs record both its provenance and its exact commit. # PKGBUILDs record both its provenance and its exact commit.
local rc_pub stable_pub local stable_pub
rc_pub=$(published_version rc 2>/dev/null) || rc_pub="" if all_arches_at_version rc "$version"; then
if [[ "${rc_pub%-*}" == "$version" ]]; then print_success "2/7 Final $version already published to rc on every architecture"
print_success "2/7 Final $version already published to rc"
else else
if [[ "$pin_ver" != "$version" ]]; then if [[ "$pin_ver" != "$version" ]]; then
print_info "2/7 Pinning final $version from tag v$version..." print_info "2/7 Pinning final $version from tag v$version..."
@@ -810,22 +826,21 @@ cmd_ship() {
else else
print_info "2/7 Final $version pinned — re-triggering build" print_info "2/7 Final $version pinned — re-triggering build"
fi fi
trigger_rc_build || true trigger_rc_build || exit 1
wait_for_published rc "$version" || exit 1 wait_for_published rc "$version" || exit 1
fi fi
# 3. Promote rc -> stable # 3. Promote rc -> stable
stable_pub=$(published_version stable 2>/dev/null) || stable_pub="" if all_arches_at_version stable "$version"; then
if [[ "${stable_pub%-*}" == "$version" ]]; then print_success "3/7 Stable already serves $version on every architecture"
print_success "3/7 Stable already serves $version"
else else
host_advance rc stable || exit 1 host_advance rc stable || exit 1
stable_pub=$(published_version stable 2>/dev/null) || stable_pub="" if ! all_arches_at_version stable "$version"; then
if [[ "${stable_pub%-*}" != "$version" ]]; then print_error "Promotion ran but stable does not serve $version on every architecture"
print_error "Promotion ran but stable serves '${stable_pub:-nothing}' — investigate before continuing"
exit 1 exit 1
fi 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 fi
# 4. Final pins onto master (keeps edge overlap publishing and the repo record) # 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="" last=$(newest_release_branch 2>/dev/null) || last=""
if [[ -n "$last" && "$STABLE_VER" != "<unreachable>" ]]; then if [[ -n "$last" && "$STABLE_VER" != "<unreachable>" ]]; then
last_ver=$(branch_to_version "$last") 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 { 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" echo "ship|$last_ver is tagged but not fully shipped — resume ship"
return return
@@ -924,7 +939,7 @@ next_step() { # prints "<command>|<description>"
echo "ship|Final $TRAIN_VER is pinned — finish shipping (re-runs are safe)" echo "ship|Final $TRAIN_VER is pinned — finish shipping (re-runs are safe)"
elif [[ "$pin_commit" != "$TRAIN_HEAD" ]]; then elif [[ "$pin_commit" != "$TRAIN_HEAD" ]]; then
echo "rc|$TRAIN has commits newer than $pin_ver — cut the next candidate" 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" echo "rc|$pin_ver is pinned but not published — re-run rc to re-trigger/wait"
else else
echo "ship|$pin_ver is published to rc — test it, then ship" 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 "makepkg available (checksums)" command -v makepkg
check "curl available" command -v curl check "curl available" command -v curl
check "upstream reachable ($UPSTREAM_URL)" git ls-remote "$UPSTREAM_URL" HEAD check "upstream reachable ($UPSTREAM_URL)" git ls-remote "$UPSTREAM_URL" HEAD
local ch local ch arch
for ch in edge stable; do for ch in edge stable; do
if published_version "$ch" >/dev/null 2>&1; then for arch in $(published_arches); do
print_success "$ch channel db readable" 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 else
print_error "$ch channel db readable" print_warning "rc/$arch channel db not readable — bootstrap it first: bin/repo bootstrap-rc (on the build host)"
failures=$((failures + 1))
fi fi
done 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 local host
if host=$(repo_host); then if host=$(repo_host); then
check "build host ssh ($host)" ssh -o ConnectTimeout=10 "$host" true check "build host ssh ($host)" ssh -o ConnectTimeout=10 "$host" true
@@ -1016,10 +1035,10 @@ cmd_doctor() {
else 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" 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 fi
if command -v docker >/dev/null && docker info >/dev/null 2>&1; then if [[ -n "$CONTAINER_ENGINE" ]] && "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "docker available (ISO builds possible here)" print_success "$CONTAINER_ENGINE available (ISO builds possible here)"
else else
print_warning "docker unavailable — ISO builds will print instructions instead" print_warning "container engine unavailable — ISO builds will print instructions instead"
fi fi
echo "" echo ""
if ((failures == 0)); then 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 "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.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" 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 "" echo ""
if ((failures == 0)); then if ((failures == 0)); then
print_success "Self-test passed" 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 # leave nvidia-580xx-dkms and opencl-nvidia-580xx behind. An output's own name
# still matches, for pushing just one of them on purpose. # still matches, for pushing just one of them on purpose.
# #
# pkgbase comes from .PKGINFO rather than the PKGBUILD: it is what makepkg # Package identity comes from .PKGINFO rather than the filename or PKGBUILD: it
# actually recorded, and it needs no guessing about which directory built what. # is what makepkg actually recorded, and it handles epochs and split packages.
pkgbase_of() { package_identity_of() {
bsdtar -xOf "$1" .PKGINFO 2>/dev/null | 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=() FILES=()
@@ -161,14 +166,18 @@ if [[ -z "$PACKAGES" ]]; then
FILES=("${ALL_FILES[@]}") FILES=("${ALL_FILES[@]}")
else else
declare -A MATCHED=() declare -A MATCHED=()
declare -A LATEST_FILE=()
declare -A LATEST_VERSION=()
for file in "${ALL_FILES[@]}"; do for file in "${ALL_FILES[@]}"; do
# name-version-release-arch.pkg.tar.zst -> name IFS=$'\t' read -r pkgname pkgbase pkgver < <(package_identity_of "$BUILD_OUTPUT_DIR/$file")
pkgname="${file%-*-*-*.pkg.tar.*}"
pkgbase=$(pkgbase_of "$BUILD_OUTPUT_DIR/$file")
for wanted in $PACKAGES; do for wanted in $PACKAGES; do
if [[ "$pkgname" == "$wanted" || "$pkgbase" == "$wanted" ]]; then if [[ "$pkgname" == "$wanted" || "$pkgbase" == "$wanted" ]]; then
FILES+=("$file")
MATCHED["$wanted"]=1 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 break
fi fi
done done
@@ -181,6 +190,8 @@ else
exit 1 exit 1
fi fi
done done
mapfile -t FILES < <(printf '%s\n' "${LATEST_FILE[@]}" | sort)
fi fi
if [[ ${#FILES[@]} -eq 0 ]]; then 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 # 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. # this run. Absent for a manual run, which is fine — the report just omits it.
QUEUED_PACKAGES="" 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") [[ -s "$QUEUE_FILE" ]] && QUEUED_PACKAGES=$(grep -c '' "$QUEUE_FILE")
if [[ "$DRY_RUN" != true ]]; then if [[ "$DRY_RUN" != true ]]; then
+15 -10
View File
@@ -62,8 +62,8 @@ if [[ ! -d "$REPO_DIR" ]]; then
exit 1 exit 1
fi fi
# Check Docker is available # Check the selected container engine is available
check_docker check_engine
# Find package files to confirm before running Docker # Find package files to confirm before running Docker
cd "$REPO_DIR" cd "$REPO_DIR"
@@ -88,24 +88,29 @@ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 0 exit 0
fi fi
# Build/update the Docker image (always use x86_64 for removal - it's architecture independent) # repo-remove is architecture-independent, so use a host-native edge image.
# repo-remove is mirror-independent — always use the edge x86_64 image TOOL_ARCH=$(docker_native_arch) || {
build_docker_image "$BUILD_DIR" "x86_64" "edge" print_error "Unsupported host architecture: $(uname -m)"
exit 1
}
build_docker_image "$BUILD_DIR" "$TOOL_ARCH" "edge"
acquire_release_lock || exit 1 acquire_release_lock || exit 1
print_info "Removing package..." print_info "Removing package..."
# Ensure directory is writable by container user # Rootless Podman uses keep-id and leaves host ownership/modes intact.
make_dir_writable "$REPO_DIR" if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$REPO_DIR"
fi
# Run the removal script in Docker (always use x86_64 image) # Run the removal script in the host-native image.
docker run --rm --platform linux/amd64 \ "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$TOOL_ARCH")" \
-e ARCH="$ARCH" \ -e ARCH="$ARCH" \
-e MIRROR="$MIRROR" \ -e MIRROR="$MIRROR" \
-v "$REPO_ROOT:/pkgs.omarchy.org" \ -v "$REPO_ROOT:/pkgs.omarchy.org" \
-v "$BUILD_DIR:/build:ro" \ -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=$? RESULT=$?
+59 -22
View File
@@ -15,6 +15,8 @@ set -e
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..") BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh" source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
CHECK_ONLY=false CHECK_ONLY=false
SKIP_TIMERS=false SKIP_TIMERS=false
@@ -61,10 +63,12 @@ if command -v apt-get >/dev/null 2>&1; then
DISTRO="debian" DISTRO="debian"
PKG_BSDTAR="libarchive-tools" PKG_BSDTAR="libarchive-tools"
PKG_DOCKER="docker.io" PKG_DOCKER="docker.io"
PKG_PODMAN="podman"
elif command -v pacman >/dev/null 2>&1; then elif command -v pacman >/dev/null 2>&1; then
DISTRO="arch" DISTRO="arch"
PKG_BSDTAR="libarchive" PKG_BSDTAR="libarchive"
PKG_DOCKER="docker" PKG_DOCKER="docker"
PKG_PODMAN="podman"
else else
print_error "Unsupported distribution — need apt-get or pacman" print_error "Unsupported distribution — need apt-get or pacman"
exit 1 exit 1
@@ -72,18 +76,36 @@ fi
print_info "Distribution: $DISTRO" 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 if [[ "$CHECK_ONLY" != true && $EUID -ne 0 ]]; then
print_error "Run as root (installing packages and systemd units)" print_error "Run as root (installing packages and systemd units)"
exit 1 exit 1
fi fi
# Docker and the release timers are both systemd units. Say so plainly rather # The release timers are systemd units. Say so plainly rather than failing
# than failing later on a missing command — a container is the usual way to end # later on a missing command — a container is the usual way to end up here,
# up here, and it cannot be a repository host. # and it cannot be a repository host.
if [[ "$CHECK_ONLY" != true ]] && ! command -v systemctl >/dev/null 2>&1; then if [[ "$CHECK_ONLY" != true ]] && ! command -v systemctl >/dev/null 2>&1; then
print_error "systemctl not found — the repository host must run systemd" print_error "systemctl not found — the repository host must run systemd"
echo "" 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." echo "container; run setup on the host itself."
exit 1 exit 1
fi fi
@@ -139,24 +161,27 @@ else
fi fi
echo "" 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 # version from Docker's own repository rather than the distribution's, and
# replacing that underneath a working builder would be a poor trade for # replacing that underneath a working builder would be a poor trade for
# tidiness. # tidiness.
print_info "Checking Docker..." print_info "Checking $ENGINE_NAME..."
if command -v docker >/dev/null 2>&1; then if command -v "$CONTAINER_ENGINE" >/dev/null 2>&1; then
print_step "docker present: $(docker --version 2>/dev/null | head -1)" print_step "$CONTAINER_ENGINE present: $("$CONTAINER_ENGINE" --version 2>/dev/null | head -1)"
if docker info >/dev/null 2>&1; then if "$CONTAINER_ENGINE" info >/dev/null 2>&1; then
print_success "Docker is installed and running — leaving it alone" print_success "$ENGINE_NAME is installed and available — leaving it alone"
elif [[ "$CHECK_ONLY" == true ]]; then 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 else
print_info "Docker is installed but not running — starting it" print_info "Docker is installed but not running — starting it"
systemctl enable --now docker.service 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" print_success "Docker started"
else else
print_error "Docker is installed but still not responding" print_error "Docker is installed but still not responding"
@@ -165,16 +190,24 @@ if command -v docker >/dev/null 2>&1; then
fi fi
fi fi
elif [[ "$CHECK_ONLY" == true ]]; then elif [[ "$CHECK_ONLY" == true ]]; then
print_warning "Would install $PKG_DOCKER and enable it" if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
else print_warning "Would install $PKG_ENGINE and enable it"
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"
else else
print_error "Docker installed but not responding" print_warning "Would install $PKG_ENGINE"
echo " Check 'systemctl status docker' — builds cannot run without it." 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 exit 1
fi fi
fi fi
@@ -237,6 +270,10 @@ echo ""
# --- release timers ---------------------------------------------------------- # --- 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) TIMERS=(omarchy-check-versions omarchy-auto-release-edge omarchy-auto-release-rc omarchy-auto-release-stable)
if [[ "$SKIP_TIMERS" == true ]]; then if [[ "$SKIP_TIMERS" == true ]]; then
+16 -9
View File
@@ -52,8 +52,8 @@ if [[ ! -d "$BUILD_OUTPUT_DIR" ]]; then
exit 1 exit 1
fi fi
# Check Docker is available # Check the selected container engine is available
check_docker check_engine
# Check GPG credentials are in environment # Check GPG credentials are in environment
if [[ -z "$GPG_PRIVATE_KEY" ]]; then if [[ -z "$GPG_PRIVATE_KEY" ]]; then
@@ -66,23 +66,30 @@ if [[ -z "$GPG_PASSPHRASE" ]]; then
exit 1 exit 1
fi fi
# Build/update the Docker image (always use x86_64 for signing - it's architecture independent) # Signing is architecture-independent, so run its utility container natively
build_docker_image "$BUILD_DIR" "x86_64" "$MIRROR" # 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..." print_info "Running package signing..."
# Ensure output directory is writable by container user # Rootless Podman uses keep-id and leaves host ownership/modes intact.
make_dir_writable "$BUILD_OUTPUT_DIR" if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$BUILD_OUTPUT_DIR"
fi
# Run the signing script in Docker (always use x86_64 image) # Run the signing script in the host-native image.
docker run --rm --platform linux/amd64 \ "$CONTAINER_ENGINE" run "${CONTAINER_RUN_ARGS[@]}" --rm "$(get_platform_arg "$TOOL_ARCH")" \
-e ARCH="$ARCH" \ -e ARCH="$ARCH" \
-e MIRROR="$MIRROR" \ -e MIRROR="$MIRROR" \
-e GPG_PRIVATE_KEY="$GPG_PRIVATE_KEY" \ -e GPG_PRIVATE_KEY="$GPG_PRIVATE_KEY" \
-e GPG_PASSPHRASE="$GPG_PASSPHRASE" \ -e GPG_PASSPHRASE="$GPG_PASSPHRASE" \
-v "$BUILD_ROOT/build-output:/build-output" \ -v "$BUILD_ROOT/build-output:/build-output" \
-v "$BUILD_DIR:/build:ro" \ -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=$? 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. # rebuilding for it would ship a package built against the wrong ABI.
OFFICIAL_REPOS=" core extra multilib core-debug extra-debug " OFFICIAL_REPOS=" core extra multilib core-debug extra-debug "
# The published repository, used as the floor a bumped pkgrel has to clear. # The published repositories are the floor a bumped pkgrel has to clear.
# Only x86_64 is published today; aarch64 has no repository to compare against.
PUBLISHED_BASE_URL="${OMARCHY_PUBLISHED_BASE_URL:-https://pkgs.omarchy.org}" PUBLISHED_BASE_URL="${OMARCHY_PUBLISHED_BASE_URL:-https://pkgs.omarchy.org}"
PUBLISHED_MIRRORS=(edge stable) 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() { usage() {
cat <<EOF 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"] } { "source": "aur", "sync": false, "rebuild_on": ["qt6-base"] }
The versions the current pkgrel was bumped for are recorded alongside, in The versions the current pkgrel was bumped for are recorded per published
rebuilt_against, and written by this command: 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 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 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 # Update every package that declares rebuild_on
$0 quickshell-git # Update specific packages $0 quickshell-git # Update specific packages
Trigger versions are read from the local pacman database, so sync it first x86_64 trigger versions come from the local pacman database; aarch64 versions
(pacman -Sy) or this reports whatever that database last saw. come from the live Arch Linux ARM repository database. Only architectures in
PUBLISHED_ARCHES that the package supports are considered.
EOF EOF
} }
@@ -94,7 +101,7 @@ SPECIFIC_MODE=false
# The version of a trigger package as the build container would resolve it. # 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 # 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. # gets to say which package was left alone instead of the run dying here.
repo_version() { native_repo_version() {
local package="$1" local package="$1"
local info local info
@@ -108,9 +115,59 @@ repo_version() {
' <<<"$info" ' <<<"$info"
} }
load_alarm_repo() {
local repo="$1" db index
index="$TEMP_DIR/alarm-$repo.index"
[[ -f "$index" ]] && return 0
db="$TEMP_DIR/alarm-$repo.db"
if ! curl -fsSL --max-time 120 -o "$db" "$ALARM_BASE_URL/$repo/$repo.db" 2>/dev/null; then
print_error "Could not read the aarch64 $repo repository database"
return 1
fi
if ! tar -tf "$db" >/dev/null 2>&1; then
print_error "Unreadable aarch64 $repo repository database"
return 1
fi
tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk '
function emit() {
if (name != "" && version != "") {
print name "\t" version
if (base != "" && base != name) print base "\t" version
}
name=""; base=""; version=""
}
$0 == "%FILENAME%" { emit(); next }
$0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next }
$0 == "%BASE%" { getline; base=$0; next }
$0 == "%VERSION%" { getline; version=$0; next }
END { emit() }
' >"$index"
}
alarm_repo_version() {
local package="$1" repo version
for repo in "${ALARM_REPOS[@]}"; do
load_alarm_repo "$repo" || return 1
version=$(awk -F '\t' -v package="$package" '$1 == package { print $2; exit }' "$TEMP_DIR/alarm-$repo.index")
if [[ -n "$version" ]]; then
echo "$version"
return 0
fi
done
}
repo_version() { # repo_version <arch> <package>
case "$1" in
x86_64) native_repo_version "$2" ;;
aarch64) alarm_repo_version "$2" ;;
*) return 1 ;;
esac
}
declare -A PUBLISHED_VERSION=() declare -A PUBLISHED_VERSION=()
PUBLISHED_LOADED=false PUBLISHED_LOADED=false
PUBLISHED_AVAILABLE=true
remember_published() { remember_published() {
local name="$1" local name="$1"
@@ -131,34 +188,35 @@ load_published_versions() {
[[ "$PUBLISHED_LOADED" == true ]] && return 0 [[ "$PUBLISHED_LOADED" == true ]] && return 0
PUBLISHED_LOADED=true PUBLISHED_LOADED=true
local mirror db name base version local arch mirror db name base version
for mirror in "${PUBLISHED_MIRRORS[@]}"; do for arch in $(published_arches); do
db="$TEMP_DIR/published-$mirror.db.tar.zst" for mirror in "${PUBLISHED_MIRRORS[@]}"; do
db="$TEMP_DIR/published-$mirror-$arch.db.tar.zst"
if ! curl -fsSL --max-time 120 -o "$db" \ if ! curl -fsSL --max-time 120 -o "$db" \
"$PUBLISHED_BASE_URL/$mirror/$PUBLISHED_ARCH/omarchy.db.tar.zst" 2>/dev/null; then "$PUBLISHED_BASE_URL/$mirror/$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" print_warning "Could not read the published $mirror/$arch database; bumps are not checked against it this run"
PUBLISHED_AVAILABLE=false continue
continue fi
fi
while IFS=$'\t' read -r name base version; do while IFS=$'\t' read -r name base version; do
[[ -n "$name" && -n "$version" ]] && remember_published "$name" "$version" [[ -n "$name" && -n "$version" ]] && remember_published "$name" "$version"
[[ -n "$base" && -n "$version" ]] && remember_published "$base" "$version" [[ -n "$base" && -n "$version" ]] && remember_published "$base" "$version"
done < <( done < <(
tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk ' tar -xOf "$db" --wildcards '*/desc' 2>/dev/null | awk '
function emit() { function emit() {
if (name != "" && version != "") print name "\t" base "\t" version if (name != "" && version != "") print name "\t" base "\t" version
name=""; base=""; version="" name=""; base=""; version=""
} }
$0 == "%FILENAME%" { emit(); next } $0 == "%FILENAME%" { emit(); next }
$0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next } $0 == "%NAME%" { if (name != "" && version != "") emit(); getline; name=$0; next }
$0 == "%BASE%" { getline; base=$0; next } $0 == "%BASE%" { getline; base=$0; next }
$0 == "%VERSION%" { getline; version=$0; next } $0 == "%VERSION%" { getline; version=$0; next }
END { emit() } END { emit() }
' '
) )
done
done done
} }
@@ -179,7 +237,7 @@ pkgbuild_field() {
local package_dir="$1" local package_dir="$1"
local field="$2" 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 # 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 package_dir="$1"
local current="$2" 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 # 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[*]}..." print_info "Checking $package against ${triggers[*]}..."
local current="{}" trigger version local current="{}" arch arch_current trigger version considered=0
for trigger in "${triggers[@]}"; do for arch in $(published_arches); do
version=$(repo_version "$trigger") package_supports_arch "$package_dir" "$arch" || continue
if [[ -z "$version" ]]; then considered=$((considered + 1))
print_error " $trigger is in no official repository; leaving $package alone" arch_current="{}"
((++FAILED)) for trigger in "${triggers[@]}"; do
return 0 if ! version=$(repo_version "$arch" "$trigger"); then
fi print_error " Could not read $arch repository versions; leaving $package alone"
if ! current=$(jq -c --arg name "$trigger" --arg version "$version" '.[$name] = $version' <<<"$current"); then ((++FAILED))
print_error " Could not record $trigger $version for $package" 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)) ((++FAILED))
return 0 return 0
fi fi
done done
if ((considered == 0)); then
print_info " Skipping: not built for any published architecture"
((++SKIPPED))
return 0
fi
local recorded local recorded
if ! recorded=$(package_metadata_value "$package_dir" '.rebuilt_against' ""); then if ! recorded=$(package_metadata_value "$package_dir" '.rebuilt_against' ""); then
print_error " Could not read .omarchy/package.json for $package" print_error " Could not read .omarchy/package.json for $package"
@@ -323,13 +411,20 @@ sync_package() {
fi fi
[[ -n "$recorded" && "$recorded" != "null" ]] || recorded="{}" [[ -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 # Walk the declared triggers rather than the record, so a name the record does
# not carry reads as changed instead of going unexamined forever. # not carry reads as changed instead of going unexamined forever.
local moved local moved
if ! moved=$(jq -r --argjson recorded "$recorded" ' if ! moved=$(jq -r --argjson recorded "$recorded" '
to_entries [to_entries[] as $arch
| map(select($recorded[.key] != .value) | $arch.value | to_entries[] as $trigger
| "\(.key) \($recorded[.key] // "unrecorded") -> \(.value)") | select($recorded[$arch.key][$trigger.key] != $trigger.value)
| "\($arch.key)/\($trigger.key) \($recorded[$arch.key][$trigger.key] // "unrecorded") -> \($trigger.value)"]
| join(", ") | join(", ")
' <<<"$current"); then ' <<<"$current"); then
print_error " Could not compare recorded trigger versions for $package" print_error " Could not compare recorded trigger versions for $package"
@@ -338,7 +433,7 @@ sync_package() {
fi fi
if [[ -z "$moved" ]]; then 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)) ((++SKIPPED))
return 0 return 0
fi fi
@@ -466,11 +561,11 @@ selftest_root() {
} }
selftest_package() { 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" local dir="$root/pkgbuilds/$name"
mkdir -p "$dir/.omarchy" 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" printf '%s\n' "$metadata" > "$dir/.omarchy/package.json"
} }
@@ -514,19 +609,51 @@ selftest_published() {
cat > "$root/stub/curl" <<'STUB' cat > "$root/stub/curl" <<'STUB'
#!/bin/bash #!/bin/bash
out="" out=""
url=""
while [[ $# -gt 0 ]]; do while [[ $# -gt 0 ]]; do
case "$1" in case "$1" in
-o) out="$2"; shift 2 ;; -o) out="$2"; shift 2 ;;
*) shift ;; *) url="$1"; shift ;;
esac esac
done 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 [[ -f "$db" && -n "$out" ]] || exit 22
cp "$db" "$out" cp "$db" "$out"
STUB STUB
chmod +x "$root/stub/curl" 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() { cmd_self_test() {
local failures=0 local failures=0
local root local root
@@ -545,7 +672,8 @@ cmd_self_test() {
local root="$1" local root="$1"
shift shift
local status=0 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" echo "$status"
} }
@@ -563,7 +691,7 @@ cmd_self_test() {
check "run succeeds" 0 "$(run_case "$root")" check "run succeeds" 0 "$(run_case "$root")"
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-partial")" check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-partial")"
check "unrecorded trigger now recorded" "2-2" \ 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:" echo "Opting a package in buys a rebuild rather than a bare record:"
root=$(selftest_root fresh) root=$(selftest_root fresh)
@@ -573,7 +701,7 @@ cmd_self_test() {
check "run succeeds" 0 "$(run_case "$root")" check "run succeeds" 0 "$(run_case "$root")"
check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-fresh")" check "pkgrel bumped" 2 "$(pkgrel_of "$root/pkgbuilds/t-fresh")"
check "trigger recorded" "1-1" \ 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:" echo "An unchanged package is left alone:"
root=$(selftest_root current) root=$(selftest_root current)
@@ -612,6 +740,67 @@ cmd_self_test() {
check "suffix recorded for the next AUR sync" 1 \ check "suffix recorded for the next AUR sync" 1 \
"$(jq -r '.pkgrel.suffix' "$root/pkgbuilds/t-aur/.omarchy/package.json")" "$(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 "" echo ""
if [[ "$failures" -eq 0 ]]; then if [[ "$failures" -eq 0 ]]; then
print_success "Self-test passed" print_success "Self-test passed"
@@ -626,9 +815,9 @@ if [[ "$SELF_TEST" == true ]]; then
exit $? exit $?
fi 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 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 exit 1
fi fi
done 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. 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 Packages opt in declaratively through "upstream" in .omarchy/package.json.
opts in declaratively, via "upstream" in .omarchy/package.json (see Providers cover GitHub Releases with checksum manifests or API asset digests,
helpers/upstream-github.sh for the schema); no code needed. Anything with a semver-shaped git tags, npm dist-tags, and plain Debian Packages indexes. See
bespoke feed provides pkgbuilds/<package>/.omarchy/upstream.sh instead, a hook README.md for the schemas. Anything outside those conventions may provide
that reports the newest upstream release as JSON on stdout: pkgbuilds/<package>/.omarchy/upstream.sh, a hook that reports JSON on stdout:
{ {
"pkgver": "1.2.3", "pkgver": "1.2.3",
@@ -332,27 +332,27 @@ sync_package() {
return 0 return 0
fi fi
local github_repo has_upstream=false local provider has_upstream=false
github_repo=$(package_upstream_github_repo "$package_dir") provider=$(package_upstream_provider "$package_dir")
if package_has_upstream_provider "$package_dir"; then if package_has_upstream_provider "$package_dir"; then
has_upstream=true has_upstream=true
fi fi
# A present-but-unusable declaration fails loudly; treating it like "no # A present-but-unusable declaration fails loudly; treating it like "no
# upstream source" would silently drop the package from scheduled runs. # upstream source" would silently drop the package from scheduled runs.
if [[ "$has_upstream" == true && -z "$github_repo" ]]; then if [[ "$has_upstream" == true && -z "$provider" ]]; then
print_error "Package $package has an unusable upstream declaration (needs a github owner/repo)" print_error "Package $package has an unusable or ambiguous upstream declaration"
((++FAILED)) ((++FAILED))
return 0 return 0
fi fi
if [[ -n "$github_repo" && -f "$hook" ]]; then if [[ -n "$provider" && -f "$hook" ]]; then
print_error "Package $package declares both upstream.github and an upstream.sh hook; keep exactly one" print_error "Package $package declares both an upstream provider and an upstream.sh hook; keep exactly one"
((++FAILED)) ((++FAILED))
return 0 return 0
fi fi
if [[ -z "$github_repo" && ! -f "$hook" ]]; then if [[ -z "$provider" && ! -f "$hook" ]]; then
if [[ "$SPECIFIC_MODE" == true ]]; then if [[ "$SPECIFIC_MODE" == true ]]; then
print_error "Package $package has neither an upstream declaration nor .omarchy/upstream.sh" print_error "Package $package has neither an upstream declaration nor .omarchy/upstream.sh"
((++FAILED)) ((++FAILED))
@@ -372,10 +372,16 @@ sync_package() {
print_info "Checking $package for upstream releases..." print_info "Checking $package for upstream releases..."
local release local release release_status=0
if [[ -n "$github_repo" ]]; then if [[ -n "$provider" ]]; then
if ! release=$(github_upstream_release "$package_dir" "$min_age"); then case "$provider" in
print_error "GitHub release provider failed for $package" 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)) ((++FAILED))
return 0 return 0
fi fi
@@ -461,8 +467,8 @@ sync_package() {
# paths. Covers release selection (fallback past quarantined releases, # paths. Covers release selection (fallback past quarantined releases,
# draft/prerelease filtering, bypass, unchanged version), failure paths # draft/prerelease filtering, bypass, unchanged version), failure paths
# (unusable tags/timestamps, missing checksums), checksum template mapping # (unusable tags/timestamps, missing checksums), checksum template mapping
# for both architectures, the min_release_age backstop, the duration parser, # for both architectures, release API digests, the min_release_age backstop,
# and manifest validation. # the duration parser, and manifest validation.
cmd_self_test() { cmd_self_test() {
local failures=0 local failures=0
@@ -539,6 +545,31 @@ EOF
check "bypass lifts the quarantine" "2.0.0" "$(jq -r '.pkgver // "<none>"' <<<"$out")" 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")" 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>" out=$(github_upstream_release "$pkg" 8640000 2>/dev/null) || out="<error>"
check "everything quarantined reports no update" "{}" "$(jq -c . <<<"$out")" 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=$? rc=0; github_upstream_release "$pkg" 86400 >/dev/null 2>&1 || rc=$?
check "missing aarch64 checksum fails the sync" "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:" echo "Quarantine backstop:"
local rel st local rel st
rel=$(jq -n --arg p "$old2d" '{pkgver: "1.9.0", published_at: $p, sha256sums: {}}') 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" echo '{"source": "local", "upstream": {"github": "example/tool"}}' > "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "upstream without checksums/assets is rejected" "1" "$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" cp "$pkg/.omarchy/package.json" "$agepkg/.omarchy/package.json"
vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$? vst=0; validate_package_metadata "$agepkg" >/dev/null || vst=$?
check "the real declaration shape is accepted" "0" "$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 # End to end over the real mise-bin package: its checked-in metadata and
# PKGBUILD, the full sync_package path (selection, validation, backstop, # PKGBUILD, the full sync_package path (selection, validation, backstop,
# rewrite, read-back verification), with only the two network fetches # rewrite, read-back verification), with only the two network fetches
+38 -32
View File
@@ -84,15 +84,18 @@ echo ""
# --- queued work ------------------------------------------------------------- # --- 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 queued=false
for channel in edge rc stable; do for channel in edge rc stable; do
state_file="$STATE_DIR/.sync-needed-$channel" for arch in $(published_arches); do
if [[ -f "$state_file" ]]; then state_file=$(sync_queue_file "$channel" "$arch")
queued=true [[ "$arch" == "x86_64" && ! -f "$state_file" ]] && state_file=$(legacy_sync_queue_file "$channel")
since=$(date -r "$state_file" '+%Y-%m-%d %H:%M:%S' 2>/dev/null || echo "unknown") if [[ -f "$state_file" ]]; then
printf ' • %-7s queued since %s\n' "$channel" "$since" queued=true
fi 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 done
[[ "$queued" == false ]] && echo " (nothing queued — all channels up to date)" [[ "$queued" == false ]] && echo " (nothing queued — all channels up to date)"
echo "" echo ""
@@ -101,34 +104,37 @@ echo ""
# say so plainly: it is queued but deliberately not being retried yet. # say so plainly: it is queued but deliberately not being retried yet.
paused=false paused=false
for channel in edge rc stable; do for channel in edge rc stable; do
fail_file="$STATE_DIR/.build-failed-$channel" for arch in $(published_arches); do
[[ -f "$fail_file" ]] || continue fail_file=$(sync_fail_file "$channel" "$arch")
if [[ "$paused" == false ]]; then [[ "$arch" == "x86_64" && ! -f "$fail_file" ]] && fail_file=$(legacy_sync_fail_file "$channel")
print_error "Failing builds (backoff active)" [[ -f "$fail_file" ]] || continue
paused=true if [[ "$paused" == false ]]; then
fi print_error "Failing builds (backoff active)"
FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT="" paused=true
# shellcheck disable=SC1090 fi
source "$fail_file" 2>/dev/null || true FAILURE_COUNT=0 FAILURE_AT=0 FAILURE_FINGERPRINT=""
delay=600 # shellcheck disable=SC1090
for ((i = 1; i < FAILURE_COUNT; i++)); do source "$fail_file" 2>/dev/null || true
delay=$((delay * 2)) delay=600
((delay >= 21600)) && { delay=21600; break; } 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 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 done
if [[ "$paused" == true ]]; then 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 "" echo ""
fi 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/paths.sh"
source "$BUILD_ROOT/helpers/lock-helpers.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() { update_database() {
print_info "Updating repository database in Docker container..." print_info "Updating repository database in a container..."
# Check Docker is available check_engine
check_docker
# Ensure output directory exists # Ensure output directory exists
if [[ ! -d "$REPO_DIR" ]]; then if [[ ! -d "$REPO_DIR" ]]; then
@@ -25,20 +24,25 @@ update_database() {
exit 1 exit 1
fi fi
# Make output directory writable for container if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
make_dir_writable "$REPO_DIR" make_dir_writable "$REPO_DIR"
fi
# repo-add is architecture- and mirror-independent, so always use the edge # repo-add is architecture- and mirror-independent. Use the host-native edge
# x86_64 image. This also lets bootstrap-rc build the rc database before the # image, which also lets bootstrap-rc run before that channel exists remotely.
# rc channel exists remotely (an rc image can only build after it does). local tool_arch
build_docker_image "$BUILD_DIR" "x86_64" "edge" 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 ARCH="$ARCH" \
-e MIRROR="$MIRROR" \ -e MIRROR="$MIRROR" \
-v "$REPO_ROOT:/output" \ -v "$REPO_ROOT:/output" \
-v "$BUILD_DIR:/build:ro" \ -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 # 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; \ printf 'Server = https://mirror.omarchy.org/$repo/os/$arch\n' > /etc/pacman.d/mirrorlist; \
fi; \ fi; \
else \ else \
curl -L "https://raw.githubusercontent.com/archlinuxarm/PKGBUILDs/master/core/pacman-mirrorlist/mirrorlist" 2>/dev/null | \ printf 'Server = https://fl.us.mirror.archlinuxarm.org/aarch64/$repo\n' > /etc/pacman.d/mirrorlist; \
sed -E 's/^\s*#\s*Server\s*=/Server =/g' > /etc/pacman.d/mirrorlist && \
sed -i 's/\$arch/aarch64/g' /etc/pacman.d/mirrorlist; \
fi fi
# Bootstrap keyrings (required before pacstrap can verify packages) # 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) # Setup Omarchy keyring manually before adding repo (avoids keyserver trust issues)
# Note: Repository is removed at the end since build scripts add it dynamically. # 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 # 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 ARG MIRROR=edge
RUN pacman-key --recv-keys 40DFB630FF42BCFFB047046CF0134EE680CAC571 --keyserver keys.openpgp.org && \ RUN pacman-key --recv-keys 40DFB630FF42BCFFB047046CF0134EE680CAC571 --keyserver keys.openpgp.org && \
pacman-key --lsign-key 40DFB630FF42BCFFB047046CF0134EE680CAC571 && \ 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 -Sy --noconfirm && \
pacman -S --noconfirm omarchy-keyring && \ pacman -S --noconfirm omarchy-keyring && \
pacman-key --populate omarchy && \ pacman-key --populate omarchy && \
@@ -137,8 +140,13 @@ RUN useradd -m -G wheel -s /bin/bash builder && \
chmod 700 /home/builder/.gnupg && \ chmod 700 /home/builder/.gnupg && \
chown -R builder:builder /home/builder 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 && \ 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). # Pacman wrapper that auto-resolves package conflicts (e.g. rustup vs rust).
# makepkg only passes --noconfirm to pacman, which defaults to 'N' on conflict # makepkg only passes --noconfirm to pacman, which defaults to 'N' on conflict
+146 -57
View File
@@ -5,6 +5,10 @@
# Setup directories # Setup directories
ARCH=${ARCH:-x86_64} 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} MIRROR=${MIRROR:-edge}
DRY_RUN=${DRY_RUN:-false} DRY_RUN=${DRY_RUN:-false}
PKGBUILDS_DIR=${PKGBUILDS_DIR:-/pkgbuilds} 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} FINAL_OUTPUT_DIR=${FINAL_OUTPUT_DIR:-/pkgs.omarchy.org/$MIRROR/$ARCH}
HELPERS_DIR=${HELPERS_DIR:-/helpers} HELPERS_DIR=${HELPERS_DIR:-/helpers}
SRC_DIR=${SRC_DIR:-/src} 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" 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 if [[ "$DRY_RUN" != true ]]; then
# Import GPG keys # Import GPG keys
/build/import-gpg-keys.sh || exit 1 /build/import-gpg-keys.sh || exit 1
@@ -32,14 +64,11 @@ if [[ "$DRY_RUN" != true ]]; then
# Configure Omarchy repositories for dependency resolution # Configure Omarchy repositories for dependency resolution
echo "==> Configuring Omarchy repositories for dependency resolution..." echo "==> Configuring Omarchy repositories for dependency resolution..."
# Always add omarchy-build repo (for incremental builds) # Always add omarchy-build repo first (for incremental builds). Repository
# Packages in build-output are unsigned, so use SigLevel = Never # order is pacman's priority order, so this must precede the official repos;
sudo tee -a /etc/pacman.conf > /dev/null <<EOF # otherwise pacman can select an older official package with the same name.
# Packages in build-output are unsigned, so use SigLevel = Never.
[omarchy-build] sudo sed -i "/^\[core\]$/i [omarchy-build]\nSigLevel = Never\nServer = file://$BUILD_OUTPUT_DIR\n" /etc/pacman.conf
SigLevel = Never
Server = file://$BUILD_OUTPUT_DIR
EOF
echo " -> omarchy-build (priority 1): $BUILD_OUTPUT_DIR" echo " -> omarchy-build (priority 1): $BUILD_OUTPUT_DIR"
# Initialize empty build database if it doesn't exist # Initialize empty build database if it doesn't exist
@@ -48,23 +77,21 @@ EOF
# Create an empty database # Create an empty database
repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 repo-add omarchy-build.db.tar.zst >/dev/null 2>&1
ln -sf omarchy-build.db.tar.zst omarchy-build.db ln -sf omarchy-build.db.tar.zst omarchy-build.db
else fi
# Database exists, check if we need to rebuild it from packages # Fold any packages already in the workspace into the database, whether
if ls *.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | grep -q .; then # they came with an existing database or were dropped in by an earlier
echo "==> Rebuilding build database from existing packages..." # workflow job (OMARCHY_KEEP_BUILD_WORKSPACE). Without this a seeded
ls *.pkg.tar.* | grep -v '\.sig$' | grep -v 'omarchy-build\.db' | xargs -r repo-add omarchy-build.db.tar.zst >/dev/null 2>&1 # workspace with no database would leave those packages invisible to
ln -sf omarchy-build.db.tar.zst omarchy-build.db # dependency resolution.
fi 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
# Add omarchy repo if it has a database (stable packages) # 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 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 sudo sed -i "/^\[core\]$/i [omarchy]\nSigLevel = Optional TrustAll\nServer = file://$FINAL_OUTPUT_DIR\n" /etc/pacman.conf
[omarchy]
SigLevel = Optional TrustAll
Server = file://$FINAL_OUTPUT_DIR
EOF
echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR" echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR"
fi fi
@@ -155,26 +182,9 @@ get_local_version() {
# Returns 0 (success) if should build, 1 if should skip # Returns 0 (success) if should build, 1 if should skip
should_build_for_arch() { should_build_for_arch() {
local pkg="$1" local pkg="$1"
local current_arch="$ARCH" local pkgdir
local pkgdir=$(find_package_dir "$pkg") pkgdir=$(find_package_dir "$pkg")
local pkgbuild="$pkgdir/PKGBUILD" [[ -n "$pkgdir" ]] && package_supports_arch "$pkgdir" "$ARCH"
[[ ! -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
} }
# For VCS packages, makepkg recalculates pkgver() before the build. If the # For VCS packages, makepkg recalculates pkgver() before the build. If the
@@ -224,6 +234,31 @@ refresh_vcs_pkgver_preserving_local_pkgrel() {
fi 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 a package
build_package() { build_package() {
local pkg="$1" local pkg="$1"
@@ -280,21 +315,64 @@ build_package() {
# Build package without signing (signing is done separately) # Build package without signing (signing is done separately)
# PACMAN override uses a wrapper that adds --ask 4 to auto-resolve conflicts # 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 # (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 # Ensure output directory exists
mkdir -p "$BUILD_OUTPUT_DIR" mkdir -p "$BUILD_OUTPUT_DIR"
for pkg_file in *.pkg.tar.*; do # Copy only the artifacts makepkg declares as outputs. A PKGBUILD may use
[[ -f "$pkg_file" ]] && cp "$pkg_file" "$BUILD_OUTPUT_DIR/" # 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 done
cd "$BUILD_OUTPUT_DIR" cd "$BUILD_OUTPUT_DIR"
# Find ALL package files (handles split packages) # Add every output from this build, including split packages.
local new_pkgs=($(ls -t ${pkg}-*.pkg.tar.* 2>/dev/null | grep -v '\.sig$' | grep -v 'omarchy-build\.db'))
if [[ ${#new_pkgs[@]} -gt 0 ]]; then if [[ ${#new_pkgs[@]} -gt 0 ]]; then
repo-add omarchy-build.db.tar.zst "${new_pkgs[@]}" >/dev/null 2>&1 repo-add omarchy-build.db.tar.zst "${new_pkgs[@]}" >/dev/null 2>&1
ln -sf omarchy-build.db.tar.zst omarchy-build.db ln -sf omarchy-build.db.tar.zst omarchy-build.db
@@ -303,6 +381,24 @@ build_package() {
cd /src/$pkg 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" echo " Successfully built $pkg"
SUCCESSFUL_PACKAGES="$SUCCESSFUL_PACKAGES $pkg" SUCCESSFUL_PACKAGES="$SUCCESSFUL_PACKAGES $pkg"
return 0 return 0
@@ -421,7 +517,7 @@ check_needs_build() {
# Collect packages that should be built for the selected mirror # Collect packages that should be built for the selected mirror
collect_packages() { collect_packages() {
packages_for_unscoped_build "$MIRROR" packages_for_unscoped_build "$MIRROR" "$ARCH"
} }
# Main execution # Main execution
@@ -473,13 +569,6 @@ if [[ -n "$PACKAGES" ]]; then
else else
# Build all packages that need updates from the relevant directories # Build all packages that need updates from the relevant directories
while IFS= read -r pkg; do 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 if check_needs_build "$pkg"; then
PACKAGES_TO_BUILD+=("$pkg") PACKAGES_TO_BUILD+=("$pkg")
else 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() { container_engine_supported() {
if ! command -v docker &>/dev/null; then [[ "$CONTAINER_ENGINE" == "docker" || "$CONTAINER_ENGINE" == "podman" ]]
print_error "Docker is not installed" }
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 exit 1
fi fi
if ! docker info &>/dev/null; then if [[ -z "$CONTAINER_ENGINE" ]]; then
print_error "Docker daemon is not running" print_error "No working container engine found (tried Docker, then Podman)"
print_warning "Start Docker with: sudo systemctl start docker" 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 exit 1
fi fi
} }
docker_native_arch() {
case "$(uname -m)" in
x86_64) echo x86_64 ;;
aarch64 | arm64) echo aarch64 ;;
*) return 1 ;;
esac
}
setup_qemu() { setup_qemu() {
# Setup QEMU for building ARM64 packages on x86_64 hosts local target_arch="${1:-aarch64}"
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" 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 exit 1
fi 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() { build_docker_image() {
@@ -39,31 +116,45 @@ build_docker_image() {
;; ;;
esac esac
print_info "Building Docker image for $arch ($platform) using $mirror mirror..." print_info "Building container image for $arch ($platform) using $mirror mirror..."
docker buildx build \ if [[ "$CONTAINER_ENGINE" == "docker" ]]; then
--platform "$platform" \ "$CONTAINER_ENGINE" buildx build \
--build-arg MIRROR="$mirror" \ --platform "$platform" \
--load \ --build-arg MIRROR="$mirror" \
-t "$image_tag" \ --load \
-f "$build_dir/Dockerfile" \ -t "$image_tag" \
"$build_dir" -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() { get_platform_arg() {
local arch="$1" local arch="$1"
case "$arch" in case "$arch" in
x86_64) echo "--platform linux/amd64" ;; x86_64) echo "--platform=linux/amd64" ;;
aarch64) echo "--platform linux/arm64" ;; aarch64) echo "--platform=linux/arm64" ;;
*) echo "" ;; *) return 1 ;;
esac esac
} }
make_dir_writable() { make_dir_writable() {
local dir="$1" local dir="$1"
if [ "$(id -u)" -eq 0 ]; then if (( EUID == 0 )); then
chmod -R 777 "$dir" chmod -R 777 "$dir"
else 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 fi
} }
+116 -11
View File
@@ -15,7 +15,11 @@
# { "source": "local", "channels": ["edge"] } # { "source": "local", "channels": ["edge"] }
# { "source": "local", "channels": ["edge", "rc", "stable"] } # { "source": "local", "channels": ["edge", "rc", "stable"] }
# { "source": "local", "min_release_age": "24h" } # { "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-aur also writes upstream_commit for AUR-backed packages, and
# bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on. # bin/sync-rebuilds writes rebuilt_against for packages declaring rebuild_on.
@@ -140,6 +144,55 @@ package_has_pkgbuild() {
[[ -f "$pkgdir/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 # Channel membership: where a package may be published. Packages without a
# `channels` key are members of every channel (they flow edge -> rc -> stable). # `channels` key are members of every channel (they flow edge -> rc -> stable).
package_has_channels() { package_has_channels() {
@@ -298,9 +351,12 @@ packages_for_mirror() {
packages_for_unscoped_build() { packages_for_unscoped_build() {
local mirror="$1" local mirror="$1"
local arch="${2:-${ARCH:-x86_64}}"
package_dirs | while IFS= read -r pkgdir; do 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" basename "$pkgdir"
fi fi
done done
@@ -446,17 +502,50 @@ validate_package_metadata() {
# `has` rather than `// {}`: jq's // treats false as absent, which would # `has` rather than `// {}`: jq's // treats false as absent, which would
# let "upstream": false slip through as an empty declaration. # let "upstream": false slip through as an empty declaration.
if ! jq -e ' 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 if has("upstream") | not then true
elif (.upstream | type) != "object" then false elif (.upstream | type) != "object" then false
else .upstream | else .upstream |
((.github // "") | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z")) ([has("github"), has("git_tags"), has("npm"), has("debian")] | map(select(.)) | length) == 1
and ((.checksums // "") | type == "string" and length > 0) and if has("github") then
and ((.assets // {}) | type == "object" and length > 0 and (to_entries | all( (.github | type == "string" and test("\\A[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\\z"))
(.key | test("\\A[a-z0-9_]+\\z")) and (.value | type == "string" and length > 0) 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 end
' "$metadata" >/dev/null; then ' "$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 return 1
fi fi
@@ -481,12 +570,28 @@ validate_package_metadata() {
return 1 return 1
fi fi
if ! jq -e '(.rebuilt_against // {}) | type == "object" and (to_entries | all(.value | type == "string" and length > 0))' "$metadata" >/dev/null; then if ! jq -e '
echo "invalid rebuilt_against for $(basename "$pkgdir"): must be an object mapping package names to versions" 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 return 1
fi 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" echo "invalid rebuilt_against for $(basename "$pkgdir"): records a package that rebuild_on does not name"
return 1 return 1
fi fi
+56
View File
@@ -5,6 +5,62 @@
ARCH=${ARCH:-x86_64} ARCH=${ARCH:-x86_64}
MIRROR=${MIRROR:-edge} 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 package channels, in pipeline order: packages move edge -> rc -> stable
VALID_MIRRORS="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 # A package whose upstream ships tagged GitHub releases needs no upstream.sh
# manifest asset needs no upstream.sh hook: the whole feed is data, declared # hook: the whole feed is data, declared in .omarchy/package.json --
# in .omarchy/package.json --
# #
# "upstream": { # "upstream": {
# "github": "jdx/mise", # "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 # {tag} and {pkgver} interpolate into asset names; tags may carry a leading
# "v", which is stripped for pkgver. Drafts and prereleases are ignored. The # "v", which is stripped for pkgver. Drafts and prereleases are ignored. The
# provider emits the same JSON contract as an upstream.sh hook, so # provider emits the same JSON contract as an upstream.sh hook, so
# bin/sync-upstream's validation and min_release_age backstop apply # 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() { # Return the single declarative provider selected by a package. An empty
local pkgdir="$1" # result means either no provider or an invalid/ambiguous declaration; the
# `objects` drops a non-object upstream value (validation rejects those # caller distinguishes those through package_has_upstream_provider().
# separately) instead of erroring the jq pipeline. package_upstream_provider() {
package_metadata_value "$pkgdir" '(.upstream? | objects | .github)' "" 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 # 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" 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 # Emits the newest qualifying release as hook-contract JSON. min_release_age
# is honored during selection (newest release older than the window wins, # 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. # 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. # not silently choose from.
github_upstream_release() { github_upstream_release() {
local package_dir="$1" min_age="${2:-0}" 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") metadata=$(metadata_file_for_dir "$package_dir")
repo=$(jq -r '(.upstream? | objects | .github) // ""' "$metadata") repo=$(jq -r '(.upstream? | objects | .github) // ""' "$metadata")
@@ -59,9 +284,46 @@ github_upstream_release() {
echo "invalid upstream.github repository: '${repo:-<empty>}'" >&2 echo "invalid upstream.github repository: '${repo:-<empty>}'" >&2
return 1 return 1
fi fi
checksums_name=$(jq -r '(.upstream? | objects | .checksums) // ""' "$metadata") # Enforced here as well as in validate_package_metadata: the scheduled sync
if [[ -z "$checksums_name" ]]; then # reaches this provider without running the validator first.
echo "upstream.checksums names the checksum manifest asset and is required" >&2 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 return 1
fi fi
local arches local arches
@@ -77,6 +339,9 @@ github_upstream_release() {
echo "could not fetch the release feed for $repo" >&2 echo "could not fetch the release feed for $repo" >&2
return 1 return 1
fi 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 candidates=0 best_tag="" best_pkgver="" best_published_at=""
local tag published_at pkgver published_epoch local tag published_at pkgver published_epoch
@@ -129,33 +394,57 @@ github_upstream_release() {
return 0 return 0
fi fi
local checksums local checksums=""
if ! checksums=$(github_fetch_checksums "$repo" "$best_tag" "$checksums_name"); then 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 echo "could not fetch $checksums_name for $repo $best_tag" >&2
return 1 return 1
fi fi
local jq_args=(--arg pkgver "$best_pkgver" --arg published_at "$best_published_at") local result
local jq_filter='{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}' result=$(jq -n --arg pkgver "$best_pkgver" --arg published_at "$best_published_at" \
local arch template filename checksum '{pkgver: $pkgver, published_at: $published_at, sha256sums: {}}')
local arch template filename checksum checksum_source sums
for arch in "${arches[@]}"; do for arch in "${arches[@]}"; do
if [[ ! "$arch" =~ ^[a-z0-9_]+$ ]]; then if [[ ! "$arch" =~ ^[a-z0-9_]+$ ]]; then
echo "invalid architecture key in upstream.assets: '$arch'" >&2 echo "invalid architecture key in upstream.assets: '$arch'" >&2
return 1 return 1
fi fi
template=$(jq -r --arg arch "$arch" '.upstream.assets[$arch]' "$metadata") sums='[]'
filename=${template//\{pkgver\}/$best_pkgver} while IFS= read -r template; do
filename=${filename//\{tag\}/$best_tag} filename=${template//\{pkgver\}/$best_pkgver}
# Manifest lines are "<sha256> <name>", with the name sometimes prefixed filename=${filename//\{tag\}/$best_tag}
# "./" (sha256sum of a local path) or "*" (binary-mode marker). if [[ "$use_digests" == "true" ]]; then
checksum=$(awk -v f="$filename" '$2 == f || $2 == "./" f || $2 == "*" f { print $1; exit }' <<<"$checksums") # Only a "sha256:<hex>" digest is stripped to its hex; any other shape
if [[ ! "$checksum" =~ ^[0-9a-f]{64}$ ]]; then # falls through empty and fails the check below.
echo "no valid checksum for $filename in $repo $best_tag $checksums_name" >&2 checksum=$(jq -r --arg tag "$best_tag" --arg name "$filename" '
return 1 first(.[] | select(.tag_name == $tag)) | (.assets // [])[]
fi | select(.name == $name) | (.digest // "")
jq_args+=(--arg "sum_$arch" "$checksum") | if type == "string" and test("\\Asha256:[0-9a-f]{64}\\z") then ltrimstr("sha256:") else "" end
jq_filter+=" | .sha256sums[\"$arch\"] = [\$sum_$arch]" ' <<<"$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 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", "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 pkgname=1password
pkgver=8.12.34
_tarver=8.12.34 pkgrel=36
_tar="1password-${_tarver}.x64.tar.gz"
pkgver=${_tarver//-/_}
pkgrel=34
conflicts=('1password-beta' '1password-beta-bin') conflicts=('1password-beta' '1password-beta-bin')
pkgdesc="Password manager and secure wallet" pkgdesc="Password manager and secure wallet"
arch=('x86_64') arch=('x86_64' 'aarch64')
url='https://1password.com' url='https://1password.com'
license=('LicenseRef-1Password-Proprietary') license=('LicenseRef-1Password-Proprietary')
options=(!strip) options=(!strip)
install="1password.install" install="1password.install"
source=(https://downloads.1password.com/linux/tar/stable/${CARCH}/${_tar}{,.sig}) source_x86_64=(
sha256sums=('297784aa66770b645607a7f04c9ba2c4aebed4f46d21202487f521ba572b7b13' "https://downloads.1password.com/linux/tar/stable/x86_64/1password-${pkgver}.x64.tar.gz"
'ec085bef60de748895d3c51a8208301ba2ac8fb47db99334539ba4bd1d3260d7' "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') validpgpkeys=('3FEF9748469ADBE15DA7CA80AC2D62742012EA22')
package() { package() {
depends=('hicolor-icon-theme' 'libgtk-3.so=0' 'nss' 'xdg-utils') 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 # Go to source directory
cd "1password-${_tarver}.x64" cd "1password-${pkgver}.${_tararch}"
# Install icons # Install icons
resolutions=(32x32 64x64 256x256 512x512) resolutions=(32x32 64x64 256x256 512x512)
@@ -33,6 +51,12 @@ package() {
# Install desktop file # Install desktop file
install -Dm0644 resources/1password.desktop -t "${pkgdir}"/usr/share/applications/ 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. # Fill in policy kit file with a list of (the first 10) human users of the system.
export POLICY_OWNERS 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' ' ')" 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 # Move package contents to /opt/1Password
cd "${srcdir}" cd "${srcdir}"
install -dm0755 "${pkgdir}"/opt install -dm0755 "${pkgdir}"/opt
mv "1password-${_tarver}.x64" "${pkgdir}/opt/1Password" mv "1password-${pkgver}.${_tararch}" "${pkgdir}/opt/1Password"
# Cleanup un-needed files # Cleanup un-needed files
rm "${pkgdir}"/opt/1Password/com.1password.1Password.policy "${pkgdir}"/opt/1Password/com.1password.1Password.policy.tpl "${pkgdir}"/opt/1Password/install_biometrics_policy.sh rm "${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 pkgver=0.6.0
pkgrel=2 pkgrel=2
pkgdesc="Control brightness on Apple Displays connected via USB-C" pkgdesc="Control brightness on Apple Displays connected via USB-C"
arch=('x86_64') arch=('x86_64' 'aarch64')
url="https://github.com/omakasui/asdcontrol" url="https://github.com/omakasui/asdcontrol"
license=('GPL2') license=('GPL2')
options=('!debug') options=('!debug')
+1 -2
View File
@@ -1,4 +1,3 @@
[asusctl] [asusctl]
source = "git" 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", "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 # Contributor: Static_Rocket
pkgbase=asusctl pkgbase=asusctl
pkgname=( pkgname=(asusctl rog-control-center)
asusctl
rog-control-center
)
pkgver=6.4.0 pkgver=6.4.0
pkgrel=1.1 pkgrel=1
pkgdesc="A control daemon, tools, and a collection of crates for interacting with ASUS ROG laptops" pkgdesc="Daemon and tools to control your ASUS ROG laptop"
arch=('x86_64') arch=('x86_64')
url="https://asus-linux.org" url="https://asus-linux.org"
license=('MPL-2.0') license=('MPL-2.0')
makedepends=( makedepends=('cargo' 'fontconfig')
clang source=("${pkgbase}-${pkgver}.tar.gz::https://github.com/OpenGamingCollective/asusctl/archive/${pkgver}.tar.gz")
cmake b2sums=('e90074e904f364386ad661784bd9fc2e929e83ea06ac62fd1d34eb03490cefe8aae3bed2722162f1e8ea5d69248138cb5fd9f90c3a18443d1d8c04cf732b928a')
fontconfig
git
hicolor-icon-theme
libayatana-appindicator
libinput
libusb
rust
seatd
systemd
)
source=("git+https://github.com/opengamingcollective/asusctl.git#tag=$pkgver")
b2sums=('0cfdc7f792fc6499cde9e4c9c6d82e484605e1b2878bb9f3a0e0696f5910b9ba7898f124fecd5fb3f39a0787f81e5163721606290099d757365dc3d6715a1bcf')
prepare() { prepare() {
cd "${pkgbase}" cd "${pkgbase}-${pkgver}"
export RUSTUP_TOOLCHAIN=stable
# Keep rust/cargo build-dependency management inside the build directory cargo fetch --locked --target host-tuple
export CARGO_HOME="${srcdir}/cargo"
# Follow Rust package guidelines
## https://wiki.archlinux.org/title/Rust_package_guidelines
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
} }
build() { build() {
cd "${pkgbase}" cd "${pkgbase}-${pkgver}"
export RUSTUP_TOOLCHAIN=stable
# Keep rust/cargo build-dependency management inside the build directory export CARGO_TARGET_DIR=target
export CARGO_HOME="${srcdir}/cargo" make build
# Follow Rust package guidelines
## https://wiki.archlinux.org/title/Rust_package_guidelines
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
make build
} }
package_asusctl() { package_asusctl() {
pkgdesc="${pkgdesc/tools/CLI tools}" pkgdesc="An utility for Linux to control many aspects of various ASUS laptops"
depends=( depends=('glibc' 'libgcc' 'libusb' 'systemd-libs')
glibc optdepends=(
libgcc 'acpi_call: fan control'
libusb 'asusctltray: tray profile switcher'
systemd 'rog-control-center: graphical user interface for asusctl'
systemd-libs 'supergfxctl: hybrid GPU control'
) )
conflicts=(gnome-shell-extension-asusctl-gnome) install=asusctl.install
install=asusctl.install
optdepends=(
'acpi_call: fan control'
'asusctltray: tray profile switcher'
'rog-control-center: app to control asusctl'
'supergfxctl: hybrid GPU control'
)
cd "${pkgbase}" cd "${pkgbase}-${pkgver}"
export CARGO_HOME="${srcdir}/cargo" make DESTDIR="${pkgdir}" \
make DESTDIR="${pkgdir}" \ install-asusctl \
install-asusctl \ install-asusd \
install-asusd \ install-asusd_user \
install-asusd_user \ install-asus-shutdown \
install-asus-shutdown \ install-data-asusd \
install-data-asusd \ install-data-asusd_user
install-data-asusd_user
} }
package_rog-control-center() { package_rog-control-center() {
depends=( pkgdesc="Graphical user interface for asusctl"
asusctl depends=('asusctl' 'fontconfig' 'glibc' 'hicolor-icon-theme' 'libgcc' 'systemd-libs')
fontconfig
freetype2
glibc
hicolor-icon-theme
libayatana-appindicator
libgcc
libinput
libxkbcommon
mesa
seatd
systemd-libs
)
pkgdesc="App to control asusctl"
cd "${pkgbase}" cd "${pkgbase}-${pkgver}"
export CARGO_HOME="${srcdir}/cargo" make DESTDIR="${pkgdir}" \
make DESTDIR="${pkgdir}" \ install-rog_gui \
install-data-rog_gui \ install-data-rog_gui
install-rog_gui
} }
+13 -14
View File
@@ -1,19 +1,18 @@
post_install() { post_install() {
printf ":: asusd provides a service that is activated by an udev rule on\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 ":: startup. Please reboot the system or run\n"
printf ":: # systemctl start asusd.service\n" printf ":: # systemctl start asusd.service\n"
printf ":: to make it work.\n" printf ":: to make it work.\n"
printf ":: See https://gitlab.com/asus-linux/asusctl#kernel-support.\n" printf ":: See https://github.com/OpenGamingCollective/asusctl#kernel-requirements\n"
printf ":: for latest required kernel patches/versions\n" printf ":: for latest required kernel patches/versions\n"
} }
post_upgrade() { post_upgrade() {
if systemctl is-active asusd.service --quiet if systemctl is-active asusd.service --quiet; then
then printf ":: asusd service will be restarted…\n"
printf ":: asusd service will be restarted…\n" systemctl daemon-reload
systemctl daemon-reload systemctl restart asusd.service
systemctl restart asusd.service fi
fi printf ":: See https://github.com/OpenGamingCollective/asusctl#kernel-requirements\n"
printf ":: See https://gitlab.com/asus-linux/asusctl#kernel-support.\n" printf ":: for latest required kernel patches/versions\n"
printf ":: for latest required kernel patches/versions\n"
} }
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"source": "aur", "source": "aur",
"release_ring": "fast", "release_ring": "fast",
"upstream_commit": "fe2a6345e271f618dc3adf31e48557a717866395" "upstream_commit": "1e7bd48ef192b3d6a2854916e75e14ea43f6f0b7"
} }
+5 -5
View File
@@ -1,6 +1,6 @@
# Maintainer: Basecamp <support@basecamp.com> # Maintainer: Basecamp <support@basecamp.com>
pkgname=basecamp-cli pkgname=basecamp-cli
pkgver=0.9.1 pkgver=0.10.0
pkgrel=1 pkgrel=1
pkgdesc="CLI for Basecamp project management" pkgdesc="CLI for Basecamp project management"
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
@@ -13,10 +13,10 @@ optdepends=(
'zsh: for zsh shell completions' 'zsh: for zsh shell completions'
'fish: for fish 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_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.9.1/basecamp_${pkgver}_linux_arm64.tar.gz") source_aarch64=("https://github.com/basecamp/basecamp-cli/releases/download/v0.10.0/basecamp_${pkgver}_linux_arm64.tar.gz")
sha256sums_x86_64=('bcb1fa3a03e702bbf81a3004ded4bc7808605e41106cbc768ba4006b89e0db2f') sha256sums_x86_64=('ca8b3a085f90323c8ab1694b83ff624cf10c459618ca6010ce204534f3f01987')
sha256sums_aarch64=('ff50313024b6ca14e40146e30e1c9c9c00a4e4103fe55615eeee3e7c359d88d1') sha256sums_aarch64=('dd1b5db88b9b309e95ae1ec0d642d23f4955a3abe159bffe00796a0ef6030c1d')
package() { package() {
install -Dm755 "basecamp" "${pkgdir}/usr/bin/basecamp" install -Dm755 "basecamp" "${pkgdir}/usr/bin/basecamp"
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"source": "aur", "source": "aur",
"release_ring": "fast", "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 # Automation repository: https://github.com/fabifont/claude-code-aur
pkgname=claude-code pkgname=claude-code
pkgver=2.1.252 pkgver=2.1.263
pkgrel=1 pkgrel=1
pkgdesc="An agentic coding tool that lives in your terminal" pkgdesc="An agentic coding tool that lives in your terminal"
arch=('x86_64' 'aarch64') 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") source_aarch64=("claude-${pkgver}-aarch64::https://downloads.claude.ai/claude-code-releases/${pkgver}/linux-arm64/claude")
sha256sums=('SKIP') sha256sums=('SKIP')
sha256sums_x86_64=('a715a45105e593fc9808d035d77781f88480b9897975a9df41837f0c591bd4b3') sha256sums_x86_64=('26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba')
sha256sums_aarch64=('6c0b32eaa936954a0f4d4ad2595f8416af288d9b3cbaf19e3cdd9a95d7c8853e') sha256sums_aarch64=('7d25d7c8ae6c6e009cc7dae4e817f674179fd31fb7761bcd56fee4c2902b4c03')
package() { package() {
install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude" install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude"
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"source": "aur", "source": "aur",
"upstream_commit": "76b9ce02837a49de0fa50cfa3da5d1c0c692ece5" "upstream_commit": "0e012cbfe1ea6cbbf15e2ada9cf93cca7aaa9ca0"
} }
+3 -3
View File
@@ -1,17 +1,17 @@
# Maintainer: bjarneo <https://github.com/bjarneo> # Maintainer: bjarneo <https://github.com/bjarneo>
pkgname=cliamp pkgname=cliamp
pkgver=1.63.2 pkgver=2.0.1
pkgrel=1 pkgrel=1
pkgdesc='A retro terminal music player inspired by Winamp 2.x' pkgdesc='A retro terminal music player inspired by Winamp 2.x'
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
url='https://github.com/bjarneo/cliamp' url='https://github.com/bjarneo/cliamp'
license=('MIT') 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' optdepends=('pipewire-alsa: audio output on PipeWire systems'
'pulseaudio-alsa: audio output on PulseAudio systems') 'pulseaudio-alsa: audio output on PulseAudio systems')
makedepends=('go') makedepends=('go')
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/bjarneo/cliamp/archive/refs/tags/v${pkgver}.tar.gz") source=("${pkgname}-${pkgver}.tar.gz::https://github.com/bjarneo/cliamp/archive/refs/tags/v${pkgver}.tar.gz")
sha256sums=('968ff98c1e49bae8a0ce63acf5c77a9621ef70756a048e3df5e79454b82a9eef') sha256sums=('2c5885665dba5ed2e8dc156bce64751199a92efed7f63959c65e985759b73732')
build() { build() {
cd "${pkgname}-${pkgver}" cd "${pkgname}-${pkgver}"
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"source": "aur", "source": "aur",
"release_ring": "fast", "release_ring": "fast",
"upstream_commit": "00756d82c31b0056800bb98220c83800ebb85e72" "upstream_commit": "c5073d62f60f3973a14340eb1b40fc5b5a4ea9f0"
} }
+4 -4
View File
@@ -1,18 +1,18 @@
# Maintainer: Gunther Schulz <dev@guntherschulz.de> # Maintainer: Gunther Schulz <dev@guntherschulz.de>
pkgname=cursor-bin pkgname=cursor-bin
pkgver=3.18.25 pkgver=3.19.13
pkgrel=1 pkgrel=1
pkgdesc='AI-first coding environment' pkgdesc='AI-first coding environment'
arch=('x86_64') arch=('x86_64')
url="https://www.cursor.com" url="https://www.cursor.com"
license=('LicenseRef-Cursor_EULA') license=('LicenseRef-Cursor_EULA')
# upstream uses Electron newer than internal VSCode # upstream uses Electron newer than internal VSCode
_electron=electron40 _electron=electron42
depends=(xdg-utils ripgrep $_electron nodejs depends=(xdg-utils ripgrep $_electron nodejs
'gcc-libs' 'hicolor-icon-theme' 'libxkbfile') 'gcc-libs' 'hicolor-icon-theme' 'libxkbfile')
options=(!strip !debug) # Don't break ext of VSCode 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" 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} "https://gitlab.archlinux.org/archlinux/packaging/packages/code/-/raw/main/code."{sh,mjs}
rg.sh) rg.sh)
@@ -20,7 +20,7 @@ sha512sums=('SKIP'
'937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37'
'793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033'
'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a') 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a')
sha512sums[0]=5f25c9dccf75c764b6266e74136ad5cf6e99330804c7ae3ed22d4210304791b1ac1233efb07b291da490fbfe3d4801cb46680a171ddf6b2815e0267741ec339a sha512sums[0]=3c952035fa1809caded7059b7addbb01a9143cd248ecff27cdb3cb3af47d4bc824ed60dfd9757e105a342e53280e075d81f775790295a3272818bab5a994b305
noextract=(cursor_${pkgver}_amd64.deb) # avoid double tarball noextract=(cursor_${pkgver}_amd64.deb) # avoid double tarball
_app=usr/share/cursor/resources/app _app=usr/share/cursor/resources/app
package() { 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 pkgname=dropbox-cli
pkgver=2024.04.17 pkgver=2024.04.17
pkgrel=2 pkgrel=2.1
pkgdesc="Command line interface for Dropbox" pkgdesc="Command line interface for Dropbox"
arch=("any") arch=("x86_64")
url="https://www.dropbox.com" url="https://www.dropbox.com"
license=("GPL-3.0-or-later") license=("GPL-3.0-or-later")
makedepends=("gdk-pixbuf2") 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", "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 pkgname=github-copilot-cli
_pkgexec=copilot _pkgexec=copilot
pkgver=1.0.82 pkgver=1.0.83
pkgrel=1 pkgrel=1
pkgdesc="GitHub Copilot CLI brings the power of Copilot coding agent directly to your terminal." 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" url="https://github.com/github/copilot-cli"
_urlraw="https://raw.githubusercontent.com/github/copilot-cli/v${pkgver}" _urlraw="https://raw.githubusercontent.com/github/copilot-cli/v${pkgver}"
arch=("x86_64") arch=("x86_64" "aarch64")
license=("LicenseRef-GitHub-Copilot") license=("LicenseRef-GitHub-Copilot")
@@ -30,8 +30,10 @@ source=("https://registry.npmjs.org/${_npmmodule}/-/copilot-${pkgver}.tgz"
"CHANGELOG-${pkgver}.md::${_urlraw}/changelog.md") "CHANGELOG-${pkgver}.md::${_urlraw}/changelog.md")
noextract=("copilot-${pkgver}.tgz") noextract=("copilot-${pkgver}.tgz")
b2sums=('41c8306b58a142afe162bad9822b150babb0f3903fe7392aa7106fcb0ffce4483c0a2ebda8bb1d2036199deb52dc83d9a0e9408b8e11f7b0bc2a8f57c6841425' sha256sums=(
'5d2dfc870356f83b9b890d351b21bc9339100b0bfdce405ee77751e934dbeab0f8656f5eee85119a06aa3b01dcf3c91224ce28c24ff318eb1b13fdb6082037cd') '135506fc2b13163ab55dbf76a06e2fbcbad04ecac76b4e9c6659f0ba309e6a86'
'0c0064a10effac8adf9ad97338bafaa0d7d7d5bf191cc1c0384e05ff4366d36c'
)
# Document: https://wiki.archlinux.org/title/Node.js_package_guidelines # Document: https://wiki.archlinux.org/title/Node.js_package_guidelines
package() { package() {
@@ -124,4 +126,3 @@ package() {
ln -sf "/usr/lib/node_modules/${_npmmodule}/LICENSE.md" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" 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> # Maintainer: David Heinemeier Hansson <david@hey.com>
# Nous builds Hermes Desktop for macOS and Windows only -- their download page # Build a prebuilt release; Omarchy seeds it into the user's Hermes checkout
# offers a .dmg and an .exe and tells Linux users to install from a terminal -- # so the upstream updater can rebuild and relaunch it in place.
# 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.
pkgname=hermes-desktop pkgname=hermes-desktop
pkgver=2026.8.18 pkgver=2026.8.31
pkgrel=2 pkgrel=3
pkgdesc='Native desktop shell for Hermes Agent' pkgdesc='Native desktop shell for Hermes Agent'
arch=('x86_64') arch=('x86_64')
url='https://github.com/NousResearch/hermes-agent' url='https://github.com/NousResearch/hermes-agent'
@@ -32,7 +18,7 @@ depends=(
'dbus' 'dbus'
'expat' 'expat'
'curl' 'curl'
'gcc-libs' 'gcc'
'gdk-pixbuf2' 'gdk-pixbuf2'
'git' 'git'
'glib2' 'glib2'
@@ -52,19 +38,24 @@ depends=(
'libxfixes' 'libxfixes'
'libxkbcommon' 'libxkbcommon'
'libxrandr' 'libxrandr'
'make'
'mesa' 'mesa'
'nodejs'
'npm'
'nspr' 'nspr'
'nss' 'nss'
'pango' 'pango'
'python'
'systemd-libs' 'systemd-libs'
'util-linux'
'xdg-utils' '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 # The build runs the repo's own npm workspace install, which fetches Electron
# and rebuilds node-pty against it. # and rebuilds node-pty against it.
makedepends=('git' 'imagemagick' 'nodejs' 'npm' 'python') makedepends=('imagemagick')
# Electron bundles prebuilt binaries that stripping corrupts. # Electron bundles prebuilt binaries that stripping corrupts.
options=('!strip' '!debug') options=('!strip' '!debug')
@@ -74,23 +65,27 @@ options=('!strip' '!debug')
# before falling back to `git rev-parse`. That fallback is wrong here: makepkg # 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 # builds inside this repository, so git ascends out of srcdir and stamps the
# app with an omarchy-pkgs commit that means nothing upstream. # app with an omarchy-pkgs commit that means nothing upstream.
_commit=e624e9fde561e1add9388384012b295fde669ade _commit=29112bef099274229cadff79cdff7bf7b99c4b77
_srcdir="hermes-agent-${pkgver}" _srcdir="hermes-agent-${pkgver}"
source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz" source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz"
'hermes-desktop.sh' 'hermes-desktop.sh'
'hermes-desktop.desktop' 'hermes-desktop.desktop'
'hermes-desktop.png') 'hermes-desktop.png'
sha256sums=('1e3d39d3638ec15fa9d31af262568a953e9272090deb1c50c44cd401175f5b80' 'runtime.patch'
'f5833b969ce451aadee9f08d92db55cce3c7c9213175080590bf37444854d676' 'runtime-test.py')
sha256sums=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217'
'094d5f3191109a80eea9f23053b78a2e00dbecf90d62d1ca04c8e48866251469'
'3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4' '3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4'
'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52') 'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52'
'03b67e26c234c797a6b1d4c9f34a57502dba37f462540e47ce6c88f6ea79302a'
'461e1120e7e6779f531c114d9926479e47fab79113d1b4646efea36bc771b3c5')
build() { build() {
cd "${srcdir}/${_srcdir}" cd "${srcdir}/${_srcdir}"
export GITHUB_SHA="${_commit}" 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 # The desktop workspace resolves against the repo root, so the install has to
# happen there rather than in apps/desktop. # happen there rather than in apps/desktop.
@@ -100,6 +95,10 @@ build() {
npm run pack npm run pack
} }
check() {
python "${srcdir}/runtime-test.py" "${srcdir}/${_srcdir}" "${srcdir}/runtime.patch" "${srcdir}/hermes-desktop.sh"
}
package() { package() {
cd "${srcdir}/${_srcdir}/apps/desktop/release/linux-unpacked" cd "${srcdir}/${_srcdir}/apps/desktop/release/linux-unpacked"
@@ -108,6 +107,12 @@ package() {
install -Dm755 "${srcdir}/hermes-desktop.sh" "${pkgdir}/usr/bin/${pkgname}" 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" \ install -Dm644 "${srcdir}/hermes-desktop.desktop" \
"${pkgdir}/usr/share/applications/${pkgname}.desktop" "${pkgdir}/usr/share/applications/${pkgname}.desktop"
@@ -125,11 +130,6 @@ package() {
install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \ install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \
"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt"
# Chromium's setuid sandbox is only needed where unprivileged user namespaces # The launcher requires the namespace sandbox on the target host.
# are unavailable; where they work, setuid root is the worse of the two. chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
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
} }
+63 -30
View File
@@ -1,41 +1,74 @@
#!/bin/bash #!/bin/bash
set -euo pipefail set -euo pipefail
# Hermes Desktop is a shell around a Hermes runtime, and it only works against unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME
# 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.
export HERMES_DESKTOP_IGNORE_EXISTING=1 export HERMES_DESKTOP_IGNORE_EXISTING=1
# Chromium cannot reliably infer the Secret Service password-store backend # Reconcile direct package installs and interrupted Omarchy setup as well.
# 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.
if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then
omarchy-install-hermes-cli >/dev/null 2>&1 || true omarchy-install-hermes-cli >/dev/null 2>&1 || true
fi fi
# Chromium's own Ozone detection falls back to XWayland often enough to matter, hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}")
# and the result is a blurry window on every scaled display. Ask for Wayland parent=${hermes_home%/*}
# directly, unless the user has already picked a platform themselves. if [[ ${parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then
platform_flags=() hermes_home=${parent%/*}
if [[ -n "${WAYLAND_DISPLAY:-}" || ${XDG_SESSION_TYPE:-} == wayland ]]; then hermes_home=${hermes_home:-/}
platform_flags=(--ozone-platform=wayland) fi
export HERMES_HOME="$hermes_home"
for flag in "$@"; do runtime="$hermes_home/hermes-agent"
case "$flag" in native="$runtime/apps/desktop/release/linux-unpacked/Hermes"
--ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;; if [[ ! -x $native || ! -x $runtime/venv/bin/hermes ]]; then
esac native=/opt/hermes-desktop/Hermes
done
fi 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 pkgver=0.2.1
pkgrel=1 pkgrel=1
pkgdesc="An alternative share picker for hyprland with window and monitor previews" 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" url="https://github.com/WhySoBad/hyprland-preview-share-picker"
license=(MIT) license=(MIT)
depends=('gtk4' 'gtk4-layer-shell' 'xdg-desktop-portal-hyprland' 'hyprland') 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> # Maintainer: Mikko Nyman <mikko@nyman.xyz>
# Based on the Arch libfprint package by Jan Alexander Steffens (heftig). # Based on the Arch libfprint package by Jan Alexander Steffens (heftig).
# #
# Carried the focaltech_moc driver and the FocalTech FT9349 device ID # Omarchy's fingerprint setup installs this in place of stock libfprint, so
# (USB 2808:a97a, ASUS ExpertBook Ultra B9406CAA) while mainline libfprint # readers upstream supports ahead of an Arch release reach users by bumping
# releases (1.94.x) lacked them. libfprint 1.94.100 ships both, so Omarchy # the pin here. Pinned to the commit adding the Synaptics reader USB
# has moved back to stock libfprint and no longer installs this package. # 06cb:010b, missing from stock libfprint 1.94.100; the FocalTech MOC support
# # carried by the previous pin is retained.
# Kept around pinned to the last known good commit as an escape hatch;
# the fallback patch stays for the conditional in prepare().
pkgname=libfprint-git pkgname=libfprint-git
pkgver=1.94.10.r12.gd79f157 pkgver=1.94.100.r10.g6f9479c
pkgrel=1.1 pkgrel=1
# The repo already carries an untested 1.94.10.r220.g695663f build that sorts # Retain the epoch used to supersede the previously published untested build.
# above the pinned pkgver; the epoch makes the pinned build win the version
# comparison and gives it a fresh filename that can be promoted.
epoch=1 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/" url="https://fprint.freedesktop.org/"
arch=(x86_64) arch=(x86_64)
license=(LGPL-2.1-or-later) license=(LGPL-2.1-or-later)
@@ -47,7 +43,7 @@ provides=(libfprint libfprint-2.so)
conflicts=(libfprint) conflicts=(libfprint)
groups=(fprint) groups=(fprint)
source=( 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" "focaltech_moc-add-pid-0xA97A.patch"
) )
b2sums=( b2sums=(
@@ -79,9 +75,6 @@ build() {
) )
arch-meson libfprint build "${meson_options[@]}" arch-meson libfprint build "${meson_options[@]}"
meson compile -C build 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() { 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" pkgname="limine-mkinitcpio-hook"
_pkgver=1.38.0 _pkgver=1.38.0
_extver="" _extver=""
_gradle_version=9.7.1
pkgver="${_pkgver}${_extver}" pkgver="${_pkgver}${_extver}"
pkgrel=1 pkgrel=1.1
pkgdesc="Install kernels for the Limine bootloader." pkgdesc="Install kernels for the Limine bootloader."
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
url="https://gitlab.com/Zesko/limine-entry-tool" url="https://gitlab.com/Zesko/limine-entry-tool"
source=("${_pkgname}::git+${url}.git#tag=${pkgver}") 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_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") license=("GPL3")
provides=('limine-entry-tool') provides=('limine-entry-tool')
options=(!debug !strip) options=(!debug !strip)
@@ -27,12 +29,14 @@ optdepends=(
'sbctl: Signs UEFI boot files for Secure Boot when enabled' 'sbctl: Signs UEFI boot files for Secure Boot when enabled'
'journalctl-desktop-notification: Sends desktop notifications when errors occur' 'journalctl-desktop-notification: Sends desktop notifications when errors occur'
) )
makedepends=('git' 'gradle') makedepends=('git')
makedepends_x86_64=('gradle')
backup=(etc/limine-entry-tool.conf) backup=(etc/limine-entry-tool.conf)
conflicts=('limine-entry-tool') conflicts=('limine-entry-tool')
sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01') sha256sums=('11dd8211898585f79d8361d97606965bb2ca214dccace5b133dc5121e4bb4d01')
sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e') sha256sums_x86_64=('3f4a89de8eaa96f2ed677f09957c7e872cd8467aad3537f8b5394c1b8c4b942e')
sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7') sha256sums_aarch64=('22286f7ecd21b9aedb3226b9bf797469e1bd3eefc491e12ef3dd49b452d230b7'
'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a')
prepare() { prepare() {
[[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}"
@@ -48,7 +52,9 @@ build() {
export GRAALVM_HOME="$srcdir/${_graalvm_version}" export GRAALVM_HOME="$srcdir/${_graalvm_version}"
export JAVA_HOME="${GRAALVM_HOME}" export JAVA_HOME="${GRAALVM_HOME}"
export NATIVE_IMAGE_OPTIONS="-march=compatibility --future-defaults=all" 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() { 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 # Maintainer: Zesko
pkgname="limine-snapper-sync" pkgname="limine-snapper-sync"
pkgver=1.31.0 pkgver=1.31.0
pkgrel=1 pkgrel=1.1
_gradle_version=9.7.1
pkgdesc="Integrates Limine boot entries with Snapper snapshots." pkgdesc="Integrates Limine boot entries with Snapper snapshots."
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
url="https://gitlab.com/Zesko/limine-snapper-sync" url="https://gitlab.com/Zesko/limine-snapper-sync"
source=("${pkgname}::git+${url}.git#tag=${pkgver}") 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_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") license=("GPL3")
options=(!debug !strip) options=(!debug !strip)
_graalvm_version=graalvm_ce_jdk25 _graalvm_version=graalvm_ce_jdk25
@@ -25,12 +27,14 @@ optdepends=(
'b3sum: Fast Blake3 hash function to prevent duplication.' 'b3sum: Fast Blake3 hash function to prevent duplication.'
'xxhash: Fast hashing utility for deduplication with shorter hashes.' 'xxhash: Fast hashing utility for deduplication with shorter hashes.'
) )
makedepends=('git' 'gradle') makedepends=('git')
makedepends_x86_64=('gradle')
backup=(etc/limine-snapper-sync.conf) backup=(etc/limine-snapper-sync.conf)
conflicts=('limine-snapper-cli' 'limine-snapper-sync-git') conflicts=('limine-snapper-cli' 'limine-snapper-sync-git')
sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40') sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40')
sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0') sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0')
sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71') sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71'
'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a')
prepare() { prepare() {
[[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}" [[ -d "${_graalvm_version}" ]] && rm -rf "${_graalvm_version}"
@@ -46,7 +50,9 @@ build() {
export GRAALVM_HOME="$srcdir/${_graalvm_version}" export GRAALVM_HOME="$srcdir/${_graalvm_version}"
export JAVA_HOME="${GRAALVM_HOME}" export JAVA_HOME="${GRAALVM_HOME}"
export NATIVE_IMAGE_OPTIONS="-march=compatibility" 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() { package() {
+2 -2
View File
@@ -1,6 +1,6 @@
# Maintainer: Jake Weaver <jakeweaver at hey dot com> # Maintainer: Jake Weaver <jakeweaver at hey dot com>
pkgname=link-studio pkgname=link-studio
pkgver=1.0.2 pkgver=1.0.4
pkgrel=1 pkgrel=1
pkgdesc="Native Linux controller for Insta360 Link webcams" pkgdesc="Native Linux controller for Insta360 Link webcams"
arch=('x86_64') arch=('x86_64')
@@ -28,7 +28,7 @@ optdepends=(
'whisper-cpp: offline meeting transcription' 'whisper-cpp: offline meeting transcription'
) )
source=("${pkgname}-${pkgver}.tar.gz::https://github.com/jweaver60/link-studio/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz") source=("${pkgname}-${pkgver}.tar.gz::https://github.com/jweaver60/link-studio/releases/download/v${pkgver}/${pkgname}-${pkgver}.tar.gz")
sha256sums=('b0fc912d8b7924661b57e427280c9766cbb0f01b7a2b3f3cdb1885969f8cfdc6') sha256sums=('f04156d34ecf5e858bd259dca824f970af53ff177185516fafff15766a0880e9')
build() { build() {
cd "link_studio-${pkgver}" cd "link_studio-${pkgver}"
+3 -3
View File
@@ -1,6 +1,6 @@
# Maintainer: Jeff Dickey <releases@mise.jdx.dev> # Maintainer: Jeff Dickey <releases@mise.jdx.dev>
pkgname=mise-bin pkgname=mise-bin
pkgver=2026.8.15 pkgver=2026.9.1
pkgrel=1 pkgrel=1
pkgdesc="dev tools, env vars, task runner" pkgdesc="dev tools, env vars, task runner"
arch=('x86_64' 'aarch64') arch=('x86_64' 'aarch64')
@@ -14,8 +14,8 @@ provides=('mise')
conflicts=('mise') conflicts=('mise')
source_x86_64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-x64.tar.xz") 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") source_aarch64=("https://github.com/jdx/mise/releases/download/v${pkgver}/mise-v${pkgver}-linux-arm64.tar.xz")
sha256sums_x86_64=('0ed4ee8bcf229718bead0d0aa83de865803f3fe25cc6c71a41e3a8bd57c40a33') sha256sums_x86_64=('bbc21f8fe9b2aa09f74250be15f9bf32e87562e1039d858ad91d7944f057c8e2')
sha256sums_aarch64=('d5f9b7e6b36b5cf5aaab0dc4d7e29ee74224281b1a81a228bdc1cd1f3edf9d49') sha256sums_aarch64=('7d0c28d44bd7e1c5444a7d867146afabe43592c728329bc0ed5ec59ca7106c48')
package() { package() {
install -Dm755 "${srcdir}/mise/bin/mise" "${pkgdir}/usr/bin/mise" install -Dm755 "${srcdir}/mise/bin/mise" "${pkgdir}/usr/bin/mise"
+1 -1
View File
@@ -2,7 +2,7 @@
"source": "local", "source": "local",
"release_ring": "fast", "release_ring": "fast",
"upstream": { "upstream": {
"github": "tsouth89/omakade", "github": "btsouth/omakade",
"checksums": "SHA256SUMS", "checksums": "SHA256SUMS",
"assets": { "assets": {
"any": "omakade-{pkgver}.tar.gz" "any": "omakade-{pkgver}.tar.gz"
+9 -7
View File
@@ -1,15 +1,17 @@
pkgname=omakade pkgname=omakade
pkgver=1.2.3 pkgver=1.7.0
pkgrel=1 pkgrel=1
pkgdesc='A beautiful, local-first game library for Omarchy' pkgdesc='A beautiful, local-first game library for Omarchy'
arch=('x86_64') arch=('x86_64' 'aarch64')
url='https://tsouth89.github.io/omakade/' url='https://btsouth.github.io/omakade/'
license=('GPL-3.0-or-later') license=('GPL-3.0-or-later')
depends=('glib2' 'libsecret' 'qt6-base' 'qt6-declarative' 'qt6-wayland' 'sdl3') depends=('glib2' 'hicolor-icon-theme' 'libsecret' 'libzip' 'openssl' 'qt6-base' 'qt6-declarative' 'qt6-imageformats' 'qt6-svg' 'qt6-wayland' 'sdl3' 'wayland' 'zstd')
makedepends=('cmake' 'ninja' 'pkgconf') # 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') options=('!debug')
source=("$pkgname-$pkgver.tar.gz::https://github.com/tsouth89/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz") source=("$pkgname-$pkgver.tar.gz::https://github.com/btsouth/omakade/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz")
sha256sums=('dcbb57caf982317ae0fd75a1a97c48b80d2b02f92d65b38030876a172eb25606') sha256sums=('715cd5e0cb860514276b1af57fe08ced71c1c69a6aeebd93aa51f1ca367eefa8')
build() { build() {
cmake -S "$pkgname-$pkgver" -B build -G Ninja \ 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=4.0.0
_pkgver_base_tag=v3.8.2 _pkgver_base_tag=v3.8.2
pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH (quattro branch tip)' 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' url='https://github.com/basecamp/omarchy'
license=('MIT') license=('MIT')
provides=('omarchy') provides=('omarchy')
@@ -17,18 +20,6 @@ depends=(
'omarchy-keyring' 'omarchy-keyring'
'omarchy-settings-dev' '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 # Wayland / Hyprland core
'hyprland' 'hyprland'
'quickshell' 'quickshell'
@@ -55,6 +46,29 @@ depends=(
'ttf-jetbrains-mono-nerd-basic' '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=( makedepends=(
'git' 'git'
) )
+60 -11
View File
@@ -5,7 +5,12 @@ pkgrel=1
_pkgver_base=4.0.0 _pkgver_base=4.0.0
_pkgver_base_tag=v3.8.2 _pkgver_base_tag=v3.8.2
pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers (quattro branch tip)' 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' url='https://github.com/basecamp/omarchy'
license=('MIT') license=('MIT')
provides=('omarchy-settings') provides=('omarchy-settings')
@@ -44,15 +49,10 @@ makedepends=(
# scriptlet WILL clobber user edits on each upgrade (documented limitation). # scriptlet WILL clobber user edits on each upgrade (documented limitation).
backup=( backup=(
'etc/fastfetch/config.jsonc' '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/docker/daemon.json'
'etc/gnupg/dirmngr.conf' 'etc/gnupg/dirmngr.conf'
'etc/sysctl.d/99-omarchy-sysctl.conf' 'etc/sysctl.d/99-omarchy-sysctl.conf'
'etc/sysctl.d/90-omarchy-file-watchers.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/10-ignore-power-button.conf'
'etc/systemd/logind.conf.d/20-inhibit-delay.conf' 'etc/systemd/logind.conf.d/20-inhibit-delay.conf'
'etc/systemd/resolved.conf.d/10-disable-multicast.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/plocate-updatedb.service.d/ac-only.conf'
'etc/systemd/system.conf.d/20-omarchy-nofile.conf' 'etc/systemd/system.conf.d/20-omarchy-nofile.conf'
'etc/systemd/user.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-theme.conf'
'etc/sddm.conf.d/10-wayland.conf' 'etc/sddm.conf.d/10-wayland.conf'
'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-power-profile.rules'
'etc/udev/rules.d/99-omarchy-wifi-powersave.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=( optdepends=(
'omarchy-dev: Full Omarchy desktop (quattro branch tip)' 'omarchy-dev: Full Omarchy desktop (quattro branch tip)'
'inxi: System information for omarchy-debug' 'inxi: System information for omarchy-debug'
'fastfetch: System information for omarchy-upload-log' '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: 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-mkinitcpio-hook: Picks up the shipped /etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'limine-snapper-sync: Boot-entry sync for snapper snapshots' 'limine-snapper-sync: Boot-entry sync for snapper snapshots'
@@ -147,6 +160,14 @@ package() {
install -d "$pkgdir/usr/share/omarchy/config" install -d "$pkgdir/usr/share/omarchy/config"
cp -a config/. "$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 # Package-owned defaults with real system/XDG locations. User config remains
# higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d, # higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d,
# ~/.config/environment.d, ~/.config/fontconfig, ~/.config/xdg-terminals.list, # ~/.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" 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 # 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. # memory pressure costs one app scope instead of the compositor session.
# Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. # Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. Both this and
install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" # the zram vendor drop-in belong to the x86_64 memory stack: the aarch64
install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" # 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, # Same for applications/** — used by omarchy-refresh-applications,
# omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share. # omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share.
@@ -190,6 +216,25 @@ package() {
# stage separately below). # stage separately below).
install -d "$pkgdir/etc" install -d "$pkgdir/etc"
cp -a etc/. "$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 # 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 # directory must be 0750 to match the Arch sudo package's filesystem
# convention (otherwise pacman warns about dir-mode drift on every install). # convention (otherwise pacman warns about dir-mode drift on every install).
@@ -280,6 +325,10 @@ EOF
# live root config. # live root config.
install -d "$pkgdir/usr/share/omarchy/default" install -d "$pkgdir/usr/share/omarchy/default"
cp -a default/. "$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`. # Snapper config template used by the install-time `snapper create-config`.
install -Dm644 default/snapper/root \ 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() { _etc_overrides_apply() {
echo "Applying Omarchy etc-overrides..." 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 # 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 # 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. # installed or pacman will preserve its packaged file as a noisy .pacnew.
@@ -21,10 +46,6 @@ _etc_overrides_apply() {
install -d /etc/cups install -d /etc/cups
cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf
fi 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/plymouth-plymouthd.conf /etc/plymouth/plymouthd.conf
cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc
chmod 0644 /etc/os-release /etc/security/faillock.conf /etc/nsswitch.conf \ 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 pkgver=4.0.2
pkgrel=1 pkgrel=1
pkgdesc='Omarchy user defaults, /etc/skel content, fonts, plymouth theme, and support helpers' 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' url='https://github.com/basecamp/omarchy'
license=('MIT') license=('MIT')
conflicts=('omarchy-settings-dev') conflicts=('omarchy-settings-dev')
@@ -53,15 +58,10 @@ makedepends=(
# scriptlet WILL clobber user edits on each upgrade (documented limitation). # scriptlet WILL clobber user edits on each upgrade (documented limitation).
backup=( backup=(
'etc/fastfetch/config.jsonc' '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/docker/daemon.json'
'etc/gnupg/dirmngr.conf' 'etc/gnupg/dirmngr.conf'
'etc/sysctl.d/99-omarchy-sysctl.conf' 'etc/sysctl.d/99-omarchy-sysctl.conf'
'etc/sysctl.d/90-omarchy-file-watchers.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/10-ignore-power-button.conf'
'etc/systemd/logind.conf.d/20-inhibit-delay.conf' 'etc/systemd/logind.conf.d/20-inhibit-delay.conf'
'etc/systemd/resolved.conf.d/10-disable-multicast.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/plocate-updatedb.service.d/ac-only.conf'
'etc/systemd/system.conf.d/20-omarchy-nofile.conf' 'etc/systemd/system.conf.d/20-omarchy-nofile.conf'
'etc/systemd/user.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-theme.conf'
'etc/sddm.conf.d/10-wayland.conf' 'etc/sddm.conf.d/10-wayland.conf'
'etc/udev/rules.d/99-omarchy-power-profile.rules' 'etc/udev/rules.d/99-omarchy-power-profile.rules'
'etc/udev/rules.d/99-omarchy-wifi-powersave.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=( optdepends=(
'omarchy: Full Omarchy desktop' 'omarchy: Full Omarchy desktop'
'inxi: System information for omarchy-debug' 'inxi: System information for omarchy-debug'
'fastfetch: System information for omarchy-upload-log' '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: 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-mkinitcpio-hook: Picks up the shipped /etc/mkinitcpio.conf.d/omarchy_hooks.conf'
'limine-snapper-sync: Boot-entry sync for snapper snapshots' 'limine-snapper-sync: Boot-entry sync for snapper snapshots'
@@ -142,6 +155,14 @@ package() {
install -d "$pkgdir/usr/share/omarchy/config" install -d "$pkgdir/usr/share/omarchy/config"
cp -a config/. "$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 # Package-owned defaults with real system/XDG locations. User config remains
# higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d, # higher priority: ~/.config/uwsm/default, ~/.config/uwsm/env.d,
# ~/.config/environment.d, ~/.config/fontconfig, ~/.config/xdg-terminals.list, # ~/.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" 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 # 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. # memory pressure costs one app scope instead of the compositor session.
# Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. # Thresholds live in etc/systemd/oomd.conf.d/10-omarchy.conf. Both this and
install -Dm644 default/systemd/user/app.slice.d/10-oomd.conf "$pkgdir/usr/lib/systemd/user/app.slice.d/10-oomd.conf" # the zram vendor drop-in belong to the x86_64 memory stack: the aarch64
install -Dm644 default/systemd/zram-generator.conf.d/90-omarchy.conf "$pkgdir/usr/lib/systemd/zram-generator.conf.d/90-omarchy.conf" # 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, # Same for applications/** — used by omarchy-refresh-applications,
# omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share. # omarchy-install-gaming-*, etc. to copy .desktop files into ~/.local/share.
@@ -185,6 +214,25 @@ package() {
# stage separately below). # stage separately below).
install -d "$pkgdir/etc" install -d "$pkgdir/etc"
cp -a etc/. "$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 # 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 # directory must be 0750 to match the Arch sudo package's filesystem
# convention (otherwise pacman warns about dir-mode drift on every install). # convention (otherwise pacman warns about dir-mode drift on every install).
@@ -275,6 +323,10 @@ EOF
# live root config. # live root config.
install -d "$pkgdir/usr/share/omarchy/default" install -d "$pkgdir/usr/share/omarchy/default"
cp -a default/. "$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`. # Snapper config template used by the install-time `snapper create-config`.
install -Dm644 default/snapper/root \ 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() { _etc_overrides_apply() {
echo "Applying Omarchy etc-overrides..." 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 # 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 # 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. # installed or pacman will preserve its packaged file as a noisy .pacnew.
@@ -21,10 +46,6 @@ _etc_overrides_apply() {
install -d /etc/cups install -d /etc/cups
cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf cp -f /usr/share/omarchy/etc-overrides/cups-cups-browsed.conf /etc/cups/cups-browsed.conf
fi 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/plymouth-plymouthd.conf /etc/plymouth/plymouthd.conf
cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc cp -f /usr/share/omarchy/etc-overrides/dot.bashrc /etc/skel/.bashrc
chmod 0644 /etc/os-release /etc/security/faillock.conf /etc/nsswitch.conf \ 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 pkgver=4.0.2
pkgrel=1 pkgrel=1
pkgdesc='Beautiful, modern, and opinionated Arch Linux by DHH' 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' url='https://github.com/basecamp/omarchy'
license=('MIT') license=('MIT')
conflicts=('omarchy-dev') conflicts=('omarchy-dev')
@@ -31,18 +34,6 @@ depends=(
# from the same _commit, and forces the pair to upgrade together. # from the same _commit, and forces the pair to upgrade together.
"omarchy-settings=${pkgver}" "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 # Wayland / Hyprland core
'hyprland' 'hyprland'
'quickshell' 'quickshell'
@@ -69,6 +60,29 @@ depends=(
'ttf-jetbrains-mono-nerd-basic' '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=( makedepends=(
'git' '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 pkgver=1.20.1
pkgrel=1 pkgrel=1
pkgdesc="Native Wayland screenshot and annotation overlay for Hyprland" pkgdesc="Native Wayland screenshot and annotation overlay for Hyprland"
arch=('x86_64') arch=('x86_64' 'aarch64')
url="https://github.com/tobi/omasnap" url="https://github.com/tobi/omasnap"
license=('MIT' 'OFL-1.1') license=('MIT' 'OFL-1.1')
depends=( depends=(
+1 -1
View File
@@ -1,4 +1,4 @@
{ {
"source": "aur", "source": "aur",
"upstream_commit": "e2a85e53e2a73cec9170a5fa631805f62b98bb36" "upstream_commit": "cbc25a60a791ccda07ed5e8300e8acb267d23007"
} }
+2 -2
View File
@@ -1,5 +1,5 @@
pkgname=omazed pkgname=omazed
pkgver=2.1.1 pkgver=2.1.2
pkgrel=1 pkgrel=1
pkgdesc="Live theme switching for Zed in Omarchy - automatically synchronize your Zed editor theme with your Omarchy system theme" pkgdesc="Live theme switching for Zed in Omarchy - automatically synchronize your Zed editor theme with your Omarchy system theme"
arch=('any') arch=('any')
@@ -10,7 +10,7 @@ makedepends=('git')
backup=() backup=()
install=omazed.install install=omazed.install
source=("$pkgname-$pkgver.tar.gz::https://github.com/aps6/$pkgname/archive/refs/tags/v$pkgver.tar.gz") source=("$pkgname-$pkgver.tar.gz::https://github.com/aps6/$pkgname/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('083757c5385179f241ac0c6e9dde05ba0c7f465c1fed617779d1ebe2a6dfe036') sha256sums=('ca0708c86969547b14958a307ae3df89f63d92ffa68af3b5c6bf79c036b8cab9')
package() { package() {
cd "$srcdir/$pkgname-$pkgver" cd "$srcdir/$pkgname-$pkgver"
-5
View File
@@ -17,11 +17,6 @@ post_upgrade() {
echo "" echo ""
echo "==> Omazed updated to $(pacman -Q omazed | cut -d' ' -f2)" echo "==> Omazed updated to $(pacman -Q omazed | cut -d' ' -f2)"
echo "" echo ""
echo " Complete the update!"
echo ""
echo " To complete this update, run:"
echo ""
echo " omazed setup"
echo "" echo ""
} }
@@ -1,5 +1,5 @@
{ {
"source": "aur", "source": "aur",
"release_ring": "fast", "release_ring": "fast",
"upstream_commit": "7298bbbde771f2f33d0942fc429bd27643a9986a" "upstream_commit": "134a0e93624d39306f94dc4384dbd102b8d37bb2"
} }
+5 -5
View File
@@ -2,7 +2,7 @@
# shellcheck disable=SC2034 # shellcheck disable=SC2034
# Maintainer: Chmouel Boudjnah <chmouel@chmouel.com> # Maintainer: Chmouel Boudjnah <chmouel@chmouel.com>
pkgname=openai-codex-bin pkgname=openai-codex-bin
pkgver=0.152.0 pkgver=0.153.4
pkgrel=1 pkgrel=1
pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated" pkgdesc="Arch Linux package for OpenAI's Codex CLI - Auto Updated"
arch=('x86_64' 'aarch64') 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-${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" "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' sha256sums_x86_64=('f479424eca092484dc40d87ae28c44f4cc40234a60045d6131e493800d814a30'
'449cefe35b9f347e3fdbf121e816339b37825eb0bfee7de8298a0a61b6687cba') 'f95830a869590957664bbfc67bccb08773806b693670baf15908176f89b4cd31')
sha256sums_aarch64=('37da6b486503c8a42cc4604d2a3d80d388df896dd251e9225f4f3d49b08c2e8c' sha256sums_aarch64=('5cda6182bd94c3a30f2eb63a495489ebf7f691fddb14d70f48c6c1a5071b6cde'
'134183c7f5bb2245ed4bb15cfcf00d1a64950adbdd089d33c003c8bcf13267e9') 'd8047b8d33370d6090e729d27eb76de60a2686baa1c143c138c9b05dc70d813b')
source_aarch64=( source_aarch64=(
"codex-${pkgver}-aarch64.tar.gz::https://github.com/openai/codex/releases/download/rust-v${pkgver}/codex-aarch64-unknown-linux-musl.tar.gz" "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. # the version and checksums below from that repository's package index.
pkgname=openai-codex-desktop pkgname=openai-codex-desktop
pkgver=26.825.51511 pkgver=26.901.51231
pkgrel=1 pkgrel=1
pkgdesc="Official ChatGPT desktop app with Codex" pkgdesc="Official ChatGPT desktop app with Codex"
arch=('x86_64' 'aarch64') 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}") source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}")
noextract=("${_deb_x86_64}" "${_deb_aarch64}") noextract=("${_deb_x86_64}" "${_deb_aarch64}")
sha256sums=('b3a4503b5931f102444bc7015c3cf4e40266cf034e0d682bd2a407dc5b3ee58c') sha256sums=('b3a4503b5931f102444bc7015c3cf4e40266cf034e0d682bd2a407dc5b3ee58c')
sha256sums_x86_64=('3554b0022c6cfb513326f43fd11f718835a77086ac4d7ca2ff3ebbba2d4c7f45') sha256sums_x86_64=('62580188d87c3d3a9369dab7c73b42a8a32518d4df8a2d5bae6466ddeac5c05e')
sha256sums_aarch64=('125e36522d43c75bd7958477846ba6b1cddf2eb19cefcb57dda80be1742f917f') sha256sums_aarch64=('02a2f5c6cb69509c62abcbdd13c76b139cdb2ca9edde7537239ddde024077ea0')
package() { package() {
cd "${srcdir}" 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=() user_flags=()
config_home="${XDG_CONFIG_HOME:-}" config_home="${XDG_CONFIG_HOME:-}"
[[ -n "$config_home" || -z "${HOME:-}" ]] || config_home="$HOME/.config" [[ -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 if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do while IFS= read -r line || [[ -n "$line" ]]; do
@@ -16,7 +16,8 @@ if [[ -n "$flags_file" && -f "$flags_file" && -r "$flags_file" ]]; then
fi fi
# Chromium's own Ozone detection falls back to XWayland often enough to matter, # 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=() platform_flags=()
if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then
platform_flags=(--ozone-platform=wayland) platform_flags=(--ozone-platform=wayland)
@@ -28,4 +29,4 @@ if [[ -n "${WAYLAND_DISPLAY:-}" || "${XDG_SESSION_TYPE:-}" == wayland ]]; then
done done
fi 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", "source": "local",
"upstream_commit": "a850f2d07e825f61cb01b87931d2ae5f1802dde1" "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 pkgname=qmk-hid
_srcname=qmk_hid _srcname=qmk_hid
pkgver=0.1.11 pkgver=0.1.13
pkgrel=3 pkgrel=1
pkgdesc="Commandline tool for interacting with QMK devices over HID" pkgdesc="Commandline tool for interacting with QMK devices over HID"
arch=('x86_64') arch=('x86_64' 'aarch64')
url="https://github.com/FrameworkComputer/qmk_hid" url="https://github.com/FrameworkComputer/qmk_hid"
license=('BSD') license=('BSD')
depends=('gcc-libs' 'systemd-libs' 'libcap') depends=('gcc-libs' 'systemd-libs' 'libcap')
makedepends=('cargo') makedepends=('cargo')
source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz") source=("$pkgname-$pkgver.tar.gz::$url/archive/v$pkgver.tar.gz")
b2sums=('e898d27cfe8350f4a8cd2938a3d066935093daf1292f41fc10b8f1937059f2b59e4ac34a987f26c8258a5d3b15a900059024fe3a18ed793f7e3b524939ab4c71') sha256sums=('8a058395b8ba56ff54308a681223f11a6032c54df42b4d0223143ccf09d0c858')
options=('!lto') options=('!lto')
prepare() { prepare() {
@@ -8,7 +8,7 @@
"qt6-wayland" "qt6-wayland"
], ],
"rebuilt_against": { "rebuilt_against": {
"qt6-base": "6.11.2-2", "qt6-base": "6.11.2-3",
"qt6-declarative": "6.11.2-1", "qt6-declarative": "6.11.2-1",
"qt6-wayland": "6.11.2-1" "qt6-wayland": "6.11.2-1"
} }
+1 -1
View File
@@ -3,7 +3,7 @@
_pkgname=quickshell _pkgname=quickshell
pkgname="$_pkgname-git" pkgname="$_pkgname-git"
pkgver=0.3.0.r20.g28771c7 pkgver=0.3.0.r20.g28771c7
pkgrel=2 pkgrel=3
pkgdesc='Flexible toolkit for making desktop shells with QtQuick' pkgdesc='Flexible toolkit for making desktop shells with QtQuick'
arch=(x86_64 aarch64) arch=(x86_64 aarch64)
url='https://git.outfoxxed.me/quickshell/quickshell' url='https://git.outfoxxed.me/quickshell/quickshell'
@@ -1,4 +1,3 @@
{ {
"source": "aur", "source": "local"
"upstream_commit": "80c65c27a9d5511d2b4ce2ca7ad2396ad528f0da"
} }
@@ -1,7 +1,10 @@
# Maintainer: Alexandre Bouvier <contact@amb.tf> # Maintainer: Alexandre Bouvier <contact@amb.tf>
_pkgname=retroarch-joypad-autoconfig _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 pkgname=$_pkgname-git
pkgver=1.22.0.r24.g739cee1 pkgver=1.22.0.r96.g0331510
pkgrel=1 pkgrel=1
pkgdesc="RetroArch joypad autoconfig files" pkgdesc="RetroArch joypad autoconfig files"
arch=('any') arch=('any')
@@ -12,7 +15,7 @@ makedepends=('git')
optdepends=('retroarch') optdepends=('retroarch')
provides=("$_pkgname") provides=("$_pkgname")
conflicts=("$_pkgname") conflicts=("$_pkgname")
source=("$_pkgname::git+$url.git") source=("$_pkgname::git+$url.git#commit=$_commit")
b2sums=('SKIP') b2sums=('SKIP')
pkgver() { 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