Files
Arch-R/scripts/repo/gen-pacman-repo
T
Douglas Teles faa9c6f6c4 update: stop the linux package from wiping out-of-tree modules
Installing the linux package through pacman overwrote modules.dep with
the copy generated at kernel build time, which predates every
out-of-tree module: the joypad and the WiFi drivers vanished from the
index and the console booted with dead input and no network
(reproduced on real hardware). Two independent layers fix it: the repo
generator no longer ships any depmod-generated index inside packages
(modules.builtin*/order stay, they are kernel-owned depmod inputs),
and a new alpm hook rebuilds the index over the full overlay tree
after any transaction that touches kernel modules. linux goes to rel 3
so updated devices pick the clean package.
2026-07-08 21:22:34 -03:00

241 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# SPDX-License-Identifier: GPL-2.0
# Copyright (C) 2026 ArchR (https://github.com/archr-linux/Arch-R)
#
# gen-pacman-repo: build a pacman repo (archr-core.db + .pkg.tar.zst per
# package) out of a finished ArchR build tree.
#
# Inputs:
# BUILD_DIR - the build.* dir that has install_pkg/<pkg>/ trees
# (defaults to the most recent build.*-RK3326 under cwd)
# OUT_DIR - where the .pkg.tar.zst and .db get written
# (defaults to ${BUILD_DIR}/../repo-out)
# CHANNEL - stable|next|dev. Goes into PKGINFO and the .db name
# (defaults to dev)
# SIGNER - GPG key id to sign packages and the .db with.
# If empty, packages are not signed (good for local CI).
#
# Output:
# ${OUT_DIR}/archr.db (+.sig if signed)
# ${OUT_DIR}/archr.files
# ${OUT_DIR}/<pkg>-<ver>-<rel>-aarch64.pkg.tar.zst (+.sig)
#
# This is the GitHub-Releases-as-mirror path: after gen-pacman-repo,
# `gh release create repo-${CHANNEL} ${OUT_DIR}/*` uploads everything
# and pacman clients pick it up via the mirrorlist URL.
set -euo pipefail
BUILD_DIR="${BUILD_DIR:-$(ls -1dt build.*-RK3326*aarch64* 2>/dev/null | head -1)}"
[ -z "${BUILD_DIR:-}" ] && { echo "BUILD_DIR not set and no build.*-RK3326*aarch64* found"; exit 2; }
[ -d "${BUILD_DIR}/install_pkg" ] || { echo "no install_pkg/ under ${BUILD_DIR}"; exit 2; }
OUT_DIR="${OUT_DIR:-${BUILD_DIR}/../repo-out}"
CHANNEL="${CHANNEL:-dev}"
SIGNER="${SIGNER:-}"
REPO_NAME="archr"
ARCH="aarch64"
mkdir -p "${OUT_DIR}"
echo "gen-pacman-repo: build=${BUILD_DIR} out=${OUT_DIR} channel=${CHANNEL} signer=${SIGNER:-none}"
# Per-package metadata. Today the package.mk format does not carry a
# clean semver, so we synthesize: PKG_VERSION if numeric, else the
# build date. Real PKGBUILDs replace this when Arch-ification 2.3 lands.
build_one_pkg() {
local src="$1"
local dirname
dirname="$(basename "${src}")"
# Skip anything that is not an actually built package tree.
[ -d "${src}" ] || return 0
# The install_pkg dir name is "<pkgname>-<version_or_githash>". Strip
# the trailing suffix to recover the package name; the suffix is
# either a 40-char git hash or starts with a digit (semver).
local name version
name="$(echo "${dirname}" | sed -E 's/-[0-9a-f]{40}$//; s/-[0-9].*$//')"
# Try to recover the exact PKG_VERSION from a matching package.mk
# under either the ArchR overlay or the top-level packages tree.
# Search any depth; multiple categories share the same package name
# in some cases, take the ArchR overlay first.
local pkgmk=""
pkgmk="$(find projects/ArchR/packages -mindepth 2 -maxdepth 6 \
-path "*/${name}/package.mk" -print -quit 2>/dev/null)"
if [ -z "${pkgmk}" ]; then
pkgmk="$(find packages -mindepth 2 -maxdepth 6 \
-path "*/${name}/package.mk" -print -quit 2>/dev/null)"
fi
version=""
if [ -n "${pkgmk}" ]; then
version="$(awk -F'"' '/^PKG_VERSION/ {print $2; exit}' "${pkgmk}")"
fi
# Some package.mk files set PKG_VERSION dynamically, e.g.:
# PKG_VERSION="$(get_pkg_version gstreamer)"
# awk pulls the unresolved shell expansion as a string, which then
# turns into garbage like "..get_pkg_version.gstreamer." after the
# charset filter. Detect any $( ... ), :- or ${ and bail to the
# dirname fallback below, which holds the version the build system
# actually substituted.
case "${version}" in
*'$('* | *'${'* ) version="" ;;
esac
# Fallback: pull the suffix back out of the dirname.
if [ -z "${version}" ]; then
version="${dirname#${name}-}"
fi
version="${version:-$(date +%Y%m%d)}"
# pacman version must match [a-zA-Z0-9.+_]+. Long hashes are fine
# by this charset, but '/' or '~' would break: tr the rest. Use
# printf instead of echo to avoid the trailing newline being
# turned into a stray '.' by tr -c.
version="$(printf '%s' "${version}" | tr -c 'a-zA-Z0-9.+_' '.')"
# pkgrel comes from the package's PKG_REV (the standard knob for "same
# upstream version, new package build", e.g. a changed local overlay
# like a joypad autoconfig). Hardcoding 1 here meant any local-only
# change never produced a newer package, so pacman never saw it as an
# update. Default to 1 when PKG_REV is unset or dynamic.
local pkgrel=1
if [ -n "${pkgmk}" ]; then
local rev
rev="$(awk -F'"' '/^PKG_REV/ {print $2; exit}' "${pkgmk}")"
case "${rev}" in
''|*'$('* | *'${'* ) : ;;
*) pkgrel="${rev}" ;;
esac
fi
local outfile="${OUT_DIR}/${name}-${version}-${pkgrel}-${ARCH}.pkg.tar.zst"
# .PKGINFO sidecar that pacman reads on install. pkgbase is
# required by repo-add (pacman 7+); xdata pkgtype=pkg matches
# makepkg output so clients treat us as a normal split-package.
local pkginfo="${src}/.PKGINFO"
cat >"${pkginfo}" <<EOF
# Generated by gen-pacman-repo
pkgname = ${name}
pkgbase = ${name}
xdata = pkgtype=pkg
pkgver = ${version}-${pkgrel}
pkgdesc = ArchR ${name} (channel: ${CHANNEL})
url = https://arch-r.io
builddate = $(date +%s)
packager = ArchR Build <build@arch-r.io>
size = $(du -sb "${src}" | cut -f1)
arch = ${ARCH}
license = GPL
EOF
# Pack with .PKGINFO as the FIRST tar entry; pacman/repo-add stream
# the archive and need .PKGINFO up front to validate. Drop the "./"
# prefix so listings match what makepkg produces.
(
cd "${src}"
filelist="$(mktemp)"
{
echo ".PKGINFO"
# .image is build-system plumbing (raw kernel + symbols the
# image script consumes); shipping it would dump ~25MB at
# /.image on every kernel update for nothing.
find . -mindepth 1 -maxdepth 1 -not -name '.PKGINFO' -not -name '.image' -printf '%P\n'
} > "${filelist}"
# The depmod-generated indexes are stale by construction inside a
# package (the kernel builds them before any out-of-tree module
# exists); shipping them wiped the joypad and WiFi drivers from
# modules.dep on every linux update. The device rebuilds them via
# the archr-depmod hook; modules.builtin*/order stay (kernel-owned
# depmod inputs).
tar --zstd -cf "${outfile}" --owner=0 --group=0 \
--exclude='*/modules/*/modules.dep' \
--exclude='*/modules/*/modules.dep.bin' \
--exclude='*/modules/*/modules.alias' \
--exclude='*/modules/*/modules.alias.bin' \
--exclude='*/modules/*/modules.symbols' \
--exclude='*/modules/*/modules.symbols.bin' \
--exclude='*/modules/*/modules.devname' \
--exclude='*/modules/*/modules.softdep' \
--exclude='*/modules/*/modules.weakdep' \
--exclude='*/modules/*/modules.builtin.bin' \
--exclude='*/modules/*/modules.builtin.alias.bin' \
-T "${filelist}"
rm -f "${filelist}"
)
rm -f "${pkginfo}"
# Per-package signing is intentionally NOT done here. The repo db
# carries the SHA256 of every pkg.tar.zst and is itself GPG-signed,
# which gives pacman an unbroken trust chain without doubling the
# asset count on GitHub Releases (1000-per-release cap).
# The signer is still passed to repo-add below so the db gets its
# own .sig and trust flows from there.
echo " + ${name} ${version}-${pkgrel}"
}
echo "gen-pacman-repo: packaging install_pkg/* into .pkg.tar.zst..."
for src in "${BUILD_DIR}/install_pkg/"*; do
build_one_pkg "${src}"
done
# repo-add walks the .pkg.tar.zst files and produces archr.db.tar.gz
# (which pacman renames to archr.db at fetch time) plus archr.files.
echo "gen-pacman-repo: building repo db..."
local_db="${OUT_DIR}/${REPO_NAME}.db.tar.gz"
local_files="${OUT_DIR}/${REPO_NAME}.files.tar.gz"
rm -f "${local_db}" "${local_files}"
if [ -n "${SIGNER}" ]; then
repo-add --sign --key "${SIGNER}" "${local_db}" "${OUT_DIR}"/*.pkg.tar.zst
else
repo-add "${local_db}" "${OUT_DIR}"/*.pkg.tar.zst
fi
# Pacman fetches archr.db (no .tar.gz); the conventional way is to ship
# the .tar.gz and a .db symlink alongside.
( cd "${OUT_DIR}" && \
ln -sf "${REPO_NAME}.db.tar.gz" "${REPO_NAME}.db" && \
ln -sf "${REPO_NAME}.files.tar.gz" "${REPO_NAME}.files" )
# Seed an installed (local) db. Without it the shipped image's
# /var/lib/pacman/local is empty: `pacman -Q` returns nothing and
# `pacman -Syu` (archr-update) can only install user addons, never upgrade
# the base. We register every just-built package as installed (db entry
# only; the files already live in the image squashfs) into a staging dbpath
# and tar the resulting local/ tree. It is published as a repo asset and
# pulled in on first boot by the 004-seed-pacmandb autostart hook (so no
# image-build wiring is needed; the seed always matches this build).
echo "gen-pacman-repo: seeding installed (local) db..."
seed_dbpath="${OUT_DIR}/.seed-dbpath"
rm -rf "${seed_dbpath}" "${OUT_DIR}/.seed-root"
mkdir -p "${seed_dbpath}/local" "${seed_dbpath}/sync" "${OUT_DIR}/.seed-root"
cp "${local_db}" "${seed_dbpath}/sync/${REPO_NAME}.db"
# The host pacman runs with the host architecture and signature policy,
# which rejects our unsigned aarch64 payload; give it a minimal config
# scoped to this seed operation.
seed_conf="${seed_dbpath}/pacman.conf"
printf '[options]\nArchitecture = aarch64\nSigLevel = Never\n' > "${seed_conf}"
if fakeroot pacman -U --dbonly --noconfirm --config "${seed_conf}" \
--root "${OUT_DIR}/.seed-root" --dbpath "${seed_dbpath}" \
--cachedir "${OUT_DIR}" \
"${OUT_DIR}"/*.pkg.tar.zst >/dev/null 2>&1; then
tar -C "${seed_dbpath}" -czf "${OUT_DIR}/${REPO_NAME}-localdb.tar.gz" local
echo " wrote ${REPO_NAME}-localdb.tar.gz ($(ls -1 "${seed_dbpath}/local" | wc -l) entries)"
else
echo " WARNING: local-db seed failed; base will not be pacman-upgradable" >&2
fi
rm -rf "${seed_dbpath}" "${OUT_DIR}/.seed-root"
echo ""
echo "gen-pacman-repo: ready at ${OUT_DIR}"
ls -1sh "${OUT_DIR}" | head -20
echo ""
echo "Next: gh release create repo-${CHANNEL} \\"
echo " --repo archr-linux/archr-repo \\"
echo " --title \"ArchR repo ${CHANNEL} $(date +%Y%m%d)\" \\"
echo " \"${OUT_DIR}\"/*"