Add a native Arch/CachyOS package via its own AUR publish workflow #39
@@ -19,7 +19,10 @@ name: Publish AUR Package
|
|||||||
# does NOT commit anything back to this repo — `packaging/arch/PKGBUILD` stays
|
# does NOT commit anything back to this repo — `packaging/arch/PKGBUILD` stays
|
||||||
# a hand-maintained template with a placeholder version, and every real,
|
# a hand-maintained template with a placeholder version, and every real,
|
||||||
# published version lives only in AUR's own git history, which is where a
|
# published version lives only in AUR's own git history, which is where a
|
||||||
# PKGBUILD's revision history is expected to live.
|
# PKGBUILD's revision history is expected to live. A corollary worth knowing:
|
||||||
|
# a hand-edit made directly in the AUR repo (outside this workflow) is
|
||||||
|
# silently overwritten the next time this runs, since every run renders fresh
|
||||||
|
# from this repo's template rather than starting from AUR's current state.
|
||||||
#
|
#
|
||||||
# ## Required secret
|
# ## Required secret
|
||||||
#
|
#
|
||||||
@@ -51,16 +54,26 @@ jobs:
|
|||||||
|
|
||||||
- name: Resolve version and find the Linux asset
|
- name: Resolve version and find the Linux asset
|
||||||
id: resolve
|
id: resolve
|
||||||
|
env:
|
||||||
|
VERSION_INPUT: ${{ inputs.version }}
|
||||||
|
GH_PAT: ${{ secrets.GH_PAT }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
VERSION="${{ inputs.version }}"
|
# Authenticated when the secret is available (it is, everywhere
|
||||||
if [ -z "$VERSION" ]; then
|
# else in this repo's workflows) to avoid the unauthenticated
|
||||||
|
# 60-requests/hour-per-IP cap; still works without it, just at that
|
||||||
|
# lower limit, since this hits nothing but a public repo's public
|
||||||
|
# releases.
|
||||||
|
AUTH=()
|
||||||
|
[ -n "${GH_PAT}" ] && AUTH=(-H "Authorization: Bearer ${GH_PAT}")
|
||||||
|
|
||||||
|
if [ -z "${VERSION_INPUT}" ]; then
|
||||||
echo "No version given — resolving the latest GitHub release"
|
echo "No version given — resolving the latest GitHub release"
|
||||||
RELEASE_JSON=$(curl -fsS "https://api.github.com/repos/${GITHUB_REPO}/releases/latest")
|
RELEASE_JSON=$(curl -fsS "${AUTH[@]}" "https://api.github.com/repos/${GITHUB_REPO}/releases/latest")
|
||||||
else
|
else
|
||||||
echo "Using requested version ${VERSION}"
|
echo "Using requested version ${VERSION_INPUT}"
|
||||||
RELEASE_JSON=$(curl -fsS "https://api.github.com/repos/${GITHUB_REPO}/releases/tags/v${VERSION}")
|
RELEASE_JSON=$(curl -fsS "${AUTH[@]}" "https://api.github.com/repos/${GITHUB_REPO}/releases/tags/v${VERSION_INPUT}")
|
||||||
fi
|
fi
|
||||||
|
|
||||||
TAG=$(echo "$RELEASE_JSON" | jq -r '.tag_name')
|
TAG=$(echo "$RELEASE_JSON" | jq -r '.tag_name')
|
||||||
@@ -72,9 +85,12 @@ jobs:
|
|||||||
# lowercase Cargo binary name, and asset naming is exactly the kind
|
# lowercase Cargo binary name, and asset naming is exactly the kind
|
||||||
# of thing that silently drifts if a future Tauri upgrade changes
|
# of thing that silently drifts if a future Tauri upgrade changes
|
||||||
# bundler defaults — a hardcoded pattern here would then 404
|
# bundler defaults — a hardcoded pattern here would then 404
|
||||||
# forever until someone noticed.
|
# forever until someone noticed. `head -1` guards against a release
|
||||||
DEB_URL=$(echo "$RELEASE_JSON" | jq -r '.assets[] | select(.name | endswith("_amd64.deb")) | .browser_download_url')
|
# somehow carrying more than one matching asset, which would
|
||||||
DEB_NAME=$(echo "$RELEASE_JSON" | jq -r '.assets[] | select(.name | endswith("_amd64.deb")) | .name')
|
# otherwise pass the emptiness check below and then break the
|
||||||
|
# download step with two URLs on one line.
|
||||||
|
DEB_URL=$(echo "$RELEASE_JSON" | jq -r '.assets[] | select(.name | endswith("_amd64.deb")) | .browser_download_url' | head -1)
|
||||||
|
DEB_NAME=$(echo "$RELEASE_JSON" | jq -r '.assets[] | select(.name | endswith("_amd64.deb")) | .name' | head -1)
|
||||||
if [ -z "$DEB_URL" ] || [ "$DEB_URL" = "null" ]; then
|
if [ -z "$DEB_URL" ] || [ "$DEB_URL" = "null" ]; then
|
||||||
echo "No *_amd64.deb asset found on release ${TAG}" >&2
|
echo "No *_amd64.deb asset found on release ${TAG}" >&2
|
||||||
exit 1
|
exit 1
|
||||||
@@ -119,19 +135,22 @@ jobs:
|
|||||||
# kind of thing that looks correct, passes review, and breaks the
|
# kind of thing that looks correct, passes review, and breaks the
|
||||||
# next time someone touches it. `re.sub` with `count=1` and an
|
# next time someone touches it. `re.sub` with `count=1` and an
|
||||||
# exact `.format`-free literal match is boring and that's the
|
# exact `.format`-free literal match is boring and that's the
|
||||||
# point: every substitution below fails loudly (KeyError / the
|
# point: every substitution below fails loudly (an assertion /
|
||||||
# assertions after) rather than silently no-op'ing if the
|
# the checks after) rather than silently no-op'ing if the
|
||||||
# template's shape ever drifts from what this expects.
|
# template's shape ever drifts from what this expects.
|
||||||
#
|
#
|
||||||
# pkgrel resets to 1 for a new pkgver — a packaging-only fix to the
|
# pkgrel resets to 1 for a new pkgver — a packaging-only fix to the
|
||||||
# same upstream version (a dependency bump, say) is what pkgrel is
|
# same upstream version (a dependency bump, say) is what pkgrel is
|
||||||
# for, and this workflow always republishes the current PKGBUILD
|
# for, and this workflow always republishes the current PKGBUILD
|
||||||
# verbatim rather than incrementing anything, so 1 is always
|
# verbatim rather than incrementing anything, so 1 is always
|
||||||
# correct here.
|
# correct for what this workflow does. It is NOT correct for a
|
||||||
python3 - "$VERSION" "$DEB_NAME" "$DEB_SHA256" "$LICENSE_SHA256" <<'PY'
|
# dependency-only fix republished at the *same* pkgver: pkgrel
|
||||||
|
# would be forced back to 1, and no existing installation sees an
|
||||||
|
# upgrade. That case needs a manual pkgrel bump in the template
|
||||||
|
# before dispatching, which this workflow has no input for.
|
||||||
|
python3 - "$VERSION" "$DEB_NAME" "$DEB_SHA256" "$LICENSE_SHA256" "$GITHUB_REPO" <<'PY'
|
||||||
import re, sys
|
import re, sys
|
||||||
version, deb_name, deb_sha, license_sha = sys.argv[1:5]
|
version, deb_name, deb_sha, license_sha, github_repo = sys.argv[1:6]
|
||||||
github_repo = "shadowdao/triple-c"
|
|
||||||
|
|
||||||
with open("PKGBUILD") as f:
|
with open("PKGBUILD") as f:
|
||||||
text = f.read()
|
text = f.read()
|
||||||
@@ -167,9 +186,20 @@ jobs:
|
|||||||
- name: Validate with makepkg and namcap
|
- name: Validate with makepkg and namcap
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
docker run --rm -v "$PWD/rendered:/work" -w /work archlinux:latest bash -c '
|
|
||||||
|
# A bind mount (`docker run -v "$PWD/...":/work`) is the more
|
||||||
|
# obvious way to write this, and was the first draft — but on a
|
||||||
|
# containerized Gitea act_runner job, `$PWD` is a path inside this
|
||||||
|
# job's own container, which the daemon's host cannot resolve; the
|
||||||
|
# mount would silently attach an empty directory instead of failing
|
||||||
|
# loudly. `docker cp` moves real bytes across that boundary
|
||||||
|
# regardless of where the daemon actually lives, which is what
|
||||||
|
# makes this work under both a bind-mount-capable runner and a
|
||||||
|
# containerized one.
|
||||||
|
docker pull archlinux:latest
|
||||||
|
CID=$(docker create -w /work archlinux:latest bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
pacman -Sy --noconfirm base-devel namcap sudo git openssh >/dev/null
|
pacman -Syu --noconfirm --needed base-devel namcap sudo git openssh >/dev/null
|
||||||
useradd -m builder
|
useradd -m builder
|
||||||
chown -R builder:builder /work
|
chown -R builder:builder /work
|
||||||
echo "builder ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/builder
|
echo "builder ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/builder
|
||||||
@@ -178,11 +208,24 @@ jobs:
|
|||||||
echo "--- namcap ---"
|
echo "--- namcap ---"
|
||||||
NAMCAP_OUT=$(sudo -u builder bash -c "cd /work && namcap PKGBUILD *.pkg.tar.*" || true)
|
NAMCAP_OUT=$(sudo -u builder bash -c "cd /work && namcap PKGBUILD *.pkg.tar.*" || true)
|
||||||
echo "$NAMCAP_OUT"
|
echo "$NAMCAP_OUT"
|
||||||
if echo "$NAMCAP_OUT" | grep -q "^[a-zA-Z0-9_-]*bin E:"; then
|
# Matches "triple-c-bin E:", "PKGBUILD (triple-c-bin) E:" and any
|
||||||
|
# split-package variant ("triple-c-bin-debug E:") alike — namcap
|
||||||
|
# uses more than one line shape for its two rule families, and
|
||||||
|
# namcap itself exits 0 regardless of what it reports, so this
|
||||||
|
# grep is the only thing standing between an E: and a green job.
|
||||||
|
if echo "$NAMCAP_OUT" | grep -q " E: "; then
|
||||||
echo "namcap reported an error — see above" >&2
|
echo "namcap reported an error — see above" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
'
|
')
|
||||||
|
mkdir -p rendered
|
||||||
|
docker cp rendered/. "${CID}:/work"
|
||||||
|
# `docker start -a` streams output and its exit code is the
|
||||||
|
# container's own — the same failure this would have hit with a
|
||||||
|
# bind mount still fails the job the same way.
|
||||||
|
docker start -a "${CID}"
|
||||||
|
docker cp "${CID}:/work/.SRCINFO" rendered/.SRCINFO
|
||||||
|
docker rm -f "${CID}" >/dev/null
|
||||||
|
|
||||||
- name: Push to AUR
|
- name: Push to AUR
|
||||||
env:
|
env:
|
||||||
@@ -197,10 +240,19 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p ~/.ssh
|
mkdir -p ~/.ssh
|
||||||
|
# Created with the final mode before any bytes land in it, rather
|
||||||
|
# than a plain redirect followed by chmod, which leaves the key
|
||||||
|
# world-readable for whatever window falls between the two calls.
|
||||||
|
install -m 600 /dev/null ~/.ssh/aur
|
||||||
echo "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur
|
echo "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur
|
||||||
chmod 600 ~/.ssh/aur
|
# TOFU, not verification — accepted here because pinning AUR's
|
||||||
|
# actual host key needs a value fetched from somewhere trusted
|
||||||
|
# ahead of time, which this workflow doesn't have, and getting a
|
||||||
|
# pinned value wrong fails every future run rather than just this
|
||||||
|
# one. A keyscan failure below surfaces later as an opaque
|
||||||
|
# "Host key verification failed" rather than a clear one here.
|
||||||
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
|
ssh-keyscan -H aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
|
||||||
export GIT_SSH_COMMAND="ssh -i ~/.ssh/aur -o UserKnownHostsFile=~/.ssh/known_hosts"
|
export GIT_SSH_COMMAND="ssh -i ~/.ssh/aur -o IdentitiesOnly=yes -o UserKnownHostsFile=~/.ssh/known_hosts"
|
||||||
|
|
||||||
git clone "${AUR_REPO}" aur-repo
|
git clone "${AUR_REPO}" aur-repo
|
||||||
cp rendered/PKGBUILD rendered/.SRCINFO aur-repo/
|
cp rendered/PKGBUILD rendered/.SRCINFO aur-repo/
|
||||||
@@ -213,4 +265,10 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
git commit -m "triple-c-bin: update to ${VERSION}"
|
git commit -m "triple-c-bin: update to ${VERSION}"
|
||||||
git push origin master
|
# AUR itself uses `master`, which is what a fresh, not-yet-created
|
||||||
|
# AUR package's empty repo advertises on clone — but the *local*
|
||||||
|
# branch name after cloning an empty repo falls back to whatever
|
||||||
|
# this runner's `init.defaultBranch` is if the server sends no
|
||||||
|
# symref, so naming the destination explicitly is what keeps this
|
||||||
|
# working if that default is ever `main` instead of `master`.
|
||||||
|
git push origin HEAD:master
|
||||||
|
|||||||
+17
-6
@@ -41,11 +41,15 @@ provides=('triple-c')
|
|||||||
conflicts=('triple-c')
|
conflicts=('triple-c')
|
||||||
# !strip: the upstream .deb's binary is already the release build Tauri
|
# !strip: the upstream .deb's binary is already the release build Tauri
|
||||||
# produced and tested; re-stripping a prebuilt binary is unnecessary risk for
|
# produced and tested; re-stripping a prebuilt binary is unnecessary risk for
|
||||||
# no benefit. !debug: there is no debug info in a release binary for
|
# no benefit. It's also what actually suppresses makepkg's debug-package
|
||||||
# makepkg's debug-package machinery to extract, so without this it builds an
|
# machinery here (debug-package extraction requires strip; verified in a
|
||||||
# empty usr/src/debug/ tree for nothing (confirmed with namcap against a real
|
# real build — with !strip alone, no debug package is produced at all).
|
||||||
# build — this was its only non-cosmetic complaint, once the license file
|
# !debug is kept anyway, explicit about intent rather than relying on that
|
||||||
# below was added).
|
# side effect. Without either, makepkg built a usr/src/debug/triple-c-bin
|
||||||
|
# tree containing a dangling .build-id symlink, which is a real namcap
|
||||||
|
# error (not just the empty-directory warning it looks like) — there is no
|
||||||
|
# debug info in this release binary for the machinery to have extracted in
|
||||||
|
# the first place.
|
||||||
options=('!strip' '!debug')
|
options=('!strip' '!debug')
|
||||||
# Tauri names the asset after `productName` verbatim ("Triple-C"), not the
|
# Tauri names the asset after `productName` verbatim ("Triple-C"), not the
|
||||||
# lowercase Cargo binary name — verified against the real release, not
|
# lowercase Cargo binary name — verified against the real release, not
|
||||||
@@ -68,7 +72,14 @@ package() {
|
|||||||
# this app carries no separate resource directory under usr/lib/, so there
|
# this app carries no separate resource directory under usr/lib/, so there
|
||||||
# is nothing that could disagree between Debian's and Arch's package trees
|
# is nothing that could disagree between Debian's and Arch's package trees
|
||||||
# for it to land in the wrong place.
|
# for it to land in the wrong place.
|
||||||
ar x "Triple-C_${pkgver}_amd64.deb"
|
#
|
||||||
|
# Globbed rather than named literally: the publish workflow discovers the
|
||||||
|
# real asset name from the release itself specifically so a Tauri bundler
|
||||||
|
# naming change can't silently break this — naming the file again here
|
||||||
|
# would throw that away and fail this one line with an opaque "No such
|
||||||
|
# file or directory" instead. `source=()` above guarantees exactly one
|
||||||
|
# `*_amd64.deb` entry, so the glob can only ever match that one file.
|
||||||
|
ar x ./*_amd64.deb
|
||||||
tar xf data.tar.* -C "$pkgdir"
|
tar xf data.tar.* -C "$pkgdir"
|
||||||
|
|
||||||
install -Dm644 "$srcdir/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
install -Dm644 "$srcdir/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/LICENSE"
|
||||||
|
|||||||
@@ -44,3 +44,10 @@ the workflow rewrites them for every real publish and never commits the
|
|||||||
result back here, so don't read this file's `pkgver` as "the last published
|
result back here, so don't read this file's `pkgver` as "the last published
|
||||||
version." Everything else (`depends`, `pkgdesc`, `package()`) is meant to be
|
version." Everything else (`depends`, `pkgdesc`, `package()`) is meant to be
|
||||||
edited by hand normally, the same as any other PKGBUILD.
|
edited by hand normally, the same as any other PKGBUILD.
|
||||||
|
|
||||||
|
**A hand-edit made directly in the AUR repo is silently overwritten the
|
||||||
|
next time this workflow runs.** Every run renders fresh from *this*
|
||||||
|
repo's template rather than starting from whatever AUR's copy currently
|
||||||
|
looks like, so a quick fix pushed straight to AUR (bumping `pkgrel` for a
|
||||||
|
packaging-only issue, say) survives only until the next dispatch. Make
|
||||||
|
the fix here instead.
|
||||||
|
|||||||
Reference in New Issue
Block a user