Merge current Cursor packaging and disable updates on ARM

This commit is contained in:
Scott Jones committed 2026-09-22 20:07:30 -04:00
commit ee8431a661
356 files changed
+8193 -495

No files matched your search

+27
View File
@@ -0,0 +1,27 @@
# Trust list for PR builds.
#
# A pull request only builds packages (and spins up builder droplets) when
# its author is trusted: repository collaborators are trusted automatically
# and do not need listing; external contributors listed here are trusted
# too. Anyone else gets the plan only, until a maintainer either adds them
# here or applies the "build-approved" label to that one PR. The label also
# releases GitHub's approval hold for that PR's build and test workflows.
# It remains effective while attached, without vouching for the author's
# other PRs. An explicit denouncement cannot be overridden by the label.
#
# Syntax:
# github:username
# -github:username reason for denouncement
#
# Keep entries sorted alphabetically.
github:bjarneo
github:DanWahlin
github:dhh
github:f-trycua
github:HANCORE-linux
github:kwilczynski
github:ryanrhughes
github:scottjones
github:spencerbull
github:tcballard
github:tobi
+78
View File
@@ -0,0 +1,78 @@
const BUILD = '.github/workflows/build-pr.yml';
const TESTS = '.github/workflows/test.yml';
module.exports = async function approve({ github, context, core, vouchStatus,
sleep = ms => new Promise(resolve => setTimeout(resolve, ms)), attempts = 36 }) {
// Missing/failed vouch lookups must not become approval. Denouncements
// remain absolute, just as they are in the package build gate.
if (!['unknown', 'bot', 'collaborator', 'vouched'].includes(vouchStatus)) {
throw new Error(`Cannot approve workflows: vouch status is ${vouchStatus || 'missing'}.`);
}
const expected = context.payload.pull_request;
const eventTime = Date.parse(expected.updated_at);
if (!Number.isFinite(eventTime)) throw new Error('Missing PR event timestamp.');
const approved = new Set();
let precedingBuild;
const stillApproved = async () => {
const { data: pr } = await github.rest.pulls.get({
...context.repo, pull_number: expected.number,
});
return pr.state === 'open' && pr.head.sha === expected.head.sha &&
pr.labels.some(label => label.name === 'build-approved');
};
// The label and PR-run events arrive independently. Wait for the build
// belonging to this event, rather than returning after approving an older
// run and leaving the new label-triggered run stuck behind GitHub's gate.
for (let attempt = 0; attempt < attempts; attempt++) {
if (attempt) await sleep(5000);
if (!await stillApproved()) {
core.info('PR closed, head changed, or build-approved removed; stopping.');
return;
}
const all = await github.paginate(github.rest.actions.listWorkflowRunsForRepo, {
...context.repo, event: 'pull_request', head_sha: expected.head.sha, per_page: 100,
});
const runs = all.filter(run =>
run.event === 'pull_request' && run.head_sha === expected.head.sha &&
run.head_repository?.id === expected.head.repo.id && run.head_branch === expected.head.ref &&
[BUILD, TESTS].includes(run.path) &&
// Fork runs awaiting approval often have no pull_requests entries.
(!run.pull_requests?.length || run.pull_requests.some(pr => pr.number === expected.number))
).sort((a, b) => a.id - b.id);
const newestBuild = runs.findLast(run => run.path === BUILD);
if (!newestBuild || !(Date.parse(newestBuild.created_at) >= eventTime) ||
!runs.some(run => run.path === TESTS &&
(context.payload.action === 'labeled' || Date.parse(run.created_at) >= eventTime))) continue;
if (precedingBuild) {
const { data: run } = await github.rest.actions.getWorkflowRun({
...context.repo, run_id: precedingBuild,
});
// Approve older builds first, and let them acquire concurrency before
// releasing a newer build. Otherwise an older queued run could start
// last and cancel the label-triggered build that carries approval.
if (!['in_progress', 'completed'].includes(run.status) || run.conclusion === 'action_required') continue;
precedingBuild = undefined;
}
const pending = runs.filter(run => run.conclusion === 'action_required' && !approved.has(run.id) &&
// If the newest build already runs (e.g. a maintainer approved it),
// don't resurrect an obsolete hold that could cancel that newer run.
(run.path !== BUILD || run.id === newestBuild.id || newestBuild.conclusion === 'action_required'));
if (!pending.length) return;
const run = pending[0];
// Recheck after the API reads, immediately before exercising write access.
if (!await stillApproved()) return;
await github.rest.actions.approveWorkflowRun({ ...context.repo, run_id: run.id });
approved.add(run.id);
core.info(`Approved ${run.path} run ${run.id} for PR #${expected.number}.`);
if (run.path === BUILD) precedingBuild = run.id;
if (pending.length === 1) return;
}
throw new Error('Timed out waiting for PR workflows. Remove and reapply build-approved to retry.');
};
+46
View File
@@ -0,0 +1,46 @@
name: Approve PR workflows
# A pull_request workflow cannot approve itself: GitHub can hold it before
# any job starts. This workflow only runs trusted default-branch code and
# releases the ordinary, unprivileged PR workflows after build approval.
on:
pull_request_target:
types: [opened, synchronize, reopened, labeled]
permissions:
contents: read
pull-requests: read
actions: write
concurrency:
group: approve-pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
approve:
# Match build-pr.yml's events, including other labels applied while this
# PR still carries build-approved: each labeled event creates a build.
if: contains(github.event.pull_request.labels.*.name, 'build-approved')
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
# Never check out the PR head or its merge ref with this write token.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- id: vouch
uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1
with:
user: ${{ github.event.pull_request.user.login }}
allow-fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Approve this PR's pending build and test runs
uses: actions/github-script@v7
env:
VOUCH_STATUS: ${{ steps.vouch.outputs.status }}
with:
script: |
const approve = require('./.github/scripts/approve-pr-workflows.cjs');
await approve({ github, context, core, vouchStatus: process.env.VOUCH_STATUS });
+254
View File
@@ -0,0 +1,254 @@
name: Build changed packages
# Build every package directory a PR touches, one job per package per arch, on
# the self-hosted droplet pool. Artifacts are unsigned; publish.yml signs and
# publishes them on merge.
#
# Tooling runs from the base branch; a PR supplies only pkgbuilds/. The
# vouch gate limits who may spend compute; this limits what their PR can run.
# No paths filter: approved PRs must report the required `result` even when
# no package directory changed. Those PRs get an empty matrix and a passing
# result in seconds; unapproved PRs wait for maintainer approval.
on:
pull_request:
types: [opened, synchronize, reopened, labeled]
workflow_dispatch:
inputs:
packages:
description: "Space-separated package directories to build"
required: true
concurrency:
group: build-${{ github.event.pull_request.number || github.run_id }}
cancel-in-progress: true
jobs:
# Builds cost real machines, so they run only for trusted authors:
# collaborators, anyone in .github/VOUCHED.td (read from the default
# branch, so a PR cannot vouch for itself), or a PR a maintainer has
# labelled "build-approved". Everyone else gets this job's plan output
# while the required `result` stays pending until a maintainer approves.
changes:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.list.outputs.matrix }}
count: ${{ steps.gate.outputs.count }}
trusted: ${{ steps.gate.outputs.trusted }}
vouch_status: ${{ steps.vouch.outputs.status }}
empty: ${{ steps.list.outputs.empty }}
steps:
# Same rule as the build job: bin/build-matrix comes from the base
# branch tip, the package directories from the PR head.
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref || github.sha }}
fetch-depth: 0
persist-credentials: false
- if: github.event_name == 'pull_request'
run: |
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.head.sha }}"
git checkout "${{ github.event.pull_request.head.sha }}" -- pkgbuilds/
# Bootstrap: the PR that introduces this tooling has a base without
# it. Take the plan helper from the PR head in that one case; it
# runs on a hosted runner and only prints a plan.
if [[ ! -x bin/build-matrix ]]; then
git checkout "${{ github.event.pull_request.head.sha }}" -- bin/build-matrix helpers/
echo "::notice::base branch has no bin/build-matrix; using the PR's copy for planning"
fi
- id: vouch
if: github.event_name == 'pull_request'
uses: mitchellh/vouch/action/check-user@f23dbb5e745334f97414ec70463ce7301071a661 # v1
with:
user: ${{ github.event.pull_request.user.login }}
allow-fail: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- id: approval
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const { data: pr } = await github.rest.pulls.get({
...context.repo, pull_number: context.payload.pull_request.number,
});
// Approving or rerunning a held run keeps its original event,
// which may predate the label. Read the current approval instead.
core.setOutput('approved', pr.state === 'open' &&
pr.head.sha === context.payload.pull_request.head.sha &&
pr.labels.some(label => label.name === 'build-approved'));
# One matrix entry per package per architecture. Every package builds
# once, against edge; the channels it ships to on merge are carried
# along for information. A filename means one set of bytes.
- id: list
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [[ -n "${{ github.event.inputs.packages }}" ]]; then
names="${{ github.event.inputs.packages }}"
else
names=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" -- pkgbuilds \
| awk -F/ '$1=="pkgbuilds" && NF>2 {print $2}' | sort -u)
fi
matrix=$(printf '%s\n' $names | bin/build-matrix)
# A package directory whose exact tree already has a build artifact
# (label <pkg>-<arch>-<tree hash>, uploaded only after a successful
# build) is not built again. Pushing a fix for one package to a PR
# that touches fifty rebuilds one, not fifty; publish.yml finds the
# same artifacts on merge. workflow_dispatch is an explicit request
# and always builds.
if [[ "${{ github.event_name }}" == pull_request ]]; then
head="${{ github.event.pull_request.head.sha }}"
kept=(); reused=()
while read -r entry; do
package=$(jq -r .package <<<"$entry"); arch=$(jq -r .arch <<<"$entry")
label="$package-$arch-$(git rev-parse "$head:pkgbuilds/$package")"
found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \
| jq -r '[.artifacts[] | select(.expired|not)] | length' || echo 0)
if (( found > 0 )); then reused+=("$label"); else kept+=("$entry"); fi
done < <(jq -c '.include[]' <<<"$matrix")
matrix=$(printf '%s\n' "${kept[@]}" | jq -sc '{include: .}')
if (( ${#reused[@]} )); then
printf '==> already built, reusing the artifact: %s\n' "${reused[@]}"
{ echo "Reused existing build artifacts (${#reused[@]}):"; printf -- '- %s\n' "${reused[@]}"; } >> "$GITHUB_STEP_SUMMARY"
fi
fi
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
echo "planned=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT"
jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix"
# A PR whose diff against its base is empty changes nothing: its
# content already landed some other way (a sync PR beat it, or a
# merge from master swallowed it). Merging it would record a change
# that isn't one. Flag it so `result` fails rather than passes.
if [[ "${{ github.event_name }}" == pull_request ]]; then
total=$(git diff --name-only "${{ github.event.pull_request.base.sha }}" "${{ github.event.pull_request.head.sha }}" | wc -l)
echo "empty=$([[ $total -eq 0 ]] && echo true || echo false)" >> "$GITHUB_OUTPUT"
echo "files changed vs base: $total"
else
echo "empty=false" >> "$GITHUB_OUTPUT"
fi
- id: gate
env:
STATUS: ${{ github.event_name == 'workflow_dispatch' && 'dispatch' || steps.vouch.outputs.status }}
AUTHOR: ${{ github.event.pull_request.user.login }}
APPROVED: ${{ steps.approval.outputs.approved || 'false' }}
PLANNED: ${{ steps.list.outputs.planned }}
run: |
case "$STATUS" in
bot|collaborator|vouched|dispatch) trusted=true ;;
# A denouncement is absolute: the label cannot override it.
denounced) trusted=false ;;
unknown) trusted=$APPROVED ;;
*) trusted=false ;;
esac
echo "trusted=$trusted" >> "$GITHUB_OUTPUT"
if [[ $trusted == true ]]; then
echo "count=$PLANNED" >> "$GITHUB_OUTPUT"
echo "Author $AUTHOR is trusted ($STATUS); building $PLANNED package(s)."
else
echo "count=0" >> "$GITHUB_OUTPUT"
echo "::warning::Author $AUTHOR is not trusted ($STATUS). $PLANNED package build(s) planned but not run."
if [[ $STATUS == denounced ]]; then
echo "::warning::The author is denounced in .github/VOUCHED.td; the build-approved label does not apply."
else
echo "::warning::A maintainer can add the author to .github/VOUCHED.td, or apply the 'build-approved' label to build this PR."
fi
fi
build:
needs: changes
if: needs.changes.outputs.count != '0'
runs-on: [self-hosted, omarchy-builder]
timeout-minutes: 180
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.changes.outputs.matrix) }}
steps:
# Tooling from base: everything that executes on this droplet's host
# (bin/, helpers/, build/) comes from the base branch. Only the PR's
# package directories are overlaid. A PR can therefore change what
# gets built, never how the runner builds it. A PR that changes both
# tooling and a package builds the package with the OLD tooling; land
# the tooling first. workflow_dispatch has no PR and runs as checked out.
# The base branch tip, not the event's base.sha: that sha is a snapshot
# taken at the PR's last push, so a tooling fix on master would never
# reach an open PR until someone pushed to it (seen on the daily sync
# PR after the artifact packing fix landed).
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref || github.sha }}
persist-credentials: false
- name: Overlay the PR's package directories onto base tooling
if: github.event_name == 'pull_request'
run: |
set -euo pipefail
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.head.sha }}"
git checkout "${{ github.event.pull_request.head.sha }}" -- pkgbuilds/
echo "tooling: $(git rev-parse --short HEAD) (base) packages: ${{ github.event.pull_request.head.sha }} (PR head)"
git status --short | head
- name: Build ${{ matrix.package }} (${{ matrix.arch }}, ships to ${{ matrix.channels }})
id: build
env:
CONTAINER_ENGINE: docker
run: bin/build --mirror edge --arch ${{ matrix.arch }} --package ${{ matrix.package }}
# The artifact label carries the package directory's git tree hash so
# the publish step can find the build for exactly the tree that merged.
# The package file inside keeps makepkg's standard name untouched.
# The artifact label uses the PR head's tree for this package: that is
# the tree that merges, and what publish looks up.
- name: Tree hash
id: tree
run: echo "hash=$(git rev-parse "${{ github.event.pull_request.head.sha || github.sha }}:pkgbuilds/${{ matrix.package }}")" >> "$GITHUB_OUTPUT"
# The upload action rejects a path containing ':', which is how makepkg
# names a package with an epoch. The files ride inside packages.tar
# (helpers/artifact-helpers.sh); publish.yml unpacks it. Only a
# successful build uploads: the artifact's existence is what lets the
# planner above and publish.yml skip rebuilding this exact tree.
- name: Pack artifact
id: pack
run: |
source helpers/artifact-helpers.sh
pack_packages build-output/edge/${{ matrix.arch }} packages.tar
tar -tvf packages.tar
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.package }}-${{ matrix.arch }}-${{ steps.tree.outputs.hash }}
path: packages.tar
if-no-files-found: error
retention-days: 7
# `result` is required by branch protection. An unvouched author awaiting
# approval gets a differently named informational check, leaving `result`
# unreported (pending). Skipping or passing a job named `result` would count
# as satisfying the requirement even though no build was authorized.
# Actual planning/build failures and denouncements still report `result`.
result:
name: ${{ needs.changes.result == 'success' && needs.changes.outputs.trusted == 'false' && needs.changes.outputs.vouch_status == 'unknown' && needs.changes.outputs.empty == 'false' && 'Awaiting build approval' || 'result' }}
needs: [changes, build]
if: always()
runs-on: ubuntu-latest
steps:
- run: |
echo "trusted=${{ needs.changes.outputs.trusted }} build=${{ needs.build.result }}"
if [[ "${{ needs.changes.result }}" != "success" ]]; then
echo "::error::Build planning or the trust check failed. See the changes job."
exit 1
fi
# Nothing to merge: the PR's diff against its base is empty. Its
# change already landed elsewhere. Close it rather than merge it.
if [[ "${{ needs.changes.outputs.empty }}" == "true" ]]; then
echo "::error::This PR changes no files relative to its base. Its content is already on the target branch; close it instead of merging."
exit 1
fi
if [[ "${{ needs.changes.outputs.trusted }}" == "false" && "${{ needs.changes.outputs.vouch_status }}" == "unknown" && "${{ needs.changes.outputs.empty }}" == "false" ]]; then
echo "::notice::Awaiting maintainer build approval. Apply 'build-approved' to this PR or vouch for the author in .github/VOUCHED.td."
echo "Package builds are waiting for maintainer approval. Apply **build-approved** to this PR to start them. The required **result** check remains pending." >> "$GITHUB_STEP_SUMMARY"
exit 0
fi
if [[ "${{ needs.changes.outputs.trusted }}" != "true" ]]; then
echo "::error::Builds are blocked: the author is denounced or the trust result is invalid. The build-approved label cannot override this."
exit 1
fi
[[ "${{ needs.build.result }}" == "success" || "${{ needs.build.result }}" == "skipped" ]]
+118
View File
@@ -0,0 +1,118 @@
name: Refresh builder images
on:
schedule:
- cron: '23 4 * * *'
push:
branches: [master]
paths:
- build/**
- bin/builder-image
- helpers/paths.sh
- helpers/docker-helpers.sh
- tests/build-isolation.sh
- .github/workflows/builder-images.yml
workflow_dispatch:
pull_request:
paths:
- build/**
- bin/builder-image
- helpers/paths.sh
- helpers/docker-helpers.sh
- tests/build-isolation.sh
- .github/workflows/builder-images.yml
# Complete each refresh before another can replace its tested image tags.
concurrency:
group: builder-images-${{ github.event.pull_request.number || 'master' }}
cancel-in-progress: false
permissions:
contents: read
jobs:
# Exercise proposed image changes on native runners with a read-only token.
# Publishing is a separate master-only job with its own write permission.
validate:
if: github.event_name == 'pull_request'
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
env:
CONTAINER_ENGINE: docker
CANDIDATE_IMAGE: omarchy-builder-candidate:${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build a fresh environment
run: bin/builder-image build --arch "${{ matrix.arch }}" --mirror edge --tag "$CANDIDATE_IMAGE" --fresh
- name: Test isolated package builds
env:
TEST_BUILDER_IMAGE: ${{ env.CANDIDATE_IMAGE }}
run: tests/build-isolation.sh
refresh:
if: github.repository == 'omacom/omarchy-pkgs' && github.ref == 'refs/heads/master'
strategy:
fail-fast: false
matrix:
include:
- arch: x86_64
runner: ubuntu-24.04
- arch: aarch64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
permissions:
contents: read
packages: write
env:
CONTAINER_ENGINE: docker
REGISTRY_IMAGE: ghcr.io/omacom/omarchy-pkg-builder
CANDIDATE_IMAGE: omarchy-builder-candidate:${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Build a fresh environment
run: bin/builder-image build --arch "${{ matrix.arch }}" --mirror edge --tag "$CANDIDATE_IMAGE" --fresh
- name: Test isolated package builds
env:
TEST_BUILDER_IMAGE: ${{ env.CANDIDATE_IMAGE }}
run: tests/build-isolation.sh
- name: Publish tested image
env:
GH_TOKEN: ${{ github.token }}
GH_ACTOR: ${{ github.actor }}
DOCKER_CONFIG: ${{ runner.temp }}/builder-registry-auth
run: |
set -euo pipefail
mkdir -p "$DOCKER_CONFIG"
trap 'rm -rf "$DOCKER_CONFIG"' EXIT
printf '%s' "$GH_TOKEN" | docker login ghcr.io --username "$GH_ACTOR" --password-stdin
key=$(bin/builder-image key --arch "${{ matrix.arch }}" --mirror edge)
version="$REGISTRY_IMAGE:$key-$GITHUB_RUN_ID-$GITHUB_RUN_ATTEMPT"
docker tag "$CANDIDATE_IMAGE" "$version"
docker push "$version"
# GHCR creates new packages private. Do not advertise an image to
# fork PRs until it is public. This is a one-time package setting.
anonymous_config=$(mktemp -d "$RUNNER_TEMP/builder-anonymous.XXXXXX")
if ! DOCKER_CONFIG="$anonymous_config" docker manifest inspect "$version" >/dev/null; then
rm -rf "$anonymous_config"
echo "::error::Make the omacom/omarchy-pkg-builder GHCR package public, then rerun this job. The previous matching image remains selected."
exit 1
fi
rm -rf "$anonymous_config"
docker tag "$CANDIDATE_IMAGE" "$REGISTRY_IMAGE:$key"
docker push "$REGISTRY_IMAGE:$key"
digest=$(docker image inspect "$version" --format '{{index .RepoDigests 0}}')
printf '### Builder image (%s)\n\nInput key: `%s`\n\nImage: `%s`\n' \
"${{ matrix.arch }}" "$key" "$digest" >> "$GITHUB_STEP_SUMMARY"
+326
View File
@@ -0,0 +1,326 @@
name: Publish merged packages
# On every push to master: for each package directory the push touched and
# each architecture it supports, find the PR build artifact for exactly that
# tree (label = <pkg>-<arch>-<treehash>), or build it now when there is
# none, then publish that one artifact into every channel the package ships
# to. One build, one file, several databases: a filename means one set of
# bytes everywhere, and channels are views over a shared pool.
#
# Secrets live in the "publish" environment, restricted to master:
# GPG_PRIVATE_KEY, GPG_PASSPHRASE the channel signing key
# R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_ENDPOINT
# OMARCHY_PUBLISH_PREFIX (environment variable, not secret) points a proof
# run at a scratch prefix inside the live bucket; empty means the real
# channel paths.
on:
push:
branches: [master]
paths: ["pkgbuilds/**"]
workflow_dispatch:
inputs:
packages:
description: "Space-separated package directories to publish from master"
required: true
# Merges serialize. Two publishes into one channel at once would race on
# the database; queued is fine, cancelled is not.
concurrency:
group: publish
cancel-in-progress: false
jobs:
changes:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.list.outputs.matrix }}
count: ${{ steps.list.outputs.count }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- id: list
run: |
if [[ -n "${{ github.event.inputs.packages }}" ]]; then
names="${{ github.event.inputs.packages }}"
else
names=$(git diff --name-only "${{ github.event.before }}..${{ github.sha }}" -- pkgbuilds \
| awk -F/ '$1=="pkgbuilds" && NF>2 {print $2}' | sort -u)
fi
matrix=$(printf '%s\n' $names | bin/build-matrix)
echo "matrix=$matrix" >> "$GITHUB_OUTPUT"
echo "count=$(jq '.include | length' <<<"$matrix")" >> "$GITHUB_OUTPUT"
jq -r '.include[] | "\(.package) \(.arch) -> \(.channels)"' <<<"$matrix"
# One job for the whole merge. It collects every PR artifact for the
# merged tree (building only what has none), then walks each channel and
# architecture slot exactly once: pull that database, add every package
# that belongs in it, upload. Six slots, six round trips, however many
# packages the merge carried. One process is the only writer, so there
# is no race between packages; the run-level concurrency group above
# keeps one merge from overlapping the next.
publish:
needs: changes
if: needs.changes.outputs.count != '0'
runs-on: [self-hosted, omarchy-builder]
environment: publish
timeout-minutes: 240
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# Every matrix entry, as a file the shell steps can loop over:
# package arch channels publish_arches
- name: Plan
run: |
jq -r '.include[] | "\(.package) \(.arch) \(.channels|gsub(" ";",")) \(.publish_arches|gsub(" ";","))"' \
<<'EOF_MATRIX' > plan.txt
${{ needs.changes.outputs.matrix }}
EOF_MATRIX
cat plan.txt
# Fetch each package's PR artifact into build-output/edge/<arch>/, or
# build it when no artifact exists for exactly this tree. An artifact
# carries its package files inside packages.tar (see build-pr.yml and
# helpers/artifact-helpers.sh: the upload action rejects the colon in
# an epoch filename).
- name: Collect artifacts
env:
GH_TOKEN: ${{ github.token }}
CONTAINER_ENGINE: docker
run: |
set -uo pipefail
source helpers/artifact-helpers.sh
# sources.jsonl: where each package's files came from, or that the
# build failed. A failed build ends the run before any publish, and
# the record says so instead of the report job finding nothing.
: > sources.jsonl
failed=0
while read -r package arch channels publish_arches; do
hash=$(git rev-parse "HEAD:pkgbuilds/$package")
label="$package-$arch-$hash"
found=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/actions/artifacts?name=$label&per_page=5" \
| jq -r '[.artifacts[] | select(.expired|not)] | sort_by(.created_at) | last | .archive_download_url // empty')
mkdir -p "build-output/edge/$arch"
if [[ -n "$found" ]]; then
echo "==> $label: PR artifact"
rm -rf /tmp/artifact; mkdir -p /tmp/artifact
if curl -fsSL -H "Authorization: Bearer $GH_TOKEN" -o /tmp/artifact.zip "$found" \
&& unzip -oq /tmp/artifact.zip -d /tmp/artifact \
&& unpack_packages /tmp/artifact "build-output/edge/$arch"; then
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"pr-artifact"}' >> sources.jsonl
else
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"artifact-download-failed"}' >> sources.jsonl; failed=1; break
fi
else
# bin/build plans against the public channel first. If the
# channel already holds master's version there is nothing to
# build and nothing to publish: a re-run for a package that
# turned out to be fine. Record it and move on.
plan=$(CONTAINER_ENGINE=docker bin/build --dry-run --mirror edge --arch "$arch" --package "$package" 2>&1 | grep -E '^==> Plan complete' || true)
# "Packages that would build:" followed by nothing means none.
if [[ -n "$plan" && -z "$(sed -E 's/.*would build: *//' <<<"$plan" | tr -d '[:space:]')" ]]; then
echo "==> $label: already published at master's version, nothing to do"
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"already-published"}' >> sources.jsonl
continue
fi
echo "==> $label: no artifact for this tree, building"
if OMARCHY_KEEP_BUILD_WORKSPACE=1 bin/build --mirror edge --arch "$arch" --package "$package"; then
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"built"}' >> sources.jsonl
else
jq -nc --arg p "$package" --arg a "$arch" '{package:$p, arch:$a, source:"build-failed"}' >> sources.jsonl; failed=1; break
fi
fi
done < plan.txt
ls -1 build-output/edge/*/*.pkg.tar.zst 2>/dev/null || true
if (( failed )); then
# Write the record now; the publish step will not run.
jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \
--slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \
'{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:[]}' \
> publish-record.json
cat publish-record.json
exit 1
fi
- name: Publish
env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }}
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
# The token is scoped to the bucket; it may not CreateBucket, and
# rclone's existence check is a CreateBucket in disguise.
RCLONE_CONFIG_R2_NO_CHECK_BUCKET: "true"
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
OMARCHY_PUBLISH_PREFIX: ${{ vars.OMARCHY_PUBLISH_PREFIX }}
# repo-add, gpg and bsdtar are Arch tools; run the publish inside the
# builder image (host-native, edge) with the workspace mounted.
run: |
set -euo pipefail
if ! compgen -G "build-output/edge/*/*.pkg.tar.zst" >/dev/null; then
echo "Nothing to publish: every requested package is already published at master's version."
jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \
--slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \
'{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:[]}' \
> publish-record.json
cat publish-record.json
exit 0
fi
docker image inspect omarchy-pkg-builder:latest-x86_64-edge >/dev/null 2>&1 \
|| docker buildx build --load -t omarchy-pkg-builder:latest-x86_64-edge --build-arg MIRROR=edge build
# Group the merge's files by the (channel, architecture) slot each
# belongs to. A package's files live under build-output/edge/<built
# arch>/ and are named <pkgname>-<ver>-<arch|any>.pkg.tar.zst; a
# split package's outputs share the pkgbase's directory, so match
# on the artifact list rather than the name.
# pkgbase is read inside the builder image: the Ubuntu host has no
# bsdtar. One container call maps every file to its pkgbase.
docker run --rm -v "$PWD:/w:ro" -w /w omarchy-pkg-builder:latest-x86_64-edge bash -c '
for f in build-output/edge/*/*.pkg.tar.zst; do
printf "%s %s\n" "$f" "$(bsdtar -xOf "$f" .PKGINFO | awk -F" = " "\$1==\"pkgbase\"{print \$2}")"
done' > pkgbase.txt
declare -A slot_files=()
while read -r package arch channels publish_arches; do
for f in build-output/edge/"$arch"/*.pkg.tar.zst; do
# Only files this package produced (its PKGINFO pkgbase).
[[ $(awk -v f="$f" '$1==f{print $2}' pkgbase.txt) == "$package" ]] || continue
for mirror in ${channels//,/ }; do
for parch in ${publish_arches//,/ }; do
slot_files["$mirror/$parch"]+="$f "
done
done
done
done < plan.txt
# Deterministic slot order: edge before rc before stable, x86_64
# before aarch64, so a failure leaves the earlier rings consistent.
# Every slot's outcome goes into publish-record.json for the report
# job: what was published, where, from which artifact, and whether
# the slot succeeded. A failing slot stops the loop (set -e) but the
# record still shows everything before it landed.
: > slots.jsonl
record_slot() { jq -nc --arg m "$1" --arg a "$2" --arg st "$3" --arg files "$4" \
'{mirror:$m, arch:$a, status:$st, packages:[$files | split(" ") | .[] | select(length>0) | sub(".*/";"") | sub("\\.pkg\\.tar\\.zst$";"")]}' >> slots.jsonl; }
status=0
for mirror in edge rc stable; do
for parch in x86_64 aarch64; do
files=${slot_files["$mirror/$parch"]:-}
[[ -n "$files" ]] || continue
echo "==> $mirror/$parch: $files"
if docker run --rm \
-e GPG_PRIVATE_KEY -e GPG_PASSPHRASE -e OMARCHY_PUBLISH_PREFIX \
-e RCLONE_CONFIG_R2_TYPE -e RCLONE_CONFIG_R2_PROVIDER -e RCLONE_CONFIG_R2_ENDPOINT -e RCLONE_CONFIG_R2_NO_CHECK_BUCKET \
-e RCLONE_CONFIG_R2_ACCESS_KEY_ID -e RCLONE_CONFIG_R2_SECRET_ACCESS_KEY \
-v "$PWD:/w:ro" -w /w \
omarchy-pkg-builder:latest-x86_64-edge \
bin/publish-artifact --remote R2:omarchy-pkgs --mirror "$mirror" --arch "$parch" $files; then
record_slot "$mirror" "$parch" published "$files"
else
record_slot "$mirror" "$parch" failed "$files"
status=1
break 2
fi
done
done
jq -n --arg sha "${{ github.sha }}" --arg run "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
--arg prefix "${OMARCHY_PUBLISH_PREFIX:-}" --arg event "${{ github.event_name }}" \
--slurpfile slots slots.jsonl --slurpfile sources sources.jsonl --slurpfile plan <(jq -c '.include[]' <<<'${{ needs.changes.outputs.matrix }}' | jq -sc .) \
'{time: (now|todate), commit:$sha, run:$run, event:$event, target: (if $prefix=="" then "live" else $prefix end), plan:$plan[0], sources:$sources, slots:$slots}' \
> publish-record.json
cat publish-record.json
exit $status
- name: Keep the publish record
if: always()
uses: actions/upload-artifact@v4
with:
name: publish-record-${{ github.run_id }}
path: publish-record.json
retention-days: 90
# Tell people what happened. A comment on the merged PR (found by the
# merge commit, so squash and rebase merges work too) and a line appended
# to a running JSON log in the bucket, next to the packages it describes,
# so the history is public and can be rendered later.
report:
needs: [changes, publish]
if: always() && needs.publish.result != 'skipped'
runs-on: ubuntu-latest
environment: publish
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/download-artifact@v4
with:
name: publish-record-${{ github.run_id }}
- name: Render
id: render
run: |
jq -r --arg outcome "${{ needs.publish.result }}" '
def pkgs: [.packages[] | select(test("-debug-")|not)] | join(", ");
def src: if .source=="pr-artifact" then "PR artifact" elif .source=="built" then "built here" elif .source=="already-published" then "already published, skipped" else "**" + .source + "**" end;
"### Publish " + (if $outcome=="success" then "succeeded" else "FAILED" end) +
" → **" + .target + "**" + (if .target!="live" then " (proof prefix, not live)" else "" end),
"",
"Packages: " + ([.sources[] | .package + " (" + .arch + ", " + src + ")"] | join("; ")),
"",
(if (.slots|length) > 0 then (.slots[] | "- `" + .mirror + "/" + .arch + "`: " + (if .status=="published" then "" else "**" + .status + "** " end) + pkgs)
elif ([.sources[] | select(.source=="already-published")] | length) == (.sources|length) then "_Nothing to publish: already at master'"'"'s version everywhere._"
else "_Nothing was published._" end),
"",
(if ([.sources[] | select(.source|test("failed"))] | length) > 0 then "_A build failed, so no channel was touched._\n"
elif (.plan|length) > (.slots|length) then "_Some planned slots did not run because an earlier slot failed._\n" else "" end),
"Commit " + .commit[0:7] + " · [run](" + .run + ")"
' publish-record.json > comment.md
cat comment.md
- name: Append to the publish log in the bucket
env:
RCLONE_CONFIG_R2_TYPE: s3
RCLONE_CONFIG_R2_PROVIDER: Cloudflare
RCLONE_CONFIG_R2_NO_CHECK_BUCKET: "true"
RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
RCLONE_CONFIG_R2_ENDPOINT: ${{ secrets.R2_ENDPOINT }}
run: |
curl -fsSL https://downloads.rclone.org/rclone-current-linux-amd64.zip -o rclone.zip && unzip -jq rclone.zip '*/rclone' && chmod +x rclone
# One JSON object per line, newest last. Served at
# https://pkgs.omarchy.org/publish-log.jsonl
./rclone copy R2:omarchy-pkgs/publish-log.jsonl . --s3-no-head 2>/dev/null || : > publish-log.jsonl
jq -c . publish-record.json >> publish-log.jsonl
./rclone copyto publish-log.jsonl R2:omarchy-pkgs/publish-log.jsonl --s3-no-head
echo "log now has $(wc -l < publish-log.jsonl) entries"
- name: Comment on the merged PR
# Only for a push: the merge commit names its PR. A dispatch runs
# from master's head, whose PR merged something else entirely, so
# commenting there would attach this run's report to the wrong PR.
if: github.event_name == 'push'
env:
GH_TOKEN: ${{ github.token }}
run: |
pr=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" --jq '.[0].number // empty')
if [[ -n "$pr" ]]; then
gh pr comment "$pr" -R "${{ github.repository }}" --body-file comment.md
echo "commented on #$pr"
else
echo "no PR for ${{ github.sha }} (manual dispatch?); skipping PR comment"
fi
result:
needs: [changes, publish]
if: always()
runs-on: ubuntu-latest
steps:
- run: |
echo "publish result: ${{ needs.publish.result }}"
[[ "${{ needs.publish.result }}" == "success" || "${{ needs.publish.result }}" == "skipped" ]]
+16 -2
View File
@@ -1,9 +1,11 @@
name: Tests
# PR-only. Publishing on push has its own workflow and is what verifies the
# merged tree: it resolves every package against the live channel and refuses
# a filename that already exists with different bytes, so two PRs cannot land
# the same version twice. A post-merge test run would only repeat the PR's.
on:
pull_request:
push:
branches: [master]
workflow_dispatch:
jobs:
@@ -30,6 +32,12 @@ jobs:
with:
persist-credentials: false
- name: Test PR workflow approval
run: node --test tests/pr-workflow-approval.cjs
- name: Test builder images
run: node --test tests/builder-image.cjs
# An Arch container for vercmp: version ordering has to be decided by
# the same comparator pacman uses on users' machines.
- name: Run self-tests
@@ -40,10 +48,16 @@ jobs:
archlinux:base-devel bash -lc '
set -euo pipefail
pacman -Syu --noconfirm git jq python libarchive
python tests/oma-service-removal.py
python tests/upstream-watch.py
./bin/sync-upstream self-test
./bin/sync-rebuilds --self-test
./bin/omarchy-pkgs self-test
./bin/omarchy-release self-test
./tests/partial-release.sh
./tests/published-build-plan.sh
./tests/controller.sh
./tests/artifact-helpers.sh
pacman -S --noconfirm --quiet rclone >/dev/null
./tests/publish-artifact.sh
'
+1
View File
@@ -40,3 +40,4 @@ pkgbuilds/yay/yay/
# Python helpers and offline tests
__pycache__/
.release-verification/
+55
View File
@@ -825,6 +825,45 @@ using real containers and pacman transactions. It uses the prepared builder
image, or an image named by `TEST_BUILDER_IMAGE`; CI builds the small fixture
image in `tests/build-isolation.Dockerfile`.
### Daily builder images
`Refresh builder images` builds fresh `edge` environments daily at 04:23 UTC,
when their inputs change on `master`, and on manual dispatch. x86_64 and
aarch64 build on native GitHub-hosted runners, without occupying the DO
package-builder pool. Each candidate must pass `tests/build-isolation.sh`,
including real package builds, before publication to
`ghcr.io/omacom/omarchy-pkg-builder`. Only `master` in this repository can
publish; PR workflows cannot replace the shared images.
PRs that change image inputs also build and test both candidates on native
runners, with a read-only token and no registry publication.
The compatibility tag contains the architecture, mirror, and a hash of the
entire `build/` context, including executable bits and symlink targets but
excluding checkout timestamps and ownership. This deliberately invalidates
images when mounted build scripts change too. `v1` identifies the image build
contract; change it if the invocation or compatibility rules change. Each
successful refresh also gets a run-specific tag for diagnosis and rollback.
A failed build, isolation test, or push leaves the previous compatible image
selected. Scheduled builds use `--pull --no-cache` so unchanged Dockerfiles
still pick up fresh Arch packages.
To build and test a candidate locally:
```bash
bin/builder-image key --arch x86_64 --mirror edge
bin/builder-image build --arch x86_64 --mirror edge --tag builder-candidate:test --fresh
CONTAINER_ENGINE=docker TEST_BUILDER_IMAGE=builder-candidate:test tests/build-isolation.sh
```
The workflow uses its repository `GITHUB_TOKEN` with `packages: write`; no
registry PAT is needed. **First publication needs one package setting:** GHCR
creates the package private. In the `omacom/omarchy-pkg-builder` package
settings, change visibility to **Public**, then rerun the failed refresh job.
The workflow checks anonymous registry access before advancing the compatible
tag, so fork PRs will not be directed to an image they cannot pull. Subsequent
refreshes preserve that package visibility. This change only produces images;
package jobs keep their existing behavior until image consumption is enabled.
## Version Management
Packages are only rebuilt if:
@@ -844,6 +883,22 @@ The repository includes GitHub workflows and systemd services for automated rele
1. **sync-upstream.yml** (Every 6 hours): Watches direct upstream feeds and updates owned recipes. Successful package updates reach a PR even if another feed fails; failed recipes stay untouched and the workflow remains red.
2. **sync-rebuilds.yml** (Every 6 hours): Bumps pkgrel for packages whose `rebuild_on` dependencies have moved in the official repositories and opens a PR.
To approve builds for an unvouched contributor's PR, apply **`build-approved`**.
Until approval, the PR shows **Awaiting build approval** and its required
`result` check stays pending, keeping the PR blocked from merging without
reporting a failed build. Actual build failures and denouncements still fail.
Applying the label triggers a package build and automatically releases GitHub's
pending build and test workflows for that PR's current commit. The approval workflow
runs only trusted default-branch code; package builds and tests stay in the
ordinary PR workflows. It may take a few minutes for GitHub to register and
release all the runs.
The label stays effective for that PR while attached, including later commits;
it does not vouch for the author's other PRs. Removing it stops further label
approvals, but does not cancel runs already released. An explicit denouncement
in `.github/VOUCHED.td` still blocks builds. If the approval workflow times out,
remove and reapply the label to retry.
#### Systemd Services
All four units run **every 5 minutes**, staggered by a minute each, so a push
+3
View File
@@ -92,6 +92,8 @@ while [[ $# -gt 0 ]]; do
echo " OMARCHY_KEEP_BUILD_WORKSPACE=1 keep build-output and reuse packages already there"
echo " OMARCHY_SKIP_BUILDER_IMAGE=1 use the existing builder image instead of building it"
echo " OMARCHY_DEFER_RUNTIME_DEPS=true build the omarchy pair with --nodeps (transaction verified later)"
echo " OMARCHY_PUBLISHED_REPO_URL=<url> channel to plan and resolve against when no local tree exists"
echo " (default https://pkgs.omarchy.org; empty disables the fallback)"
echo ""
exit 0
;;
@@ -256,6 +258,7 @@ DOCKER_ARGS=(
-e MIRROR="$MIRROR"
-e PACKAGES="$PACKAGES"
-e OMARCHY_RC_PINS="${OMARCHY_RC_PINS:-}"
-e OMARCHY_PUBLISHED_REPO_URL="${OMARCHY_PUBLISHED_REPO_URL-https://pkgs.omarchy.org}"
-e DEFER_RUNTIME_DEPS="$DEFER_RUNTIME_DEPS"
-e BUILD_PLAN_DIR=/build-plan
-v "$PLAN_DIR:/build-plan"
+54
View File
@@ -0,0 +1,54 @@
#!/bin/bash
# Print the PR build matrix for a set of package directories as JSON: one
# entry per package per supported architecture. Every package builds exactly
# once, against edge, and that one artifact is what every channel ships:
# channels are databases over a shared pool of files, and a filename must
# mean one set of bytes. "channels" lists where the artifact is published on
# merge: edge for everything, plus rc and stable immediately for the fast
# ring. Eligibility comes from package_builds_for_mirror, the rule the
# release host uses, so CI and the host cannot disagree.
#
# Usage: build-matrix [--arch <arch>|all] <package>...
# Reads package names on stdin when none are given. With no --arch, every
# architecture in CI_ARCHES (default "x86_64 aarch64") the package supports.
# Output: {"include":[{"package":"x","arch":"x86_64","channels":"edge rc stable","publish_arches":"x86_64"},...]}
# arch is where it builds; publish_arches lists every architecture
# database the file goes into (all of them for arch=any).
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/paths.sh"
source "$BUILD_ROOT/helpers/package-metadata.sh"
ARCHES=${CI_ARCHES:-x86_64 aarch64}
if [[ "${1:-}" == --arch ]]; then [[ $2 == all ]] || ARCHES=$2; shift 2; fi
for a in $ARCHES; do require_valid_arch "$a"; done
if (( $# )); then names=("$@"); else mapfile -t names; fi
entries=()
for name in "${names[@]}"; do
[[ -n "$name" ]] || continue
pkgdir="$PKGBUILDS_DIR/$name"
[[ -d "$pkgdir" ]] || continue
# skip_build packages still build on their own PR (explicit --package
# semantics); the host's unscoped runs are what skip them.
channels=""
for mirror in $VALID_MIRRORS; do
package_builds_for_mirror "$pkgdir" "$mirror" && channels="$channels $mirror"
done
channels=${channels# }
[[ -n "$channels" ]] || continue
# An arch=any package produces one architecture-independent file, so it
# builds once, on the first architecture, and that file serves every
# channel database of every architecture.
if [[ " $(package_arches "$pkgdir" "${ARCHES%% *}") " == *" any "* ]]; then
entries+=("$(jq -nc --arg p "$name" --arg a "${ARCHES%% *}" --arg c "$channels" --arg pa "$ARCHES" '{package:$p, arch:$a, channels:$c, publish_arches:$pa}')")
continue
fi
for arch in $ARCHES; do
package_supports_arch "$pkgdir" "$arch" || continue
entries+=("$(jq -nc --arg p "$name" --arg a "$arch" --arg c "$channels" '{package:$p, arch:$a, channels:$c, publish_arches:$a}')")
done
done
printf '%s\n' "${entries[@]}" | jq -sc '{include: .}'
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
# Build a reusable package environment from this checkout's own inputs.
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/paths.sh"
usage() {
echo "Usage: bin/builder-image {key|build} [--arch x86_64|aarch64] [--mirror edge|rc|stable] [--tag IMAGE] [--fresh]"
}
command=${1:-}
[[ $# -eq 0 ]] || shift
tag=""
fresh=false
while (( $# )); do
case "$1" in
--arch) ARCH=${2:?Missing architecture}; shift 2 ;;
--mirror) MIRROR=${2:?Missing mirror}; shift 2 ;;
--tag) tag=${2:?Missing image tag}; shift 2 ;;
--fresh) fresh=true; shift ;;
*) usage >&2; exit 1 ;;
esac
done
require_valid_arch "$ARCH"
validate_mirror "$MIRROR" || { echo "Invalid mirror: $MIRROR" >&2; exit 1; }
case "$command" in key|build) ;; *) usage >&2; exit 1 ;; esac
if [[ $command == key && ( -n $tag || $fresh == true ) ]]; then
usage >&2
exit 1
fi
# Include the whole build context, conservatively including mounted build
# scripts too. Normalize timestamps and ownership so fresh checkouts agree;
# retain file contents, names, executable bits and symlink targets. Bump v1
# if the image build invocation or this compatibility contract changes.
hash=$(tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner \
--format=gnu -cf - -C "$BUILD_DIR" . | sha256sum | cut -d' ' -f1)
key="v1-$ARCH-$MIRROR-$hash"
if [[ $command == key ]]; then
echo "$key"
exit 0
fi
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/docker-helpers.sh"
check_engine
platform=$(get_platform_arg "$ARCH")
tag=${tag:-omarchy-pkg-builder:latest-$ARCH-$MIRROR}
revision=$(git -C "$BUILD_ROOT" rev-parse HEAD 2>/dev/null || echo unknown)
args=("$platform" --build-arg "MIRROR=$MIRROR"
--label "org.omarchy.builder.key=$key"
--label "org.opencontainers.image.source=https://github.com/omacom/omarchy-pkgs"
--label "org.opencontainers.image.revision=$revision"
--label "org.opencontainers.image.created=$(date -u +%Y-%m-%dT%H:%M:%SZ)"
--tag "$tag" --file "$BUILD_DIR/Dockerfile")
if [[ $fresh == true ]]; then
# A daily build must refresh Arch even when its Dockerfile has not changed.
args+=(--no-cache)
if [[ $CONTAINER_ENGINE == docker ]]; then args+=(--pull); else args+=(--pull=always); fi
fi
if [[ $CONTAINER_ENGINE == docker ]]; then
docker buildx build --load "${args[@]}" "$BUILD_DIR"
else
podman build "${args[@]}" "$BUILD_DIR"
fi
+2 -2
View File
@@ -172,8 +172,8 @@ check_package() {
# it because that exact filename is already published with different bytes.
# If the artifact for this version already exists in the channel, there is
# nothing to build regardless of which direction the versions differ.
if compgen -G "$REPO_ROOT/$mirror/$ARCH/${pkg}-${pkgbuild_version}-*.pkg.tar."[!s]* >/dev/null 2>&1; then
print_warning "$pkg $pkgbuild_version is already published — this checkout is behind the channel; not queueing"
if package_version_is_published "$REPO_ROOT/$mirror/$ARCH" "$pkg" "$pkgbuild_version" "$ARCH"; then
print_warning "$pkg $pkgbuild_version is already published; not queueing"
return 1
fi
+118
View File
@@ -0,0 +1,118 @@
#!/bin/bash
# Publish built packages into one channel of the remote repository,
# incrementally and immutably.
#
# publish-artifact --mirror <edge|rc|stable> --arch <arch> <pkg files...>
#
# What it does, in order:
# 1. pull the channel's current database from the remote
# 2. refuse if any package filename already exists on the remote
# 3. sign each package (GPG_PRIVATE_KEY / GPG_PASSPHRASE)
# 4. repo-add the packages into the pulled database (replaces the entry
# for that name; nothing else in the channel is touched)
# 5. upload packages, then signatures, then the database last
#
# Never overwrites: uploads use --ignore-existing for packages and the
# pre-check in step 2 makes a same-name collision a hard failure rather than
# a silent skip. The database is the only object rewritten, and it is
# uploaded only after every file it references is present.
#
# The remote is an rclone remote (REMOTE, default the production one);
# OMARCHY_PUBLISH_PREFIX can point a proof run at a scratch prefix.
set -euo pipefail
BUILD_ROOT=$(realpath "${BASH_SOURCE[0]%/*}/..")
source "$BUILD_ROOT/helpers/message-helpers.sh"
source "$BUILD_ROOT/helpers/paths.sh"
REMOTE=${REMOTE:-pkgs.omarchy.org:omarchy-pkgs}
PREFIX=${OMARCHY_PUBLISH_PREFIX:-}
FILES=()
while [[ $# -gt 0 ]]; do
case $1 in
--mirror) MIRROR=$2; require_valid_mirror "$MIRROR"; shift 2 ;;
--arch) ARCH=$2; require_valid_arch "$ARCH"; shift 2 ;;
--remote) REMOTE=$2; shift 2 ;;
-h|--help) sed -n '2,22p' "$0"; exit 0 ;;
-*) print_error "Unknown option: $1"; exit 1 ;;
*) FILES+=("$1"); shift ;;
esac
done
(( ${#FILES[@]} )) || { print_error "No package files given"; exit 1; }
: "${GPG_PRIVATE_KEY:?}"; GPG_PASSPHRASE=${GPG_PASSPHRASE-}
DEST="$REMOTE/${PREFIX:+$PREFIX/}$MIRROR/$ARCH"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
print_header "Publish to $DEST"
# --- 0. sanity: every file is a package, named as makepkg names it ---------
for f in "${FILES[@]}"; do
[[ -f "$f" && "$f" == *.pkg.tar.* && "$f" != *.sig ]] || { print_error "Not a package file: $f"; exit 1; }
name=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="pkgname"{print $2}')
ver=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="pkgver"{print $2}')
pkgarch=$(bsdtar -xOf "$f" .PKGINFO | awk -F' = ' '$1=="arch"{print $2}')
[[ "$(basename "$f")" == "$name-$ver-$pkgarch.pkg.tar."* ]] || {
print_error "Filename does not match PKGINFO ($name-$ver-$pkgarch): $(basename "$f")"; exit 1; }
[[ "$pkgarch" == any || "$pkgarch" == "$ARCH" ]] || { print_error "$f is $pkgarch, publishing to $ARCH"; exit 1; }
done
# --- 1. pull the current database -----------------------------------------
mkdir -p "$WORK/repo"
listing=$(rclone lsf "$DEST/" --s3-no-head 2>/dev/null || true)
if grep -q '^omarchy.db.tar.zst$' <<<"$listing"; then
rclone copy "$DEST/omarchy.db.tar.zst" "$WORK/repo" --s3-no-head
rclone copy "$DEST/omarchy.files.tar.zst" "$WORK/repo" --s3-no-head 2>/dev/null || true
print_info "Pulled current database ($(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') entries)"
else
print_warning "No database at $DEST — creating a new one"
fi
# --- 2. same-name collisions ----------------------------------------------
# A filename must mean one set of bytes across every channel. The same file
# reaching a channel that already holds it (a fast-ring publish after edge,
# a re-run, a later promotion) is fine: it is skipped on upload and only the
# database entry is added. Different bytes under a name the channel already
# has is the one thing this must never do.
for f in "${FILES[@]}"; do
b=$(basename "$f")
grep -qxF "$b" <<<"$listing" || continue
remote_sum=$(rclone hashsum md5 "$DEST/$b" --s3-no-head 2>/dev/null | awk '{print $1}')
local_sum=$(md5sum "$f" | awk '{print $1}')
if [[ -n "$remote_sum" && "$remote_sum" == "$local_sum" ]]; then
print_info "Already published with identical bytes, adding to the database only: $b"
else
print_error "Already published with DIFFERENT bytes, refusing to overwrite: $b"
echo " Bump pkgrel; published filenames are immutable."
exit 1
fi
done
# --- 3. sign ---------------------------------------------------------------
export GNUPGHOME="$WORK/gnupg"; mkdir -m 700 "$GNUPGHOME"
echo "$GPG_PRIVATE_KEY" | gpg --batch --quiet --import
KEY_ID=$(gpg --list-secret-keys --with-colons | awk -F: '$1=="sec"{print $5; exit}')
[[ -n "$KEY_ID" ]] || { print_error "No secret key imported"; exit 1; }
for f in "${FILES[@]}"; do
cp "$f" "$WORK/repo/"
gpg --batch --yes --pinentry-mode loopback --passphrase "$GPG_PASSPHRASE" \
--detach-sign --no-armor --local-user "$KEY_ID" "$WORK/repo/$(basename "$f")"
print_step "signed $(basename "$f")"
done
# --- 4. repo-add (replaces the entry for each pkgname) ---------------------
( cd "$WORK/repo" && repo-add --quiet omarchy.db.tar.zst "${FILES[@]/#*\//}" )
ln -sf omarchy.db.tar.zst "$WORK/repo/omarchy.db"
ln -sf omarchy.files.tar.zst "$WORK/repo/omarchy.files"
print_info "Database now has $(tar -tf "$WORK/repo/omarchy.db.tar.zst" | grep -c '/$') entries"
# --- 5. upload: packages, signatures, database last -----------------------
rclone copy "$WORK/repo" "$DEST" --s3-no-head --ignore-existing --filter '- *.sig' --filter '+ *.pkg.tar.*' --filter '- *'
rclone copy "$WORK/repo" "$DEST" --s3-no-head --ignore-existing --filter '+ *.pkg.tar.*.sig' --filter '- *'
# Re-verify every referenced file is really there before the db goes up.
listing=$(rclone lsf "$DEST/" --s3-no-head)
for f in "${FILES[@]}"; do
b=$(basename "$f")
grep -qxF "$b" <<<"$listing" && grep -qxF "$b.sig" <<<"$listing" || { print_error "Upload incomplete: $b"; exit 1; }
done
rclone copy "$WORK/repo" "$DEST" --s3-no-head --checksum --copy-links --filter '+ omarchy.db*' --filter '+ omarchy.files*' --filter '- *'
print_success "Published ${#FILES[@]} package(s) to $DEST"
+3 -1
View File
@@ -129,6 +129,7 @@ RUN pacman -Syu --noconfirm && \
wget \
curl \
jq \
rclone \
gnupg && \
pacman -Scc --noconfirm && \
rm -rf /var/cache/pacman/pkg/*
@@ -146,7 +147,8 @@ RUN useradd -m -G wheel -s /bin/bash builder && \
# be skipped at signing. Pin the extension so both architectures match.
RUN sed -i 's/^#MAKEFLAGS=.*/MAKEFLAGS="-j$(nproc)"/' /etc/makepkg.conf && \
sed -i 's/^COMPRESSZST=.*/COMPRESSZST=(zstd -c -z -q --threads=0 -)/' /etc/makepkg.conf && \
sed -i "s|^PKGEXT=.*|PKGEXT='.pkg.tar.zst'|" /etc/makepkg.conf
sed -i "s|^PKGEXT=.*|PKGEXT='.pkg.tar.zst'|" /etc/makepkg.conf && \
sed -i 's|^#\?PACKAGER=.*|PACKAGER="Omarchy <pkgs@omarchy.org>"|' /etc/makepkg.conf
# Pacman wrapper that auto-resolves package conflicts (e.g. rustup vs rust).
# makepkg only passes --noconfirm to pacman, which defaults to 'N' on conflict
+41 -5
View File
@@ -26,6 +26,29 @@ DEFER_RUNTIME_DEPS=${DEFER_RUNTIME_DEPS:-false}
source "$HELPERS_DIR/package-metadata.sh"
# Where the channel's published database is read from for planning. On the
# repository host it is the published tree itself. Anywhere else (a CI runner,
# a fresh clone) that tree is absent, so the database is fetched from the
# public channel and the same URL serves as pacman's dependency repository.
# Set OMARCHY_PUBLISHED_REPO_URL= (empty) to disable the remote fallback.
PUBLISHED_REPO_URL=${OMARCHY_PUBLISHED_REPO_URL-https://pkgs.omarchy.org}
PUBLISHED_DB_DIR="$FINAL_OUTPUT_DIR"
PUBLISHED_REPO_SERVER=""
if [[ ! -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" && ! -f "$FINAL_OUTPUT_DIR/omarchy.db" && -n "$PUBLISHED_REPO_URL" ]]; then
remote_channel="$PUBLISHED_REPO_URL/$MIRROR/$ARCH"
remote_db_dir=$(mktemp -d /tmp/omarchy-published.XXXXXX) || exit 1
# Cache-bust: the channel sits behind a CDN that serves a stale database
# for a while after a sync.
if curl -fsSL "$remote_channel/omarchy.db.tar.zst?$(date +%s)" -o "$remote_db_dir/omarchy.db.tar.zst"; then
PUBLISHED_DB_DIR="$remote_db_dir"
PUBLISHED_REPO_SERVER="$remote_channel"
echo "==> No local published tree; planning against $remote_channel"
else
rm -rf "$remote_db_dir"
echo "==> No local published tree and $remote_channel is unavailable; treating the channel as empty"
fi
fi
if [[ $DEFER_RUNTIME_DEPS != "false" && $DEFER_RUNTIME_DEPS != "true" ]]; then
echo "DEFER_RUNTIME_DEPS must be true or false" >&2
exit 1
@@ -118,10 +141,15 @@ if [[ "$DRY_RUN" != true ]]; then
fi
touch "$BUILD_PLAN_DIR/repository-initialized" || exit 1
# Add omarchy repo if it has a database (stable packages)
# Add omarchy repo if it has a database (stable packages). The local tree
# is trusted as-is; the public channel is verified against the omarchy
# keyring the image already carries.
if [[ -f "$FINAL_OUTPUT_DIR/omarchy.db.tar.zst" ]] || [[ -f "$FINAL_OUTPUT_DIR/omarchy.db" ]]; then
sudo sed -i "/^\[core\]$/i [omarchy]\nSigLevel = Optional TrustAll\nServer = file://$FINAL_OUTPUT_DIR\n" /etc/pacman.conf
echo " -> omarchy (priority 2): $FINAL_OUTPUT_DIR"
elif [[ -n "$PUBLISHED_REPO_SERVER" ]]; then
sudo sed -i "/^\[core\]$/i [omarchy]\nSigLevel = Required DatabaseOptional\nServer = $PUBLISHED_REPO_SERVER\n" /etc/pacman.conf
echo " -> omarchy (priority 2): $PUBLISHED_REPO_SERVER"
fi
# Sync pacman database
@@ -159,10 +187,10 @@ LOCAL_VERSION_CACHE_LOADED=false
LOCAL_VERSION_CACHE_DB=""
load_local_versions() {
local db="$FINAL_OUTPUT_DIR/omarchy.db.tar.zst"
local db="$PUBLISHED_DB_DIR/omarchy.db.tar.zst"
if [[ ! -f "$db" ]]; then
db="$FINAL_OUTPUT_DIR/omarchy.db"
db="$PUBLISHED_DB_DIR/omarchy.db"
fi
[[ -f "$db" ]] || return 0
@@ -531,9 +559,17 @@ check_needs_build() {
if [[ "$local_version" == "$pkgbuild_version" ]]; then
return 1 # Already up to date
else
return 0 # Needs building
fi
# Match check-versions: a retained archive is already published even when
# the DB now indexes a newer release (for example, 4.0.4rc1 vs 4.0.3).
# Rebuilding it would produce different bytes under an immutable filename.
if package_version_is_published "$FINAL_OUTPUT_DIR" "$pkg" "$pkgbuild_version" "$ARCH"; then
echo " + $pkg $pkgbuild_version - archive already published; skipping rebuild"
return 1
fi
return 0 # Needs building
}
# Collect packages that should be built for the selected mirror
+79
View File
@@ -0,0 +1,79 @@
# CI spike: build PRs on ephemeral DigitalOcean droplets
Status: spike. Nothing here publishes. The repository host keeps building and
signing on merge exactly as before.
## Pieces
- `.github/workflows/build-pr.yml` — on a PR touching `pkgbuilds/**`, one job
per changed package on runners labelled `omarchy-builder`. Uploads the
unsigned `.pkg.tar.zst` as a workflow artifact (7 days).
- `runner-cloud-init.yaml` — Ubuntu 24.04 user-data: docker + buildx, the
GitHub runner registered `--ephemeral`, runs one job, powers off.
- `controller.sh` — systemd timer every minute on a small always-on droplet.
Polls for queued jobs with our label, creates one g5-32vcpu-64gb-50gb droplet (ric1) per job up
to `MAX_DROPLETS`, deletes droplets that are powered off or older than
`MAX_AGE_MINUTES`. No inbound endpoint. Plain curl against both APIs, no
doctl and no gh: a token in the environment cannot pick the wrong account
the way a saved doctl context can. Needs curl and jq.
`tests/controller.sh` exercises every decision against canned responses.
- `controller-box/` — the always-on droplet: unit, timer, env template,
cloud-init, and `create.sh` to stand it up with one API call.
## Standing up the controller box
DIGITALOCEAN_TOKEN=<omarchy account> GITHUB_TOKEN=<fine-grained PAT> \
REPO=omacom/omarchy-pkgs ci/controller-box/create.sh <branch>
The GitHub PAT is fine-grained, scoped to the one repo: Actions read,
Administration read+write (registration tokens). The DO token is baked into
the box's env file, so it is the account that pays for builder droplets.
Watch it with `journalctl -u omarchy-controller -f` on the box.
## What the spike proved (2026-09-17, fork ryanrhughes/omarchy-pkgs)
- `bin/build` works from a bare clone: with no local published tree it
plans against and resolves from `https://pkgs.omarchy.org/<mirror>/<arch>`.
- Droplet create → runner registered: ~70 s. omarchy-fish PR job: 2 min
including the builder image build. Droplet powers off after the job.
- linux-omarchy on a c-32 droplet: 30 min wall clock for the build job
(23:39 → 00:09), 254 MB artifact. Cold start ~90 s before the job began.
- A PR whose PKGBUILD fails to build turns the required check red and GitHub
refuses the merge (`mergeStateStatus=BLOCKED`, `gh pr merge` refuses
without `--admin`).
- Controller: one queued job + one busy droplet ⇒ creates exactly one more;
reaps powered-off droplets on the next tick.
## Not done (required before this touches the real repo)
- Tooling from base: check out master's `bin/ helpers/ build/` and overlay
only the PR's `pkgbuilds/<name>`; today a PR can edit the build script
and it runs on the droplet. The vouch gate limits who can do that, not
what they can do.
- DigitalOcean cloud firewall on the `omarchy-builder` tag: no inbound, no
egress to private ranges or the metadata address.
- A fine-grained GitHub token for the real repository (the one on the
controller box is scoped to the fork), and the publish environment's
secrets set there.
- Disable the host's auto-release timers for any channel CI publishes to,
so two writers never touch one database.
## Done since the spike README was first written
- Controller as a systemd timer on its own droplet, plain curl, self-test.
- Build once against edge; one artifact per package per architecture,
published into every channel it belongs to (fast ring: all three at
once). arch=any builds once for every architecture database.
- Publish is incremental and immutable: pull the channel db, refuse
different bytes under an existing name, accept identical bytes, upload
packages then signatures then the db.
- aarch64 under QEMU with credential-preserving binfmt.
- Vouch gate: collaborators, `.github/VOUCHED.td`, or the `build-approved`
label; denounced authors cannot be overridden by the label.
- Tests run on PRs only; `result`, `self-tests`, `build-isolation` are the
required checks with strict up-to-date branches.
## Cleanup
doctl compute droplet list --tag-name omarchy-builder
doctl compute droplet delete -f <id>
+45
View File
@@ -0,0 +1,45 @@
#cloud-config
# The always-on controller droplet (smallest size is fine). Clones the repo
# for ci/controller.sh, installs the unit and timer, and starts polling.
#
# Substitute before use:
# __REPO_URL__ https://github.com/omacom/omarchy-pkgs.git
# __BRANCH__ branch carrying ci/ (master once merged)
# __ENV_B64__ base64 of a filled-in controller.env.example
# __SSH_KEYS_JSON__ JSON array of public keys authorized for root
package_update: true
packages: [curl, jq, git]
# Root stays reachable by key so the journal can be read. Two things stand
# in the way on DO images: disable_root rewrites root's keys into a stub, and
# with no account ssh key attached DO expires root's password, which makes
# sshd refuse every non-interactive session with "password change required".
disable_root: false
chpasswd:
expire: false
ssh_authorized_keys: __SSH_KEYS_JSON__
users:
- name: controller
shell: /bin/bash
write_files:
# defer: write after the users module has created the controller group,
# otherwise chown to root:controller fails and the unit cannot read this.
- path: /etc/omarchy-controller.env
permissions: "0640"
owner: root:controller
encoding: b64
defer: true
content: __ENV_B64__
runcmd:
- chage -d "$(date +%F)" -M -1 root
- chown root:controller /etc/omarchy-controller.env && chmod 0640 /etc/omarchy-controller.env
- git clone --depth 1 --branch __BRANCH__ __REPO_URL__ /opt/omarchy-pkgs
- mkdir -p /run/omarchy-controller && chown controller:controller /run/omarchy-controller
- echo "d /run/omarchy-controller 0755 controller controller -" > /etc/tmpfiles.d/omarchy-controller.conf
# runcmd is executed by /bin/sh: no brace expansion.
- cp /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.service /opt/omarchy-pkgs/ci/controller-box/omarchy-controller.timer /etc/systemd/system/
- systemctl daemon-reload
- systemctl enable --now omarchy-controller.timer
+15
View File
@@ -0,0 +1,15 @@
# /etc/omarchy-controller.env — mode 0600, owned by root, read by systemd.
DIGITALOCEAN_TOKEN=dop_v1_...
# Fine-grained PAT scoped to the repo: Actions: read, Administration: read+write
GITHUB_TOKEN=github_pat_...
REPO=omacom/omarchy-pkgs
LABEL=omarchy-builder
TAG=omarchy-builder
REGION=ric1
SIZE=g5-32vcpu-64gb-50gb
MAX_DROPLETS=6
MAX_AGE_MINUTES=200
LOCK=/run/omarchy-controller/lock
# Operator public keys for root on every builder droplet (JSON array).
# create.sh fills this from the operators' GitHub keys.
SSH_KEYS_JSON=[]
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# Create the controller droplet with plain curl. Run from a laptop, once.
#
# DIGITALOCEAN_TOKEN=... GITHUB_TOKEN=... ci/controller-box/create.sh [branch]
#
# The DO token given here is baked into the box's env file, so it must be the
# token for the account that should pay for builder droplets.
set -euo pipefail
here=$(dirname "$0")
: "${DIGITALOCEAN_TOKEN:?}" "${GITHUB_TOKEN:?}"
REPO=${REPO:-omacom/omarchy-pkgs}
BRANCH=${1:-master}
REGION=${REGION:-ric1}
NAME=${NAME:-omarchy-controller}
# Optional DO ssh key ids as a JSON array, e.g. SSH_KEYS='[123]', for reading
# the journal while bringing the box up. Not needed once it works.
SSH_KEYS=${SSH_KEYS:-[]}
# Public keys authorized for root: the operators' GitHub keys, fetched at
# creation so the box never depends on an ssh_key API scope. Override with
# ADMIN_GITHUB_USERS.
ADMIN_GITHUB_USERS=${ADMIN_GITHUB_USERS:-ryanrhughes dhh}
ssh_keys_json=$(for u in $ADMIN_GITHUB_USERS; do curl -fsS "https://github.com/$u.keys"; done | jq -R . | jq -sc .)
[[ $(jq length <<<"$ssh_keys_json") -gt 0 ]] || { echo "no ssh keys fetched for $ADMIN_GITHUB_USERS" >&2; exit 1; }
env_file=$(sed -e "s|^DIGITALOCEAN_TOKEN=.*|DIGITALOCEAN_TOKEN=$DIGITALOCEAN_TOKEN|" \
-e "s|^GITHUB_TOKEN=.*|GITHUB_TOKEN=$GITHUB_TOKEN|" \
-e "s|^REPO=.*|REPO=$REPO|" \
-e "s|^SSH_KEYS_JSON=.*|SSH_KEYS_JSON=$ssh_keys_json|" "$here/controller.env.example")
userdata=$(sed -e "s|__REPO_URL__|https://github.com/$REPO.git|" -e "s|__BRANCH__|$BRANCH|" \
-e "s|__ENV_B64__|$(printf '%s\n' "$env_file" | base64 -w0)|" \
-e "s|__SSH_KEYS_JSON__|$ssh_keys_json|" "$here/cloud-init.yaml")
body=$(jq -n --arg name "$NAME" --arg region "$REGION" --arg ud "$userdata" --argjson keys "$SSH_KEYS" \
'{name:$name, region:$region, size:"s-1vcpu-1gb", image:"ubuntu-24-04-x64", tags:["omarchy-controller"], user_data:$ud, ssh_keys:$keys}')
# Refuse to create a second one.
existing=$(curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
"https://api.digitalocean.com/v2/droplets?tag_name=omarchy-controller" | jq '.droplets | length')
if (( existing > 0 )); then echo "a controller droplet already exists" >&2; exit 1; fi
curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" -H "Content-Type: application/json" \
-X POST -d "$body" https://api.digitalocean.com/v2/droplets | jq -r '"created \(.droplet.name) id=\(.droplet.id)"'
@@ -0,0 +1,12 @@
[Unit]
Description=Provision ephemeral omarchy-builder runner droplets for queued jobs
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=controller
EnvironmentFile=/etc/omarchy-controller.env
ExecStart=/opt/omarchy-pkgs/ci/controller.sh
# The reaper's safety net is time, not state; a hung tick must not hold the lock.
TimeoutStartSec=240
@@ -0,0 +1,10 @@
[Unit]
Description=Run the omarchy-builder controller every minute
[Timer]
OnBootSec=1min
OnUnitActiveSec=1min
AccuracySec=5s
[Install]
WantedBy=timers.target
+125
View File
@@ -0,0 +1,125 @@
#!/bin/bash
# Droplet-per-job controller for the omarchy-builder runner pool.
#
# Run from a systemd timer every minute on a small always-on droplet. No
# inbound endpoint: it polls GitHub for queued jobs wanting our label, creates
# one ephemeral droplet per job (up to MAX_DROPLETS), and deletes droplets
# that have powered off or exceeded MAX_AGE_MINUTES. The reaper does not
# trust its own bookkeeping: it lists by tag and acts on what DigitalOcean
# reports.
#
# Talks to both APIs with curl. No doctl: its saved contexts silently choose
# an account; a token in the environment cannot. Needs curl and jq.
#
# Environment:
# DIGITALOCEAN_TOKEN DO API token for the account that pays for droplets
# GITHUB_TOKEN fine-grained PAT: Actions read, Administration write
# REPO owner/name
set -euo pipefail
REPO=${REPO:?owner/name}
: "${DIGITALOCEAN_TOKEN:?}" "${GITHUB_TOKEN:?}"
LABEL=${LABEL:-omarchy-builder}
TAG=${TAG:-omarchy-builder}
REGION=${REGION:-ric1}
SIZE=${SIZE:-g5-32vcpu-64gb-50gb}
IMAGE=${IMAGE:-ubuntu-24-04-x64}
MAX_DROPLETS=${MAX_DROPLETS:-4}
MAX_AGE_MINUTES=${MAX_AGE_MINUTES:-200}
RUNNER_VERSION=${RUNNER_VERSION:-2.337.0}
CLOUD_INIT=${CLOUD_INIT:-$(dirname "$0")/runner-cloud-init.yaml}
# Operator public keys authorized on every builder (JSON array of strings).
# The box's env file carries them; empty means no root login.
SSH_KEYS_JSON=${SSH_KEYS_JSON:-[]}
LOCK=${LOCK:-/tmp/omarchy-controller.lock}
log() { echo "$(date '+%F %T') $*"; }
# The only two places the outside world is touched. The self-test overrides
# both, so every decision below is exercised against canned responses.
do_api() { # do_api <path> [curl args...]
local path=$1; shift
curl -fsS -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
-H "Content-Type: application/json" "https://api.digitalocean.com/v2/$path" "$@"
}
gh_api() { # gh_api <path> [curl args...]
local path=$1; shift
curl -fsS -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" "https://api.github.com/$path" "$@"
}
# --- reap ------------------------------------------------------------------
reap() {
local now id status created age
now=$(date +%s)
while read -r id status created; do
[[ -n "$id" ]] || continue
age=$(( (now - $(date -d "$created" +%s)) / 60 ))
if [[ $status == off ]] || (( age > MAX_AGE_MINUTES )); then
log "deleting droplet $id (status=$status age=${age}m)"
do_api "droplets/$id" -X DELETE
fi
done < <(do_api "droplets?tag_name=$TAG&per_page=200" |
jq -r '.droplets[] | "\(.id) \(.status) \(.created_at)"')
}
# --- demand ----------------------------------------------------------------
queued_jobs() {
local run
gh_api "repos/$REPO/actions/runs?status=queued&per_page=50" --get \
| jq -r '.workflow_runs[].id' |
while read -r run; do
gh_api "repos/$REPO/actions/runs/$run/jobs" \
| jq -r --arg l "$LABEL" '.jobs[] | select(.status=="queued") | select(.labels | index($l)) | .id'
done | wc -l
}
live_droplets() {
do_api "droplets?tag_name=$TAG&per_page=200" | jq '[.droplets[] | select(.status != "off")] | length'
}
busy_runners() {
gh_api "repos/$REPO/actions/runners?per_page=100" \
| jq --arg l "$LABEL" '[.runners[] | select(.busy) | select(any(.labels[]; .name == $l))] | length'
}
# --- create ----------------------------------------------------------------
create_droplet() {
local token userdata name body
token=$(gh_api "repos/$REPO/actions/runners/registration-token" -X POST | jq -r .token)
userdata=$(sed -e "s|__REPO__|$REPO|g" -e "s|__RUNNER_TOKEN__|$token|g" \
-e "s|__RUNNER_LABELS__|$LABEL|g" -e "s|__RUNNER_VERSION__|$RUNNER_VERSION|g" \
-e "s|__SSH_KEYS_JSON__|$SSH_KEYS_JSON|" "$CLOUD_INIT")
name="$TAG-$(date +%s)-$RANDOM"
body=$(jq -n --arg name "$name" --arg region "$REGION" --arg size "$SIZE" --arg image "$IMAGE" \
--arg tag "$TAG" --arg ud "$userdata" \
'{name:$name, region:$region, size:$size, image:$image, tags:[$tag], user_data:$ud, monitoring:false}')
log "creating $name ($SIZE)"
do_api droplets -X POST -d "$body" | jq -r '"created droplet \(.droplet.id)"'
}
controller_tick() {
reap
local queued live busy available need room
queued=$(queued_jobs)
live=$(live_droplets)
busy=$(busy_runners)
# A live droplet whose runner is busy is spoken for. Only droplets still
# booting or listening can absorb a queued job.
available=$(( live - busy )); (( available < 0 )) && available=0
need=$(( queued - available ))
(( need > 0 )) || return 0
room=$(( MAX_DROPLETS - live ))
(( need > room )) && need=$room
if (( need <= 0 )); then
log "at cap ($live/$MAX_DROPLETS, $busy busy) with $queued queued"
return 0
fi
local i
for (( i = 0; i < need; i++ )); do create_droplet; done
}
if [[ "${CONTROLLER_LIBRARY_ONLY:-}" != 1 ]]; then
exec 9>"$LOCK"; flock -n 9 || exit 0
controller_tick
fi
+81
View File
@@ -0,0 +1,81 @@
#cloud-config
# Ephemeral GitHub Actions runner for omarchy-pkgs package builds.
#
# Boots an Ubuntu droplet, installs docker and the runner, registers ONCE with
# --ephemeral, runs exactly one job, then powers off. The controller (or the
# reaper) deletes the powered-off droplet. Nothing here holds a long-lived
# credential: the registration token is single-use and expires in an hour.
#
# Substitute before use:
# __REPO__ owner/name
# __RUNNER_TOKEN__ registration token (gh api -X POST repos/O/R/actions/runners/registration-token)
# __RUNNER_LABELS__ e.g. omarchy-builder
# __RUNNER_VERSION__ e.g. 2.329.0
# Operators can reach a builder by key while it lives; it powers off after
# one job anyway. Keys are substituted by the controller (__SSH_KEYS_JSON__).
disable_root: false
chpasswd:
expire: false
ssh_authorized_keys: __SSH_KEYS_JSON__
package_update: true
packages:
- docker.io
- docker-buildx
- unzip
- git
- curl
- jq
- rsync
users:
- name: runner
groups: [docker]
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
write_files:
# defer: write after users/groups exist, so /home/runner is created by
# useradd (owned by runner) rather than by this module as root.
- path: /home/runner/start.sh
permissions: "0755"
owner: runner:runner
defer: true
content: |
#!/bin/bash
set -euo pipefail
cd /home/runner
mkdir -p actions-runner && cd actions-runner
arch=$(uname -m); [[ $arch == x86_64 ]] && arch=x64
curl -fsSL -o runner.tgz \
"https://github.com/actions/runner/releases/download/v__RUNNER_VERSION__/actions-runner-linux-${arch}-__RUNNER_VERSION__.tar.gz"
tar xzf runner.tgz && rm runner.tgz
./config.sh --unattended --ephemeral \
--url "https://github.com/__REPO__" \
--token "__RUNNER_TOKEN__" \
--name "do-$(hostname)" \
--labels "__RUNNER_LABELS__" \
--replace
./run.sh
# One job done. Power off; the controller deletes powered-off droplets.
sudo poweroff
runcmd:
# With no account ssh key attached, DO expires root's password, and sshd
# then refuses every non-interactive session. Clear it first so operators
# can read the logs of a builder that never registers.
- chage -d "$(date +%F)" -M -1 root
- systemctl enable --now docker
# aarch64 builds run under user-mode emulation (DO has no arm droplets).
# Register QEMU with the F and C flags via tonistiigi/binfmt, exactly as
# helpers/docker-helpers.sh setup_qemu does. Ubuntu's qemu-user-static
# registers without C, so sudo inside the emulated container fails with
# "effective uid is not 0"; multiarch/qemu-user-static is abandoned at QEMU
# 7.2, under which qmake's compiler probe returns nothing on current gcc
# ("failed to parse default include paths", PR #517). Pin the emulator
# version: the tag is the only thing that decides what every aarch64 build
# runs under. Best-effort: an x86-only job never needs it.
- docker run --rm --privileged docker.io/tonistiigi/binfmt:qemu-v10.2.3-68 --uninstall qemu-aarch64 --install arm64 || true
- chown -R runner:runner /home/runner
- sudo -u runner /home/runner/start.sh > /home/runner/runner.log 2>&1
+2
View File
@@ -168,6 +168,8 @@ in `origin` and has no effect on release selection.
These packages were already excluded from automatic AUR updates. The migration preserves that policy.
`linux-firmware-cirrus` is a deliberate hold: a self-retiring shim that ships Arch's linux-firmware-cirrus 20260910-2 payload to stable while stable's Arch snapshot is on 20260810-2 (Dell XPS 13 DX13260 / 1028:0e54 speaker firmware). It is versioned 20260810-3 so the genuine Arch package supersedes it as soon as the snapshot advances; bumping it to the Arch version would defeat that. Delete the recipe once stable's snapshot carries linux-firmware >= 20260910.
## Package-specific boundaries
- NVIDIA watches remain on the 580 driver branch.
+44
View File
@@ -0,0 +1,44 @@
#!/bin/bash
# Package files cross from a PR build to publish.yml as one GitHub Actions
# artifact. actions/upload-artifact rejects any path containing ':', and a
# package with an epoch is named `name-1:ver-rel-arch.pkg.tar.zst` by
# makepkg. So the files ride inside a tar with a plain name and keep their
# own names untouched: pacman clients and bin/publish-artifact both rely on
# the filename matching PKGINFO.
#
# Both functions run under the workflow's `bash -e`: nothing in them may
# return non-zero except the final failure.
# package_files <dir>: the *.pkg.tar.zst directly in <dir>, one per line.
# Signatures and the scratch database next to them are not packages.
package_files() {
local f
for f in "$1"/*.pkg.tar.zst; do
[[ -e "$f" ]] && printf '%s\n' "$f"
done
return 0
}
# pack_packages <dir> <tar>: every package in <dir> into <tar>.
pack_packages() {
local dir=$1 out=$2 files=()
mapfile -t files < <(package_files "$dir")
(( ${#files[@]} )) || { echo "pack_packages: no *.pkg.tar.zst in $dir" >&2; return 1; }
tar -cf "$out" -C "$dir" -- "${files[@]##*/}"
}
# unpack_packages <artifact dir> <dest>: the packages an unzipped artifact
# carried, into <dest>. Packed artifacts hold packages.tar; artifacts from
# builds before packing hold the bare files. The bare form can go once
# those artifacts have expired (7-day retention).
unpack_packages() {
local src=$1 dest=$2 files=()
mkdir -p "$dest"
if [[ -f "$src/packages.tar" ]]; then
tar -xf "$src/packages.tar" -C "$dest"
return 0
fi
mapfile -t files < <(package_files "$src")
(( ${#files[@]} )) || { echo "unpack_packages: nothing to unpack in $src" >&2; return 1; }
cp -- "${files[@]}" "$dest/"
}
+13 -2
View File
@@ -91,8 +91,19 @@ setup_qemu() {
exit 1
fi
# 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
# Register emulators for builds whose target differs from the host, with
# the F and C flags (tonistiigi/binfmt always sets both). The image tag pins
# the QEMU version every emulated build runs under; multiarch/qemu-user-static
# stopped at QEMU 7.2, which breaks qmake's compiler probe on current gcc.
# Keep ci/runner-cloud-init.yaml on the same tag. Uninstall first: install
# leaves an existing registration (an older emulator) in place and exits 0.
local platform_arch
case "$target_arch" in
aarch64) platform_arch=arm64 ;;
x86_64) platform_arch=amd64 ;;
*) platform_arch="$target_arch" ;;
esac
if ! "$CONTAINER_ENGINE" run --rm --privileged docker.io/tonistiigi/binfmt:qemu-v10.2.3-68 --uninstall "qemu-$target_arch" --install "$platform_arch" >/dev/null 2>&1; then
print_error "Failed to set up QEMU emulation"
exit 1
fi
+12
View File
@@ -191,6 +191,18 @@ package_supports_arch() {
esac
}
# The channel DB indexes only its newest version, but older published archives
# remain immutable. Both the scheduler and build planner must skip an existing
# filename even when the checkout differs from the version currently indexed.
package_version_is_published() {
local repo_dir="$1" package="$2" version="$3" target="$4" path
for path in "$repo_dir/$package-$version-$target.pkg.tar."* \
"$repo_dir/$package-$version-any.pkg.tar."*; do
[[ -f "$path" && "$path" != *.sig ]] && return 0
done
return 1
}
# Channel membership: where a package may be published. Packages without a
# `channels` key are members of every channel (they flow edge -> rc -> stable).
package_has_channels() {
+4 -4
View File
@@ -1,6 +1,6 @@
# Maintainer: Bjarne Øverli <bjarne@oever.li>
pkgname=aether
pkgver=4.29.8
pkgver=4.30.0
pkgrel=1
pkgdesc='Desktop theming application - extract colors from wallpapers and apply cohesive themes'
arch=('x86_64' 'aarch64')
@@ -10,9 +10,9 @@ depends=('webkit2gtk-4.1' 'gtk3')
source=("aether-${pkgver}.tar.gz::https://github.com/omacom/aether/archive/refs/tags/v${pkgver}.tar.gz")
source_x86_64=("aether-linux-amd64-${pkgver}::https://github.com/omacom/aether/releases/download/v${pkgver}/aether-linux-amd64")
source_aarch64=("aether-linux-arm64-${pkgver}::https://github.com/omacom/aether/releases/download/v${pkgver}/aether-linux-arm64")
sha256sums=('b83e0eeb1332b4ed655389a051d5b9b14a3e109968b7f278005e52c5e69a1e9c')
sha256sums_x86_64=('d3d2d07b32da7a495221ed271ee66f4a1e344c91dcec9b267ec0a74ab6e36462')
sha256sums_aarch64=('ffcfd23d0375a3f0c0ce014821cc5e1670f2e061c35e991340e75352dac9b731')
sha256sums=('f67c8d2c6f27f67a755bc279ece5ddb194f1bb165648280b9a7be86904d36ff5')
sha256sums_x86_64=('75bda600ddd3ecab3338de5c0c5d5e2c9f08cfc0c465b63f8e6cb9c5cb60d68e')
sha256sums_aarch64=('a91d800736def74d86e19d8acbecc4bda3d7c3e64fb95273f809707104c3a5bc')
noextract=("aether-linux-amd64-${pkgver}" "aether-linux-arm64-${pkgver}")
package() {
@@ -0,0 +1,17 @@
{
"source": "local",
"release_ring": "fast",
"origin": {
"aur": "bambustudio-bin",
"commit": "b962c12d14873f94669e0e256a444f957b1843cd"
},
"upstream": {
"watch": {
"regex": "https://api.github.com/repos/bambulab/BambuStudio/releases/latest",
"pattern": "\"name\"\\s*:\\s*\"BambuStudio_ubuntu24\\.04-v(?P<version>[0-9]+(?:\\.[0-9]+)*)-(?P<build>[0-9]+)\\.AppImage\"",
"variables": {
"_build": "{build}"
}
}
}
}
@@ -0,0 +1,12 @@
[Desktop Entry]
Name=Bambu Studio
GenericName=3D Printing Software
Comment=Slicer for Bambu Lab and other 3D printers
Exec=/usr/bin/bambu-studio %U
Icon=BambuStudio
Terminal=false
Type=Application
Categories=Graphics;3DGraphics;Engineering;
MimeType=x-scheme-handler/bambustudio;x-scheme-handler/bambustudioopen;model/stl;model/3mf;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;
Keywords=3D;Printing;Slicer;gcode;stl;3mf;
StartupWMClass=bambu-studio
+48
View File
@@ -0,0 +1,48 @@
# Maintainer: goll <adrian.goll+aur[at]gmail>
# Contributor: George Woodall <georgewoodall82@gmail.com>
pkgname=bambustudio-bin
pkgver=02.08.02.61
pkgrel=1
pkgdesc="PC Software for BambuLab's 3D printers"
arch=("x86_64")
url="https://github.com/bambulab/BambuStudio"
license=('AGPL-3.0-only')
conflicts=('bambustudio' 'bambustudio-git')
depends=('cairo' 'dbus' 'fontconfig' 'gcc-libs' 'glib2' 'glibc'
'gst-libav' 'gst-plugins-base-libs' 'gstreamer' 'gtk3' 'libglvnd'
'libx11' 'mesa' 'pango' 'wayland' 'webkit2gtk-4.1')
makedepends=('7zip')
options=('!strip' '!debug')
# The upstream watch updates the timestamp together with pkgver.
_build=20260820225108
source=("bambustudio-${pkgver}.AppImage::https://github.com/bambulab/BambuStudio/releases/download/v${pkgver}/BambuStudio_ubuntu24.04-v${pkgver}-${_build}.AppImage"
"BambuStudio.desktop"
"bambu-studio")
noextract=("bambustudio-${pkgver}.AppImage")
sha256sums=(
'd501b103fac5424513ec0e8d6bc145fb30719de2c7d94d7320d723740c81a7fd'
'f10718a8b201cad64800746fe8167ccc032c545d05f7ad8caa99eb5fb975f2a1'
'a3a5c8f6a8b287e42b93957e9602621923c766e0b6a3f10c14eca10b023b15f2'
)
prepare() {
# Read the embedded SquashFS without executing or modifying the AppImage.
rm -rf "$srcdir/squashfs-root"
7z x "$srcdir/bambustudio-${pkgver}.AppImage" -o"$srcdir/squashfs-root" >/dev/null
}
package() {
cd "$srcdir/squashfs-root"
install -Dm755 AppRun "$pkgdir/opt/$pkgname/AppRun"
cp -a bin resources "$pkgdir/opt/$pkgname/"
local icon size
for icon in usr/share/icons/hicolor/*/apps/BambuStudio.png; do
size="${icon#usr/share/icons/hicolor/}"
install -Dm644 "$icon" "$pkgdir/usr/share/icons/hicolor/$size"
done
install -Dm755 "$srcdir/bambu-studio" "$pkgdir/usr/bin/bambu-studio"
install -Dm644 "$srcdir/BambuStudio.desktop" \
"$pkgdir/usr/share/applications/BambuStudio.desktop"
}
+2
View File
@@ -0,0 +1,2 @@
#!/bin/bash
exec "/opt/bambustudio-bin/AppRun" "$@"
+3 -3
View File
@@ -4,7 +4,7 @@
# Automation repository: https://github.com/fabifont/claude-code-aur
pkgname=claude-code
pkgver=2.1.272
pkgver=2.1.278
pkgrel=1
pkgdesc="An agentic coding tool that lives in your terminal"
arch=('x86_64' 'aarch64')
@@ -29,8 +29,8 @@ source_x86_64=("claude-${pkgver}-x86_64::https://downloads.claude.ai/claude-code
source_aarch64=("claude-${pkgver}-aarch64::https://downloads.claude.ai/claude-code-releases/${pkgver}/linux-arm64/claude")
sha256sums=('SKIP')
sha256sums_x86_64=('d81396a668eb76fbddb49a2a5841f1b5d7af96b4c1f6500ced92f2c988f5bcd4')
sha256sums_aarch64=('214a90efdd16ee0ea81132ffecced588dba394d178cc494f285ba04b5288c8de')
sha256sums_x86_64=('5c4735937844e84f8a93306e841a5b0e12252909b07870f789b190468da147ab')
sha256sums_aarch64=('7de6cab134e48321148e30182c98614118e8f4666819412bead45865190b34ed')
package() {
install -Dm755 "${srcdir}/claude-${pkgver}-${CARCH}" "${pkgdir}/opt/claude-code/bin/claude"
+3 -3
View File
@@ -6,7 +6,7 @@
# repository's package index.
pkgname=claude-desktop
pkgver=1.52386.6
pkgver=2.2553.1
pkgrel=1
pkgdesc="Official Claude desktop app with Claude Code"
arch=('x86_64' 'aarch64')
@@ -63,8 +63,8 @@ source_x86_64=("${_deb_x86_64}::${_pool}/${_deb_x86_64}")
source_aarch64=("${_deb_aarch64}::${_pool}/${_deb_aarch64}")
noextract=("${_deb_x86_64}" "${_deb_aarch64}")
sha256sums=('edfdbc63b65891ef7c481b07086c7e630fc102c042b6ed65331a52fcaf72b14a')
sha256sums_x86_64=('2e83a76c6ed9187671bfe80664fc6d59840171f4a2a81f408662c879a67f4e0a')
sha256sums_aarch64=('882f4a52a86b07ecff989d8db87c5ec292d43f21d0a6557d3e8b01e113b18190')
sha256sums_x86_64=('6700fdd84e77a6b8c93912c2f69eb5d1e40fa99bcd9d37f438f809ef2a6fe6f8')
sha256sums_aarch64=('0003a6f9605a210f03c38670d62cd59c71153c2702aa4427e4cabe2e2e5f3390')
package() {
cd "${srcdir}"
+5 -5
View File
@@ -3,7 +3,7 @@
# Maintainer: caarlos0 <carlos@charm.sh>
pkgname='crush-bin'
pkgver=0.94.2
pkgver=0.96.0
pkgrel=1
pkgdesc='A powerful terminal-based AI assistant for developers, providing intelligent coding assistance directly in your terminal.'
url='https://charm.sh/crush'
@@ -13,16 +13,16 @@ provides=('crush')
conflicts=('crush')
source_aarch64=("${pkgname}_${pkgver}_aarch64.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_arm64.tar.gz")
sha256sums_aarch64=('3a9d00d135632b6b3f8821814ceb81512839d346d628ebac23a445b126e1f51c')
sha256sums_aarch64=('667062a39d499506b0fe151148f8d7a1c5cb5722902080d44bb3dd2ddafbf5c1')
source_armv7h=("${pkgname}_${pkgver}_armv7h.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_armv7.tar.gz")
sha256sums_armv7h=('0c183c369af79a5e37e45cc98df2093e3381c1fd4c7fb3dc204d3c765f96f138')
sha256sums_armv7h=('1de4c1ccb237743e4debb8c302df612fcef5c9b3b5378f5b65c8c6f8bc15cbfa')
source_i686=("${pkgname}_${pkgver}_i686.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_i386.tar.gz")
sha256sums_i686=('d7b54a61bd112ba8c98b1ddb2229e93d53688d2bed8794cb9e3c6055489620ad')
sha256sums_i686=('4ec66431565de5721afb7afdd99e45ff6bc9c7e667bd18d9696ea7c5622e158d')
source_x86_64=("${pkgname}_${pkgver}_x86_64.tar.gz::https://github.com/charmbracelet/crush/releases/download/v${pkgver}/crush_${pkgver}_Linux_x86_64.tar.gz")
sha256sums_x86_64=('50df13841b617956690d3ca2881ed8601798e557ff187513ab7daff355621dc3')
sha256sums_x86_64=('5b33303a404acacf761c027e9fa9e69d4d2dd050c2690abe40877c49574b7475')
package() {
case "$CARCH" in
+3 -3
View File
@@ -18,7 +18,7 @@
# binary to point at pm.sh, a stand-in that declines and names pacman instead.
pkgname=cua-driver-bin
pkgver=0.28.1
pkgver=0.28.2
pkgrel=1
pkgdesc="Computer-use driver for native GUI apps: accessibility-tree snapshots and input injection"
arch=('x86_64' 'aarch64')
@@ -46,8 +46,8 @@ source_x86_64=("https://github.com/trycua/cua/releases/download/cua-driver-rs-v$
source_aarch64=("https://github.com/trycua/cua/releases/download/cua-driver-rs-v${pkgver}/cua-driver-rs-${pkgver}-linux-arm64.tar.gz")
sha256sums=('c0779290c1d4783169aa3dbfb55feb505e563ef8a004bbf55298ceffcfbda8d9'
'c76e251c3ed424200eac52bec35ba534336307fabd83a175ab0b47e2084ab0d8')
sha256sums_x86_64=('a068b6e477893b77ced74bceccf7db7483cf140e8d54150ce5849b6252b90bcf')
sha256sums_aarch64=('a863951ef0699fd25091adb87bd114d69709b887fdfc059e795aca49ef8ac19c')
sha256sums_x86_64=('8f3e5b669e2bcd98d0eecc64f40640aac77f358b6332a06abc6ee79991620f7d')
sha256sums_aarch64=('cadd7e6b757c3ce50f2b5f6e273c154ea48450fb5fcaff744209b382915eddf5')
case "${CARCH}" in
x86_64) _platform="linux-x86_64" ;;
@@ -0,0 +1,57 @@
{
"files": {
"CMakeLists.txt": "7e874a595e1abd708cb0626bd9f0f58d79b4b4bbc6d99b45a0cbe1c5c53b43bc",
"LICENSE.md": "c0779290c1d4783169aa3dbfb55feb505e563ef8a004bbf55298ceffcfbda8d9",
"SOURCE-PROVENANCE.json": "54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75",
"cmake/DetectHyprlandAPI.cmake": "216133ec0eb141c3696bf3770a23e63e46521c9e91a0245a7cb75c20a75ba2c5",
"cmake/VerifyRuntime.cmake": "5869f79a418e7aa6178d2b9166c36cd01c3093c2579b647624968244db57b761",
"include/cua_hyprland/protocol.hpp": "7051463b3c61a2dc93c388e66b6136b7bb524f8694350f249f9d2b5c55826493",
"include/cua_hyprland/session.hpp": "e6a968e4f2ac28122cb7413a0e318f6222d2a0a1b7b0c45f4ab419a78639ebea",
"include/cua_hyprland/status.hpp": "56a9656647c0f4eeb0c588cd4b98a77df198d1f90421f973e9a80e6802f61495",
"src/drag_geometry.hpp": "c5b783d15ff197f22938f08f8d176bab5d45544fe989d150f15cb99295acedb8",
"src/foreground_route.hpp": "4aa016c237b33c15e352a9f5f64bbbca7e1a9c1671ffd593a95a0d87994fb519",
"src/inject_server.cpp": "0935283580c50fcf0e4ad956f858885700536002b1d86d2f078da9c4404ee9e8",
"src/inject_server.hpp": "67de008b4d6983371207bb22a57bab154b1dedda9b38d1207d3ac8cb379382eb",
"src/input_client_deadline.hpp": "00a91a789ff698820607449ff7152e2fb50d0f315e0fa9c5c3855752bfffe2ef",
"src/input_experiment.cpp": "7017748c782b64b0bc1d257f4ade1a8d46fd19ce940ecc21f22d628614fcef37",
"src/input_experiment.hpp": "9da2ddab7f0de6e9cf02aea53e9119acef17ef8513af849bb5aa3d137ebd12c3",
"src/input_grant.hpp": "90b544b2f559bacd920b85ab5915f09ff201de3c05a052ce1b71ff71f767e6a6",
"src/keyboard_layout.hpp": "bc2ec039ac1974caebbb66fe4acb7a2a81832c9054b3aa644a9adcc8954a2467",
"src/owned_socket_path.hpp": "8e784656d944c700f3ded383c93a8cadf846dabdaa4cc12673bcb637c26d9fd1",
"src/passive_pointer_target.hpp": "ede36fd9fd6e95ae5923c751f12591084392be9d2270eb06ad64eb4f245169fa",
"src/plugin.cpp": "712fd73ef8e9046e0fd91531b7bf5da50ce37ccca9df174160137e1924a74b09",
"src/primary_trace.cpp": "e9468d1a3f3be2a90d47bf8c4a638ad8a60fe10b6297582ab7825751cb707aa9",
"src/primary_trace.hpp": "8d62535fb0b24a02bb80d9a8dcd540b39204afb2f3b4bcb5cabd5275c3b5eaa7",
"src/protocol.cpp": "bd083d65efb05e80946566dc535b1a6fadaa581c66ec327eff41796feba795b1",
"src/seat_lifetime.hpp": "386cf5c72c178f8eec0824f2a7d46fa755b0bb000861f9a6e00802f6b81fb779",
"src/session.cpp": "0105c7ba5f9e2dbdd9a21f48be0bc1f2bde930f6aa19f77e2216403d7790e4df",
"src/status.cpp": "e46e81e5e8ae3b1f50af5dcaa6c1776e236321c788fe61b402c9923c797b1270",
"tests/agent_keymap_test.py": "9b112f520a97a77a0d55f1009cd2594988d3c1e8e7bcaa39f213ffad2644dc56",
"tests/cmake-api/CMakeLists.txt": "6a66c8f98023f029998af917fcd3ec6b1388607bdf7acaf5bdfaef9141962685",
"tests/cmake-api/include/src/plugins/PluginAPI.hpp": "86c8ad51e668908d18928cef1b04e8e6d32a33894525640b52f0b31769c870a0",
"tests/desktop_fault_policy_fixture.cpp": "ac24d675ebc64cc98148e852eb5aba5858bffc06332678d14276d04b317aaf15",
"tests/desktop_fault_policy_test.py": "c3f624c0239036babd23eaaf1bb6b722f3a0c3321a5d3d6668ae7ab4910ca95c",
"tests/drag_geometry_test.cpp": "d32ea649d008fc051fe18555d6fbc54ba5d057b61880b648c5df8aa076a53fbc",
"tests/foreground_modifiers_test.py": "abf0ddde2d51c6639c8bdca8fdda51cdc8f922b57575a00fc8925c683ccc3bb2",
"tests/foreground_route_test.cpp": "1024168828b13ee6abd8242941e73c042e9381b39108e3ada74823039c7e7932",
"tests/input_client_deadline_test.cpp": "d62373a7815d531f1269c9a838773595f43e8bcef6482fa140edb162e59a6cac",
"tests/input_grant_test.cpp": "1f327b7ee678189ebad6a50bb1b9bd06767521cebc9cfb478d92de4a8bf7e7fe",
"tests/keyboard_layout_test.cpp": "3bb0fade4675d7ad92a81eb4a1c5201dd1d01bcc418b7ea59a4284dc235e5fb2",
"tests/mock-hyprland/mock.hpp": "3aeb1a4b9d6b83506b66c129d3fa812330fad4509fe218a0dbb99dd2bb5b6319",
"tests/mock-hyprland/src/config/values/types/BoolValue.hpp": "47cf2cca89f71a273573968cb9b8ba46a1496841d6c892756ebf123500a7ecb3",
"tests/mock-hyprland/src/plugins/PluginAPI.hpp": "5654d90ec9090a88bea3d31f8a79617d4c79742b09068648e64448319d395110",
"tests/owned_socket_path_test.cpp": "eaff6b5c6f148eca6c8650ee3dc212a5e892f002ee4f68b2a1290c7205ee7e42",
"tests/passive_pointer_target_test.cpp": "2aeef1de1dc8932b26ab8c41b83fb16a4289ff96c177a5816088f07b9a168948",
"tests/plugin_api_test.cpp": "1e7e200c309996ee945c88e172273ae942be2837e24564d422dee79d8b77d8a2",
"tests/plugin_input_lifetime_test.cpp": "82e57b335ea1216ea24cca07fe4feebafebdebb779785fde20b8dab6ee222e1b",
"tests/protocol_test.cpp": "119cfe0df81c0c00036a2d181764eda7601d6ee72459c2275a96226d4f670447",
"tests/seat_lifetime_test.cpp": "b07570edbe0a142f97c54560eeb93e8327c435ab3b8cbc7496d55175e387b78a",
"tests/status_test.cpp": "b8990efc53ec3820cfe498c920b9220c4b70615ad585468558e032e7619e32f3",
"tests/transport_test.cpp": "deef114a950a27eaff0a530165ddf7db0bfc0fe7e8fb55bdbb66135c8e0c04c9",
"verify.py": "fb35d62313ff4661f892f88666919b33b160f8b6d4fb2d5d52610708bf7f4a54"
},
"patch_sha256": "e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7",
"schema": 1,
"upstream_manifest_sha256": "54f514664c84e1358a435f29cd6befd5661b0b133d76997191c000b10f021a75",
"upstream_revision": "cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7"
}
+53 -23
View File
@@ -1,21 +1,21 @@
# Verified upstream kit with a local package-revision profile; source/tooling are unchanged.
# Verified upstream kit plus a separately pinned Omarchy keyboard-remap patch.
# Normal reruns need a fresh build directory; makepkg -e reuses verified extracted trees.
# Profile kit: original source bytes and separately committed packaging tooling.
# shellcheck shell=bash disable=SC2034,SC2154
pkgname=cua-hyprland-plugin
pkgver=0.26.1
pkgrel=3
pkgdesc='Cua input candidate for reviewed profile omarchy-hyprland-0562r3'
pkgrel=6
pkgdesc='Cua input candidate for reviewed profile omarchy-hypr0562r3-aq0151-remaps'
arch=('x86_64')
url='https://github.com/trycua/cua'
license=('MIT')
depends=('hyprland=0.56.2-3' 'aquamarine=0.15.0-2' 'glibc=2.44+r24+g16be1518495f-1' 'hyprcursor=0.1.13-7' 'hyprgraphics=0.5.1-4' 'hyprlang=0.6.8-5' 'hyprutils=0.14.2-1' 'libgcc=16.2.1+r23+gd564253eb6c8-1' 'libstdc++=16.2.1+r23+gd564253eb6c8-1' 'libxkbcommon=1.13.2-1' 'wayland=1.26.0-1' 'python>=3.11' 'binutils')
makedepends=('cmake>=3.30' 'ninja' 'pkgconf' 'gcc')
depends=('hyprland=0.56.2-3' 'aquamarine=0.15.1-1' 'glibc=2.44+r24+g16be1518495f-1' 'hyprcursor=0.1.13-7' 'hyprgraphics=0.5.1-4' 'hyprlang=0.6.8-5' 'hyprutils=0.14.2-1' 'libgcc=16.2.1+r23+gd564253eb6c8-1' 'libstdc++=16.2.1+r23+gd564253eb6c8-1' 'libxkbcommon=1.13.2-1' 'wayland=1.26.0-1' 'python>=3.11' 'binutils')
makedepends=('cmake>=3.30' 'ninja' 'pkgconf' 'gcc' 'patch')
options=('!strip' '!debug' '!lto')
_stem='cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7'
_archive_sha256='47bca9e018f32f4fcfe683f91c7475c60368f3b65d318cc35c1f2de88a4ee9ab'
_kit_sha256='1391084a903254fb16d251522dba41ae34a06cfa4b2f6b7ba91a3cceeb895921'
_profile_sha256='eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07'
_kit_sha256='819779b93655d603d9ebb0d33ea052326c3374674a1d473886106af25e0fffdd'
_profile_sha256='a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e'
_verifier_sha256='480f649cbd35a0ddc7f49dc0a3a44785402c9e0ec9653b0f8ea4965d52d7f900'
_cxx="${CUA_RELEASE_CXX:-/usr/bin/g++}"
_download_name='cua-hyprland-plugin-0.26.1-cc54254464c0c9aebfd6547fe7e4a0ceaf0456d7-profile-omarchy-stable-20260910-kit-1.1.0-5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a-67f89ceb47edd973aa748820eadd02ddbfb3d3e4.tar.gz'
@@ -24,7 +24,17 @@ source=('https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0
'PROFILE.json')
noextract=("$_download_name")
sha256sums=('a89bfa7f6490f598719dfabdd2a5badc137e27b0764fd25e6a05e1f969433520'
'eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07')
'a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e')
# Downstream inputs are also checked explicitly when makepkg integrity is skipped.
declare -gA _downstream_sha256=(
['independent-keymaps.patch']='e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7'
['DOWNSTREAM-PROVENANCE.json']='e0c95350ec3ff2dd54a05e9377d79ddffffdb1cf494d05553a90207a3a919f7e'
['downstream.py']='7c9725805ad038737bd4d346c85f27991ce9ddfff436a32ebf515cad1bf98ca1'
['downstream_test.py']='7b9a992979b22a5e061fdf011ac6be59a3f1ae6e0d7c0a8ff631f74e71984c5a'
)
source+=('independent-keymaps.patch' 'DOWNSTREAM-PROVENANCE.json' 'downstream.py' 'downstream_test.py')
sha256sums+=('e74d9021111a5c217b12f3e13c1bbdd260e829c60db5fc2f7293ba4846e1a7d7' 'e0c95350ec3ff2dd54a05e9377d79ddffffdb1cf494d05553a90207a3a919f7e' '7c9725805ad038737bd4d346c85f27991ce9ddfff436a32ebf515cad1bf98ca1' '7b9a992979b22a5e061fdf011ac6be59a3f1ae6e0d7c0a8ff631f74e71984c5a')
_verify_download() {
python3 -I - "$SRCDEST/$_download_name" "$_download_sha256" "$srcdir" "$1" "$SRCDEST/PROFILE.json" <<'CUA_DOWNLOAD_PY'
@@ -61,33 +71,34 @@ with tarfile.open(fileobj=io.BytesIO(data), mode='r:gz') as contents:
require(digest(content) == expected[member.name], 'outer kit member checksum mismatch')
payload[member.name] = content
require(payload.keys() == expected.keys(), 'outer kit inventory mismatch')
# Arch's -3 package has the same compositor and all 498 headers/pkg-config
# files as -2. Derive a version-only profile with the original source/tooling
# and byte checks intact; record its own profile and kit provenance digests.
# Derive the reviewed Hyprland -3/Aquamarine 0.15.1 profile while preserving
# upstream source/tooling and compiler, compositor, header and runtime hashes.
profile_data = Path(profile_path).read_bytes()
require(digest(profile_data) == 'eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07',
require(digest(profile_data) == 'a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e',
'local profile checksum mismatch')
profile = json.loads(payload['PROFILE.json'])
profile.update(profile_id='omarchy-hyprland-0562r3', package_release=3)
profile.update(profile_id='omarchy-hypr0562r3-aq0151-remaps', package_release=6)
profile['hyprland']['package_version'] = '0.56.2-3'
require(json.loads(profile_data) == profile, 'local profile changes more than the reviewed package revision')
profile['runtime']['packages']['aquamarine'] = '0.15.1-1'
require(json.loads(profile_data) == profile, 'local profile changes more than the reviewed package versions')
payload['PROFILE.json'] = profile_data
provenance = json.loads(payload['KIT-PROVENANCE.json'])
provenance['profile_sha256'] = digest(profile_data)
payload['KIT-PROVENANCE.json'] = (json.dumps(provenance, sort_keys=True, indent=2) + '\n').encode()
recipe = payload['PKGBUILD'].decode()
for old, new in [('pkgrel=2\n', 'pkgrel=3\n'), ('omarchy-stable-20260910', profile['profile_id']),
for old, new in [('pkgrel=2\n', 'pkgrel=6\n'), ('omarchy-stable-20260910', profile['profile_id']),
('hyprland=0.56.2-2', 'hyprland=0.56.2-3'),
('aquamarine=0.15.0-2', 'aquamarine=0.15.1-1'),
('5dacea79a6a5927e59fca7b51e6c04e94fa45133ac1b8fb7f4e1f442d113eb9a', digest(profile_data)),
('7beb736adfd334eed52e84070177634269e3a09f8bb25971b38606933ff4c997', digest(payload['KIT-PROVENANCE.json']))]:
recipe = recipe.replace(old, new)
payload['PKGBUILD'] = recipe.encode()
payload['SHA256SUMS'] = ''.join(f'{digest(body)} {name}\n' for name, body in sorted(payload.items())
if name != 'SHA256SUMS').encode()
expected.update({'PROFILE.json': 'eebba812a7513b471cf8969364334a4bde1eaa9c4519157701c771c1b6cd1b07',
'KIT-PROVENANCE.json': '1391084a903254fb16d251522dba41ae34a06cfa4b2f6b7ba91a3cceeb895921',
'PKGBUILD': '4c5ef50c5a8d556d461afe283b13b228fb09d2558b37fb76257e13e25846325b',
'SHA256SUMS': '2ea22af60f8c86df588fd7db103d9f444d9ac4018b704a48ddff2545ff7ce999'})
expected.update({'PROFILE.json': 'a2eade8b8195036be2c666788a1d28ac722403285a35987d92b3344e095f9a5e',
'KIT-PROVENANCE.json': '819779b93655d603d9ebb0d33ea052326c3374674a1d473886106af25e0fffdd',
'PKGBUILD': 'c3e4149eba3f7def10ef700b30b9d0abcea994e3dd32fb169014874a0b75687c',
'SHA256SUMS': 'd9057a9534f7a820ee04cbf5d60db567c5072fa96d1f71030a6a85b4bb7ce881'})
for name, content in payload.items():
require(digest(content) == expected[name], 'derived kit checksum mismatch: ' + name)
require(srcdir.is_dir() and not srcdir.is_symlink(), 'srcdir must be a real directory')
@@ -132,17 +143,31 @@ _verify() {
printf '%s %s\n' "$_profile_sha256" "$srcdir/cua-profile-kit/PROFILE.json" | sha256sum -c - || return 1
printf '%s %s\n' "$_verifier_sha256" "$srcdir/cua-profile-kit/profile_verify.py" | sha256sum -c - || return 1
python3 "$srcdir/cua-profile-kit/profile_verify.py" --kit "$srcdir/cua-profile-kit" --kit-sha256 "$_kit_sha256" \
--archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --source "$srcdir/$_stem" --cxx "$_cxx" "$@"
--archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --source "$srcdir/$_stem" --cxx "$_cxx"
}
_downstream() {
local name
for name in independent-keymaps.patch DOWNSTREAM-PROVENANCE.json downstream.py downstream_test.py; do
printf '%s %s\n' "${_downstream_sha256[$name]}" "$SRCDEST/$name" | sha256sum -c - || return 1
done
python3 -B "$SRCDEST/downstream.py" "$1" \
--pristine "$srcdir/$_stem" --source "$srcdir/omarchy-source" \
--patch "$SRCDEST/independent-keymaps.patch" --manifest "$SRCDEST/DOWNSTREAM-PROVENANCE.json" \
--kit "$srcdir/cua-profile-kit" --kit-sha256 "$_kit_sha256" \
--archive "$srcdir/cua-profile-kit/${_stem}.tar.gz" --cxx "$_cxx" "${@:2}"
}
prepare() {
_verify_download extract || return 1
_verify
_verify || return 1
_downstream prepare
}
build() {
_verify || return 1
cmake -S "$srcdir/$_stem" -B "$srcdir/build" -G Ninja \
_downstream check || return 1
cmake -S "$srcdir/omarchy-source" -B "$srcdir/build" -G Ninja \
-DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER="$_cxx" \
-DPKG_CONFIG_EXECUTABLE=/usr/bin/pkgconf -DPKG_CONFIG_ARGN= \
-DPKG_CONFIG_USE_CMAKE_PREFIX_PATH=OFF -DCMAKE_PREFIX_PATH= \
@@ -155,6 +180,8 @@ build() {
check() {
_verify || return 1
_downstream check || return 1
python3 -B "$SRCDEST/downstream_test.py" || return 1
(
unset LD_PRELOAD FAKEROOTKEY FAKED_MODE
ctest --test-dir "$srcdir/build" --output-on-failure --no-tests=error
@@ -163,7 +190,7 @@ check() {
package() {
check || return 1
_verify --build "$srcdir/build" --output "$srcdir/BUILD-PROVENANCE.json" || return 1
_downstream build --build "$srcdir/build" --output "$srcdir/BUILD-PROVENANCE.json" || return 1
install -Dm755 "$srcdir/build/cua-hyprland-plugin.so" \
"$pkgdir/usr/lib/cua/hyprland/cua-hyprland-plugin.so" || return 1
install -Dm644 "$srcdir/$_stem/LICENSE.md" \
@@ -175,4 +202,7 @@ package() {
for name in KIT-PROVENANCE.json PROFILE.json profile_verify.py; do
install -Dm644 "$srcdir/cua-profile-kit/$name" "$pkgdir/usr/share/$pkgname/$name" || return 1
done
for name in DOWNSTREAM-PROVENANCE.json independent-keymaps.patch; do
install -Dm644 "$SRCDEST/$name" "$pkgdir/usr/share/$pkgname/$name" || return 1
done
}
+3 -3
View File
@@ -12,12 +12,12 @@
"sha256": "da8fcacf347bcbed83edc40108c6e2298da095e22246bd764e9bb382786cebb2"
},
"kit_version": "1.1.0",
"package_release": 3,
"profile_id": "omarchy-hyprland-0562r3",
"package_release": 6,
"profile_id": "omarchy-hypr0562r3-aq0151-remaps",
"runtime": {
"basename": "libstdc++.so.6.0.36",
"packages": {
"aquamarine": "0.15.0-2",
"aquamarine": "0.15.1-1",
"glibc": "2.44+r24+g16be1518495f-1",
"hyprcursor": "0.1.13-7",
"hyprgraphics": "0.5.1-4",
+36 -92
View File
@@ -1,8 +1,8 @@
# Optional Cua Hyprland plugin
This package targets **Omarchy stable x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Cua's native qualification is recorded in [the kit's qualification record](https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0-omarchy-stable-20260910/QUALIFICATION.md) and [Cua #3698](https://github.com/trycua/cua/pull/3698). Omabot replay and Omarchy's merge decision are recorded in [omarchy-pkgs #346](https://github.com/omacom/omarchy-pkgs/pull/346). Scheduling the recipe does not expand the qualified stable target.
This package targets **Omarchy x86_64**, with Inkscape `1.4.4-6` and two independent background-input lanes. Release `6` is an edge candidate for Aquamarine `0.15.1-1`; it retains the keyboard-remap patch introduced in release `5`, which includes the Omarchy patch for independent agent keymaps, operation-specific foreground checks, and compatible Num Lock state; the upstream native qualification below covers the unpatched source, not this change. Cua's native qualification is recorded in [the kit's qualification record](https://github.com/trycua/cua/releases/download/cua-hyprland-kit-v1.1.0-omarchy-stable-20260910/QUALIFICATION.md) and [Cua #3698](https://github.com/trycua/cua/pull/3698). Omabot replay and Omarchy's merge decision are recorded in [omarchy-pkgs #346](https://github.com/omacom/omarchy-pkgs/pull/346). Scheduling the recipe does not expand the qualified stable target.
The plugin is optional. Cua Driver works independently, and installation does not load the plugin or enable input. The package follows the normal edge-to-RC-to-stable promotion path instead of the fast release ring. Its PKGBUILD limits builds to x86_64; only stable x86_64 is a qualified target.
The plugin is optional. Cua Driver works independently, and installation does not load the plugin or enable input. The package follows the normal edge-to-RC-to-stable promotion path instead of the fast release ring. Its PKGBUILD limits builds to x86_64. The upstream qualification covers the original stable profile; the updated Aquamarine profile needs its own Omabot validation before promotion.
## Source and build profile
@@ -10,29 +10,25 @@ The package uses the [Driver 0.26.1 plugin source](https://github.com/trycua/cua
including the [desktop-fault cleanup repair](https://github.com/trycua/cua/pull/3702).
It is not a repackaging of the unmodified 0.24.0 plugin.
The qualified Driver pairing is `cua-driver-bin 0.27.0-1`, with input protocol v3. Driver 0.27.0 contains the bounded stale-geometry retry validated with this pinned module; its production plugin source is unchanged from the source used here. Discovery protocol v2 is separate. A newer Driver release is a changed pairing and requires affected replay before promotion.
The qualified upstream Driver pairing is `cua-driver-bin 0.27.0-1`, with input protocol v3. Driver 0.27.0 contains the bounded stale-geometry retry validated with the upstream module; its production plugin source is the base for the downstream patch used here. Discovery protocol v2 is separate. A newer Driver release is a changed pairing and requires affected replay before promotion.
Profile `omarchy-hyprland-0562r3`, kit tooling `1.1.0`, and package release `3` pin:
Profile `omarchy-hypr0562r3-aq0151-remaps`, kit tooling `1.1.0`, and package release `6` pin:
- Hyprland `0.56.2-3`, headers `0.56.2`, and measured executable/header hashes.
- GCC `16.2.1 20260810`, including compiler bytes and emitted ELF identity.
- Shared runtime `libstdc++.so.6.0.36`, its bytes, and exact ABI package versions.
- Shared runtime `libstdc++.so.6.0.36`, its bytes, and exact ABI package versions, including Aquamarine `0.15.1-1`.
This profile derives from Cua's `omarchy-stable-20260910` profile. Arch's
Hyprland `-3` package splits out `hyprpm` and changes package dependencies;
its compositor executable and all 498 header/pkg-config files are byte-identical
to `-2`. Both executables have SHA-256
`da8fcacf347bcbed83edc40108c6e2298da095e22246bd764e9bb382786cebb2`.
The checked-in `PROFILE.json` changes only the profile name, package release,
and exact Hyprland package version. Compiler, runtime, source, executable,
and header identities remain unchanged. The download wrapper verifies the
The checked-in `PROFILE.json` changes only the profile name, package release, and exact Hyprland and Aquamarine package versions. Compiler, libstdc++ runtime, upstream source, compositor executable, and header identities remain unchanged; the separately recorded patch changes the build source. The download wrapper verifies the
original kit before deriving the updated profile, recipe, and provenance,
then verifies every derived member against its recorded digest.
The native qualification below was recorded with package release `2` and
Hyprland `-2`. This packaging update does not claim a new application or
Driver replay. The `-3` dependency must reach a destination channel before
this artifact can be installed there; publication still follows edge → RC → stable.
Hyprland `-2`. The downstream keymap change and Aquamarine update need their own application and Driver replay before promotion. Hyprland `0.56.2-3` and Aquamarine `0.15.1-1` must both reach a destination channel before this artifact can be installed there; publication still follows edge → RC → stable.
The generated `PKGBUILD` identifies the immutable kit download, outer checksum,
and member checksums. The kit records the full source and tooling revisions,
@@ -47,7 +43,25 @@ and production flags remain mandatory. Packaging runs all bundled CTests even
with `--nocheck` or `--repackage`; `--skipinteg` does not bypass recipe checks.
Production input is built in; experimental signed input and tracing are off.
## What is qualified
The pristine upstream archive, manifest, and verifier remain unchanged. `independent-keymaps.patch` is applied to a separate source tree, and `DOWNSTREAM-PROVENANCE.json` pins the patch and every resulting source file. Build, check, and package revalidate both trees, including when makepkg integrity checks are skipped. `BUILD-PROVENANCE.json` records the upstream base under `source`, the applied change under `downstream`, and the final module digest; the downstream manifest and patch are installed beside it. This preserves the existing compiler, headers, runtime, and consumer checks without representing the modified module as an unmodified upstream build.
## Aquamarine dependency refresh
Release `5` required Aquamarine `0.15.0-2`. When the edge mirror moved to `0.15.1-1`, pacman could no longer resolve that dependency, even after a full database refresh. Release `6` derives a new profile from the same verified upstream kit and pins `0.15.1-1` in both the package dependencies and the installed compatibility verifier. Source, patch, compiler, compositor, headers, and libstdc++ hashes remain pinned; the original upstream qualification does not establish compatibility with the changed Aquamarine package.
A package release bump alone cannot repair future dependency drift: the checked-in profile and derived kit checksums must agree with the new environment, and affected native checks must pass before publication. Do not remove exact dependencies or selectively downgrade a library to bypass a mismatch.
## Keyboard behavior
Each background lane owns a canonical US keymap and independent modifier state. The physical keyboard keeps its layout, Compose key, and remaps. No installation or activation step edits `input:kb_*`. Existing Driver keycodes are interpreted by the agent keyboard, so this does not add Unicode, IME, or new Driver text routes.
Plain click, scroll, drag, and foreground activation do not require a canonical keyboard layout. Foreground keys still use the primary seat: the plugin checks the requested key and modifier sequence against its actual XKB map before activation or input. Unrelated remaps are accepted; a sequence whose symbols or modifier/lock transitions differ from the canonical meaning is refused with `unsupported_layout`. Arbitrary foreground layout translation remains outside protocol v3.
Foreground typing preserves Num Lock and admits a requested key sequence only when its symbols and shortcut semantics still match the canonical meaning. Num Lock does not block unaffected letters, top-row digits, Enter, or compatible shortcuts; a keypad sequence whose meaning changes is refused. Caps Lock, other unsupported lock states, held or latched modifiers, and nonzero layout groups remain guarded.
Both routes retain target/conflict checks and cancellation on desktop/keymap changes. `hyprctl -j cua:status` exposes `keyboard_layout_independent: true` and `foreground_numlock_compatible: true` for installers to distinguish this implementation from an older mapped module. The marker does not identify every future package revision; plugin updates still require a fresh desktop session.
## Historical upstream qualification
The initial app scope is native Wayland Inkscape `1.4.4-6` with the canonical
US keymap. Two lanes require independent Driver processes and distinct native
@@ -93,7 +107,7 @@ background refusal never authorizes a hidden foreground fallback or unlock.
Build the unsigned candidate in edge:
```sh
bin/repo build --package cua-hyprland-plugin --arch x86_64 --mirror edge
./bin/build --package cua-hyprland-plugin --arch x86_64 --mirror edge
```
In a fresh worker matching the reviewed profile:
@@ -131,7 +145,7 @@ kit-provenance digest:
```sh
python3 /usr/share/cua-hyprland-plugin/profile_verify.py \
--kit /usr/share/cua-hyprland-plugin \
--kit-sha256 1391084a903254fb16d251522dba41ae34a06cfa4b2f6b7ba91a3cceeb895921 \
--kit-sha256 819779b93655d603d9ebb0d33ea052326c3374674a1d473886106af25e0fffdd \
--consumer /usr/lib/cua/hyprland/cua-hyprland-plugin.so
```
@@ -146,98 +160,28 @@ hyprctl plugin load /usr/lib/cua/hyprland/cua-hyprland-plugin.so
hyprctl -j cua:status
```
Loading alone does not enable input. Before opting in, save open work and save
the exact current personal input configuration. The backup command refuses a
symlinked input file and refuses to replace an earlier backup:
Loading alone does not enable input. Use Omarchy's explicit Cua Input toggle when available; it verifies the installed profile and loaded capability and removes the legacy copied toggle's keyboard override. If it reports an older mapped plugin, disable Cua Input and log out and back in before enabling it again. Never hot-unload and reload the module.
```sh
test -f "$HOME/.config/hypr/input.lua" && \
test ! -L "$HOME/.config/hypr/input.lua" && \
test ! -e "$HOME/.config/hypr/input.lua.cua-before" && \
cp --archive -- "$HOME/.config/hypr/input.lua" \
"$HOME/.config/hypr/input.lua.cua-before"
```
If `input.lua` is a symlink, stop here: back up and later restore its resolved
target explicitly instead of using the commands below.
The background-input admission guard requires the exact XKB keymap
`rules=evdev`, `model=pc105`, `layout=us`, empty variant and options, and no
custom keymap file. Stock Omarchy 4.0.3 English (US) is not that literal
configuration: it leaves rules and model empty and sets
`compose:caps,shift:both_capslock_cancel`. Those options make Caps Lock the
Compose key and both Shift keys the Caps Lock/cancel chord. The required empty
options restore ordinary Caps Lock behavior and remove both stock shortcuts
while Cua input is enabled. Any other effective value is intentionally refused
as `unsupported_layout`; do not weaken or bypass that admission guard.
Append this override to `~/.config/hypr/input.lua` so it follows any existing
input settings. It both selects the exact admitted keymap and enables the
trusted local transport:
For manual activation, add only this plugin setting to a sourced Hyprland Lua configuration file, preserving all existing input settings:
```lua
hl.config({
input = {
kb_rules = "evdev",
kb_model = "pc105",
kb_layout = "us",
kb_variant = "",
kb_options = "",
kb_file = "",
},
plugin = { cua = { enabled = true } },
})
```
Reload, then read back every keymap value rather than relying on the source
file alone:
Then reload and inspect status:
```sh
hyprctl reload
for name in kb_rules kb_model kb_layout kb_variant kb_options kb_file; do
value=$(hyprctl -j getoption "input:$name" | jq -r '.str')
printf '%s=%s\n' "$name" "$value"
done
hyprctl -j cua:status
```
The keymap readback must be exactly:
```text
kb_rules=evdev
kb_model=pc105
kb_layout=us
kb_variant=
kb_options=
kb_file=
```
A runtime keyword or Lua evaluation without `hyprctl reload` does not reconcile
the input sockets. Continue only when status also reports input protocol v3,
input capability, socket paths, and the expected compositor identity. Do not
disable NumLock; that was required only by a strict qualification observer, not
by the demonstrated background-input contract.
Start Driver with `CUA_DRIVER_RS_ENABLE_WAYLAND=1`. For the activation check,
use background input in a new disposable Inkscape document to create a text
object containing `CUA activation check`. Save it under a new temporary
filename, then verify the text in both a fresh Driver snapshot and the reopened
saved SVG. Never test against an existing document, and do not automatically
replay an action with a partial or unknown outcome.
After the check, restore the exact saved configuration and reload it:
```sh
command mv --force -- "$HOME/.config/hypr/input.lua.cua-before" \
"$HOME/.config/hypr/input.lua"
hyprctl reload
hyprctl -j cua:status
```
Confirm that the prior keymap values are back and status reports input disabled.
Retained inert agent pointers can remain until the compositor exits; disabling
input does not unload the mapped module. If you intentionally keep activation,
retain the backup until you are ready to perform this exact restoration.
Continue only when status reports `keyboard_layout_independent: true`, `foreground_numlock_compatible: true`, input protocol v3, input capability, socket paths, and the expected compositor identity. Do not change `kb_layout`, `kb_options`, or NumLock for background input. If you previously followed the stock-US override instructions, remove only that Cua-specific override and reload to restore your underlying personal settings.
Start Driver with `CUA_DRIVER_RS_ENABLE_WAYLAND=1`. In a new disposable Inkscape document, test an admitted background key operation and pointer operation, then verify the result in both a fresh snapshot and a saved/reopened SVG. Driver text-route restrictions still apply. Never test against an existing document or automatically replay an action with a partial or unknown outcome.
To disable input, turn the Cua Input toggle off, or remove the manual `plugin.cua.enabled` setting and reload. Confirm that status reports input disabled. Retained inert agent pointers can remain until the compositor exits; disabling input does not unload the mapped module.
Before an incompatible desktop update, remove operator-added plugin activation
settings, save work, and exit the graphical session. From a text console, run
@@ -0,0 +1,96 @@
#!/usr/bin/env python3
"""Verify the Omarchy patch separately from the unchanged upstream source kit."""
import argparse
import hashlib
import importlib.util
import json
from pathlib import Path, PurePosixPath
import shutil
import subprocess
def require(condition, message):
if not condition:
raise ValueError(message)
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def inventory(root):
require(root.is_dir() and not root.is_symlink(), "source must be a real directory")
result = {}
for path in root.rglob("*"):
require(not path.is_symlink() and (path.is_dir() or path.is_file()), "nonregular source entry")
if path.is_file():
result[path.relative_to(root).as_posix()] = digest(path)
return result
def verify_inputs(pristine, patch, manifest):
require(manifest["schema"] == 1, "unsupported downstream schema")
require(patch.is_file() and not patch.is_symlink() and digest(patch) == manifest["patch_sha256"],
"downstream patch checksum mismatch")
require(digest(pristine / "SOURCE-PROVENANCE.json") == manifest["upstream_manifest_sha256"],
"downstream base manifest mismatch")
require(manifest["files"], "empty downstream inventory")
for name in manifest["files"]:
path = PurePosixPath(name)
require(name and not path.is_absolute() and path.as_posix() == name and
".." not in path.parts and "\\" not in name, "invalid downstream path")
def verify_tree(source, manifest):
require(inventory(source) == manifest["files"], "patched source inventory/checksum mismatch")
def prepare(pristine, source, patch, manifest):
require(not source.exists() and not source.is_symlink(), "patched source requires a fresh destination")
shutil.copytree(pristine, source)
subprocess.run(["patch", "--batch", "--fuzz=0", "-p1", "-i", str(patch.resolve())], cwd=source, check=True)
verify_tree(source, manifest)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("mode", choices=("prepare", "check", "build"))
parser.add_argument("--pristine", required=True, type=Path)
parser.add_argument("--source", required=True, type=Path)
parser.add_argument("--patch", required=True, type=Path)
parser.add_argument("--manifest", required=True, type=Path)
parser.add_argument("--kit", required=True, type=Path)
parser.add_argument("--kit-sha256", required=True)
parser.add_argument("--archive", required=True, type=Path)
parser.add_argument("--cxx", required=True, type=Path)
parser.add_argument("--build", type=Path)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
try:
# PKGBUILD authenticates the verifier and this helper before execution.
spec = importlib.util.spec_from_file_location("upstream_profile", args.kit / "profile_verify.py")
upstream = importlib.util.module_from_spec(spec)
spec.loader.exec_module(upstream)
profile, kit = upstream.verify_kit(args.kit, args.kit_sha256)
base = upstream.verify_archive(args.archive, profile)
require(upstream.verify_source(args.pristine, profile) == base, "upstream source identity mismatch")
manifest = upstream.read_json(args.manifest.read_bytes())
verify_inputs(args.pristine, args.patch, manifest)
if args.mode == "prepare":
prepare(args.pristine, args.source, args.patch, manifest)
else:
verify_tree(args.source, manifest)
if args.mode == "build":
require(args.build is not None and args.output is not None, "build evidence requires output")
native = upstream.verify_native(args.cxx, profile)
native["module_sha256"] = upstream.verify_build(args.build, args.source, args.cxx, profile)
native["module_runtime_sha256"] = profile["runtime"]["sha256"]
args.output.write_bytes(upstream.json_bytes(dict(native, source=base, profile=profile,
kit=kit, downstream=manifest)))
except (ValueError, KeyError, TypeError, OSError, subprocess.CalledProcessError) as error:
parser.exit(1, f"error: {error}\n")
if __name__ == "__main__":
main()
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""Exercise downstream integrity with real patch application and tampering."""
import hashlib
from pathlib import Path
import tempfile
import unittest
import downstream
class DownstreamTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.pristine = self.root / "pristine"
self.pristine.mkdir()
(self.pristine / "SOURCE-PROVENANCE.json").write_text("upstream\n")
(self.pristine / "input.cpp").write_text("old\n")
self.patch = self.root / "change.patch"
self.patch.write_text("--- a/input.cpp\n+++ b/input.cpp\n@@ -1 +1 @@\n-old\n+new\n")
self.source = self.root / "patched"
self.manifest = {
"schema": 1,
"patch_sha256": downstream.digest(self.patch),
"upstream_manifest_sha256": downstream.digest(self.pristine / "SOURCE-PROVENANCE.json"),
"files": {"SOURCE-PROVENANCE.json": downstream.digest(self.pristine / "SOURCE-PROVENANCE.json"),
"input.cpp": hashlib.sha256(b"new\n").hexdigest()},
}
def test_applies_patch_without_changing_upstream(self):
downstream.verify_inputs(self.pristine, self.patch, self.manifest)
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
self.assertEqual((self.pristine / "input.cpp").read_text(), "old\n")
self.assertEqual((self.source / "input.cpp").read_text(), "new\n")
def test_changed_patch_refuses(self):
self.patch.write_text(self.patch.read_text().replace("+new", "+bad"))
with self.assertRaisesRegex(ValueError, "patch checksum"):
downstream.verify_inputs(self.pristine, self.patch, self.manifest)
def test_changed_base_manifest_refuses(self):
(self.pristine / "SOURCE-PROVENANCE.json").write_text("different\n")
with self.assertRaisesRegex(ValueError, "base manifest"):
downstream.verify_inputs(self.pristine, self.patch, self.manifest)
def test_tampered_missing_and_extra_files_refuse(self):
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
file = self.source / "input.cpp"
for content in ("tampered\n", None):
if content is None:
file.unlink()
else:
file.write_text(content)
with self.assertRaisesRegex(ValueError, "inventory/checksum"):
downstream.verify_tree(self.source, self.manifest)
file.write_text("new\n")
(self.source / "unexpected.cpp").write_text("extra\n")
with self.assertRaisesRegex(ValueError, "inventory/checksum"):
downstream.verify_tree(self.source, self.manifest)
def test_symlink_and_reused_destination_refuse(self):
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
with self.assertRaisesRegex(ValueError, "fresh destination"):
downstream.prepare(self.pristine, self.source, self.patch, self.manifest)
file = self.source / "input.cpp"
file.unlink()
file.symlink_to(self.pristine / "input.cpp")
with self.assertRaisesRegex(ValueError, "nonregular"):
downstream.verify_tree(self.source, self.manifest)
if __name__ == "__main__":
unittest.main()
File diff suppressed because it is too large. Load diff
+7 -4
View File
@@ -1,7 +1,7 @@
# Maintainer: Gunther Schulz <dev@guntherschulz.de>
pkgname=cursor-bin
pkgver=3.20.21
pkgver=3.21.16
pkgrel=3
pkgdesc='AI-first coding environment'
arch=('x86_64' 'aarch64')
@@ -18,13 +18,13 @@ depends_aarch64=(
libxkbcommon libxrandr mesa nspr nss pango systemd-libs which
)
options=(!strip !debug) # Don't break ext of VSCode
_commit=f09fca384ceca23f7bf21f9c23655b162641d747
_commit=8ae78e8eee1e63479c7e0504b664bc0a80c6800f
source_x86_64=("https://downloads.cursor.com/production/${_commit}/linux/x64/deb/amd64/deb/cursor_${pkgver}_amd64.deb"
"https://gitlab.archlinux.org/archlinux/packaging/packages/code/-/raw/main/code."{sh,mjs}
rg.sh)
sha512sums_x86_64=('8329138d207309d16410f1cb58da18eea1a034a35f2bdd022ef9b373bb8f1b3d80e489e65256b7283c40e10da77962d158bfa3a64e742337a942633810d80dbe' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a')
sha512sums_x86_64=('032c86a5d51f154ce36b1a0bf34aa06b2d117666b4372a787ea3117fc0b2b1686e952c721388f2eaf7787f2e0581378c98608048126f7470df2e85e9dbd75ca4' '937299c6cb6be2f8d25f7dbc95cf77423875c5f8353b8bd6cd7cc8e5603cbf8405b14dbf8bd615db2e3b36ed680fc8e1909410815f7f8587b7267a699e00ab37' '793f9ff6306e3992ac89802d98110cba288ea1181a901467333293b7d76182ef9792c2a39ff49d9347a18a174b1f42bc58862091dff583f4146c2704eea28033' 'e79fe7659f59d1ae02fc68816399bfd31587315df6cdb6ccf1d0ca76f7cdc692c2a42b30591c0091147bd97ef14b1c7745dc26bd7cb3ea6bba45698e5044fa2a')
source_aarch64=("https://downloads.cursor.com/production/${_commit}/linux/arm64/deb/arm64/deb/cursor_${pkgver}_arm64.deb")
sha512sums_aarch64=('fe2e2bb77901d24bc2e21b1528b8fef2f75e5b68078f47ea96109f2903dea730a074b851a56f9a0d5404dc35243e2a7f156cbd24a5c687df74590a9b93ce0bad')
sha512sums_aarch64=('88a163c130e7ee8d9f29b93ccf1258e9f5eb0138d4de5d2333f5bc2992c0a4d0c3a72e8f5912b2e9ac9819a8d758de8e7249cd33bdf3ac631271001ca92700f7')
noextract=(cursor_${pkgver}_amd64.deb cursor_${pkgver}_arm64.deb) # avoid double tarball
_app=usr/share/cursor/resources/app
package() {
@@ -37,6 +37,9 @@ package() {
fi
bsdtar -xOf "${srcdir}/${deb}" data.tar.xz | tar -xJf - -C "$pkgdir" "${excludes[@]}"
cd "$pkgdir"
# Disable Cursor's bundled updater; Omarchy manages updates via pacman (#238).
sed -i '/^[[:space:]]*"\(backupUpdateUrl\|updateUrl\)":/d' \
"${_app}/product.json"
mv usr/share/zsh/{vendor-completions,site-functions}
if [[ "$CARCH" == aarch64 ]]; then
install -d usr/bin
+3 -3
View File
@@ -1,7 +1,7 @@
# Maintainer: Ismet Togay <ismet.togay at gmail dot com>
# Contributor: Christopher Cooper <christopher@cg505.com>
pkgname=cursor-cli
pkgver=2026.09.10.1.fd3934a
pkgver=2026.09.18.1.9a7762b
# Upstream is YYYY.MM.DD-<hash>. pkgver cannot contain hyphens, and hashes are
# not monotonically ordered, so pkgver is YYYY.MM.DD.<n>.<hash>: n resets to 1
# on a new date and increments when the same date gets a new hash.
@@ -25,8 +25,8 @@ source_x86_64=("cursor-cli-${_upstream_ver}-x86_64.tar.gz::https://downloads.cur
source_aarch64=("cursor-cli-${_upstream_ver}-aarch64.tar.gz::https://downloads.cursor.com/lab/${_upstream_ver}/linux/arm64/agent-cli-package.tar.gz")
b2sums=('d241ee9895bdb1c17514438fde8528222a8f2326568bd7a033d7a1b11432ce6b4575ff1a50625764bfe6bc6f8a9dc060f7439c3be7e95f8fd02912cdd37a011d'
'1928e04c713e13911ea607f84c3e4a2fed1f76af9795503811078f43d2b53c753e28b2233e553fc17e766831800fb0dbc272aad2a80b387f95ba6071d7d4116a')
b2sums_x86_64=('121c0128fd630565c7000b86ae53bf76e73fd72c1ab8ba2509fae7739b1c7f4bed0587a82137340303ac4704f3344cf63a10eab0e8bea262c8e48bbb21bcb742')
b2sums_aarch64=('bfc0f540190214396df3cbb93c411ab8055677bc1dd0b0e76d1b914d6c6d90af12519434227ba8b38a38249f1bfe0a8848f47e16dc048b4fa005d366815307be')
b2sums_x86_64=('3fccee6929df1042d03461895e56c222a996d3ae9e4f9c61dcc5e6ab7b1d075d3265c21a44f48dd94de0dcde4f8012cc39bfbf323e2bb0c6106cac79885c35ae')
b2sums_aarch64=('3d3bb0a3cb7e2409acf4925f207eaa4e3f41782c3c947e2834b69664b116b972da0b67eea17147fc524ea248af9919494570adc7c48d12c44f12deca17ba2c28')
prepare() {
# Block cursor-agent auto-updates by making its versions directory
+1 -1
View File
@@ -2,7 +2,7 @@
pkgname=dell-xps-touchpad-haptics
pkgver=1.0.0
pkgrel=3
pkgrel=4
pkgdesc="Synaptics haptic touchpad presets for Dell XPS on Omarchy"
arch=('x86_64')
url="https://github.com/omacom-io/omarchy-pkgs"
@@ -3,6 +3,7 @@ _default_level="high"
_env_path="/etc/dell-xps-touchpad-haptics.env"
_legacy_env_path="/etc/omarchy-dell-haptic-touchpad.env"
_legacy_override_dir="/etc/systemd/system/dell-xps-haptic-touchpad.service.d"
_runuser_path="/usr/bin/runuser"
_existing_home() {
local line value
@@ -124,18 +125,13 @@ _ensure_user_config() {
local config_dir="$home/.config/omarchy"
local config_path="$config_dir/dell-haptic.conf"
if [[ ! -f $config_path ]] && ! env HOME="$home" USER="$user" LOGNAME="$user" \
if [[ ! -f $config_path ]] && ! "$_runuser_path" --user "$user" -- \
/usr/bin/env HOME="$home" USER="$user" LOGNAME="$user" \
/usr/bin/dell-xps-touchpad-haptics set "$_default_level"; then
echo ":: Failed to create ${config_path} for user '$user'." >&2
return 1
fi
if [[ -f $config_path ]]; then
chown "$user:$user" "$home/.config" 2>/dev/null || true
chown "$user:$user" "$config_dir" 2>/dev/null || true
chown "$user:$user" "$config_path" 2>/dev/null || true
fi
return 0
}
@@ -0,0 +1,19 @@
{
"source": "local",
"origin": {
"aur": "dotnet-core-bin",
"commit": "2c499d7ce634efb8e93eee4c4239490b02e98e09"
},
"upstream": {
"watch": {
"json": "https://builds.dotnet.microsoft.com/dotnet/release-metadata/10.0/releases.json",
"path": "latest-sdk",
"fields": {
"runtime": "latest-runtime"
},
"variables": {
"_runtimever": "{runtime}"
}
}
}
}
+131
View File
@@ -0,0 +1,131 @@
# Maintainer: Attila Greguss <floyd0122[at]gmail[dot]com>
# Co-Maintainer: Nate Plumm <nate[at]ceresta[dot]com>
pkgbase=dotnet-core-bin
pkgname=(
'dotnet-host-bin'
'aspnet-runtime-bin'
'dotnet-runtime-bin'
'dotnet-sdk-bin'
'dotnet-targeting-pack-bin'
'aspnet-targeting-pack-bin'
)
# Version the split family by SDK release; dependencies expose runtime versions.
pkgver=10.0.401
_runtimever=10.0.12
_sdkver=$pkgver
_short_ver=10.0
pkgrel=1
arch=('x86_64' 'armv7h' 'aarch64')
url='https://www.microsoft.com/net/core'
license=('MIT')
options=('staticlibs')
source=('dotnet.sh')
source_armv7h=("https://builds.dotnet.microsoft.com/dotnet/Sdk/${_sdkver}/dotnet-sdk-${_sdkver}-linux-arm.tar.gz")
source_aarch64=("https://builds.dotnet.microsoft.com/dotnet/Sdk/${_sdkver}/dotnet-sdk-${_sdkver}-linux-arm64.tar.gz")
source_x86_64=("https://builds.dotnet.microsoft.com/dotnet/Sdk/${_sdkver}/dotnet-sdk-${_sdkver}-linux-x64.tar.gz")
sha512sums=('768151c7179fb6a126b3de9cae01e363e8894f6fab384b1e2c5066c2adca4578638983b1b62aea10dd18045e6d6e8f8ea13280481134de94f004a118919b2c06')
sha512sums_armv7h=('94a8a52862ca9f0de1075a468d6e4e307a1d4463098a9e8266e66939709a812b0126e212cce7e8c6feae6b078d86c8b77e088814a0e32531edb0da0a1ce90c11')
sha512sums_aarch64=('58ace73ced6b4360754689a686bdfb8a317f4da6cb8bb416dbc7d0ba9f47e43e3c09f5eb1f1a1cfaacbd10df9558da4882bf2a5e195d6ab56a02c1f9f76102ed')
sha512sums_x86_64=('51c8b999af9e8dd9998c9edc5944e19a90788862068acd38694e098889054ce8c23d4f0c5cccfa16bf187d044562359e5ee69a9f8ad0bbe913ba90311fbce25b')
# Keep each split package's notices usable when installed independently.
_install_license() {
install -Dm644 LICENSE.txt "$pkgdir/usr/share/licenses/$pkgname/LICENSE.txt"
install -Dm644 ThirdPartyNotices.txt "$pkgdir/usr/share/licenses/$pkgname/ThirdPartyNotices.txt"
}
package_dotnet-host-bin() {
pkgdesc='A generic driver for the .NET Core Command Line Interface (binary)'
provides=("dotnet-host" "dotnet-host=${_runtimever}")
conflicts=('dotnet-host')
depends=(
'libgcc'
'libstdc++'
'glibc'
)
install -dm 755 "${pkgdir}"/usr/{bin,lib,share/{dotnet,dnx}}
cp -dr --no-preserve='ownership' dotnet host dnx "${pkgdir}"/usr/share/dotnet/
_install_license
ln -sf /usr/share/dotnet/dotnet "${pkgdir}"/usr/bin/dotnet
ln -sf /usr/share/dotnet/dnx "${pkgdir}"/usr/bin/dnx
ln -sf /usr/share/dotnet/host/fxr/"${_runtimever}"/libhostfxr.so "${pkgdir}"/usr/lib/libhostfxr.so
install -Dm 644 "${srcdir}"/dotnet.sh -t "${pkgdir}"/etc/profile.d/
}
package_dotnet-runtime-bin() {
pkgdesc='The .NET Core runtime (binary)'
depends=(
"dotnet-host>=${_runtimever}"
'libgcc'
'libstdc++'
'glibc'
'icu'
'libunwind'
'zlib'
'openssl'
)
optdepends=('lttng-ust2.12: CoreCLR tracing')
provides=("dotnet-runtime=${_runtimever}" "dotnet-runtime-${_short_ver}")
conflicts=("dotnet-runtime=${_runtimever}" "dotnet-runtime-${_short_ver}")
install -dm 755 "${pkgdir}"/usr/share/{dotnet/shared,licenses}
cp -dr --no-preserve='ownership' shared/Microsoft.NETCore.App "${pkgdir}"/usr/share/dotnet/shared/
_install_license
}
package_aspnet-runtime-bin() {
pkgdesc='The ASP.NET Core runtime (binary)'
depends=('dotnet-runtime-bin')
provides=("aspnet-runtime=${_runtimever}" "aspnet-runtime-${_short_ver}")
conflicts=("aspnet-runtime=${_runtimever}" "aspnet-runtime-${_short_ver}")
install -dm 755 "${pkgdir}"/usr/share/{dotnet/shared,licenses}
cp -dr --no-preserve='ownership' shared/Microsoft.AspNetCore.App "${pkgdir}"/usr/share/dotnet/shared/
_install_license
}
package_dotnet-sdk-bin() {
pkgdesc='The .NET Core SDK (binary)'
depends=(
'glibc'
'libgcc'
'libstdc++'
'dotnet-runtime-bin'
'dotnet-targeting-pack-bin'
'aspnet-runtime-bin'
'aspnet-targeting-pack-bin'
)
provides=("dotnet-sdk-bin" "dotnet-sdk=${pkgver}" "dotnet-sdk-${_short_ver}=${pkgver}")
conflicts=("dotnet-sdk-bin" "dotnet-sdk=${pkgver}" "dotnet-sdk-${_short_ver}")
install -dm 755 "${pkgdir}"/usr/share/{dotnet,licenses}
cp -dr --no-preserve='ownership' sdk sdk-manifests templates "${pkgdir}"/usr/share/dotnet/
_install_license
}
package_dotnet-targeting-pack-bin() {
pkgdesc='The .NET Core targeting pack (binary)'
provides=(dotnet-targeting-pack=${_runtimever} dotnet-targeting-pack-${_short_ver})
conflicts=(dotnet-targeting-pack=${_runtimever} dotnet-targeting-pack-${_short_ver})
if [ $CARCH = 'x86_64' ]; then msarch=x64;
elif [ $CARCH = 'armv7h' ]; then msarch=arm;
elif [ $CARCH = 'aarch64' ]; then msarch=arm64; fi
install -dm 755 "${pkgdir}"/usr/share/{dotnet,dotnet/packs,licenses}
cp -dr --no-preserve='ownership' packs/Microsoft.NETCore.App.{Host.linux-${msarch},Ref} "${pkgdir}"/usr/share/dotnet/packs/
_install_license
}
package_aspnet-targeting-pack-bin() {
pkgdesc='The ASP.NET Core targeting pack (binary)'
depends=(dotnet-targeting-pack-bin)
provides=(aspnet-targeting-pack=${_runtimever} aspnet-targeting-pack-${_short_ver})
conflicts=(aspnet-targeting-pack=${_runtimever} aspnet-targeting-pack-${_short_ver})
install -dm 755 "${pkgdir}"/usr/share/{dotnet,dotnet/packs,licenses}
cp -dr --no-preserve='ownership' packs/Microsoft.AspNetCore.App.Ref "${pkgdir}"/usr/share/dotnet/packs/
_install_license
}
+19
View File
@@ -0,0 +1,19 @@
# Set location for AppHost lookup
[ -z "$DOTNET_ROOT" ] && export DOTNET_ROOT=/usr/share/dotnet
# Add dotnet directory to PATH, according to docs it must be added, plus VSCode C# Dev Kit doesn't work without this.
# See https://learn.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual#set-environment-variables-system-wide
case "$PATH" in
*"$DOTNET_ROOT"* ) true ;;
* ) PATH="$PATH:$DOTNET_ROOT" ;;
esac
# Add dotnet tools directory to PATH
[ -z "$DOTNET_TOOLS_PATH" ] && export DOTNET_TOOLS_PATH="$HOME/.dotnet/tools"
case "$PATH" in
*"$DOTNET_TOOLS_PATH"* ) true ;;
* ) PATH="$PATH:$DOTNET_TOOLS_PATH" ;;
esac
# Extract self-contained executables under HOME to avoid multi-user issues from using the default '/var/tmp'
[ -z "$DOTNET_BUNDLE_EXTRACT_BASE_DIR" ] && export DOTNET_BUNDLE_EXTRACT_BASE_DIR="${XDG_CACHE_HOME:-"$HOME"/.cache}/dotnet_bundle_extract"
+2 -2
View File
@@ -4,7 +4,7 @@
# Contributor: David Manouchehri <d@32t.ca>
pkgname=dropbox
pkgver=268.4.4124
pkgver=270.4.3312
pkgrel=1
pkgdesc="A free service that lets you bring your photos, docs, and videos anywhere and share them easily."
arch=("x86_64")
@@ -27,7 +27,7 @@ source=("DropboxGlyph_Blue.svg"
"dropbox@.service"
"https://edge.dropboxstatic.com/dbx-releng/client/dropbox-lnx.x86_64-$pkgver.tar.gz"{,.asc})
sha256sums=('9ba76205ec5838db85d822f23cfd7e2112fd2757e8031d8374709f102143c548' '1610ff57e8b20ee7a37682c3cc505da4ddc9cec2bd7234c90c0f2073657521d2' '6c67a9c8c95c08fafafd2f1d828074b13e3347b05d2e4f4bf4e62746115d7477' '98581e65a91ae1f19ed42edcdaaa52e102298b5da0d71b50089393d364474d3d' 'fccaaa9fbe008e56729fafe13b581e2106f38a8b6f5d61de8bf546f349d5b155' 'SKIP')
sha256sums=('9ba76205ec5838db85d822f23cfd7e2112fd2757e8031d8374709f102143c548' '1610ff57e8b20ee7a37682c3cc505da4ddc9cec2bd7234c90c0f2073657521d2' '6c67a9c8c95c08fafafd2f1d828074b13e3347b05d2e4f4bf4e62746115d7477' '98581e65a91ae1f19ed42edcdaaa52e102298b5da0d71b50089393d364474d3d' '35404957d2a15dcac998d53cbec692d5236e197493f6c009accd91ea9aa8f34c' 'SKIP')
# The PGP key fingerprint should match the one on https://www.dropbox.com/help/desktop-web/linux-commands
validpgpkeys=(
'1C61A2656FB57B7E4DE0F4C1FC918B335044912E' # Dropbox Automatic Signing Key <linux@dropbox.com>
+17
View File
@@ -0,0 +1,17 @@
# elsewhen
Installs the Elsewhen world clock plugin from the `v{pkgver}` GitHub tag archive into `/usr/share/omarchy/shell/plugins/omacom.elsewhen/` (the directory name is the plugin id the shell scans for, not the package name), plus `LICENSE` under `/usr/share/licenses/elsewhen/` and the upstream `README.md` under `/usr/share/doc/elsewhen/`. The shell scans this directory alongside its bundled plugins.
`package()` copies an explicit allow-list (`manifest.json`, every `*.qml` and `*.js`, `cities.json`, `world.json`, `worldclock-data.py`), so `tests/`, `.github/` and `.gitignore` never ship, and it fails the build if `manifest.json` is missing, does not declare `omacom.elsewhen`, or does not name a present `Panel.qml` as the entry point. An upstream release that adds a runtime file outside those patterns needs the allow-list extended here; the sync only moves versions and checksums. Every file is 0644: `Panel.qml` runs the script as `python3 <plugin dir>/worldclock-data.py`, so it needs no execute bit. No install hook: Omarchy restarts the shell after `omarchy update`, and nothing here may write into a user home. The script's only writes go to `$XDG_CACHE_HOME/omacom-elsewhen/`, which it creates itself at runtime.
Dependencies, cited as `file: tool` in the upstream tree:
- `omarchy`: `ArcText.qml`, `Chip.qml`, `EarthRow.qml`, `Globe.qml`, `MiniGlobe.qml`, `MoonDot.qml`, `Panel.qml: import qs.Commons`; `EarthRow.qml`, `Globe.qml`, `Panel.qml: import qs.Ui`. Owns `/usr/share/omarchy` and the shell plugin directory.
- `quickshell`: `Globe.qml`, `Panel.qml: import Quickshell`; `Globe.qml`, `MiniGlobe.qml`, `Panel.qml: import Quickshell.Io` (`Process`, `FileView`, `StdioCollector`).
- `python`: `Panel.qml: python3 <plugin dir>/worldclock-data.py` (the facts process). `worldclock-data.py` imports only `json`, `os`, `sys`, `time`, `urllib`.
- Left implicit as members of `base`, per Arch convention: `bash` (`Panel.qml`, `Globe.qml: bash -c` wraps every probe), `coreutils` (`Panel.qml`, `Globe.qml: date`, one probe per refresh), `systemd` (`Panel.qml: timedatectl show`, `timedatectl list-timezones`, each with a fallback: `/etc/localtime` for the home zone and `find /usr/share/zoneinfo` for the catalog), `sed` and `grep` (`Panel.qml`: the symlink target of `/etc/localtime` and the zoneinfo catalog filter), `findutils` and `tzdata` (that fallback; `worldclock-data.py: /usr/share/zoneinfo/zone1970.tab`). `omarchy` cannot run without any of them either.
- Not a dependency: `iso-codes`. Only `tests/currency_check.py` reads `/usr/share/iso-codes/json`, to validate the currency table before a release; the runtime never touches it.
Release tracking: `bin/sync-upstream` follows `omacom/elsewhen` through the `upstream.watch.github` provider, which reads the GitHub Releases feed (drafts and prereleases excluded; a tag with no published Release is not seen) and matches exactly `vX.Y.Z`, the grammar upstream's `scripts/set-version.sh` enforces. A newer release rewrites `pkgver`, resets `pkgrel` to 1, fetches `archive/refs/tags/v{pkgver}.tar.gz` again and rewrites `sha256sums` from the download. The Release's `published_at` is what lets `min_release_age: 24h` hold a fresh release for a day; `release_ring: fast` builds it straight to rc and stable as well as edge.
The watch only moves on a version increase, so the first release's digest is filled in by hand (`curl -fsSL <archive url> | sha256sum`), which is why the recipe carries a placeholder until the `v0.1.0` tag exists. Until upstream has published at least one Release, the watch finds nothing and fails the scheduled `sync-upstream` run for every package in the batch, so this recipe stays a draft until then.
+11
View File
@@ -0,0 +1,11 @@
{
"source": "local",
"release_ring": "fast",
"min_release_age": "24h",
"upstream": {
"watch": {
"github": "omacom/elsewhen",
"pattern": "v(?P<version>[0-9]+\\.[0-9]+\\.[0-9]+)"
}
}
}
+63
View File
@@ -0,0 +1,63 @@
# Maintainer: Spencer Bull <spencer.bull@hey.com>
pkgname=elsewhen
pkgver=1.0.0
pkgrel=2
pkgdesc='World clock plugin for the Omarchy shell'
arch=('any')
url='https://github.com/omacom/elsewhen'
license=('MIT')
# What the plugin needs to load and run. It also shells out to bash, date
# (coreutils) and timedatectl (systemd) and reads /usr/share/zoneinfo
# (tzdata); those are members of the base group and stay implicit, per Arch
# convention. The citations for each entry are in .omarchy/README.md.
depends=(
'omarchy'
'python'
'quickshell'
)
options=('!debug')
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('3124f0c0a19ebc1b158bcf04151cddd6c733ceeead88052186b6a54c46bee263')
package() {
# Install alongside the bundled plugins in the shell's plugin directory.
local plugin="$pkgdir/usr/share/omarchy/shell/plugins/omacom.elsewhen"
cd "$srcdir/$pkgname-$pkgver" || return 1
# The shell loads the entry point each manifest declares. A tree without
# either would install cleanly and never load, so fail the build instead of
# shipping it.
[[ -f manifest.json ]] || {
echo "release tree is missing manifest.json" >&2
return 1
}
grep -Eq '"id"[[:space:]]*:[[:space:]]*"omacom\.elsewhen"' manifest.json || {
echo "manifest.json does not declare the plugin id omacom.elsewhen" >&2
return 1
}
grep -Eq '"barWidget"[[:space:]]*:[[:space:]]*"Panel\.qml"' manifest.json || {
echo "manifest.json does not name Panel.qml as the bar widget entry point" >&2
return 1
}
[[ -f Panel.qml ]] || {
echo "release tree is missing the entry point Panel.qml" >&2
return 1
}
# An explicit allow-list of runtime files, so tests/, .github/ and the rest
# of the repository never reach the package. Directories end up 0755 and
# every file 0644: worldclock-data.py runs as `python3 <path>` and needs no
# execute bit. An unmatched glob is left literal and fails install, which
# is the right outcome for a release tree missing its QML or JS.
local file
for file in manifest.json cities.json world.json worldclock-data.py *.qml *.js; do
install -Dm644 "$file" "$plugin/$file"
done
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
}
+9 -1
View File
@@ -85,6 +85,14 @@ sharelink_qml=$(tar -xOzf "$tarball" "$expected_root/ui/ShareLink.qml")
copyfile_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/copyfile.rs")
regfile_rs=$(tar -xOzf "$tarball" "$expected_root/src/backend/regfile.rs")
# Every check below pins a literal line except the O_NOFOLLOW one. That check
# guards a property -- the copy opens its source with O_NOFOLLOW, so a symlink
# swapped in cannot redirect the read -- and pinning the exact call expression
# made it assert the spelling instead. v0.3.0 moved the first argument from
# `src` to `src.at` when directory-relative opens landed, kept O_NOFOLLOW, and
# hardened symlink handling further; the literal still refused it. Match the
# call and the flag together so a rename cannot read as a removed fix, while
# dropping O_NOFOLLOW still fails.
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" ||
@@ -92,7 +100,7 @@ if ! grep -Fq 'a.push("--".to_string());' <<<"$archive_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 'regfile::open_if_regular(src, O_NOFOLLOW)' <<<"$copyfile_rs" ||
! grep -Eq 'open_if_regular\(.*O_NOFOLLOW' <<<"$copyfile_rs" ||
! grep -Fq '.custom_flags(O_NONBLOCK | extra_flags)' <<<"$regfile_rs"; then
printf 'Release %s does not contain every required upstream security fix\n' "$best_tag" >&2
exit 1
+37 -4
View File
@@ -1,8 +1,8 @@
# Maintainer: GM <gianmarcomorales@icloud.com>
pkgname=flea
pkgver=0.2.1
pkgrel=3
pkgver=0.3.1
pkgrel=2
pkgdesc='Fast, keyboard-first file manager for Omarchy'
arch=('x86_64' 'aarch64')
url='https://github.com/thisisgm/flea'
@@ -15,7 +15,10 @@ depends=(
'glib2'
'glibc'
'gvfs'
'gvfs-afc'
'gvfs-dnssd'
'gvfs-gphoto2'
'gvfs-mtp'
'gvfs-nfs'
'gvfs-smb'
'hicolor-icon-theme'
@@ -28,6 +31,7 @@ depends=(
'qt6-multimedia'
'qt6-webengine'
'shared-mime-info'
'usbmuxd'
'util-linux'
'wl-clipboard'
'xdg-terminal-exec'
@@ -48,7 +52,7 @@ options=('!debug')
source=(
"$url/releases/download/v$pkgver/$pkgname-v$pkgver.tar.gz"
)
sha256sums=('75f9ac0274a09a0d55cf7d9187943983c1b78a9e443465738b3ac8af8f2a77e9')
sha256sums=('b146ac3f5025da987eae623c4392c44ce69a6a7275a8df3ec1a84563920a4dd2')
build() {
cd "$pkgname-$pkgver"
@@ -60,7 +64,13 @@ build() {
check() {
cd "$pkgname-$pkgver"
export CARGO_TARGET_DIR=target
# Upstream's release profile uses fat LTO, whose final link runs on one
# thread. build() keeps it for the shipped binary; the test harness is a
# throwaway second compile, so build it in parallel in its own target
# directory.
export CARGO_TARGET_DIR=target-check
export CARGO_PROFILE_RELEASE_LTO=thin
export CARGO_PROFILE_RELEASE_CODEGEN_UNITS=16
local -a test_args=()
if ! /usr/bin/prlimit --cpu=30 --as=1073741824 \
@@ -99,6 +109,12 @@ check() {
# not provide. Keep executable fixtures in the remaining suites on the normal
# temp root (/dev/shm is noexec). Note that /dev/shm does NOT buy finer
# timestamps -- see the skip below.
# shelfundo::tests::undo_refuses_to_walk_a_stranger_back deletes a file,
# creates another under the same name, and expects undo to tell them
# apart by (dev, ino, kind). ext4 hands the freed inode number straight
# back to the next create, so on the builder's /tmp the stranger is
# identical and undo walks it back. tmpfs allocates inode numbers from a
# counter and never reuses one, which is what the test assumes.
local -a filesystem_tests=(
backend::menu_actions::tests::
backend::menudelete::tests::
@@ -106,6 +122,7 @@ check() {
backend::trashbrowse::tests::
backend::trashdelete::
backend::trashmanifest::tests::
shelfundo::tests::
)
# redo_refuses_changed_sources_and_destination_collisions writes a file and
# then immediately asks redo to notice the edit. flea decides "changed" from
@@ -123,6 +140,19 @@ check() {
--skip backend::redo::tests::redo_refuses_changed_sources_and_destination_collisions
)
# These three expect spawning a missing or failing program to be reported
# as such. aarch64 builds run under QEMU user-mode emulation, where
# glibc's posix_spawn cannot see the child's failed execve: the spawn
# "succeeds" with exit 127, and the timing test's child never sleeps.
# Passes natively.
if [[ $CARCH == aarch64 ]]; then
test_args+=(
--skip backend::child::tests::a_child_is_noticed_when_it_exits_rather_than_at_the_next_poll_boundary
--skip backend::child::tests::a_child_that_never_started_is_told_apart_from_one_that_ran_and_failed
--skip backend::menu_registry::tests::unavailable_failed_and_oversized_queries_are_named_errors
)
fi
local test_tmp test_status=0 suite
test_tmp=$(mktemp -d /dev/shm/flea-tests.XXXXXXXX) || return 1
TMPDIR="$test_tmp" cargo test --frozen --release -- \
@@ -158,6 +188,9 @@ package() {
install -Dm644 ui/qmldir ui/*.qml -t "$pkgdir/usr/share/flea/ui"
install -Dm644 ui/js/*.js -t "$pkgdir/usr/share/flea/ui/js"
# Flea's Shelf setting installs this plugin into the user's plugin directory.
install -Dm644 shelf/manifest.json shelf/README.md shelf/*.qml shelf/*.js \
-t "$pkgdir/usr/share/flea/shelf"
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"
}
+10
View File
@@ -0,0 +1,10 @@
{
"source": "local",
"release_ring": "fast",
"upstream": {
"watch": {
"pattern": "v(?P<version>[0-9]+(?:\\.[0-9]+)*)",
"git_tags": "https://github.com/ghostty-org/ghostty.git"
}
}
}
+123
View File
@@ -0,0 +1,123 @@
# Ghostty for x86_64 and aarch64, built from the upstream release source tarball.
#
# Ghostty 1.3.x requires Zig 0.15.2 exactly. Distribution toolchains can move
# ahead, so use the verified upstream toolchain for each architecture only at
# package-build time rather than publishing a second Zig package.
pkgbase=ghostty
pkgname=(ghostty ghostty-shell-integration ghostty-terminfo ghostty-nautilus)
pkgver=1.3.1
pkgrel=3
pkgdesc='Fast, native, feature-rich terminal emulator pushing modern features'
arch=(x86_64 aarch64)
url='https://github.com/ghostty-org/ghostty'
license=(MIT)
depends=(
bzip2
fontconfig
freetype2
glib2
glibc
gtk4
gtk4-layer-shell
harfbuzz
libadwaita
libpng
oniguruma
pixman
wayland
zlib
)
makedepends=(
blueprint-compiler
curl
gettext
pkgconf
)
_zigver=0.15.2
_archive="$pkgbase-$pkgver"
source=(
"https://release.files.ghostty.org/$pkgver/$_archive.tar.gz"
'build-data-llvm.patch'
)
sha256sums=(
'3349d25600ffbda281197a18314f7d18791969cffe9474f0ff16a45a9ebfccdb'
'd9f5781b748651fa1ff7b919f4a79cd8570118faefe2848f64f02ea4220257ba'
)
source_x86_64=("https://ziglang.org/download/$_zigver/zig-x86_64-linux-$_zigver.tar.xz")
sha256sums_x86_64=('02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239')
source_aarch64=("https://ziglang.org/download/$_zigver/zig-aarch64-linux-$_zigver.tar.xz")
sha256sums_aarch64=('958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f')
prepare() {
cd "$_archive"
# Zig's native x86 linker cannot read .sframe relocations in Arch's crt1.o.
# Use bundled LLVM for the build-data helper, as the main executable does.
if [[ "$CARCH" == x86_64 ]]; then
patch -Np1 -i "$srcdir/build-data-llvm.patch"
fi
PATH="$srcdir/zig-$CARCH-linux-$_zigver:$PATH" \
ZIG_GLOBAL_CACHE_DIR="$srcdir/zig-global-cache" \
./nix/build-support/fetch-zig-cache.sh
}
build() {
cd "$_archive"
# A '-' suffix is a SemVer prerelease and selects Ghostty's tip channel.
# Keep the package revision as build metadata on the stable release.
PATH="$srcdir/zig-$CARCH-linux-$_zigver:$PATH" \
DESTDIR=build \
zig build \
--prefix /usr \
--system "$srcdir/zig-global-cache/p" \
-Doptimize=ReleaseFast \
-Dgtk-x11=true \
-Dcpu=baseline \
-Dpie=true \
-Demit-docs=false \
-Dversion-string="$pkgver+omarchy.$pkgrel" \
--build-id=sha1
}
package_ghostty() {
depends+=(ghostty-shell-integration ghostty-terminfo)
optdepends=('ghostty-nautilus: Open in Ghostty context menu in GNOME Files')
cd "$_archive"
cp -a build/* "$pkgdir/"
install -Dm0644 LICENSE "$pkgdir/usr/share/licenses/ghostty/LICENSE"
rm -r "$pkgdir/usr/share/terminfo" \
"$pkgdir/usr/share/ghostty/shell-integration" \
"$pkgdir/usr/share/nautilus-python"
}
package_ghostty-shell-integration() {
pkgdesc='Shell integration scripts for Ghostty'
depends=()
cd "$_archive"
install -d "$pkgdir/usr/share/ghostty/shell-integration"
cp -a build/usr/share/ghostty/shell-integration/. \
"$pkgdir/usr/share/ghostty/shell-integration/"
install -Dm0644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}
package_ghostty-terminfo() {
pkgdesc='Terminfo for Ghostty'
depends=()
cd "$_archive"
install -d "$pkgdir/usr/share/terminfo"
cp -a build/usr/share/terminfo/x "$pkgdir/usr/share/terminfo/"
install -Dm0644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
}
package_ghostty-nautilus() {
pkgdesc='Open in Ghostty for GNOME Files'
depends=(ghostty nautilus-python)
license=(GPL-2.0-or-later)
cd "$_archive"
install -d "$pkgdir/usr/share/nautilus-python"
cp -a build/usr/share/nautilus-python/. "$pkgdir/usr/share/nautilus-python/"
}
+10
View File
@@ -0,0 +1,10 @@
--- a/src/build/GhosttyResources.zig
+++ b/src/build/GhosttyResources.zig
@@ -15,6 +15,7 @@
// This is the exe used to generate some build data.
const build_data_exe = b.addExecutable(.{
.name = "ghostty-build-data",
+ .use_llvm = true,
.root_module = b.createModule(.{
.root_source_file = b.path("src/main_build_data.zig"),
.target = b.graph.host,
+3 -3
View File
@@ -6,7 +6,7 @@ _npmmodule=@github/copilot
pkgname=github-copilot-cli
_pkgexec=copilot
pkgver=1.0.83
pkgver=1.0.86
pkgrel=1
pkgdesc="GitHub Copilot CLI brings the power of Copilot coding agent directly to your terminal."
@@ -31,8 +31,8 @@ source=("https://registry.npmjs.org/${_npmmodule}/-/copilot-${pkgver}.tgz"
noextract=("copilot-${pkgver}.tgz")
sha256sums=(
'135506fc2b13163ab55dbf76a06e2fbcbad04ecac76b4e9c6659f0ba309e6a86'
'0c0064a10effac8adf9ad97338bafaa0d7d7d5bf191cc1c0384e05ff4366d36c'
'4c6433345f08199e96dcf8db1c3e46a337dfab96cea32132d6127ffcd63a6200'
'b94d2aab574cf3e0c8d950e72430186cdd918261a1376146de971fa90ba0a672'
)
# Document: https://wiki.archlinux.org/title/Node.js_package_guidelines
+3
View File
@@ -0,0 +1,3 @@
{
"source": "local"
}
+53
View File
@@ -0,0 +1,53 @@
# Maintainer: David Heinemeier Hansson <david@hey.com>
pkgname=gliff
pkgver=0.1.0
pkgrel=1
pkgdesc="Hyprland remote desktop over SSH (Vulkan Video, 4:4:4)"
arch=('x86_64')
url="https://github.com/kevinmcconnell/gliff"
license=('MIT')
depends=('gcc-libs' 'glibc' 'wayland' 'libxkbcommon' 'libdrm' 'mesa'
'gtk4>=4.14' 'libadwaita>=1.5' 'vulkan-icd-loader' 'openssh')
makedepends=('cargo' 'pkgconf')
optdepends=('vulkan-radeon: Vulkan Video on AMD'
'vulkan-intel: Vulkan Video on Intel'
'vulkan-tools: vulkaninfo for debugging'
'vulkan-validation-layers: driver call validation for development')
options=('!debug')
# Upstream only publishes a rolling prerelease; pin its source for reproducible builds.
_commit=2edbfba52780c7ace15dbb05ca92177f493d4bc9
source=("$pkgname-$_commit.tar.gz::$url/archive/$_commit.tar.gz")
sha256sums=('2d75961085a72fb8e34149de63e312a7336daa70fd6e157e30c2cd08c14eb1c7')
prepare() {
cd "$pkgname-$_commit"
cargo fetch --locked --target "$CARCH-unknown-linux-gnu"
}
build() {
cd "$pkgname-$_commit"
export CARGO_TARGET_DIR=target
cargo build --frozen --release
}
check() {
cd "$pkgname-$_commit"
export CARGO_TARGET_DIR=target
cargo test --frozen --release --workspace
}
package() {
cd "$pkgname-$_commit"
install -Dm755 target/release/gliff "$pkgdir/usr/bin/gliff"
install -Dm755 target/release/gliff-server "$pkgdir/usr/bin/gliff-server"
install -Dm755 target/release/gliff-probe "$pkgdir/usr/bin/gliff-probe"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
install -Dm644 docs/hardware-quirks.md "$pkgdir/usr/share/doc/$pkgname/hardware-quirks.md"
}
+31 -25
View File
@@ -2,14 +2,15 @@
# Contributor: Omarchy
pkgname=grok-bot
pkgver=0.29.0
pkgver=0.47.0
pkgrel=1
_commit=f0e5bfcee649ea84c0c61369cf896cd146d72136
_commit=c1e7d7a46549956d25f53e9c0b9f59666e03aa3a
pkgdesc='Grok Bot desktop agent'
arch=('x86_64')
arch=('x86_64' 'aarch64')
url='https://x.ai/bot'
license=('custom')
depends=(
'alsa-lib'
'at-spi2-core'
'gtk3'
'hicolor-icon-theme'
@@ -25,32 +26,36 @@ optdepends=('libappindicator-gtk3: tray support')
provides=('sand')
conflicts=('sand')
options=('!strip' '!debug')
install=grok-bot.install
_deb_x86_64="grok-bot_${pkgver}_amd64.deb"
_deb_aarch64="grok-bot_${pkgver}_arm64.deb"
source=(
"${pkgname}_${pkgver}.deb::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/x64/Grok_Bot_${pkgver}.deb"
'grok-bot.sh'
'grok-bot.desktop'
)
sha256sums=('d223b5830282aef11d5c46d8f4d1edd239bf992e336405cbd288d4476b9233d4'
'6dfa6c305941afa6cbaefbeaae06d05ab5a88f31630005d25a819a160c20c7a3'
'856056c9ca63dda5d01158ce8fb6a9a7cbb3f67c13a92b573cd196d3e50f26e7')
noextract=("${pkgname}_${pkgver}.deb")
source_x86_64=(
"${_deb_x86_64}::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/x64/${_deb_x86_64}"
)
source_aarch64=(
"${_deb_aarch64}::https://downloads.cursor.com/grokbot/stable/${_commit}/linux/arm64/${_deb_aarch64}"
)
sha256sums=('6dfa6c305941afa6cbaefbeaae06d05ab5a88f31630005d25a819a160c20c7a3'
'3e2a2461ea58d17ac1777616be9ba660f7cb9ceefa9292016e36c55758bf78dd')
sha256sums_x86_64=('11ca0f51a535b97af51a352adf9c0f9ecd2e1b0430a69ae9451b688a7a065808')
sha256sums_aarch64=('836f8d19d3826c6573c31ac45c7a9b797abc73381ae0d2b1e7a8dae5410e7e46')
noextract=("${_deb_x86_64}" "${_deb_aarch64}")
package() {
bsdtar -xOf "${srcdir}/${pkgname}_${pkgver}.deb" data.tar.xz |
local deb_var="_deb_${CARCH}"
local deb="${!deb_var}"
bsdtar -xOf "${srcdir}/${deb}" data.tar.xz |
bsdtar -x -C "${pkgdir}" -f -
rm -rf "${pkgdir}/usr/share/doc" \
"${pkgdir}/usr/share/applications/sand.desktop"
local icon1024="${pkgdir}/usr/share/icons/hicolor/1024x1024/apps"
if [[ -f "${icon1024}/sand.png" && ! -f "${icon1024}/grok-bot.png" ]]; then
install -Dm644 "${icon1024}/sand.png" "${icon1024}/grok-bot.png"
fi
rm -f "${icon1024}/sand.png"
if [[ -f "${icon1024}/grok-bot.png" ]]; then
install -Dm644 "${icon1024}/grok-bot.png" \
"${pkgdir}/usr/share/icons/hicolor/512x512/apps/grok-bot.png"
fi
"${pkgdir}/usr/share/applications/sand.desktop" \
"${pkgdir}/usr/share/applications/grok-bot.desktop"
# Always install our Wayland wrapper; do not keep any /usr/bin from the .deb.
rm -f "${pkgdir}/usr/bin/grok-bot" "${pkgdir}/usr/bin/sand"
@@ -64,9 +69,10 @@ package() {
install -Dm644 "${pkgdir}/opt/Grok Bot/LICENSES.chromium.html" \
"${pkgdir}/usr/share/licenses/${pkgname}/LICENSES.chromium.html"
if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then
chmod 4755 "${pkgdir}/opt/Grok Bot/chrome-sandbox"
else
chmod 0755 "${pkgdir}/opt/Grok Bot/chrome-sandbox"
fi
# Ship chrome-sandbox without setuid. Upstream's build-time userns probe
# would measure the CI container, not the user's machine, and a setuid
# helper could not exec from "/opt/Grok Bot/" anyway (electron#44414).
# grok-bot.install tells users on kernels without unprivileged user
# namespaces how to run without the sandbox.
chmod 0755 "${pkgdir}/opt/Grok Bot/chrome-sandbox"
}
+1 -1
View File
@@ -8,6 +8,6 @@ Terminal=false
Type=Application
Categories=Development;
MimeType=x-scheme-handler/grokbot;x-scheme-handler/sand;
StartupWMClass=Grok Bot
StartupWMClass=grok-bot
StartupNotify=true
Keywords=Grok;AI;Agent;
+41
View File
@@ -0,0 +1,41 @@
# Electron's renderer sandbox needs unprivileged user namespaces, or else a
# setuid-root chrome-sandbox. Upstream omarchy-pkgs probes for user namespaces
# inside package() and sets 4755 when they are missing. That is wrong twice
# for this repo: the build runs in a CI container where the probe fails, so
# every user would get the setuid helper; and the helper lives under
# "/opt/Grok Bot/", and Electron cannot exec a setuid chrome-sandbox from a
# path with a space (electron/electron#44414), so 4755 would not even work.
#
# The package therefore always ships chrome-sandbox as 0755. This hook only
# tells the user what to do on a host that lacks unprivileged user namespaces.
# The probe drops to nobody first: pacman runs hooks as root, and root can
# unshare a user namespace even where unprivileged users cannot.
_userns_available() {
[[ -L /proc/self/ns/user ]] || return 1
if (( EUID == 0 )) && command -v setpriv >/dev/null; then
setpriv --reuid=65534 --regid=65534 --clear-groups -- unshare --user true 2>/dev/null
else
# Already unprivileged (or no setpriv): the direct probe is the real answer.
unshare --user true 2>/dev/null
fi
}
_advise() {
_userns_available && return 0
cat <<'MSG'
==> Unprivileged user namespaces are unavailable on this kernel, so Grok Bot's
renderer sandbox cannot start. A setuid chrome-sandbox is not an option
here: Electron cannot exec it from "/opt/Grok Bot/" (electron#44414).
To run without the sandbox, add this line to ~/.config/grok-bot-flags.conf:
--no-sandbox
MSG
}
post_install() {
_advise
}
post_upgrade() {
_advise
}
+5 -5
View File
@@ -1,7 +1,7 @@
# Maintainer: David Heinemeier Hansson <david@hey.com>
pkgname=herdr
pkgver=0.9.0
pkgver=0.9.1
pkgrel=1
pkgdesc="Herdr terminal workspace manager for AI coding agents"
arch=('x86_64' 'aarch64')
@@ -13,13 +13,13 @@ replaces=('omarchy-herdr')
conflicts=('omarchy-herdr')
options=('!debug' '!lto')
_zigver=0.15.2
_zigver=0.16.0
source=("herdr-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
source_x86_64=("zig-x86_64-linux-$_zigver.tar.xz::https://ziglang.org/download/$_zigver/zig-x86_64-linux-$_zigver.tar.xz")
source_aarch64=("zig-aarch64-linux-$_zigver.tar.xz::https://ziglang.org/download/$_zigver/zig-aarch64-linux-$_zigver.tar.xz")
sha256sums=('1e83bff4b05834ed8281e16f1680e8f3e58375a94b2e3f2b3d021e28e293ef9a')
sha256sums_x86_64=('02aa270f183da276e5b5920b1dac44a63f1a49e55050ebde3aecc9eb82f93239')
sha256sums_aarch64=('958ed7d1e00d0ea76590d27666efbf7a932281b3d7ba0c6b01b0ff26498f667f')
sha256sums=('03403d3ef80dcf2b954dd5d27eb636e6c4f5279d240b48de272b7f53e4b73093')
sha256sums_x86_64=('70e49664a74374b48b51e6f3fdfbf437f6395d42509050588bd49abe52ba3d00')
sha256sums_aarch64=('ea4b09bfb22ec6f6c6ceac57ab63efb6b46e17ab08d21f69f3a48b38e1534f17')
prepare() {
cd "herdr-$pkgver"
+3 -3
View File
@@ -2,8 +2,8 @@
# Maintainer: CommandMC <kate@commandmc.de>
pkgname=heroic-games-launcher-bin
pkgver=2.22.1
pkgrel=2
pkgver=2.22.3
pkgrel=1
pkgdesc="An Open source Launcher for Epic, Amazon and GOG Games"
arch=('x86_64')
url="https://heroicgameslauncher.com/"
@@ -11,7 +11,7 @@ license=('GPL-3.0-only')
_filename=Heroic-${pkgver}-linux-x64.pacman
source=("https://github.com/Heroic-Games-Launcher/HeroicGamesLauncher/releases/download/v${pkgver}/${_filename}")
noextract=("${_filename}")
sha256sums=(66ed041a93ac2817b744d3d0985194adfa408c8d35f64d9a8967fa5e2a58f2c1)
sha256sums=('ed17ce083a71dd7e49a89218052ab475edd5a654cedf443853f6baacbb0a00e9')
options=(!strip)
depends=(
which
+3
View File
@@ -0,0 +1,3 @@
{
"source": "local"
}
+42
View File
@@ -0,0 +1,42 @@
# Maintainer: David Heinemeier Hansson <david@hey.com>
pkgname=hype
pkgver=0.4.1
pkgrel=1
pkgdesc='Simple Markdown presentations with a visual slide editor'
arch=('x86_64' 'aarch64')
url='https://github.com/omacom/hype'
license=('MIT')
install='hype.install'
options=('!debug')
depends=(
'ffmpeg'
'hicolor-icon-theme'
'qt6-base>=6.9'
'qt6-declarative>=6.9'
'qt6-multimedia>=6.9'
'qt6-imageformats'
'qt6-svg'
'source-highlight'
'zlib'
'libwebp'
'xdg-desktop-portal'
)
makedepends=('gcc' 'make')
source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/v$pkgver.tar.gz")
sha256sums=('0724a9d18df7657b50dff21ffe3dec1c107e9191a9e9fd5ad40b56ed9d5d6f51')
build() {
cd "$srcdir/$pkgname-$pkgver"
./bin/build
}
package() {
cd "$srcdir/$pkgname-$pkgver"
install -Dm755 build/hype "$pkgdir/usr/bin/hype"
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
install -Dm644 pkgbuild/hype.svg "$pkgdir/usr/share/icons/hicolor/scalable/apps/hype.svg"
install -Dm644 pkgbuild/hype.desktop "$pkgdir/usr/share/applications/hype.desktop"
install -Dm644 README.md "$pkgdir/usr/share/doc/$pkgname/README.md"
}
+12
View File
@@ -0,0 +1,12 @@
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
}
post_upgrade() {
post_install
}
post_remove() {
post_install
}
@@ -2,7 +2,7 @@
pkgname="hyprland-preview-share-picker"
pkgver=0.2.1
pkgrel=1
pkgrel=2
pkgdesc="An alternative share picker for hyprland with window and monitor previews"
arch=(x86_64 aarch64)
url="https://github.com/WhySoBad/hyprland-preview-share-picker"
@@ -32,14 +32,14 @@ fn main() {
}
EOF
export RUSTUP_TOOLCHAIN=nightly
export RUSTUP_TOOLCHAIN=stable
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
}
build() {
cd "$pkgname-$pkgver"
export RUSTUP_TOOLCHAIN=nightly
export RUSTUP_TOOLCHAIN=stable
export CARGO_TARGET_DIR=target
cargo build --frozen --release
+1 -2
View File
@@ -2,14 +2,13 @@
pkgname=intel-ipu7-camera
pkgver=1.0.6
pkgrel=1
pkgrel=2
pkgdesc="Intel IPU7 MIPI camera stack for Hurrican/Performance (OV08X40 + hardware ISP)"
arch=('x86_64')
url="https://github.com/TsaiGaggery/hurrican_omarchy_enabling"
license=('GPL-2.0-or-later')
depends=(
'dkms'
'linux-headers'
'v4l2loopback-dkms'
'v4l2-relayd'
'gstreamer'
@@ -0,0 +1,12 @@
{
"source": "local",
"release_ring": "fast",
"min_release_age": "24h",
"upstream": {
"github": "DanWahlin/learn-omarchy",
"checksums": "SHA256SUMS",
"assets": {
"any": "learn-omarchy-{pkgver}.tar.gz"
}
}
}
+34
View File
@@ -0,0 +1,34 @@
# Maintainer: Dan Wahlin <dwahlin@gmail.com>
pkgname=learn-omarchy
pkgver=0.2.2
pkgrel=1
pkgdesc="Interactive, theme-aware courses for learning Omarchy"
arch=('any')
url="https://github.com/DanWahlin/learn-omarchy"
license=('MIT' 'CC-BY-4.0' 'CC0-1.0')
depends=(
'bash' 'coreutils' 'sudo' 'hyprland' 'mpv' 'nodejs>=22.6' 'omarchy'
'quickshell>=0.3' 'qt6-declarative' 'qt6-multimedia' 'qt6-multimedia-ffmpeg' 'xdg-utils'
'xdg-terminal-exec' 'nautilus'
'ttf-liberation' 'noto-fonts-emoji'
'grim' 'slurp' 'gpu-screen-recorder' 'util-linux' 'ffmpeg'
)
makedepends=('make')
optdepends=(
'btop: activity-monitor practice'
'tesseract: OCR practice'
'tesseract-data-eng: English OCR sample recognition'
'zbar: QR recognition (zbarimg)'
'qrencode: QR sample creation'
'voxtype: optional dictation practice'
)
options=('!strip')
source=("$pkgname-$pkgver.tar.gz::$url/releases/download/v$pkgver/$pkgname-$pkgver.tar.gz")
sha256sums=('67f14778c2b66d56c1504695b0e11cab603f1a002570abc94b4bae9b0bec6066')
package() {
cd "$srcdir/$pkgname-$pkgver"
node tools/prepare-release.mjs --check .
make DESTDIR="$pkgdir" PREFIX=/usr install
}
+3 -3
View File
@@ -1,7 +1,7 @@
# Maintainer: Zesko
pkgname="limine-snapper-sync"
pkgver=1.31.0
pkgrel=1.1
pkgver=1.32.0
pkgrel=1
_gradle_version=9.7.1
pkgdesc="Integrates Limine boot entries with Snapper snapshots."
arch=('x86_64' 'aarch64')
@@ -31,7 +31,7 @@ makedepends=('git')
makedepends_x86_64=('gradle')
backup=(etc/limine-snapper-sync.conf)
conflicts=('limine-snapper-cli' 'limine-snapper-sync-git')
sha256sums=('ed236f1bbab966950bf11ba5a7958e97a76e66db7fd647b7737bab4b48c9fc40')
sha256sums=('6bcc1d3ace58030204260a9a4d40d500c4822416be1b1c6edd153d1df0796b3d')
sha256sums_x86_64=('e0be791c8fda4d03b6b0a0cb824fef3149736170057b3a515252b44419606af0')
sha256sums_aarch64=('b4580d9f223d0a4b3a1757e58b18ff4c1db950e67e105fc5cb741457d2384a71'
'acd53f1edaf02f1a8ff99879f8a34b302661a057d9b063ae9e35b552f804d20a')
@@ -0,0 +1,5 @@
{
"source": "local",
"release_ring": "fast",
"sync": false
}
+63
View File
@@ -0,0 +1,63 @@
# Maintainer: Spencer Bull <spencerbull2554@gmail.com>
#
# Self-retiring shim: ships Arch's linux-firmware-cirrus 20260910-2 payload to
# the stable channel while stable's pinned Arch snapshot is still on
# linux-firmware 20260810-2 (which lacks the Dell XPS 13 DX13260 / 1028:0e54
# CS35L56 amplifier firmware aliases, leaving that machine's speakers silent).
#
# Nothing is rebuilt. The signed Arch package is verified against the Arch
# packager key and its payload reinstalled as-is, minus the files that Arch
# moved out of linux-firmware-other in 20260910 (the cs42l45 SDCA tree): on
# the stable snapshot those are still owned by linux-firmware-other 20260810-2
# and would conflict, so they are left to that package.
#
# Versioning is deliberate: 20260810-3 orders above the snapshot's 20260810-2
# and BELOW Arch's real 20260910-2, so as soon as the stable snapshot advances
# pacman replaces this shim with the genuine package in the same transaction
# that upgrades linux-firmware-other, and nothing is lost. Delete this recipe
# once stable's snapshot is at linux-firmware >= 20260910.
pkgname=linux-firmware-cirrus
pkgver=20260810
pkgrel=3
_fwver=20260910
_fwrel=2
_basever=20260810
_baserel=2
pkgdesc="Firmware files for Linux - Firmware for Cirrus Logic audio devices (Arch - payload, stable-snapshot shim)"
arch=('any')
url="https://gitlab.com/kernel-firmware/linux-firmware"
license=('LicenseRef-WHENCE' 'LicenseRef-cirrus')
depends=('linux-firmware-whence')
options=('!strip' '!debug')
_cirrus="linux-firmware-cirrus-${_fwver}-${_fwrel}-any.pkg.tar.zst"
_other="linux-firmware-other-${_basever}-${_baserel}-any.pkg.tar.zst"
source=(
"https://archive.archlinux.org/packages/l/linux-firmware-cirrus/${_cirrus}"
"https://archive.archlinux.org/packages/l/linux-firmware-cirrus/${_cirrus}.sig"
"https://archive.archlinux.org/packages/l/linux-firmware-other/${_other}"
"https://archive.archlinux.org/packages/l/linux-firmware-other/${_other}.sig"
)
noextract=("${_cirrus}" "${_other}")
sha256sums=('70100c551b079bd8abec3d9c96a16defd04766b2a4afc6d7be9128d37e9840f7'
'SKIP'
'b0f016ee0d0532b977211b0cbb630bca75184f68a9ace82675eb3cf9acce4f6c'
'SKIP')
# Jan Alexander Steffens (heftig) <heftig@archlinux.org>, Arch Linux packager
validpgpkeys=('83BC8889351B5DEBBB68416EB8AC08600F108CDF')
package() {
# Payload only; makepkg regenerates .PKGINFO/.MTREE/.BUILDINFO.
bsdtar -xf "${srcdir}/${_cirrus}" -C "${pkgdir}" \
--exclude='.PKGINFO' --exclude='.MTREE' --exclude='.BUILDINFO' \
--exclude='.INSTALL' --exclude='.CHANGELOG'
# Drop every file the snapshot's linux-firmware-other still owns.
local f
while IFS= read -r f; do
[[ -e "${pkgdir}/${f}" || -L "${pkgdir}/${f}" ]] && rm -f "${pkgdir}/${f}"
done < <(bsdtar -tf "${srcdir}/${_other}" | grep -v '^\.' | grep -v '/$')
# Remove directories emptied by the step above.
find "${pkgdir}/usr/lib/firmware" -depth -type d -empty -delete
}
@@ -0,0 +1,60 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mDMEZXeS9hYJKwYBBAHaRw8BAQdAT04Na8ee0UltkhyNi2RGHYdjZDgg+X/K8Jix
dSSQ+Ni0NkphbiBBbGV4YW5kZXIgU3RlZmZlbnMgKGhlZnRpZykgPGhlZnRpZ0Bh
cmNobGludXgub3JnPoiQBBMWCgA4AhsDAheAFiEEg7yIiTUbXeu7aEFuuKwIYA8Q
jN8FAmWq/kcFCwkIBwMFFQoJCAsFFgIDAQACHgUACgkQuKwIYA8QjN+2qAEAh/RL
Zq7Hmqv/z09yq0m6IEb0kbXaW50POi/V+2VcJ9wBAN5Ik/fFgnGMlvZF7Rugu37o
2fk7jnUVsWJca9QmytgGiHUEEBYKAB0WIQSZtmGEcqOzuBQYW67X09gjuIvbmwUC
aYWy0QAKCRDX09gjuIvbm7vJAP9xqmmI8jNGEUQNidh9yZCmVOr3vT5/sUcHGo0J
08o/TgD/Wix2/4DHfnk6fu+onERrK5uF9BAacERnCsG5lMWQ2wSJAjMEEAEKAB0W
IQSR/+BwDoBhnOtzI1yojiPjd1FOAAUCZa0cywAKCRCojiPjd1FOAIKbEACipPSS
sA2G7ntHRGEHSWKgqEKseGGr7kflVdsmJn1tjKI8/VGKa77NkCRgqIJRwmE9m9+F
Cx+a3ILmMOxsjj5RCVFce9NJuscVGHaphZcp7Z0MVoR9hhtnsyjWk/CYOAv+JLKq
NTYhdmat+ixY4fwPzjdV6sxCDL/s6Fci0zPLUam7QVr9LIS4U3UH7smkKj6jM+G/
sEn0QM02EKy2iJLlbPNN3pppYWqZ5p9xXeB6EJ89JvZMI3k92xy456JjkCgLN860
NPmdmnLLtlrGWJRgNK6+iaFjLt/BL9gU8aGe+K3ONXES2k7iZnh+oLAEV8Z07dgv
Gd0o+OFEPrC8kETbQK45r1yAjZVO+tp4dSxvV/BD+7KhUXAZnOKWLaOMomfsG17E
KKa894cBLJG7LWN/d/Uk9fjfjd4ZYbSpIALJiEoKrm1ytj2KgnxKveTtY69i63/N
0BpNVrkYIJffWR6zSosC8geAWfqm7pR6JARUAZrw0YtJbluDrRgOO0XFn8hDqRUm
FPWgXuvqNdR2JyD4aB87LKGhzVFzQvbHa+S4sG7+w5nWQB0Eca+hiVpJDpMYjoU+
f+L3yUb6POwvUJe7VT/2PYOwidnV3guTevnn9a4erKoW/6wKAr13cW+KTKTR0bdC
S0UTOWbJnbi2Hfhyr7NZgs4T4OcpH3kj+R4WPIkCMwQQAQoAHRYhBNiv3aB6W27f
p9jM2tbQVfknhD8cBQJlg4C5AAoJENbQVfknhD8cpPQP/19uAVMaG1lQNaAK9XWT
Z1/lAr6sxYT6B2+MqnRtbkr4Gh3ts7Ey5BI39HxNWRIUGUt7pq4FjCQxWeJC9mfp
/hqj9rl1s32lbBzevbkjESro+cHPfrv0vsVlwJA2W3rURQlQzbEq3fvDzc/wz4sN
vgQDiUlxH5JYXZ5OexsDATLyfHNpJh+4NxqB24axjDe/1ZK8hoYl+eX46dP9JKWB
F8GA3Ebwst3de/81oNngBjDgNPju6cvgykoT2jCBn4asp1xKekZh5ZbRG40jAeoQ
QAvEgTxIbwNf4HBVhvGjQ6G5lCNO6gQonq1X4UbHgH8tRGSSgnz2yDAdcxtaj1eC
e5BMhjuoHuxS4DCtR97Dtn7YjrYoVus5eMkhEZGuWKK4hkggsLbyUx5llxZxLXPD
mBAYLkqgZsHgiqLeHAdrEmAFaRfskVg0MzfCcEE9StWnWf0ixk3thuwQAaPI4GCB
nNLnmqCcOyCN1Qa3iXjdOzp5bU0qwFi/qFQagq0nu8sJim7q0g0Bk7J+iGRht3no
L5iT9d+8CQaN1it+L+elSYfrr/LamghRvh9epOtGUVH3GQYS4Bgo2rgktGXKtcX+
P/jR/aM034g8VPlbV+Kas/c6mIk9JzKOvJJ4+Lpvr0ZzRG32ez9dAlDogE3xASi6
sMWbY+t4unGs+nY3SqIOyHLTiHUEEBYKAB0WIQRp5kceOuBlKXUpgy5roPWiA39P
QQUCZX7ZYAAKCRBroPWiA39PQadvAQD7F/N3xuyWogrJV7TMZk2PFteviEW2Dv9d
dSUGasK3dQEAxf3HxRDvrv3yJLhgNKa+ksr4bBBmruvilpWS+X4InwKIdQQQFgoA
HRYhBDVy+iobBn8ixYrxVfi4IbQqb9zXBQJlfKbrAAoJEPi4IbQqb9zXe7MBAIzF
QqdV8CJXYIcZJtUUIQ7a/AN2enHBpoa/qXEre5bAAP4uNEUMKiDZRpHAh/KmqarM
vF+c1BOpEJbQsPqv0L5hDIh1BBAWCgAdFiEEKsCkLvsLXLx6BALtTclbbXvpiS4F
AmV8nsgACgkQTclbbXvpiS6bpwD/W0sMOH4lmR4t9Sc8hJB+uBLGYxzoNIgaNa5x
vbdm5hwBAIPYr1SVl0+yghsxg5k75jStRL2S5MZW2iSV3ynNLTkFiHkEEBYKACEW
IQSi/zo2qqVmVBCQZKsZgC+LDXD8MAUCZXe6yAMFAngACgkQGYAviw1w/DCmCAD8
Cfvn8O+N/AJTOKY8lZzk+OSX3tSTQTOUiLHKRl+RX6IA/1Bku44c1YJVZ+RhWkGQ
n0C8ZN/DYzHh9JInl3blLcIMtDhKYW4gQWxleGFuZGVyIFN0ZWZmZW5zIChoZWZ0
aWcpIDxqYW4uc3RlZmZlbnNAZ21haWwuY29tPoiTBBMWCgA7AhsDAheAAhkBFiEE
g7yIiTUbXeu7aEFuuKwIYA8QjN8FAmWq/kIFCwkIBwMFFQoJCAsFFgIDAQACHgUA
CgkQuKwIYA8QjN+ySAD+PI99JJsFWz2CaS3enxjUMCWJZJvSV9G1FqmeTKtH95oA
/ismVRjBbwbCrDDEsZVIK3NeRyRyhiWIVFXWix/KnH4CiHkEEBYKACEWIQSi/zo2
qqVmVBCQZKsZgC+LDXD8MAUCZXe6xQMFAngACgkQGYAviw1w/DDnzAD6AwROKYI8
7DZ6a1onZeR5wOV50bt2LCB4XxNiupHcpLMA/i4dmwa4Bkzyh/h+v0kN2PSssueX
7kFPNcnyhe3KbUUDuDMEZXeUSxYJKwYBBAHaRw8BAQdAkvIbYwde3OFqoAy6QOO9
BPwFNCll8tgQ6iAmQMkOjtWIeAQYFgoAIBYhBIO8iIk1G13ru2hBbrisCGAPEIzf
BQJld5RLAhsgAAoJELisCGAPEIzfhU8A/3NZzIEk3dmCAL0XLtylcFp/HExnN+5Q
RGmT0+SzzuGaAP9ozoMlSjtcGLAZMglLk8/mYzKveR89RJlB0cZtUU6UArg4BGV3
kvYSCisGAQQBl1UBBQEBB0Dh/7CubQh/MabODq3IcoqeGUzEGUPU8GXCVrDmPHih
WQMBCAeIeAQYFgoAIBYhBIO8iIk1G13ru2hBbrisCGAPEIzfBQJld5L2AhsMAAoJ
ELisCGAPEIzfao0A/AiJPB4igiyHjPgR8OpKh4Nz+pwmFrD/j5l2YC0Xi2dOAP4j
LDEa1VCNNhq7vWA8SqUjareBzHpwlG2ObUwYxORjBQ==
=OXp6
-----END PGP PUBLIC KEY BLOCK-----
@@ -0,0 +1,31 @@
diff --git a/Makefile b/Makefile
--- a/Makefile
+++ b/Makefile
@@ -935,6 +935,9 @@ KBUILD_RUSTFLAGS += -Copt-level=2
else ifdef CONFIG_CC_OPTIMIZE_FOR_SIZE
KBUILD_CFLAGS += -Os
KBUILD_RUSTFLAGS += -Copt-level=s
+else ifdef CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE_O3
+KBUILD_CFLAGS += -O3
+KBUILD_RUSTFLAGS += -Copt-level=3
endif
# Always set `debug-assertions` and `overflow-checks` because their default
diff --git a/init/Kconfig b/init/Kconfig
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1622,6 +1622,14 @@ config CC_OPTIMIZE_FOR_SIZE
Choosing this option will pass "-Os" to your compiler resulting
in a smaller kernel.
+config CC_OPTIMIZE_FOR_PERFORMANCE_O3
+ bool "Optimize harder for performance (-O3)"
+ help
+ Build with the "-O3" compiler flag: more inlining, loop
+ unrolling and vectorization than -O2, at the cost of a larger
+ kernel image and larger modules. Rust code is built at
+ opt-level 3.
+
endchoice
config HAVE_LD_DEAD_CODE_DATA_ELIMINATION
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,95 @@
diff --git a/arch/x86/include/asm/cpufeatures.h b/arch/x86/include/asm/cpufeatures.h
--- a/arch/x86/include/asm/cpufeatures.h
+++ b/arch/x86/include/asm/cpufeatures.h
@@ -471,6 +471,8 @@
#define X86_FEATURE_AUTOIBRS (20*32+ 8) /* Automatic IBRS */
#define X86_FEATURE_NO_SMM_CTL_MSR (20*32+ 9) /* SMM_CTL MSR is not present */
+#define X86_FEATURE_L2_TLB_SIZE_X32 (20*32+14) /* L2 TLB sizes are encoded as multiples of 32 */
+
#define X86_FEATURE_GP_ON_USER_CPUID (20*32+17) /* User CPUID faulting */
#define X86_FEATURE_PREFETCHI (20*32+20) /* Prefetch Data/Instruction to Cache Level */
diff --git a/arch/x86/kernel/cpu/amd.c b/arch/x86/kernel/cpu/amd.c
--- a/arch/x86/kernel/cpu/amd.c
+++ b/arch/x86/kernel/cpu/amd.c
@@ -1190,7 +1190,7 @@ static unsigned int amd_size_cache(struct cpuinfo_x86 *c, unsigned int size)
static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c)
{
- u32 ebx, eax, ecx, edx;
+ u32 ebx, eax, ecx, edx, shift, tmp;
u16 mask = 0xfff;
if (c->x86 < 0xf)
@@ -1199,10 +1199,12 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c)
if (c->extended_cpuid_level < 0x80000006)
return;
+ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5;
+
cpuid(0x80000006, &eax, &ebx, &ecx, &edx);
- tlb_lld_4k = (ebx >> 16) & mask;
- tlb_lli_4k = ebx & mask;
+ tlb_lld_4k = ((ebx >> 16) & mask) << shift;
+ tlb_lli_4k = (ebx & mask) << shift;
/*
* K8 doesn't have 2M/4M entries in the L2 TLB so read out the L1 TLB
@@ -1214,16 +1216,18 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c)
}
/* Handle DTLB 2M and 4M sizes, fall back to L1 if L2 is disabled */
- if (!((eax >> 16) & mask))
+ tmp = ((eax >> 16) & mask) << shift;
+ if (!tmp)
tlb_lld_2m = (cpuid_eax(0x80000005) >> 16) & 0xff;
else
- tlb_lld_2m = (eax >> 16) & mask;
+ tlb_lld_2m = tmp;
/* a 4M entry uses two 2M entries */
tlb_lld_4m = tlb_lld_2m >> 1;
/* Handle ITLB 2M and 4M sizes, fall back to L1 if L2 is disabled */
- if (!(eax & mask)) {
+ tmp = (eax & mask) << shift;
+ if (!tmp) {
/* Erratum 658 */
if (c->x86 == 0x15 && c->x86_model <= 0x1f) {
tlb_lli_2m = 1024;
@@ -1231,8 +1235,9 @@ static void cpu_detect_tlb_amd(struct cpuinfo_x86 *c)
cpuid(0x80000005, &eax, &ebx, &ecx, &edx);
tlb_lli_2m = eax & 0xff;
}
- } else
- tlb_lli_2m = eax & mask;
+ } else {
+ tlb_lli_2m = tmp;
+ }
tlb_lli_4m = tlb_lli_2m >> 1;
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -857,7 +857,7 @@ static void get_model_name(struct cpuinfo_x86 *c)
void cpu_detect_cache_sizes(struct cpuinfo_x86 *c)
{
- unsigned int n, dummy, ebx, ecx, edx, l2size;
+ unsigned int n, dummy, ebx, ecx, edx, l2size, shift __maybe_unused;
n = c->extended_cpuid_level;
@@ -877,7 +877,9 @@ void cpu_detect_cache_sizes(struct cpuinfo_x86 *c)
l2size = ecx >> 16;
#ifdef CONFIG_X86_64
+ shift = !!cpu_has(c, X86_FEATURE_L2_TLB_SIZE_X32) * 5;
c->x86_tlbsize += ((ebx >> 16) & 0xfff) + (ebx & 0xfff);
+ c->x86_tlbsize <<= shift;
#else
/* do processor-specific cache resizing */
if (this_cpu->legacy_cache_size)
Binary file not shown.
Loaded 100 of 356 files, more files were not shown because too many files have changed in this diff. Show more