211 lines
8.2 KiB
YAML
211 lines
8.2 KiB
YAML
---
|
|
name: Update AUR Packages
|
|
|
|
on:
|
|
schedule:
|
|
- cron: "0 0 * * *"
|
|
workflow_dispatch:
|
|
inputs:
|
|
package:
|
|
description: "Single package to update (blank = all)"
|
|
required: false
|
|
type: string
|
|
force_rebuild:
|
|
description: "Bump pkgrel even with no detected change"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
force_refresh:
|
|
description: "Re-download remote sources even without a version change"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
no_git_push:
|
|
description: "Dry run: do everything except push"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
|
|
defaults:
|
|
run:
|
|
shell: bash
|
|
|
|
jobs:
|
|
# Resolves the package list for the update matrix below, so adding or
|
|
# dropping a package is a variable edit on the org, not a commit here.
|
|
# See README "Package list".
|
|
plan:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
packages: ${{ steps.resolve.outputs.packages }}
|
|
steps:
|
|
- name: Resolve the package list
|
|
id: resolve
|
|
# Both go through the environment, never pasted into the script: the
|
|
# variable is free-form multi-line text.
|
|
env:
|
|
UPDATE_PACKAGES: ${{ vars.UPDATE_PACKAGES }}
|
|
PACKAGE_INPUT: ${{ inputs.package }}
|
|
run: |
|
|
set -euo pipefail
|
|
export LC_ALL=C
|
|
: "${UPDATE_PACKAGES:=}" "${PACKAGE_INPUT:=}"
|
|
re='^[A-Za-z0-9][A-Za-z0-9._+-]*$'
|
|
pkgs=()
|
|
declare -A seen=()
|
|
|
|
# add NAME: validate, then append unless already listed.
|
|
add() {
|
|
if [[ ! $1 =~ $re ]]; then
|
|
echo "::error::${from}: invalid package name '$1' (letters, digits and . _ + - only, starting with a letter or digit)" >&2
|
|
exit 1
|
|
fi
|
|
if [ -z "${seen[$1]:-}" ]; then
|
|
seen[$1]=1
|
|
pkgs+=("$1")
|
|
fi
|
|
}
|
|
|
|
if [ -n "${PACKAGE_INPUT//[[:space:]]/}" ]; then
|
|
# workflow_dispatch with a package name runs only that one,
|
|
# whatever UPDATE_PACKAGES says. Surrounding whitespace is
|
|
# trimmed; the rest must be a single valid name.
|
|
from="workflow_dispatch input 'package'"
|
|
want=${PACKAGE_INPUT#"${PACKAGE_INPUT%%[![:space:]]*}"}
|
|
want=${want%"${want##*[![:space:]]}"}
|
|
add "${want}"
|
|
else
|
|
# "- a\n- b", "a\nb", "- a - b" and "a b" are the same list:
|
|
# comments go first, then every whitespace run (CR included, in
|
|
# case the value was saved with CRLF line ends) becomes a line
|
|
# break. Each line is then a name, a lone "-" list marker, or a
|
|
# "-" glued to a name.
|
|
from="UPDATE_PACKAGES"
|
|
while IFS= read -r tok; do
|
|
case ${tok} in
|
|
"" | -) continue ;;
|
|
-*) tok=${tok#-} ;;
|
|
esac
|
|
add "${tok}"
|
|
done <<< "$(printf '%s\n' "${UPDATE_PACKAGES}" | sed -e 's/#.*$//' | tr -s '[:space:]' '\n')"
|
|
fi
|
|
|
|
# Fail rather than run nothing: an empty matrix would pass and look
|
|
# exactly like a night with nothing to update.
|
|
if [ "${#pkgs[@]}" -eq 0 ]; then
|
|
echo '::error::UPDATE_PACKAGES is not set: add an org or repo variable with one package per line (leading "- " optional)' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Names are validated above, so none needs JSON escaping.
|
|
json=
|
|
for p in "${pkgs[@]}"; do
|
|
json+="${json:+,}\"${p}\""
|
|
done
|
|
json="[${json}]"
|
|
|
|
echo "${#pkgs[@]} package(s) from ${from}:"
|
|
printf ' - %s\n' "${pkgs[@]}"
|
|
echo "matrix: ${json}"
|
|
echo "packages=${json}" >> "${GITHUB_OUTPUT}"
|
|
|
|
update:
|
|
needs: plan
|
|
runs-on: ubuntu-latest
|
|
# A real Arch makepkg is required. Ubuntu's build environment silently
|
|
# emits an empty .SRCINFO, which the AUR then rejects -- that is what broke
|
|
# reflex-appimage, nethlink-appimage and pman-helper. regen_srcinfo() in
|
|
# common.sh fails loudly if this ever regresses.
|
|
#
|
|
# Prebuilt by build-image.yaml (see dockerfile) so the toolchain is not
|
|
# reinstalled in all six jobs every night.
|
|
container:
|
|
image: git.bjphoster.com/aur/autoupdate:latest
|
|
credentials:
|
|
username: ${{ vars.REGISTRY_USER }}
|
|
password: ${{ secrets.GIT_TOKEN }}
|
|
|
|
strategy:
|
|
# One broken package must not cancel the rest of the matrix.
|
|
fail-fast: false
|
|
matrix:
|
|
# One entry per package, resolved by the plan job above from
|
|
# vars.UPDATE_PACKAGES or the dispatch input.
|
|
package: ${{ fromJSON(needs.plan.outputs.packages) }}
|
|
|
|
steps:
|
|
# No bootstrap step: node, git, openssh, jq, curl and the makepkg
|
|
# toolchain all come from the prebuilt image above. node in particular
|
|
# has to be present before this action runs, since actions/checkout is
|
|
# JavaScript and the runner executes it with `node` from the image.
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Configure git and SSH
|
|
env:
|
|
GIT_NAME: ${{ vars.GIT_NAME }}
|
|
GIT_EMAIL: ${{ vars.GIT_EMAIL }}
|
|
GIT_HOST: ${{ vars.GIT_HOST }}
|
|
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
|
|
AUR_SSH_KEY: ${{ secrets.AUR_SSH_KEY }}
|
|
run: |
|
|
set -euo pipefail
|
|
git config --global user.name "${GIT_NAME}"
|
|
git config --global user.email "${GIT_EMAIL}"
|
|
git config --global credential.helper store
|
|
echo "https://${GIT_EMAIL//@/%40}:${GIT_TOKEN}@${GIT_HOST}" > ~/.git-credentials
|
|
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
|
|
User aur
|
|
IdentityFile ~/.ssh/aur
|
|
IdentitiesOnly yes
|
|
EOF
|
|
chmod 600 ~/.ssh/config
|
|
|
|
# The `builder` user is baked into the image; this only hands it the
|
|
# workspace and the credentials written by the previous step.
|
|
- name: Hand over to the build user
|
|
run: |
|
|
set -euo pipefail
|
|
chown -R builder:builder "${GITHUB_WORKSPACE}"
|
|
cp -r /root/.ssh /root/.gitconfig /root/.git-credentials /home/builder/ 2>/dev/null || true
|
|
chown -R builder:builder /home/builder
|
|
|
|
- name: Update ${{ matrix.package }}
|
|
env:
|
|
GIT_HOST: ${{ vars.GIT_HOST }}
|
|
# Compared against both `true` and 'true' on purpose. A bare
|
|
# `${{ inputs.x && '1' || '' }}` is wrong if the runner hands the
|
|
# input over as the STRING "false", which is truthy -- that would
|
|
# silently enable the flag on every run.
|
|
FORCE_REBUILD: ${{ (inputs.force_rebuild == true || inputs.force_rebuild == 'true') && '1' || '' }}
|
|
FORCE_REFRESH: ${{ (inputs.force_refresh == true || inputs.force_refresh == 'true') && '1' || '' }}
|
|
NO_GIT_PUSH: ${{ (inputs.no_git_push == true || inputs.no_git_push == 'true') && '1' || '' }}
|
|
run: |
|
|
set -euo pipefail
|
|
runuser -u builder -- env \
|
|
GIT_HOST="${GIT_HOST}" \
|
|
FORCE_REBUILD="${FORCE_REBUILD}" \
|
|
FORCE_REFRESH="${FORCE_REFRESH}" \
|
|
NO_GIT_PUSH="${NO_GIT_PUSH}" \
|
|
./autoupdate.sh "${{ matrix.package }}"
|