You've already forked .autoupdate
Compare commits
1 Commits
4defd478dc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 2565c193e6 |
@@ -81,8 +81,22 @@ jobs:
|
||||
chmod 600 ~/.git-credentials
|
||||
|
||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
|
||||
# Validate the key HERE rather than letting it surface as an opaque
|
||||
# `Permission denied (publickey)` at push time, several steps later.
|
||||
if [ -z "${AUR_SSH_KEY//[[:space:]]/}" ]; then
|
||||
echo "::error::AUR_SSH_KEY secret is empty or unset" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s\n' "${AUR_SSH_KEY}" > ~/.ssh/aur
|
||||
chmod 600 ~/.ssh/aur
|
||||
if ! ssh-keygen -y -P "" -f ~/.ssh/aur >/dev/null 2>&1; then
|
||||
echo "::error::AUR_SSH_KEY is not a usable unencrypted private key." >&2
|
||||
echo "Paste the whole file including the BEGIN/END lines; a passphrase-protected key cannot be used unattended." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "AUR key fingerprint: $(ssh-keygen -lf ~/.ssh/aur | awk '{print $2}')"
|
||||
|
||||
ssh-keyscan -t rsa,ecdsa,ed25519 aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
|
||||
cat >> ~/.ssh/config <<'EOF'
|
||||
Host aur.archlinux.org
|
||||
|
||||
@@ -85,12 +85,15 @@ its `sha256sums` entry is regenerated first. A README-only edit does not.
|
||||
| `NO_GIT_PUSH=1` | dry run: do everything except push |
|
||||
| `FORCE_REBUILD=1` | bump `pkgrel` even with no detected change |
|
||||
| `FORCE_REFRESH=1` | re-download remote sources without a version change |
|
||||
| `AUR_FORCE=1` | force-push over diverged AUR history (see below) |
|
||||
| `GIT_HOST` | git host holding the package repos |
|
||||
| `GITHUB_TOKEN` | optional; avoids GitHub's 60/hour anonymous rate limit |
|
||||
|
||||
A rejected AUR push usually means someone pushed there directly. The run fails
|
||||
loudly rather than overwriting it; inspect, then re-run with `AUR_FORCE=1`.
|
||||
There is deliberately no force-push escape hatch. The AUR accepts fast-forwards
|
||||
only and refuses force pushes from anyone who is not an AUR administrator, so
|
||||
such a flag could never do anything except recommend a remedy that always fails.
|
||||
`sync-aur.sh` checks fast-forward status *before* pushing and, on failure, prints
|
||||
git's actual error and names a cause that fits it — a rejected SSH key, an
|
||||
unreachable host, or a genuine divergence — rather than assuming.
|
||||
|
||||
## Exit codes
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
# NO_GIT_PUSH=1 do everything except push (dry run)
|
||||
# FORCE_REBUILD=1 bump pkgrel even with no detected change
|
||||
# FORCE_REFRESH=1 re-download remote sources even without a version change
|
||||
# AUR_FORCE=1 force-push over diverged AUR history
|
||||
#
|
||||
# This is a RECONCILER, not a version-bump script. A new upstream release is
|
||||
# only one of the things that can make a package diverge from the AUR; a manual
|
||||
@@ -93,7 +92,11 @@ update_one() {
|
||||
# A failure in here exits non-zero and is NOT mistaken for "no new version".
|
||||
local oldver newver
|
||||
oldver="$(pkgbuild_get pkgver)"
|
||||
newver="$("$LATEST_VERSION_CMD" .)"
|
||||
# Checked explicitly rather than leaning on `set -e`, so the failure is
|
||||
# attributed to the lookup instead of surfacing several steps later as an
|
||||
# unrelated .SRCINFO complaint.
|
||||
newver="$("$LATEST_VERSION_CMD" .)" || die "upstream version lookup failed for ${pkg}"
|
||||
validate_version "$newver" "upstream version for ${pkg}"
|
||||
info "upstream ${newver}, local ${oldver:-<empty>}"
|
||||
|
||||
local version_changed=0
|
||||
@@ -267,8 +270,28 @@ extract_appimage_assets() {
|
||||
rm -rf "$(readlink -f "${tmp}/squashfs-root" 2>/dev/null || true)" "$tmp"
|
||||
}
|
||||
|
||||
# One package per process, deliberately.
|
||||
#
|
||||
# The obvious `( update_one "$pkg" ) || { rc=1; ... }` is broken: putting a
|
||||
# subshell in an && / || list disables `set -e` for its ENTIRE body, not just
|
||||
# for the subshell as a unit. update_one then sails past a failed sync-aur.sh
|
||||
# straight into `git push origin`, which is precisely the "origin ahead of the
|
||||
# AUR" state this whole tool exists to avoid -- and it happened in production.
|
||||
#
|
||||
# inner() { echo start; false; echo "REACHED CODE AFTER FAILURE"; }
|
||||
# ( inner ) || echo caught # prints REACHED CODE AFTER FAILURE
|
||||
# ( inner ) # correctly aborts
|
||||
#
|
||||
# Re-exec'ing gives each package a fresh shell whose `set -e` is intact; the
|
||||
# parent's AND-OR context cannot reach across a process boundary. The CI matrix
|
||||
# already passes exactly one package, so the loop only affects local runs.
|
||||
if [ -n "${AUTOUPDATE_SINGLE:-}" ]; then
|
||||
update_one "$1"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rc=0
|
||||
for pkg in "$@"; do
|
||||
( update_one "$pkg" ) || { rc=1; warn "${pkg} FAILED"; }
|
||||
AUTOUPDATE_SINGLE=1 "$0" "$pkg" || { rc=1; warn "${pkg} FAILED"; }
|
||||
done
|
||||
exit "$rc"
|
||||
|
||||
42
sync-aur.sh
42
sync-aur.sh
@@ -50,6 +50,16 @@ if git fetch --quiet aur-ro master 2>/dev/null; then
|
||||
log "AUR already in sync (${pkgname})"
|
||||
exit 0
|
||||
fi
|
||||
# Established BEFORE pushing, rather than inferred from a failure afterwards.
|
||||
# The AUR only ever accepts fast-forwards, so this is the whole decision --
|
||||
# there is no force to fall back on.
|
||||
if ! git merge-base --is-ancestor FETCH_HEAD HEAD; then
|
||||
die "the AUR has commits origin does not, for ${pkgname}.
|
||||
The AUR accepts fast-forwards only and refuses force pushes from anyone who
|
||||
is not an AUR administrator, so this cannot be overwritten. Rebase or replay
|
||||
the local changes onto the AUR's HEAD, then re-run."
|
||||
fi
|
||||
|
||||
log "AUR differs from origin; pushing (${pkgname})"
|
||||
else
|
||||
log "no AUR repo for ${pkgname} yet; the first push will create it"
|
||||
@@ -60,20 +70,28 @@ if [ -n "${NO_GIT_PUSH:-}" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git push aur HEAD:master; then
|
||||
if push_output="$(git push aur HEAD:master 2>&1)"; then
|
||||
printf '%s\n' "$push_output"
|
||||
log "pushed ${pkgname} to the AUR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# A rejected push here usually means the AUR has commits origin does not,
|
||||
# i.e. someone pushed directly. Overwriting that silently would destroy it.
|
||||
if [ -n "${AUR_FORCE:-}" ]; then
|
||||
warn "AUR_FORCE set: force-pushing over the AUR history for ${pkgname}"
|
||||
git push --force aur HEAD:master
|
||||
exit 0
|
||||
fi
|
||||
# Report what git actually said, then name a remedy that can work. The previous
|
||||
# version blamed every failure on a diverged history and recommended a force
|
||||
# push -- wrong on both counts for what turned out to be an unregistered SSH key.
|
||||
printf '%s\n' "$push_output" >&2
|
||||
|
||||
die "push to the AUR was rejected for ${pkgname}.
|
||||
The AUR history has probably diverged from origin (someone pushed there
|
||||
directly). Inspect it, then re-run with AUR_FORCE=1 to overwrite.
|
||||
This run leaves origin untouched and will be retried on the next pass."
|
||||
case "$push_output" in
|
||||
*"Permission denied"*|*"publickey"*|*"Could not read from remote repository"*)
|
||||
die "the AUR rejected the SSH key for ${pkgname}.
|
||||
Check that AUR_SSH_KEY holds a valid private key and that its public half is
|
||||
saved on your AUR account (My Account -> SSH Public Key; the form needs your
|
||||
account password before it will save)." ;;
|
||||
*"non-fast-forward"*|*"fetch first"*)
|
||||
die "the AUR moved between the pre-check and the push for ${pkgname}.
|
||||
Nothing to do: the next run re-reads the AUR and reconciles." ;;
|
||||
*"Host key verification failed"*|*"Could not resolve hostname"*)
|
||||
die "could not reach aur.archlinux.org for ${pkgname}; see the error above." ;;
|
||||
*)
|
||||
die "push to the AUR failed for ${pkgname}; see the git error above." ;;
|
||||
esac
|
||||
|
||||
99
test.sh
99
test.sh
@@ -59,6 +59,21 @@ EOS
|
||||
done
|
||||
[ -n "${PORT:-}" ] || { echo "could not start test http server" >&2; exit 1; }
|
||||
|
||||
# A git shim used by one test to simulate an SSH auth rejection on push.
|
||||
# Everything else passes straight through to the real git.
|
||||
REAL_GIT="$(command -v git)"
|
||||
mkdir -p "$SANDBOX/bin"
|
||||
cat > "$SANDBOX/bin/git" <<EOS
|
||||
#!/usr/bin/env bash
|
||||
if [ "\${FAKE_PUSH_AUTH_FAIL:-}" = "1" ] && [ "\$1" = "push" ] && [ "\$2" = "aur" ]; then
|
||||
echo "aur@aur.archlinux.org: Permission denied (publickey)." >&2
|
||||
echo "fatal: Could not read from remote repository." >&2
|
||||
exit 128
|
||||
fi
|
||||
exec "$REAL_GIT" "\$@"
|
||||
EOS
|
||||
chmod +x "$SANDBOX/bin/git"
|
||||
|
||||
export PKG_REPO_BASE="file://$SANDBOX/origin"
|
||||
export AUR_HTTPS_BASE="file://$SANDBOX/aur"
|
||||
export AUR_SSH_BASE="file://$SANDBOX/aur"
|
||||
@@ -333,8 +348,18 @@ test_lookup_failure_is_loud() {
|
||||
make_pkg p9
|
||||
local before; before="$(aur_head p9)"
|
||||
FAKE_VERSION_FAIL=1 run_update p9
|
||||
assert_eq "1" "$?" "broken upstream lookup: exits 1, not mistaken for 'no new version'"
|
||||
local rc=$?
|
||||
assert_eq "1" "$rc" "broken upstream lookup: exits 1, not mistaken for 'no new version'"
|
||||
assert_eq "$before" "$(aur_head p9)" "broken upstream lookup: nothing pushed"
|
||||
|
||||
# Assert the REASON, not just the exit code. This previously passed even with
|
||||
# the lookup failure being swallowed: the run carried on with an empty version
|
||||
# and exited 1 several steps later when .SRCINFO generation rejected it, which
|
||||
# is the pman-helper pkgver="" bug wearing a different hat.
|
||||
grep -q "upstream version lookup failed" "$SANDBOX/last.log" \
|
||||
&& pass "broken upstream lookup: fails AT the lookup, not later" \
|
||||
|| fail "broken upstream lookup: fails AT the lookup, not later" \
|
||||
"expected the lookup-failure message; got: $(tail -3 "$SANDBOX/last.log")"
|
||||
}
|
||||
|
||||
test_empty_version_aborts_before_commit() {
|
||||
@@ -397,6 +422,78 @@ test_multiarch_colliding_downloads() {
|
||||
assert_ne "$x" "$a" "colliding downloads: sums differ despite the shared :: target"
|
||||
}
|
||||
|
||||
# The ordering guarantee: if the AUR push fails, origin must NOT be pushed.
|
||||
#
|
||||
# This regressed in production. `( update_one ) || ...` disables set -e for the
|
||||
# whole subshell body, so a failed sync-aur.sh did not stop the run and origin
|
||||
# ended up ahead of the AUR -- the exact state the tool exists to prevent.
|
||||
test_aur_failure_leaves_origin_unpushed() {
|
||||
make_pkg p16
|
||||
local clone="$SANDBOX/manual-p16"
|
||||
git clone --quiet "file://$SANDBOX/origin/p16" "$clone"
|
||||
( cd "$clone" && sed -i 's/^pkgdesc=.*/pkgdesc="needs publishing"/' PKGBUILD \
|
||||
&& makepkg --printsrcinfo > .SRCINFO \
|
||||
&& git commit --quiet -am "change to publish" && git push --quiet origin main )
|
||||
|
||||
local origin_before aur_before
|
||||
origin_before="$(git -C "$SANDBOX/origin/p16" rev-parse main)"
|
||||
aur_before="$(aur_head p16)"
|
||||
|
||||
# Push target does not exist -> the AUR push fails, the fetch still works.
|
||||
AUR_SSH_BASE="file://$SANDBOX/nonexistent" FAKE_VERSION=1.0.0 run_update p16
|
||||
local rc=$?
|
||||
|
||||
assert_eq "1" "$rc" "aur failure: run exits 1"
|
||||
assert_eq "$aur_before" "$(aur_head p16)" "aur failure: AUR unchanged"
|
||||
assert_eq "$origin_before" "$(git -C "$SANDBOX/origin/p16" rev-parse main)" \
|
||||
"aur failure: origin NOT pushed (the ordering guarantee)"
|
||||
|
||||
# It must not blame divergence for what is a plain push failure.
|
||||
grep -q "has commits origin does not" "$SANDBOX/last.log" \
|
||||
&& fail "aur failure: not misreported as divergence" "blamed divergence for a non-divergence failure" \
|
||||
|| pass "aur failure: not misreported as divergence"
|
||||
}
|
||||
|
||||
# A real non-fast-forward must be named as such -- and must NOT suggest forcing,
|
||||
# since the AUR refuses force pushes from non-administrators.
|
||||
test_genuine_divergence_is_reported() {
|
||||
make_pkg p17
|
||||
local clone="$SANDBOX/manual-p17"
|
||||
git clone --quiet "file://$SANDBOX/aur/p17.git" "$clone"
|
||||
( cd "$clone" && echo "edited directly on the AUR" >> README.md \
|
||||
&& git commit --quiet -am "out-of-band AUR commit" \
|
||||
&& git push --quiet origin HEAD:master )
|
||||
|
||||
FAKE_VERSION=1.0.0 run_update p17
|
||||
local rc=$?
|
||||
assert_eq "1" "$rc" "divergence: exits 1"
|
||||
grep -q "has commits origin does not" "$SANDBOX/last.log" \
|
||||
&& pass "divergence: reported as divergence" \
|
||||
|| fail "divergence: reported as divergence" "$(tail -4 "$SANDBOX/last.log")"
|
||||
grep -qi "AUR_FORCE" "$SANDBOX/last.log" \
|
||||
&& fail "divergence: does not suggest forcing" "AUR_FORCE is gone; nothing should mention it" \
|
||||
|| pass "divergence: does not suggest forcing"
|
||||
}
|
||||
|
||||
test_auth_failure_is_reported_as_auth() {
|
||||
make_pkg p18
|
||||
local clone="$SANDBOX/manual-p18"
|
||||
git clone --quiet "file://$SANDBOX/origin/p18" "$clone"
|
||||
( cd "$clone" && sed -i 's/^pkgdesc=.*/pkgdesc="publish me"/' PKGBUILD \
|
||||
&& makepkg --printsrcinfo > .SRCINFO \
|
||||
&& git commit --quiet -am "change" && git push --quiet origin main )
|
||||
|
||||
PATH="$SANDBOX/bin:$PATH" FAKE_PUSH_AUTH_FAIL=1 FAKE_VERSION=1.0.0 run_update p18
|
||||
local rc=$?
|
||||
assert_eq "1" "$rc" "auth failure: exits 1"
|
||||
grep -q "rejected the SSH key" "$SANDBOX/last.log" \
|
||||
&& pass "auth failure: reported as an SSH key problem" \
|
||||
|| fail "auth failure: reported as an SSH key problem" "$(tail -4 "$SANDBOX/last.log")"
|
||||
grep -q "has commits origin does not" "$SANDBOX/last.log" \
|
||||
&& fail "auth failure: not misreported as divergence" "this is the production bug" \
|
||||
|| pass "auth failure: not misreported as divergence"
|
||||
}
|
||||
|
||||
test_srcinfo_guard() {
|
||||
make_pkg p13
|
||||
local clone="$SANDBOX/manual-p13"
|
||||
|
||||
Reference in New Issue
Block a user