#!/bin/bash # package-inventory - Compare archsetup packages vs live system # Shows: packages in archsetup but missing from system, # packages on system but not in archsetup # # Usage: package-inventory [--archsetup-only | --system-only | --summary] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")/.." && pwd)" ARCHSETUP="$SCRIPT_DIR/archsetup" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' CYAN='\033[0;36m' BOLD='\033[1m' NC='\033[0m' # ============================================================================ # Extract packages from archsetup script # ============================================================================ extract_archsetup_packages() { local tmp tmp=$(mktemp) # Extract pacman_install and aur_install calls (single package per line) grep -E '^\s*(pacman_install|aur_install)\s+' "$ARCHSETUP" \ | grep -v '^\s*#' \ | sed -E 's/^\s*(pacman_install|aur_install)\s+//' \ | sed -E 's/\s*#.*//' \ | sed 's/"//g' \ | grep -v '^\$' \ >> "$tmp" # Extract packages from for-loops: "for software in pkg1 pkg2 pkg3 ...; do" # These span multiple lines with backslash continuations awk ' /for software in/ { capture=1; sub(/.*for software in/, ""); } capture { cont = ($0 ~ /\\$/) gsub(/\\/, ""); gsub(/;.*/, ""); gsub(/do$/, ""); # Print each word as a package for (i=1; i<=NF; i++) { pkg = $i # Skip shell syntax if (pkg ~ /^\$|^#|^for|^in|^do|^done/) continue gsub(/"/, "", pkg) if (pkg != "") print pkg } if (!cont) capture=0 } ' "$ARCHSETUP" >> "$tmp" # Extract pipx packages (these won't be in pacman -Qqe, note them separately) # Skip for now — pipx packages aren't pacman-managed sort -u "$tmp" rm -f "$tmp" } # ============================================================================ # Get packages on live system # ============================================================================ get_system_packages_explicit() { pacman -Qqe | sort -u # explicitly installed } get_system_packages_all() { pacman -Qq | sort -u # all installed (including deps) } # ============================================================================ # Categorize system packages # ============================================================================ get_system_native() { pacman -Qqen | sort -u # native (official repos) } get_system_foreign() { pacman -Qqem | sort -u # foreign (AUR/manual) } # ============================================================================ # Main comparison # ============================================================================ main() { local mode="${1:-}" local archsetup_pkgs system_explicit system_all archsetup_pkgs=$(extract_archsetup_packages) system_explicit=$(get_system_packages_explicit) system_all=$(get_system_packages_all) # "not installed" = not on system at all (filters out dep-installed packages) # "not in archsetup" = explicitly installed but not declared in archsetup local missing_from_system missing_from_archsetup in_both missing_from_system=$(comm -23 <(echo "$archsetup_pkgs") <(echo "$system_all")) missing_from_archsetup=$(comm -13 <(echo "$archsetup_pkgs") <(echo "$system_explicit")) in_both=$(comm -12 <(echo "$archsetup_pkgs") <(echo "$system_explicit")) local archsetup_count system_count both_count missing_sys_count missing_arch_count archsetup_count=$(echo "$archsetup_pkgs" | wc -l) system_count=$(echo "$system_explicit" | wc -l) both_count=$(echo "$in_both" | grep -c . || true) missing_sys_count=$(echo "$missing_from_system" | grep -c . || true) missing_arch_count=$(echo "$missing_from_archsetup" | grep -c . || true) case "$mode" in --archsetup-only) echo "$missing_from_system" ;; --system-only) echo "$missing_from_archsetup" ;; --summary) print_summary "$archsetup_count" "$system_count" "$both_count" \ "$missing_sys_count" "$missing_arch_count" ;; --help|-h) echo "Usage: package-inventory [OPTION]" echo "" echo "Compare packages in archsetup script vs installed on this system." echo "" echo "Options:" echo " (none) Full report with all sections" echo " --archsetup-only Packages in archsetup but not on system (plain list)" echo " --system-only Packages on system but not in archsetup (plain list)" echo " --summary Summary counts only" echo " --help This help message" ;; "") print_full_report "$archsetup_pkgs" "$system_explicit" \ "$missing_from_system" "$missing_from_archsetup" "$in_both" \ "$archsetup_count" "$system_count" "$both_count" \ "$missing_sys_count" "$missing_arch_count" ;; *) echo "Unknown option: $mode" >&2 echo "Run with --help for usage." >&2 exit 1 ;; esac } # ============================================================================ # Output formatting # ============================================================================ print_summary() { local archsetup_count="$1" system_count="$2" both_count="$3" local missing_sys_count="$4" missing_arch_count="$5" echo -e "${BOLD}Package Inventory Summary${NC}" echo "─────────────────────────────────────" echo -e " Archsetup declares: ${CYAN}${archsetup_count}${NC} packages" echo -e " System has installed: ${CYAN}${system_count}${NC} packages (explicit)" echo -e " In common: ${GREEN}${both_count}${NC}" echo -e " In archsetup, not system: ${YELLOW}${missing_sys_count}${NC}" echo -e " On system, not archsetup: ${YELLOW}${missing_arch_count}${NC}" } print_full_report() { local archsetup_pkgs="$1" system_pkgs="$2" local missing_from_system="$3" missing_from_archsetup="$4" in_both="$5" local archsetup_count="$6" system_count="$7" both_count="$8" local missing_sys_count="$9" missing_arch_count="${10}" echo "" print_summary "$archsetup_count" "$system_count" "$both_count" \ "$missing_sys_count" "$missing_arch_count" if [[ -n "$missing_from_system" ]]; then echo "" echo -e "${BOLD}${YELLOW}In archsetup but NOT installed on this system (${missing_sys_count}):${NC}" echo -e "${YELLOW}These may need installing, or are for a different desktop/hardware.${NC}" echo "─────────────────────────────────────" echo "$missing_from_system" | while read -r pkg; do echo " $pkg" done fi if [[ -n "$missing_from_archsetup" ]]; then echo "" echo -e "${BOLD}${YELLOW}On system but NOT in archsetup (${missing_arch_count}):${NC}" echo -e "${YELLOW}Add to archsetup, or remove from system if no longer needed.${NC}" echo "─────────────────────────────────────" # Split into native and foreign for easier review local native foreign native=$(get_system_native) foreign=$(get_system_foreign) local foreign_extras native_extras foreign_extras=$(comm -12 <(echo "$missing_from_archsetup") <(echo "$foreign")) native_extras=$(comm -12 <(echo "$missing_from_archsetup") <(echo "$native")) local foreign_count native_count foreign_count=$(echo "$foreign_extras" | grep -c . || true) native_count=$(echo "$native_extras" | grep -c . || true) if [[ -n "$foreign_extras" ]]; then echo -e " ${CYAN}AUR/foreign (${foreign_count}):${NC}" echo "$foreign_extras" | while read -r pkg; do echo " $pkg" done fi if [[ -n "$native_extras" ]]; then echo -e " ${CYAN}Official repos (${native_count}):${NC}" echo "$native_extras" | while read -r pkg; do echo " $pkg" done fi fi echo "" } main "$@"