#!/bin/bash # ╔══════════════════════════════════════════════════════════════════════════════╗ # ║ setup/audit-packages.sh — Package source audit tool ║ # ║ ║ # ║ PURPOSE: ║ # ║ Verifies that installed packages come from the expected source ║ # ║ (official pacman repos, AUR, or Flatpak). Reports: ║ # ║ - Missing packages that should be installed ║ # ║ - Packages installed from the wrong source (e.g. pacman pkg from AUR) ║ # ║ - Unexpected foreign/AUR packages not declared in setup scripts ║ # ║ ║ # ║ USAGE: ║ # ║ bash audit-packages.sh # report only ║ # ║ bash audit-packages.sh --fix # report + auto-reinstall wrong-source ║ # ║ bash audit-packages.sh -f # shorthand for --fix ║ # ║ ║ # ║ WHY THIS TOOL EXISTS: ║ # ║ On Arch, some packages exist in both the official repos AND the AUR ║ # ║ (often with a different build/version). If an AUR package shadows an ║ # ║ official one, pacman -Syu won't update it. This script detects drift. ║ # ║ ║ # ║ EXIT CODES: ║ # ║ 0 — all checks passed ║ # ║ 1 — one or more issues found ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ # Verifies installed packages come from the expected source (pacman/AUR/flatpak). # Reports missing packages, wrong-source installs, and unexpected foreign packages. # Pass --fix / -f to automatically reinstall packages from the correct source. set -euo pipefail # ── Output formatting ────────────────────────────────────────────────────────── # ANSI color codes for colored terminal output RED="\e[31m"; YELLOW="\e[33m"; GREEN="\e[32m"; CYAN="\e[36m"; BOLD="\e[1m"; RESET="\e[0m" ok() { echo -e " ${GREEN}✔${RESET} $1"; } # Green checkmark: package is correct warn() { echo -e " ${YELLOW}⚠${RESET} $1"; } # Yellow warning: non-critical issue err() { echo -e " ${RED}✘${RESET} $1"; } # Red X: package missing or wrong hdr() { echo -e "\n${BOLD}${CYAN}$1${RESET}"; } # Bold cyan section header fix() { echo -e " ${CYAN}↺${RESET} $1"; } # Cyan spinner: fix action in progress # ── Fix mode flag ───────────────────────────────────────────────────────────── # Parse the --fix / -f argument. When FIX=1, wrong-source packages will be # automatically reinstalled from the correct source at the end of the audit. FIX=0 [[ "${1:-}" == "--fix" || "${1:-}" == "-f" ]] && FIX=1 # ── Issue counter ───────────────────────────────────────────────────────────── # ISSUES tracks the number of problems found. The final exit code is 1 if > 0. ISSUES=0 flag() { ISSUES=$((ISSUES + 1)); } # Increment the issue counter # ── Wrong-source package lists ──────────────────────────────────────────────── # These arrays collect packages that need reinstallation when --fix is used. WRONG_SOURCE_OFFICIAL=() # expected from pacman, installed from AUR WRONG_SOURCE_AUR=() # expected from AUR, installed from official # --------------------------------------------------------------------------- # Expected package sources — keep in sync with setup/modules/ # --------------------------------------------------------------------------- # IMPORTANT: When you add packages to any module script, add them here too. # The lists are manually maintained to stay in sync with what modules install. # ── PACMAN_PKGS: packages that MUST come from official Arch repositories ────── # If any of these are found to be installed from the AUR instead, that's a drift # issue — the AUR version may not receive official security updates. PACMAN_PKGS=( # core-packages.sh 7zip arch-install-scripts atftp atool base base-devel bind bluez btrfs-progs btop cockpit cockpit-files cockpit-podman cronie curl distrobox fail2ban fastfetch fd ffmpeg firefox flatpak gcc glib2 greetd-tuigreet grub htop inetutils iwd jq ldns less libpulse linux linux-firmware man-db mc nano neovim networkmanager openssh pciutils pipewire podman podman-compose python python-pip qrencode ruby-pkg-config rust rustup ipcalc iputils mtr net-tools nmap smartmontools symlinks tcpdump traceroute tree udisks2 udisks2-btrfs udiskie ufw usbutils vim vnstat wget wireplumber wireless_tools wpa_supplicant wprs yazi zip unzip zram-generator # shell-setup.sh zsh pyright bash atftp bash-language-server clang fzf hyfetch lua-language-server micro pulsemixer z dysk glow # hyprland.sh (official-repo portion) hyprland hyprcursor wl-clipboard hyprpaper hyprlock wofi kitty dunst nwg-dock-hyprland nwg-drawer nwg-menu nwg-look cmake meson cpio pkgconf hyprsunset hypridle ksshaskpass nm-connection-editor network-manager-applet blueman pipewire alsa-utils greetd-tuigreet grim slurp gst-plugin-pipewire imagemagick nerd-fonts otf-font-awesome pipewire-alsa pipewire-jack pipewire-pulse qt5-wayland qt6-wayland swww ttf-jetbrains-mono qt6ct xdg-desktop-portal-hyprland xdg-utils xorg-server xorg-xinit papirus-icon-theme cool-retro-term qalculate-gtk dbus thunar tumbler thunar-archive-plugin thunar-shares-plugin thunar-volman hyprpicker pcmanfm-qt ly hyprpolkitagent pavucontrol playerctl wf-recorder sound-theme-freedesktop kew ) # ── AUR_PKGS: packages that MUST come from the AUR ─────────────────────────── # If any of these are found in the official repos instead, it may mean Arch has # promoted them — but could also mean a naming collision. Flagged for review. # NOTE: AUR packages are treated as optional (warn, not err) when missing, # because they may be legitimately excluded on some systems. AUR_PKGS=( # hyprland.sh AUR portion (not in official repos) hyprland-workspaces vicinae-bin bluetuith wvkbd iwmenu walker-bin ulauncher bzmenu wofi-calc bri chamel # optional apps (only installed if selected in TUI) pamtester # PAM authentication tester pinta # Simple image editor (paint.net-like) localsend # LAN file transfer vesktop # Discord with Vencord themes onlyoffice-bin # Office suite (pre-built binary from AUR) vintagestory # Survival game wprs-git # Wayland proxy (git version) zfs-dkms # ZFS kernel module ) # ── FLATPAK_PKGS: apps installed via Flatpak (Flathub) ─────────────────────── # These are cross-distro app bundles. Listed by their Flatpak app ID. FLATPAK_PKGS=( org.prismlauncher.PrismLauncher # Minecraft launcher ) # --------------------------------------------------------------------------- # Snapshot current state # --------------------------------------------------------------------------- # Take a snapshot of what's installed now so we don't repeatedly call pacman. # This also ensures consistent results across the whole run. ALL_INSTALLED=$(pacman -Qq 2>/dev/null) # All installed packages (name only) FOREIGN=$(pacman -Qmq 2>/dev/null) # AUR / manually installed (foreign) packages OFFICIAL=$(pacman -Qnq 2>/dev/null) # Packages from official sync repos # Helper functions for membership testing — grep -qx matches whole lines exactly is_installed() { echo "$ALL_INSTALLED" | grep -qx "$1"; } is_from_official() { echo "$OFFICIAL" | grep -qx "$1"; } is_from_aur() { echo "$FOREIGN" | grep -qx "$1"; } # --------------------------------------------------------------------------- # 1. Check pacman packages # --------------------------------------------------------------------------- # For each expected official package: # - NOT installed: hard error (✘) — increment issue counter # - Installed from AUR: warning (⚠) — drift; increment counter, queue for fix # - Installed from official: pass (✔) hdr "1/3 Official-repo packages (pacman)" for pkg in "${PACMAN_PKGS[@]}"; do if ! is_installed "$pkg"; then err "$pkg — NOT INSTALLED"; flag elif is_from_aur "$pkg"; then # This package should come from official repos but is installed from AUR. # This means `pacman -Syu` won't update it — potential security gap. warn "$pkg — installed, but from AUR instead of official repo"; flag WRONG_SOURCE_OFFICIAL+=("$pkg") else ok "$pkg" fi done # --------------------------------------------------------------------------- # 2. Check AUR packages # --------------------------------------------------------------------------- # For each expected AUR package: # - NOT installed: soft warning — AUR packages are typically optional # - Installed from official repo: warning — may get wrong version from Arch # - Installed from AUR: pass (✔) hdr "2/3 AUR packages (yay)" for pkg in "${AUR_PKGS[@]}"; do if ! is_installed "$pkg"; then # AUR packages are often optional (selected in TUI installer), so this # is a warning rather than an error — intentional omission is common. warn "$pkg — not installed (optional — may be intentional)" elif is_from_official "$pkg"; then # This package should come from AUR but is installed from official repos. # The official version may differ (newer or older) — flagged for review. warn "$pkg — installed from official repo, not AUR (may need AUR build for correct version)"; flag WRONG_SOURCE_AUR+=("$pkg") else ok "$pkg" fi done # --------------------------------------------------------------------------- # 3. Check flatpak packages # --------------------------------------------------------------------------- # Flatpak packages are checked separately using the `flatpak` command. # Missing Flatpaks are soft warnings since they're always optional. hdr "3/3 Flatpak packages" if command -v flatpak &>/dev/null; then # List installed Flatpak apps (not runtimes) in the 'application' column only FLATPAK_INSTALLED=$(flatpak list --app --columns=application 2>/dev/null) for pkg in "${FLATPAK_PKGS[@]}"; do if echo "$FLATPAK_INSTALLED" | grep -qx "$pkg"; then ok "$pkg" else warn "$pkg — not installed (optional — may be intentional)" fi done else warn "flatpak not found — skipping flatpak checks" fi # --------------------------------------------------------------------------- # 4. Flag unexpected foreign (AUR) packages not in our lists # --------------------------------------------------------------------------- # Any AUR package installed on the system but NOT in our AUR_PKGS list is # unexpected. These may have been installed manually or by other tools. # They're highlighted for human review — not counted as issues. hdr "Bonus: foreign packages not declared in setup scripts" # Build a newline-separated set of declared AUR packages for grep matching AUR_SET=$(printf "%s\n" "${AUR_PKGS[@]}") while IFS= read -r pkg; do # If the foreign package is not in our declared AUR list, highlight it if ! echo "$AUR_SET" | grep -qx "$pkg"; then echo -e " ${CYAN}?${RESET} $pkg — foreign package not in setup scripts" fi done <<< "$FOREIGN" # --------------------------------------------------------------------------- # Fix: reinstall wrong-source packages # --------------------------------------------------------------------------- # When --fix is passed, reinstall any packages found in the wrong source. # Official packages are reinstalled with pacman; AUR packages with yay --aur. FIXED=0 if [ "$FIX" -eq 1 ]; then if [ ${#WRONG_SOURCE_OFFICIAL[@]} -gt 0 ] || [ ${#WRONG_SOURCE_AUR[@]} -gt 0 ]; then hdr "Reinstalling wrong-source packages" fi # Reinstall from official repo (packages that were incorrectly from AUR) for pkg in "${WRONG_SOURCE_OFFICIAL[@]}"; do fix "Reinstalling $pkg from official repo (was AUR)..." # pacman -S without --aur ensures it comes from the sync database if sudo pacman -S --noconfirm "$pkg"; then ok "$pkg reinstalled from official repo" FIXED=$((FIXED + 1)) ISSUES=$((ISSUES - 1)) # Remove from issue count since it's now fixed else err "$pkg reinstall failed" fi done # Reinstall from AUR (packages that were incorrectly from official repos) for pkg in "${WRONG_SOURCE_AUR[@]}"; do fix "Reinstalling $pkg from AUR (was official repo)..." # --aur flag ensures yay only looks in the AUR, not official repos if yay -S --aur --noconfirm "$pkg"; then ok "$pkg reinstalled from AUR" FIXED=$((FIXED + 1)) ISSUES=$((ISSUES - 1)) else err "$pkg reinstall failed" fi done fi # --------------------------------------------------------------------------- # Summary # --------------------------------------------------------------------------- echo if [ "$FIX" -eq 1 ] && [ "$FIXED" -gt 0 ]; then echo -e "${GREEN}${BOLD}Fixed ${FIXED} wrong-source package(s).${RESET}" fi if [ "$ISSUES" -eq 0 ]; then echo -e "${GREEN}${BOLD}All checks passed — no source mismatches or missing required packages.${RESET}" else echo -e "${RED}${BOLD}${ISSUES} issue(s) found — review items marked ✘ or ⚠ above.${RESET}" # Only show the --fix hint if there are packages that can actually be auto-fixed [ "$FIX" -eq 0 ] && [ $((${#WRONG_SOURCE_OFFICIAL[@]} + ${#WRONG_SOURCE_AUR[@]})) -gt 0 ] && \ echo -e "${YELLOW} Run with --fix to automatically reinstall wrong-source packages.${RESET}" exit 1 fi