#!/usr/bin/env bash # AUR-Shield Client Installer # Configures pacman to use the AUR-Shield repo set -euo pipefail SHIELD_HOST="${AUR_SHIELD_HOST:-10.90.9.102}" SHIELD_PORT="${AUR_SHIELD_PORT:-8443}" # Load config from /etc/safe-yay.conf if it exists (overrides defaults) if [ -f /etc/safe-yay.conf ]; then source /etc/safe-yay.conf fi echo "╔══════════════════════════════════════════════╗" echo "║ AUR-Shield Client Installer ║" echo "╚══════════════════════════════════════════════╝" echo "" if ! command -v pacman &>/dev/null; then echo "✗ This script requires Arch Linux (pacman)." exit 1 fi echo "Server: http://$SHIELD_HOST:$SHIELD_PORT" echo "" # Write config file echo "Writing /etc/safe-yay.conf..." sudo tee /etc/safe-yay.conf > /dev/null << EOFCONF # AUR-Shield client configuration SHIELD_HOST="$SHIELD_HOST" SHIELD_PORT="$SHIELD_PORT" EOFCONF echo "✓ Config written to /etc/safe-yay.conf" # Add pacman repo PACMAN_CONF="/etc/pacman.conf" REPO_ENTRY="[aur-shield] Server = http://$SHIELD_HOST:$SHIELD_PORT/repo SigLevel = Never" if grep -q "^\[aur-shield\]" "$PACMAN_CONF"; then echo "⚠ [aur-shield] already exists in $PACMAN_CONF — skipping" else echo "Adding [aur-shield] to $PACMAN_CONF..." sudo tee -a "$PACMAN_CONF" > /dev/null << EOF # AUR-Shield — AI-scanned AUR packages $REPO_ENTRY EOF echo "✓ Added repo entry" fi # Install safe-yay wrapper WRAPPER_PATH="/usr/local/bin/safe-yay" echo "Installing safe-yay wrapper to $WRAPPER_PATH..." sudo tee "$WRAPPER_PATH" > /dev/null << 'WRAPPER' #!/usr/bin/env bash # safe-yay — wrapper that routes AUR packages through AUR-Shield set -euo pipefail SHIELD_HOST="${AUR_SHIELD_HOST:-10.90.9.102}" SHIELD_PORT="${AUR_SHIELD_PORT:-8443}" # Load config from /etc/safe-yay.conf if it exists (overrides defaults) if [ -f /etc/safe-yay.conf ]; then source /etc/safe-yay.conf fi # Exit codes (archcanary-compatible) # 0 = clean, 1 = warning, 2 = malicious/blocked if [ $# -eq 0 ]; then echo "Usage: safe-yay [package2 ...]" echo " safe-yay -Syu # System-Update mit AUR-Scan" echo " safe-yay --scan-only # Scan without building" echo " safe-yay --check-installed # Installierte AUR-Pakete pruefen" echo " safe-yay --doctor # Check AUR-Shield setup" exit 0 fi # --doctor health check if [ "$1" = "--doctor" ]; then echo "AUR-Shield Doctor" echo "================" echo "" STATUS=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/status" 2>/dev/null) if [ $? -eq 0 ]; then MODEL=$(echo "$STATUS" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('model','?'))" 2>/dev/null) CACHE=$(echo "$STATUS" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('cached_scans',0))" 2>/dev/null) echo " ✓ Server: http://$SHIELD_HOST:$SHIELD_PORT" echo " Model: $MODEL" echo " Cached scans: $CACHE" else echo " ✗ Server: http://$SHIELD_HOST:$SHIELD_PORT NOT REACHABLE" exit 1 fi OLLAMA=$(curl -sf "http://$SHIELD_HOST:11434/api/tags" 2>/dev/null) if [ $? -eq 0 ]; then echo " ✓ Ollama: running" else echo " ⚠ Ollama: not reachable on $SHIELD_HOST" fi if grep -q "^\[aur-shield\]" /etc/pacman.conf 2>/dev/null; then echo " ✓ pacman repo: [aur-shield] configured" else echo " ⚠ pacman repo: [aur-shield] NOT in /etc/pacman.conf" fi echo "" echo "All checks passed." exit 0 fi # --check-installed: pruefe bereits installierte AUR-Pakete gegen IOC-Listen if [ "$1" = "--check-installed" ]; then echo "AUR-Shield: Installierte AUR-Pakete pruefen" echo "==========================================" echo "" # Foreign packages = AUR + manuell installierte FOREIGN=$(pacman -Qmq 2>/dev/null) if [ -z "$FOREIGN" ]; then echo "Keine AUR-Pakete installiert." exit 0 fi COUNT=$(echo "$FOREIGN" | wc -l) echo "Pruefe $COUNT installierte AUR-Pakete gegen IOC-Listen..." echo "" MALICIOUS=0 SUSPICIOUS=0 CLEAN=0 for pkg in $FOREIGN; do RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>/dev/null) if [ $? -ne 0 ]; then continue fi VERDICT=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('verdict','error'))" 2>/dev/null) case "$VERDICT" in malicious) echo " ✗ MALICIOUS: $pkg" echo "$RESPONSE" | python3 -c " import sys,json d = json.loads(sys.stdin.read()) for ioc in d.get('ioc_matches',[]): print(f\" IOC: {ioc['source']} — {ioc['description']}\") " 2>/dev/null MALICIOUS=$((MALICIOUS + 1)) ;; suspicious) echo " ⚠ SUSPICIOUS: $pkg" SUSPICIOUS=$((SUSPICIOUS + 1)) ;; clean) CLEAN=$((CLEAN + 1)) ;; esac done echo "" echo "==========================================" echo " Ergebnis:" echo " Clean: $CLEAN" echo " Suspicious: $SUSPICIOUS" echo " Malicious: $MALICIOUS" echo "==========================================" if [ $MALICIOUS -gt 0 ]; then exit 2 elif [ $SUSPICIOUS -gt 0 ]; then exit 1 fi exit 0 fi # -Syu: System-Update — scanne AUR-Updates bevor sie installiert werden if [ "$1" = "-Syu" ] || [ "$1" = "-Sy" ]; then echo "AUR-Shield: System-Update mit Pre-Scan" echo "=======================================" echo "" # Erst normales Repo-Update (ohne AUR) echo "→ Repo-Updates..." sudo pacman -Sy 2>&1 # AUR-Updates finden mit yay if command -v yay &>/dev/null; then echo "" echo "→ Pruefe AUR-Updates..." AUR_UPDATES=$(yay -Qua 2>/dev/null || true) if [ -z "$AUR_UPDATES" ]; then echo " Keine AUR-Updates." else echo " AUR-Updates gefunden:" echo "$AUR_UPDATES" echo "" echo "→ Scanne AUR-Updates durch AUR-Shield..." BLOCKED=0 SAFE_UPDATES="" while IFS= read -r line; do pkg=$(echo "$line" | awk '{print $1}') pkg=$(echo "$pkg" | sed 's/[+]$//') # strip + suffix from yay output RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>/dev/null) if [ $? -ne 0 ]; then echo " ⚠ $pkg: Scan fehlgeschlagen — ueberspringe" SAFE_UPDATES="$SAFE_UPDATES $pkg" continue fi VERDICT=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('verdict','error'))" 2>/dev/null) case "$VERDICT" in clean) echo " ✓ $pkg: clean" SAFE_UPDATES="$SAFE_UPDATES $pkg" ;; suspicious) echo " ⚠ $pkg: SUSPICIOUS" echo "$RESPONSE" | python3 -c " import sys,json d = json.loads(sys.stdin.read()) for f in d.get('findings',[])[:3]: print(f\" {f}\") " 2>/dev/null read -rp " Update $pkg trotzdem? [y/N] " FORCE if [[ "${FORCE,,}" == "y" ]]; then SAFE_UPDATES="$SAFE_UPDATES $pkg" fi ;; malicious) echo " ✗ $pkg: MALICIOUS — BLOCKIERT!" echo "$RESPONSE" | python3 -c " import sys,json d = json.loads(sys.stdin.read()) for ioc in d.get('ioc_matches',[]): print(f\" IOC: {ioc['source']} — {ioc['description']}\") " 2>/dev/null BLOCKED=$((BLOCKED + 1)) ;; esac done <<< "$AUR_UPDATES" echo "" if [ $BLOCKED -gt 0 ]; then echo "⚠ $BLOCKED Paket(e) blockiert!" fi # Safe updates installieren if [ -n "$SAFE_UPDATES" ]; then echo "→ Installiere sichere AUR-Updates: $SAFE_UPDATES" yay -S $SAFE_UPDATES else echo "→ Keine sicheren AUR-Updates." fi fi else echo " ⚠ yay nicht installiert — AUR-Updates manuell pruefen" fi # Normale Repo-Updates installieren echo "" echo "→ Repo-Updates installieren..." sudo pacman -Su 2>&1 echo "" echo "System-Update abgeschlossen." exit 0 fi # --scan-only mode SCAN_ONLY=false if [ "$1" = "--scan-only" ]; then SCAN_ONLY=true shift fi EXIT_CODE=0 for pkg in "$@"; do echo "→ Processing $pkg..." if [ "$SCAN_ONLY" = true ]; then RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>&1) || { echo " ✗ Failed to scan $pkg" echo " $RESPONSE" EXIT_CODE=1 continue } else RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/build/$pkg" 2>&1) || { echo " ✗ Failed to process $pkg" echo " $RESPONSE" EXIT_CODE=1 continue } fi VERDICT=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('verdict','error'))" 2>/dev/null) case "$VERDICT" in clean) echo " ✓ Clean — package verified" if [ "$SCAN_ONLY" = false ]; then echo " Installing via pacman..." sudo pacman -Sy "aur-shield/$pkg" || { echo " ⚠ Package not in repo yet — may need to wait for build" EXIT_CODE=1 } fi ;; suspicious) echo " ⚠ Suspicious — review recommended" echo " Report: http://$SHIELD_HOST:$SHIELD_PORT/api/report/$pkg" EXIT_CODE=1 if [ "$SCAN_ONLY" = false ]; then read -rp " Install anyway? [y/N] " FORCE if [[ "${FORCE,,}" == "y" ]]; then sudo pacman -Sy "aur-shield/$pkg" fi fi ;; malicious) echo " ✗ MALICIOUS — package blocked!" echo " Report: http://$SHIELD_HOST:$SHIELD_PORT/api/report/$pkg" echo "$RESPONSE" | python3 -c " import sys,json d = json.loads(sys.stdin.read()) for ioc in d.get('ioc_matches',[]): print(f\" IOC: {ioc['source']} — {ioc['description']}\") " 2>/dev/null EXIT_CODE=2 ;; *) echo " ? Error: $RESPONSE" EXIT_CODE=1 ;; esac done exit $EXIT_CODE WRAPPER sudo chmod +x "$WRAPPER_PATH" echo "✓ Installed safe-yay" echo "" echo "╔══════════════════════════════════════════════╗" echo "║ Client setup complete! ║" echo "╠══════════════════════════════════════════════╣" echo "║ ║" echo "║ Usage: ║" echo "║ safe-yay ║" echo "║ sudo pacman -S aur-shield/ ║" echo "║ ║" echo "║ Set server via env: ║" echo "║ export AUR_SHIELD_HOST=10.90.9.102 ║" echo "║ export AUR_SHIELD_PORT=8443 ║" echo "║ ║" echo "╚══════════════════════════════════════════════╝"