aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xinstaller/archangel23
-rw-r--r--installer/lib/common.sh61
-rw-r--r--tests/unit/test_common.bats152
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" ]
+}