aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xarchsetup14
-rwxr-xr-xscripts/audit-packages.sh135
-rw-r--r--tests/audit-packages/test_audit_packages.py127
3 files changed, 270 insertions, 6 deletions
diff --git a/archsetup b/archsetup
index 402fd9e..13ee7a0 100755
--- a/archsetup
+++ b/archsetup
@@ -796,15 +796,17 @@ install_gpu_drivers() {
if $detected_amd; then
display "task" "AMD GPU detected (via modalias) - installing drivers"
- pacman_install mesa
+ pacman_install mesa # includes VAAPI drivers (libva-mesa-driver was folded in)
pacman_install xf86-video-amdgpu
pacman_install vulkan-radeon
- pacman_install libva-mesa-driver # hardware video acceleration
fi
if $detected_nvidia; then
display "task" "NVIDIA GPU detected (via modalias) - installing drivers"
- pacman_install nvidia-dkms # DKMS version for kernel flexibility
+ # nvidia-dkms left the repos; nvidia-open-dkms is the packaged driver
+ # (Turing and newer — pre-Turing cards need an AUR legacy variant,
+ # see the NVIDIA preflight task).
+ pacman_install nvidia-open-dkms
pacman_install nvidia-utils
pacman_install nvidia-settings
pacman_install libva-nvidia-driver # hardware video acceleration
@@ -1675,8 +1677,8 @@ hyprland() {
aur_install pyprland # scratchpads, magnify, expose (fixes special workspace issues)
pacman_install waybar # status bar
pacman_install fuzzel # app launcher (native Wayland, pinentry support)
- pacman_install swww # wallpaper daemon
- aur_install waypaper # wallpaper GUI (swww frontend)
+ pacman_install awww # wallpaper daemon (swww successor; provides swww)
+ aur_install waypaper # wallpaper GUI (awww backend)
aur_install wlogout-git # logout menu
pacman_install grim # screenshot
pacman_install slurp # region select
@@ -1933,7 +1935,7 @@ desktop_environment() {
aur_install "$software"
done
- pacman_install libappindicator-gtk3 # required by some applets
+ pacman_install libayatana-appindicator # appindicator support (libappindicator-gtk3 successor)
# Browsers
diff --git a/scripts/audit-packages.sh b/scripts/audit-packages.sh
new file mode 100755
index 0000000..f7af19f
--- /dev/null
+++ b/scripts/audit-packages.sh
@@ -0,0 +1,135 @@
+#!/bin/bash
+# audit-packages.sh — verify every package archsetup installs still exists
+# at its declared source, and flag packages that moved between the official
+# repos and the AUR.
+#
+# Extraction covers direct `pacman_install pkg` / `aur_install pkg` calls
+# and `for software in a b c; do` loop lists (backslash continuations
+# included). Variable arguments ("$software") are the loop plumbing, not
+# packages, and are skipped.
+#
+# Checks:
+# official package missing from sync dbs -> MOVED TO AUR (if AUR has it)
+# or MISSING EVERYWHERE
+# AUR package gone from the AUR -> NOW IN OFFICIAL (if repos
+# have it) or MISSING EVERYWHERE
+#
+# Usage: audit-packages.sh [--list] [installer-script]
+# --list print the extracted package lists and exit (no network)
+# default audit; exit 1 when anything is missing or moved
+#
+# Env overrides (tests): AUDIT_PACMAN, AUDIT_CURL.
+
+set -u
+
+PACMAN="${AUDIT_PACMAN:-pacman}"
+CURL="${AUDIT_CURL:-curl}"
+
+mode=audit
+case "${1:-}" in
+ --list) mode=list; shift ;;
+esac
+
+script="${1:-$(dirname "$0")/../archsetup}"
+if [ ! -r "$script" ]; then
+ echo "audit-packages: cannot read installer script: $script" >&2
+ exit 2
+fi
+
+# Extract package names per source. Loop lists are attributed to the
+# install command used inside the loop body.
+extract() {
+ awk '
+ # Collect a `for software in ...` list (may span backslash-continued
+ # lines); attribute it when the loop body shows which installer runs.
+ /for[ \t]+[A-Za-z_]+[ \t]+in[ \t]/ {
+ line = $0
+ sub(/^.*[ \t]in[ \t]+/, "", line)
+ listpkgs = ""
+ while (1) {
+ stop = (line ~ /;[ \t]*do([ \t]|$)/)
+ sub(/;[ \t]*do.*$/, "", line)
+ gsub(/\\/, "", line)
+ listpkgs = listpkgs " " line
+ if (stop) break
+ if ((getline line) <= 0) break
+ }
+ pending = listpkgs
+ next
+ }
+ /^[ \t]*(pacman_install|aur_install)[ \t]/ {
+ cmd = ($0 ~ /pacman_install/) ? "official" : "aur"
+ arg = $2
+ gsub(/["'\'']/, "", arg)
+ if (arg ~ /^\$/) {
+ # loop plumbing — emit the pending for-list under this source
+ n = split(pending, w, /[ \t]+/)
+ for (i = 1; i <= n; i++) if (w[i] != "") print cmd, w[i]
+ pending = ""
+ } else if (arg != "") {
+ print cmd, arg
+ }
+ }
+ ' "$script" | sort -u
+}
+
+pairs=$(extract)
+official=$(echo "$pairs" | awk '$1 == "official" { print $2 }')
+aur=$(echo "$pairs" | awk '$1 == "aur" { print $2 }')
+
+if [ "$mode" = list ]; then
+ echo "OFFICIAL ($(echo "$official" | grep -c .)):"
+ echo "$official" | sed 's/^/ /'
+ echo "AUR ($(echo "$aur" | grep -c .)):"
+ echo "$aur" | sed 's/^/ /'
+ exit 0
+fi
+
+# One batched AUR RPC query answers existence for every AUR-relevant name.
+aur_query() {
+ # args: package names; prints the names the AUR knows
+ local args="" p
+ for p in "$@"; do args="$args&arg[]=$p"; done
+ "$CURL" -sm 20 "https://aur.archlinux.org/rpc/v5/info?${args#&}" 2>/dev/null \
+ | tr ',' '\n' | sed -n 's/.*"Name":[[:space:]]*"\([^"]*\)".*/\1/p'
+}
+
+# shellcheck disable=SC2086
+aur_known=$(aur_query $official $aur)
+
+in_repos() { "$PACMAN" -Si "$1" >/dev/null 2>&1; }
+in_aur() { echo "$aur_known" | grep -qx "$1"; }
+
+moved_to_aur="" now_official="" missing=""
+for p in $official; do
+ in_repos "$p" && continue
+ if in_aur "$p"; then moved_to_aur="$moved_to_aur $p"; else missing="$missing $p"; fi
+done
+for p in $aur; do
+ if in_aur "$p"; then
+ in_repos "$p" && now_official="$now_official $p (also in repos)"
+ continue
+ fi
+ if in_repos "$p"; then now_official="$now_official $p"; else missing="$missing $p"; fi
+done
+
+rc=0
+if [ -n "$missing" ]; then
+ echo "MISSING EVERYWHERE (not in repos, not in AUR):"
+ for p in $missing; do echo " $p"; done
+ rc=1
+fi
+if [ -n "$moved_to_aur" ]; then
+ echo "MOVED TO AUR (pacman_install will fail; switch to aur_install):"
+ for p in $moved_to_aur; do echo " $p"; done
+ rc=1
+fi
+if [ -n "$now_official" ]; then
+ echo "NOW IN OFFICIAL (aur_install works but pacman_install is cleaner):"
+ echo "$now_official" | tr ' ' '\n' | grep -v '^$' | sed 's/^/ /'
+ rc=1
+fi
+total=$(( $(echo "$official" | grep -c .) + $(echo "$aur" | grep -c .) ))
+echo "---"
+echo "$total packages audited"
+exit $rc
diff --git a/tests/audit-packages/test_audit_packages.py b/tests/audit-packages/test_audit_packages.py
new file mode 100644
index 0000000..5adb26d
--- /dev/null
+++ b/tests/audit-packages/test_audit_packages.py
@@ -0,0 +1,127 @@
+"""Tests for scripts/audit-packages.sh.
+
+The auditor extracts every package archsetup installs — direct
+pacman_install/aur_install calls plus `for software in ...` loop lists —
+then verifies each against the right source: official packages must exist
+in the sync databases, AUR packages in the AUR. Packages that moved
+between sources are flagged in both directions. Tests run the real script
+against a fixture installer snippet, a fake pacman, and a fake curl that
+serves controlled AUR RPC JSON; no network, no real pacman db.
+
+Run from repo root:
+ make test-unit (or python3 -m unittest tests.audit-packages.test_audit_packages)
+"""
+
+import json
+import os
+import shutil
+import stat
+import subprocess
+import tempfile
+import unittest
+
+REPO_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", ".."))
+SCRIPT = os.path.join(REPO_ROOT, "scripts/audit-packages.sh")
+
+FIXTURE = """
+pacman_install good-official # exists in repos
+pacman_install gone-official # vanished from repos, not in AUR
+pacman_install moved-to-aur # left repos, lives in AUR now
+aur_install good-aur # exists in AUR
+aur_install gone-aur # vanished from AUR
+aur_install now-official # promoted into the repos
+aur_install "$software" # variable — not a literal package
+for software in loop-pkg-a loop-pkg-b \\
+ loop-pkg-c; do
+ pacman_install "$software"
+done
+"""
+
+
+class AuditHarness(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.mkdtemp(prefix="audit-pkgs-test-")
+ self.fixture = os.path.join(self.tmp, "installer-snippet")
+ with open(self.fixture, "w") as f:
+ f.write(FIXTURE)
+
+ # Fake pacman: -Si exits 0 only for the packages "in the repos".
+ official = ["good-official", "now-official",
+ "loop-pkg-a", "loop-pkg-b", "loop-pkg-c"]
+ self.fake_pacman = os.path.join(self.tmp, "pacman")
+ self._stub(self.fake_pacman, (
+ 'case "$2" in\n'
+ + "".join(f" {p}) exit 0 ;;\n" for p in official)
+ + ' *) exit 1 ;;\nesac\n'
+ ))
+
+ # Fake curl: serves AUR RPC v5 info JSON for the packages "in the AUR".
+ aur = ["good-aur", "moved-to-aur"]
+ rpc = json.dumps({"results": [{"Name": p} for p in aur]})
+ self.fake_curl = os.path.join(self.tmp, "curl")
+ self._stub(self.fake_curl, f"printf '%s' '{rpc}'\nexit 0\n")
+
+ def tearDown(self):
+ shutil.rmtree(self.tmp, ignore_errors=True)
+
+ def _stub(self, path, body):
+ with open(path, "w") as f:
+ f.write("#!/bin/sh\n" + body)
+ os.chmod(path, os.stat(path).st_mode | stat.S_IXUSR)
+
+ def run_audit(self, *args):
+ env = os.environ.copy()
+ env["AUDIT_PACMAN"] = self.fake_pacman
+ env["AUDIT_CURL"] = self.fake_curl
+ return subprocess.run(
+ [SCRIPT, *args, self.fixture],
+ env=env, capture_output=True, text=True, timeout=20,
+ )
+
+
+class TestParser(AuditHarness):
+ def test_list_mode_extracts_direct_and_loop_packages(self):
+ r = self.run_audit("--list")
+ self.assertEqual(r.returncode, 0, msg=r.stderr)
+ for p in ("good-official", "gone-official", "moved-to-aur",
+ "loop-pkg-a", "loop-pkg-b", "loop-pkg-c"):
+ self.assertIn(p, r.stdout)
+ for p in ("good-aur", "gone-aur", "now-official"):
+ self.assertIn(p, r.stdout)
+
+ def test_variable_args_are_not_packages(self):
+ r = self.run_audit("--list")
+ self.assertNotIn("$software", r.stdout)
+
+
+class TestAudit(AuditHarness):
+ def test_clean_packages_pass_silently(self):
+ r = self.run_audit()
+ self.assertNotIn("good-official", r.stdout)
+ self.assertNotIn("good-aur", r.stdout)
+
+ def test_missing_everywhere_reported(self):
+ r = self.run_audit()
+ self.assertNotEqual(r.returncode, 0)
+ self.assertIn("gone-official", r.stdout)
+ self.assertIn("gone-aur", r.stdout)
+
+ def test_movers_flagged_in_both_directions(self):
+ r = self.run_audit()
+ self.assertIn("moved-to-aur", r.stdout)
+ self.assertIn("now-official", r.stdout)
+
+ def test_clean_fixture_exits_zero(self):
+ clean = os.path.join(self.tmp, "clean-snippet")
+ with open(clean, "w") as f:
+ f.write("pacman_install good-official\naur_install good-aur\n")
+ env = os.environ.copy()
+ env["AUDIT_PACMAN"] = self.fake_pacman
+ env["AUDIT_CURL"] = self.fake_curl
+ r = subprocess.run([SCRIPT, clean], env=env,
+ capture_output=True, text=True, timeout=20)
+ self.assertEqual(r.returncode, 0, msg=r.stdout + r.stderr)
+
+
+if __name__ == "__main__":
+ unittest.main()