Merge current package defaults into clipboard fix
This commit is contained in:
commit
d9748c6a15
737 files changed
+216530
-1514
No files matched your search
@@ -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 });
|
||||
@@ -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" ]]
|
||||
@@ -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"
|
||||
@@ -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" ]]
|
||||
@@ -1,91 +0,0 @@
|
||||
name: Sync AUR Packages
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Every 6 hours
|
||||
- cron: '0 */6 * * *'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
packages:
|
||||
description: 'Specific packages to sync (space-separated, leave empty for all)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Sync AUR packages
|
||||
run: |
|
||||
docker run --rm \
|
||||
-e PACKAGES="$PACKAGES" \
|
||||
-e HOST_UID="$(id -u)" \
|
||||
-e HOST_GID="$(id -g)" \
|
||||
-v "$PWD/bin:/workspace/bin:ro" \
|
||||
-v "$PWD/helpers:/workspace/helpers:ro" \
|
||||
-v "$PWD/pkgbuilds:/workspace/pkgbuilds" \
|
||||
-w /workspace \
|
||||
archlinux:base-devel bash -lc '
|
||||
set -euo pipefail
|
||||
|
||||
pacman -Syu --noconfirm git jq
|
||||
|
||||
groupadd -g "$HOST_GID" runner
|
||||
useradd -m -u "$HOST_UID" -g "$HOST_GID" runner
|
||||
chown -R runner:runner /workspace/pkgbuilds
|
||||
|
||||
if [[ -n "${PACKAGES:-}" ]]; then
|
||||
read -r -a package_args <<< "$PACKAGES"
|
||||
runuser -u runner -- ./bin/sync-aur "${package_args[@]}"
|
||||
else
|
||||
runuser -u runner -- ./bin/sync-aur
|
||||
fi
|
||||
'
|
||||
env:
|
||||
PACKAGES: ${{ github.event.inputs.packages }}
|
||||
|
||||
- name: Check for changes
|
||||
id: changes
|
||||
run: |
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
echo "has_changes=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "has_changes=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: sync AUR packages'
|
||||
title: 'chore: sync AUR packages'
|
||||
body: |
|
||||
Automated AUR package sync.
|
||||
|
||||
Package sync behavior is controlled by `.omarchy/package.json`.
|
||||
branch: auto/sync-aur
|
||||
delete-branch: true
|
||||
labels: automated
|
||||
reviewers: ryanrhughes
|
||||
|
||||
- name: Notify Basecamp on failure
|
||||
if: failure() && env.BASECAMP_CHATBOT_URL != ''
|
||||
env:
|
||||
BASECAMP_CHATBOT_URL: ${{ secrets.BASECAMP_CHATBOT_URL }}
|
||||
run: |
|
||||
curl -s -o /dev/null \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg content \
|
||||
"🔴 <strong>AUR sync failed</strong><br><a href=\"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\">View run</a>" \
|
||||
'{content: $content}')" \
|
||||
"$BASECAMP_CHATBOT_URL"
|
||||
@@ -27,9 +27,11 @@ jobs:
|
||||
# Runs in an Arch container for vercmp: whether a release is an upgrade has
|
||||
# to be decided by the same comparator pacman will use on users' machines.
|
||||
- name: Update packages from upstream release feeds
|
||||
id: sync
|
||||
run: |
|
||||
docker run --rm \
|
||||
-e PACKAGES="$PACKAGES" \
|
||||
-e UPSTREAM_GITHUB_TOKEN="$UPSTREAM_GITHUB_TOKEN" \
|
||||
-e HOST_UID="$(id -u)" \
|
||||
-e HOST_GID="$(id -g)" \
|
||||
-v "$PWD/bin:/workspace/bin:ro" \
|
||||
@@ -39,7 +41,7 @@ jobs:
|
||||
archlinux:base-devel bash -lc '
|
||||
set -euo pipefail
|
||||
|
||||
pacman -Syu --noconfirm git jq
|
||||
pacman -Syu --noconfirm git jq python libarchive
|
||||
|
||||
groupadd -g "$HOST_GID" runner
|
||||
useradd -m -u "$HOST_UID" -g "$HOST_GID" runner
|
||||
@@ -54,8 +56,12 @@ jobs:
|
||||
'
|
||||
env:
|
||||
PACKAGES: ${{ github.event.inputs.packages }}
|
||||
UPSTREAM_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Failed feeds leave their recipes untouched; completed updates still
|
||||
# reach review. The failed sync step keeps the workflow red and notifies.
|
||||
- name: Check for changes
|
||||
if: ${{ !cancelled() && steps.sync.outcome != 'skipped' }}
|
||||
id: changes
|
||||
run: |
|
||||
if [ -z "$(git status --porcelain)" ]; then
|
||||
@@ -65,7 +71,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.changes.outputs.has_changes == 'true'
|
||||
if: ${{ !cancelled() && steps.changes.outputs.has_changes == 'true' }}
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -75,8 +81,9 @@ jobs:
|
||||
Automated update of packages that track an upstream vendor release
|
||||
feed rather than the AUR.
|
||||
|
||||
Each package reports its newest release through
|
||||
`.omarchy/upstream.sh`.
|
||||
Release watches and providers are declared in `.omarchy/package.json`;
|
||||
exceptional feeds use `.omarchy/upstream.sh`. Failed package updates
|
||||
are left untouched; check the workflow result for outstanding failures.
|
||||
branch: auto/sync-upstream
|
||||
delete-branch: true
|
||||
labels: automated
|
||||
|
||||
@@ -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
|
||||
@@ -39,11 +47,20 @@ jobs:
|
||||
-w /workspace \
|
||||
archlinux:base-devel bash -lc '
|
||||
set -euo pipefail
|
||||
pacman -Syu --noconfirm git jq neovim tmux python
|
||||
pacman -Syu --noconfirm git jq python libarchive neovim tmux
|
||||
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/neovim-remote-clipboard.sh
|
||||
python tests/neovim-clipboard-tmux.py
|
||||
./tests/partial-release.sh
|
||||
./tests/published-build-plan.sh
|
||||
./tests/controller.sh
|
||||
./tests/artifact-helpers.sh
|
||||
./tests/limine-mkinitcpio-hook.sh
|
||||
pacman -S --noconfirm --quiet rclone >/dev/null
|
||||
./tests/publish-artifact.sh
|
||||
'
|
||||
Reference in new issue
Block a user