From 04cadb4e35c611c29a197551ffbbd026932d84e3 Mon Sep 17 00:00:00 2001 From: Karim Date: Wed, 24 Jun 2026 21:34:18 +0200 Subject: [PATCH] packaging: daily automated AUR rebuild for the [tanin] repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A systemd user timer (daily) drives aur-autoupdate.sh, which rebuilds the AUR-only deps (eww-git, tiramisu-git, waypaper, calamares, …) in a clean devtools chroot and refreshes tanin.db. Non -git packages are skipped when their AUR version is unchanged (RPC check) so heavy ones like calamares aren't rebuilt for nothing; -git packages rebuild every run. Unattended: a sudoers drop-in grants passwordless access to the chroot helpers. Publishing is a TANIN_PUBLISH_CMD hook (no-op until hosting is wired) — the job refreshes the local repo dir for now. One-time: aur-autoupdate.sh setup (installs devtools, makes the chroot). Co-Authored-By: Claude Opus 4.8 --- packaging/aur-autoupdate.sh | 147 +++++++++++++++++++++ packaging/systemd/tanin-aur-update.service | 18 +++ packaging/systemd/tanin-aur-update.sudoers | 7 + packaging/systemd/tanin-aur-update.timer | 11 ++ 4 files changed, 183 insertions(+) create mode 100755 packaging/aur-autoupdate.sh create mode 100644 packaging/systemd/tanin-aur-update.service create mode 100644 packaging/systemd/tanin-aur-update.sudoers create mode 100644 packaging/systemd/tanin-aur-update.timer diff --git a/packaging/aur-autoupdate.sh b/packaging/aur-autoupdate.sh new file mode 100755 index 0000000..7cebd35 --- /dev/null +++ b/packaging/aur-autoupdate.sh @@ -0,0 +1,147 @@ +#!/usr/bin/env bash +# Daily AUR rebuild for the [tanin] binary repo. +# +# aur-autoupdate.sh setup # one-time: install devtools + create the chroot +# aur-autoupdate.sh run # the daily job (driven by the systemd user timer) +# aur-autoupdate.sh status # show what each package would do, without building +# +# What it does on `run`: +# * fetch each AUR package's PKGBUILD repo into a cache, +# * build it in a clean chroot (devtools) — no build deps leak into the host, +# no interactive sudo (see the shipped sudoers drop-in), +# * but skip non -git packages whose AUR version is unchanged (calamares etc. +# don't get rebuilt every day for nothing); -git packages always rebuild, +# * repo-add the fresh packages into tanin.db, prune superseded files, +# * if TANIN_PUBLISH_CMD is set, run it to upload (hosting is not wired yet, +# so by default this only refreshes the LOCAL repo dir). +# +# Config (env overrides): +# TANIN_REPO_DIR output repo dir (default ~/projects/tanin-repo) +# TANIN_CHROOT clean chroot location (default ~/.cache/tanin-chroot) +# TANIN_AUR_CACHE AUR clone + state cache (default ~/.cache/tanin-aur) +# TANIN_PUBLISH_CMD upload hook, e.g. an rsync (default empty = local only) +set -uo pipefail + +REPO_NAME="tanin" +OUT="${TANIN_REPO_DIR:-$HOME/projects/tanin-repo}" +DB="$OUT/$REPO_NAME.db.tar.zst" +CHROOT="${TANIN_CHROOT:-$HOME/.cache/tanin-chroot}" +CACHE="${TANIN_AUR_CACHE:-$HOME/.cache/tanin-aur}" +PUBLISH="${TANIN_PUBLISH_CMD:-}" + +# AUR-only deps of TANINUX (not in the official repos). Mirrors the list in +# finish-tanin-repo.sh — keep them in sync. +AUR_PKGS=(eww-git tiramisu-git waypaper calamares librewolf-bin + arch-update timeshift-autosnap xdg-terminal-exec paru) + +log() { printf '[%s] %s\n' "$REPO_NAME-aur" "$*"; } + +# --- one-time setup -------------------------------------------------------- +setup() { + sudo pacman -S --needed --noconfirm devtools git jq curl + mkdir -p "$CHROOT" "$CACHE" "$OUT" + if [ ! -d "$CHROOT/root" ]; then + log "creating clean chroot at $CHROOT (base-devel) …" + mkarchroot "$CHROOT/root" base-devel + else + log "chroot already present at $CHROOT" + fi + log "setup done." +} + +# latest AUR version (ver-rel, may include epoch) via the RPC; empty on failure. +aur_version() { + curl -fsSL "https://aur.archlinux.org/rpc/v5/info?arg%5B%5D=$1" 2>/dev/null \ + | jq -r '.results[0].Version // empty' 2>/dev/null +} + +# best-effort version of the newest built package file in a dir (ver-rel). +built_version() { + local f; f="$(ls -t "$1"/*.pkg.tar.zst 2>/dev/null | head -1)" || return 1 + [ -n "$f" ] || return 1 + basename "$f" | sed -E 's/.*-([^-]+-[0-9]+)-[^-]+\.pkg\.tar\.zst$/\1/' +} + +# decide whether $1 needs a rebuild. -git: always. others: AUR ver != last built. +should_build() { + local p="$1" + [[ "$p" == *-git ]] && return 0 + local rpc; rpc="$(aur_version "$p")" + [ -z "$rpc" ] && return 0 # RPC down → build to be safe + local last; last="$(cat "$CACHE/$p.ver" 2>/dev/null || echo)" + [ "$rpc" != "$last" ] +} + +# fetch/refresh the AUR PKGBUILD repo for $1 into the cache. +fetch() { + local p="$1" dir="$CACHE/$p" + if [ -d "$dir/.git" ]; then + git -C "$dir" pull -q --ff-only 2>/dev/null || { rm -rf "$dir"; } + fi + [ -d "$dir/.git" ] || git clone -q "https://aur.archlinux.org/$p.git" "$dir" +} + +# build $1 in the clean chroot and copy results into the repo dir. +build_one() { + local p="$1" dir="$CACHE/$p" + fetch "$p" || { log "FETCH FAILED: $p"; return 1; } + if ! should_build "$p"; then + log "up to date: $p" + return 2 + fi + log "building: $p" + rm -f "$dir"/*.pkg.tar.zst + # -c = clean copy of the chroot each time; -r = which chroot; -- = makepkg args + if ( cd "$dir" && makechrootpkg -c -r "$CHROOT" -- --noconfirm ); then + cp "$dir"/*.pkg.tar.zst "$OUT"/ && log " ok: $p" + built_version "$dir" > "$CACHE/$p.ver" 2>/dev/null || true + return 0 + fi + log " BUILD FAILED: $p" + return 1 +} + +run() { + [ -d "$CHROOT/root" ] || { log "no chroot — run '$0 setup' first"; exit 1; } + mkdir -p "$OUT" "$CACHE" + local built=0 failed=0 + for p in "${AUR_PKGS[@]}"; do + build_one "$p"; case $? in 0) built=$((built+1));; 1) failed=$((failed+1));; esac + done + + if [ "$built" -gt 0 ]; then + log "refreshing DB ($built rebuilt)" + repo-add -q "$DB" "$OUT"/*.pkg.tar.zst >/dev/null + # keep only the newest file per package on disk + command -v paccache >/dev/null && paccache -rq -k1 -c "$OUT" >/dev/null 2>&1 || true + if [ -n "$PUBLISH" ]; then + log "publishing via TANIN_PUBLISH_CMD" + eval "$PUBLISH" || log "PUBLISH FAILED" + else + log "no TANIN_PUBLISH_CMD set — local repo only (hosting not wired yet)" + fi + else + log "nothing rebuilt — DB untouched" + fi + log "done (rebuilt=$built failed=$failed)" + [ "$failed" -eq 0 ] +} + +status() { + for p in "${AUR_PKGS[@]}"; do + if [[ "$p" == *-git ]]; then + printf '%-22s -git → always rebuild\n' "$p" + else + local rpc last; rpc="$(aur_version "$p")"; last="$(cat "$CACHE/$p.ver" 2>/dev/null || echo -)" + printf '%-22s aur=%-16s built=%-16s %s\n' "$p" "${rpc:-?}" "$last" \ + "$([ "$rpc" != "$last" ] && echo '→ REBUILD' || echo 'up-to-date')" + fi + done +} + +case "${1:-run}" in + setup) setup ;; + run) run ;; + status) status ;; + *) echo "usage: $0 {setup|run|status}" >&2; exit 2 ;; +esac diff --git a/packaging/systemd/tanin-aur-update.service b/packaging/systemd/tanin-aur-update.service new file mode 100644 index 0000000..f0f3c82 --- /dev/null +++ b/packaging/systemd/tanin-aur-update.service @@ -0,0 +1,18 @@ +[Unit] +Description=TANINUX [tanin] repo — daily AUR rebuild +Documentation=file:%h/projects/taninux/packaging/aur-autoupdate.sh +After=network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +# Adjust the path if your checkout lives elsewhere. +ExecStart=%h/projects/taninux/packaging/aur-autoupdate.sh run +# Be a good citizen — this is a background rebuild, not interactive work. +Nice=15 +IOSchedulingClass=idle +# Uncomment + set once hosting is wired, e.g.: +# Environment=TANIN_PUBLISH_CMD=rsync -a --delete %h/projects/tanin-repo/ host:/srv/tanin/ + +[Install] +WantedBy=default.target diff --git a/packaging/systemd/tanin-aur-update.sudoers b/packaging/systemd/tanin-aur-update.sudoers new file mode 100644 index 0000000..60b0a69 --- /dev/null +++ b/packaging/systemd/tanin-aur-update.sudoers @@ -0,0 +1,7 @@ +# Passwordless chroot helpers so the daily AUR rebuild runs unattended. +# Install: sudo install -m440 tanin-aur-update.sudoers /etc/sudoers.d/tanin-aur-update +# Validate: sudo visudo -cf /etc/sudoers.d/tanin-aur-update +# +# Replace "karim" if the timer runs as a different user. These are exactly the +# helpers devtools' makechrootpkg shells out to as root; nothing broader. +karim ALL=(root) NOPASSWD: /usr/bin/makechrootpkg, /usr/bin/arch-nspawn, /usr/bin/mkarchroot diff --git a/packaging/systemd/tanin-aur-update.timer b/packaging/systemd/tanin-aur-update.timer new file mode 100644 index 0000000..c26dc5f --- /dev/null +++ b/packaging/systemd/tanin-aur-update.timer @@ -0,0 +1,11 @@ +[Unit] +Description=TANINUX [tanin] repo — daily AUR rebuild (timer) + +[Timer] +# Once a day; catch up after downtime; jitter so it's not on-the-dot. +OnCalendar=daily +Persistent=true +RandomizedDelaySec=30m + +[Install] +WantedBy=timers.target