diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-09 23:45:00 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-09 23:45:00 -0500 |
| commit | 4e6f4cc66206f02e92d4a2ca2f414fad5a3439a1 (patch) | |
| tree | 27c48401b170856691023a50573a26e0032a89fc | |
| parent | 39b4a8bc5cac3a2092122f6c4fbede9bf0139286 (diff) | |
| download | archangel-4e6f4cc66206f02e92d4a2ca2f414fad5a3439a1.tar.gz archangel-4e6f4cc66206f02e92d4a2ca2f414fad5a3439a1.zip | |
feat(install): install baked AUR packages and clean the target config
Wire the baked AUR repo into the installer. Before pacstrap, install_base checks whether the ISO shipped the repo and, if so, exposes [aur] in the live /etc/pacman.conf and reads the package names from the manifest, adding them to the pacstrap set so they install into the target offline. This mirrors the existing [archzfs] handling. pacstrap resolves repos from the live system, not $MNTPOINT.
The live config already carries [aur] from the shipped ISO config, so the append is idempotent by design. A --skip-aur ISO ships no repo, and aur_repo_available gates the whole path, so the installer still works there.
configure_system strips any [aur] stanza from the target /etc/pacman.conf. pacstrap installs a stock target config with no [aur], so this is defensive, but it guarantees the installed system never references /usr/share/aur-packages, which exists only on the live ISO.
Four new common.sh helpers carry the logic: aur_repo_available, append_aur_repo (idempotent), aur_manifest_names (the manifest is the source of what to install, so the list never drifts), and strip_repo_stanza. All four covered across Normal, Boundary, and Error.
| -rwxr-xr-x | installer/archangel | 23 | ||||
| -rw-r--r-- | installer/lib/common.sh | 61 | ||||
| -rw-r--r-- | tests/unit/test_common.bats | 152 |
3 files changed, 236 insertions, 0 deletions
diff --git a/installer/archangel b/installer/archangel index ea18202..ec3017a 100755 --- a/installer/archangel +++ b/installer/archangel @@ -779,11 +779,27 @@ EOF info "ZFS will be built from source via DKMS - this ensures kernel compatibility." fi + # Expose the baked AUR local repo to pacstrap the same way as [archzfs]: + # add it to the LIVE /etc/pacman.conf so pacstrap installs the baked + # packages into the target offline (pacstrap resolves repos from the live + # system, not $MNTPOINT). Only when the ISO actually shipped the repo — a + # --skip-aur ISO won't have it. The manifest is the source of which + # packages to install, so the list never drifts from what was baked. + local aur_repo_dir="/usr/share/aur-packages" + local -a aur_packages=() + if aur_repo_available "$aur_repo_dir"; then + info "Exposing baked AUR repo to pacstrap..." + append_aur_repo /etc/pacman.conf "file://$aur_repo_dir" + mapfile -t aur_packages < <(aur_manifest_names "$aur_repo_dir/manifest.tsv") + info "Baked AUR packages to install: ${aur_packages[*]:-none}" + fi + info "Installing base packages (this takes a while)..." local packages mapfile -t packages < <(pacstrap_packages "$FILESYSTEM") \ || error "Unknown filesystem: $FILESYSTEM" + packages+=("${aur_packages[@]}") # Use yes to auto-select defaults for provider prompts yes "" | pacstrap -K $MNTPOINT "${packages[@]}" @@ -834,6 +850,13 @@ Server = https://github.com/archzfs/archzfs/releases/download/experimental SigLevel = Never EOF + # The baked [aur] repo is exposed only in the LIVE config for pacstrap. + # The installed system must not reference /usr/share/aur-packages — that + # path exists only on the live ISO. pacstrap installs a stock target + # pacman.conf (no [aur]), so this is defensive, but it guarantees the + # invariant regardless of how the target config was produced. + strip_repo_stanza aur "$MNTPOINT/etc/pacman.conf" + # Configure journald for ZFS # Problem: journald starts before ZFS mounts /var/log, so journal files # get created in tmpfs then hidden when ZFS mounts over it. diff --git a/installer/lib/common.sh b/installer/lib/common.sh index 8f44170..0317034 100644 --- a/installer/lib/common.sh +++ b/installer/lib/common.sh @@ -124,6 +124,67 @@ required_commands() { } ############################# +# AUR Local Repository +############################# +# The ISO can bake a local pacman repo of AUR packages at /usr/share/aur-packages +# (built by build-aur.sh at ISO-build time). These helpers expose it to +# pacstrap from the LIVE system, list what it holds, and keep the installed +# target's pacman.conf from referencing a path the target won't have. See +# docs/aur-local-repo-spec.org. + +# True when the baked AUR repo exists at $1 (default /usr/share/aur-packages). +# repo-add ships aur.db (a symlink) alongside aur.db.tar.gz; the airootfs copy +# may dereference the symlink to a plain file, so accept either name. A +# --skip-aur ISO ships no repo, so this gates every AUR install-time step. +aur_repo_available() { + local repo_dir="${1:-/usr/share/aur-packages}" + [[ -f "$repo_dir/aur.db" || -f "$repo_dir/aur.db.tar.gz" ]] +} + +# Append an [aur] stanza pointing at $server to the pacman.conf at $1, unless +# one is already present. Idempotent so a re-run of the installer doesn't stack +# duplicates. SigLevel = Optional TrustAll mirrors the build-side stanza: the +# repo is trusted by construction. +append_aur_repo() { + local pacman_conf="$1" server="$2" + grep -q '^\[aur\]' "$pacman_conf" && return 0 + cat >> "$pacman_conf" <<EOF + +[aur] +SigLevel = Optional TrustAll +Server = $server +EOF +} + +# Print the package names (first TSV column) from the AUR build manifest at +# $1, skipping the header. Empty output when the file is absent — the +# manifest is the single source for what to install, so the installer never +# hard-codes the baked package list. +aur_manifest_names() { + local manifest="$1" + [[ -f "$manifest" ]] || return 0 + awk -F'\t' 'NR>1 {print $1}' "$manifest" +} + +# Remove the named repo's stanza (its [name] header and the config lines up to +# the next [section] or EOF) from the pacman.conf at $2. Used to ensure the +# installed target never references the baked [aur] repo, whose +# /usr/share/aur-packages path exists only on the live ISO. A no-op when the +# stanza is absent. +strip_repo_stanza() { + local repo="$1" pacman_conf="$2" + local tmp + tmp=$(mktemp) + awk -v header="[$repo]" ' + $0 == header { skip = 1; next } + skip && /^\[/ { skip = 0 } + skip { next } + { print } + ' "$pacman_conf" > "$tmp" + mv "$tmp" "$pacman_conf" +} + +############################# # Password / Passphrase Input ############################# diff --git a/tests/unit/test_common.bats b/tests/unit/test_common.bats index a639a4e..6f9d1b1 100644 --- a/tests/unit/test_common.bats +++ b/tests/unit/test_common.bats @@ -568,3 +568,155 @@ Boot0001* ZFSBootMenu" run required_commands ext4 [ "$status" -eq 1 ] } + +############################# +# append_aur_repo +############################# +# Appends an [aur] stanza to a pacman.conf for the baked local repo, the +# same shape as the [archzfs] handling. Idempotent: a second call is a +# no-op so re-running the installer doesn't stack duplicate stanzas. + +@test "append_aur_repo adds the stanza with the given Server" { + local f + f=$(mktemp) + printf '%s\n' '[options]' '[core]' > "$f" + append_aur_repo "$f" "file:///usr/share/aur-packages" + grep -q '^\[aur\]$' "$f" + grep -q '^SigLevel = Optional TrustAll$' "$f" + grep -q '^Server = file:///usr/share/aur-packages$' "$f" + rm -f "$f" +} + +@test "append_aur_repo preserves existing repos" { + local f + f=$(mktemp) + printf '%s\n' '[core]' 'Include = /etc/pacman.d/mirrorlist' > "$f" + append_aur_repo "$f" "file:///usr/share/aur-packages" + grep -q '^\[core\]$' "$f" + grep -q '^Include = /etc/pacman.d/mirrorlist$' "$f" + rm -f "$f" +} + +@test "append_aur_repo is idempotent — no duplicate [aur] on a second call" { + local f + f=$(mktemp) + printf '%s\n' '[core]' > "$f" + append_aur_repo "$f" "file:///usr/share/aur-packages" + append_aur_repo "$f" "file:///usr/share/aur-packages" + [ "$(grep -c '^\[aur\]$' "$f")" -eq 1 ] + rm -f "$f" +} + +############################# +# strip_repo_stanza +############################# +# Removes a named repo stanza (header + its config lines up to the next +# section) so the installed target's pacman.conf never references the baked +# [aur] repo path, which won't exist on the target. + +@test "strip_repo_stanza removes the [aur] stanza and its config lines" { + local f + f=$(mktemp) + printf '%s\n' \ + '[core]' 'Include = /etc/pacman.d/mirrorlist' \ + '' '[aur]' 'SigLevel = Optional TrustAll' \ + 'Server = file:///usr/share/aur-packages' \ + '' '[extra]' 'Include = /etc/pacman.d/mirrorlist' > "$f" + strip_repo_stanza aur "$f" + ! grep -q '^\[aur\]$' "$f" + ! grep -q 'aur-packages' "$f" + rm -f "$f" +} + +@test "strip_repo_stanza preserves sections before and after [aur]" { + local f + f=$(mktemp) + printf '%s\n' \ + '[core]' 'Include = /etc/pacman.d/mirrorlist' \ + '[aur]' 'Server = file:///usr/share/aur-packages' \ + '[extra]' 'Include = /etc/pacman.d/mirrorlist' > "$f" + strip_repo_stanza aur "$f" + grep -q '^\[core\]$' "$f" + grep -q '^\[extra\]$' "$f" + rm -f "$f" +} + +@test "strip_repo_stanza handles a stanza at end of file" { + local f + f=$(mktemp) + printf '%s\n' \ + '[core]' 'Include = /etc/pacman.d/mirrorlist' \ + '[aur]' 'SigLevel = Optional TrustAll' \ + 'Server = file:///usr/share/aur-packages' > "$f" + strip_repo_stanza aur "$f" + grep -q '^\[core\]$' "$f" + ! grep -q '^\[aur\]$' "$f" + ! grep -q 'aur-packages' "$f" + rm -f "$f" +} + +@test "strip_repo_stanza is a no-op when the stanza is absent" { + local f before after + f=$(mktemp) + printf '%s\n' '[core]' '[extra]' > "$f" + before=$(cat "$f") + strip_repo_stanza aur "$f" + after=$(cat "$f") + [ "$before" = "$after" ] + rm -f "$f" +} + +############################# +# aur_repo_available +############################# + +@test "aur_repo_available is true when aur.db is present" { + local d + d=$(mktemp -d) + touch "$d/aur.db" + run aur_repo_available "$d" + [ "$status" -eq 0 ] + rm -rf "$d" +} + +@test "aur_repo_available is true when only the aur.db.tar.gz is present" { + local d + d=$(mktemp -d) + touch "$d/aur.db.tar.gz" + run aur_repo_available "$d" + [ "$status" -eq 0 ] + rm -rf "$d" +} + +@test "aur_repo_available is false when the repo db is missing" { + local d + d=$(mktemp -d) + run aur_repo_available "$d" + [ "$status" -ne 0 ] + rm -rf "$d" +} + +############################# +# aur_manifest_names +############################# + +@test "aur_manifest_names prints package names, skipping the header" { + local f + f=$(mktemp) + printf 'name\tpkgbase\tfilename\n' > "$f" + printf 'yay\tyay\tyay-12.0-1-x86_64.pkg.tar.zst\n' >> "$f" + printf 'zrepl\tzrepl\tzrepl-0.6-1-x86_64.pkg.tar.zst\n' >> "$f" + run aur_manifest_names "$f" + [ "$status" -eq 0 ] + [ "$(echo "$output" | wc -l)" -eq 2 ] + [[ "$output" == *"yay"* ]] + [[ "$output" == *"zrepl"* ]] + [[ "$output" != *"name"* ]] + rm -f "$f" +} + +@test "aur_manifest_names emits nothing for a missing manifest" { + run aur_manifest_names /nonexistent/manifest.tsv + [ "$status" -eq 0 ] + [ -z "$output" ] +} |
