feat: scan-only server mode + local build on client

Server:
- config: server.scan_only flag (default: false)
- /api/build returns scan_only=true without building when enabled
- /api/status reports scan_only mode

Client (safe-yay):
- Detects server scan_only from API response
- Scan-only server: builds locally with yay/paru after clean scan
- Full server: installs from pacman repo as before
- --noinstall flag for scan-only without build (was --scan-only)
- Suspicious: prompts for local build or repo install depending on server mode
This commit is contained in:
arch_agent
2026-08-04 10:34:34 +02:00
parent 88072de4fe
commit f015092b1f
4 changed files with 75 additions and 28 deletions
+59 -27
View File
@@ -276,10 +276,10 @@ for ioc in d.get('ioc_matches',[]):
exit 0
fi
# --scan-only mode
SCAN_ONLY=false
if [ "$1" = "--scan-only" ]; then
SCAN_ONLY=true
# --noinstall mode (scan only, don't build/install)
NOINSTALL=false
if [ "$1" = "--noinstall" ] || [ "$1" = "--scan-only" ]; then
NOINSTALL=true
shift
fi
@@ -288,43 +288,75 @@ 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
# Always scan first (server handles IOC + regex + LLM)
RESPONSE=$(curl -sf "http://$SHIELD_HOST:$SHIELD_PORT/api/scan/$pkg" 2>&1) || {
echo " ✗ Failed to scan $pkg"
echo " $RESPONSE"
EXIT_CODE=1
continue
}
VERDICT=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('verdict','error'))" 2>/dev/null)
SERVER_SCAN_ONLY=$(echo "$RESPONSE" | python3 -c "import sys,json; print(json.loads(sys.stdin.read()).get('scan_only',False))" 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"
echo " ✓ Clean — verified by AUR-Shield"
if [ "$NOINSTALL" = true ]; then
echo " (scan only — not installing)"
elif [ "$SERVER_SCAN_ONLY" = True ]; then
# Scan-only server — build locally
echo " Building locally..."
if command -v yay &>/dev/null; then
yay -S "$pkg" --noconfirm 2>&1 || {
echo " ⚠ Build failed"
EXIT_CODE=1
}
elif command -v paru &>/dev/null; then
paru -S "$pkg" --noconfirm 2>&1 || {
echo " ⚠ Build failed"
EXIT_CODE=1
}
else
echo " ⚠ No AUR helper (yay/paru) — cannot build"
EXIT_CODE=1
fi
else
# Full server — install from repo
echo " Installing via pacman repo..."
sudo pacman -Sy "aur-shield/$pkg" || {
echo " ⚠ Not in repo — building locally..."
if command -v yay &>/dev/null; then
yay -S "$pkg"
else
echo " ⚠ yay not installed — cannot build"
EXIT_CODE=1
fi
}
fi
;;
suspicious)
echo " ⚠ Suspicious — review recommended"
echo " Report: http://$SHIELD_HOST:$SHIELD_PORT/api/report/$pkg"
echo "$RESPONSE" | python3 -c "
import sys,json
d = json.loads(sys.stdin.read())
for f in d.get('findings',[])[:5]:
print(f\" {f}\")
" 2>/dev/null
EXIT_CODE=1
if [ "$SCAN_ONLY" = false ]; then
read -rp " Install anyway? [y/N] " FORCE
if [ "$NOINSTALL" = false ]; then
if [ "$SERVER_SCAN_ONLY" = True ]; then
read -rp " Build locally anyway? [y/N] " FORCE
else
read -rp " Install anyway? [y/N] " FORCE
fi
if [[ "${FORCE,,}" == "y" ]]; then
sudo pacman -Sy "aur-shield/$pkg"
if [ "$SERVER_SCAN_ONLY" = True ]; then
yay -S "$pkg"
else
sudo pacman -Sy "aur-shield/$pkg"
fi
fi
fi
;;