#!/bin/bash # # Server Toolkit QA Checker - Optimized for AI Assistant Development # Designed to help Claude track bugs during development with minimal computation # # Features: # - Structured output (easy to parse) # - Fixed subshell counter bug # - Groups by severity # - Concise summaries # - Quick file:line references # TOOLKIT_PATH="${1:-/root/server-toolkit}" if [ ! -d "$TOOLKIT_PATH" ]; then echo "ERROR: $TOOLKIT_PATH not found" exit 1 fi # Output file for easy review REPORT="/tmp/qa-report.txt" > "$REPORT" # Counters (will use temp file to avoid subshell issues) TEMP_COUNTS="/tmp/qa-counts.$$" echo "0 0 0 0" > "$TEMP_COUNTS" # Helper to increment counters (avoids subshell issue) count_issue() { local severity="$1" read crit high med low < "$TEMP_COUNTS" case "$severity" in CRITICAL) ((crit++)) ;; HIGH) ((high++)) ;; MEDIUM) ((med++)) ;; LOW) ((low++)) ;; esac echo "$crit $high $med $low" > "$TEMP_COUNTS" } echo "═══════════════════════════════════════════════════════════════" echo "SERVER TOOLKIT QA SCAN" echo "Path: $TOOLKIT_PATH" echo "Date: $(date '+%Y-%m-%d %H:%M:%S')" echo "═══════════════════════════════════════════════════════════════" echo "" # Create structured output file { echo "QA SCAN REPORT" echo "==============" echo "Timestamp: $(date)" echo "Path: $TOOLKIT_PATH" echo "" } > "$REPORT" #============================================================================== # CHECK 1: grep -F with regex anchors (CRITICAL - causes wrong results) #============================================================================== echo "[1/8] Checking: grep -F with regex anchors..." { echo "## CHECK 1: grep -F with regex anchors" echo "Severity: CRITICAL" echo "Issue: -F disables regex, \$ and ^ match literally" echo "" # Use process substitution to avoid subshell while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -qE '"\$|".*\^|\\\$'; then echo "CRITICAL|$file|$line_num|grep -F with regex anchor" count_issue "CRITICAL" fi done < <(grep -rn 'grep -F' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 2: SCRIPT_DIR collisions (HIGH - causes path errors) #============================================================================== echo "[2/8] Checking: SCRIPT_DIR variable collisions..." { echo "## CHECK 2: SCRIPT_DIR variable collisions" echo "Severity: HIGH" echo "Issue: Multiple files redefining same path variable" echo "" script_dir_count=$(grep -l "^SCRIPT_DIR=" "$TOOLKIT_PATH"/**/*.sh 2>/dev/null | wc -l) if [ "$script_dir_count" -gt 1 ]; then files=$(grep -l "^SCRIPT_DIR=" "$TOOLKIT_PATH"/**/*.sh 2>/dev/null | tr '\n' ' ') echo "HIGH|Multiple files|N/A|SCRIPT_DIR in $script_dir_count files: $files" count_issue "HIGH" fi echo "" } >> "$REPORT" #============================================================================== # CHECK 3: SYS_* variable resets (CRITICAL - breaks system detection) #============================================================================== echo "[3/8] Checking: SYS_* variable resets..." { echo "## CHECK 3: SYS_* variable resets without protection" echo "Severity: CRITICAL" echo "Issue: Re-sourcing wipes all system detection variables" echo "" while read -r file; do if grep -q '^[[:space:]]*export SYS_.*=""' "$file" && ! grep -q "SYS_DETECTION_COMPLETE" "$file"; then line_num=$(grep -n '^[[:space:]]*export SYS_.*=""' "$file" | head -1 | cut -d: -f1) echo "CRITICAL|$file|$line_num|SYS reset without guard" count_issue "CRITICAL" fi done < <(find "$TOOLKIT_PATH/lib" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 4: Missing function exports (HIGH - functions unavailable) #============================================================================== echo "[4/8] Checking: Missing function exports..." { echo "## CHECK 4: Missing function exports in libraries" echo "Severity: HIGH" echo "Issue: Functions not exported = unavailable in nested calls" echo "" while read -r file; do func_count=$(grep -cE "^[a-zA-Z_][a-zA-Z0-9_]*\(\)" "$file" 2>/dev/null || echo "0") func_count=${func_count//[$'\n\r']/} # Remove newlines export_count=$(grep -c "export -f" "$file" 2>/dev/null || echo "0") export_count=${export_count//[$'\n\r']/} # Remove newlines if [ -n "$func_count" ] && [ -n "$export_count" ] && [ "$func_count" -gt 0 ] && [ "$export_count" -eq 0 ]; then echo "HIGH|$file|N/A|$func_count functions, 0 exports" count_issue "HIGH" fi done < <(find "$TOOLKIT_PATH/lib" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 5: Integer comparisons without empty checks (HIGH - causes errors) #============================================================================== echo "[5/8] Checking: Unsafe integer comparisons (top 10)..." { echo "## CHECK 5: Integer comparisons without empty checks" echo "Severity: HIGH" echo "Issue: Empty vars cause 'integer expression expected' errors" echo "Note: Showing first 10 instances" echo "" count=0 while IFS=: read -r file line_num line_content; do var=$(echo "$line_content" | grep -oE '\$[a-zA-Z_][a-zA-Z0-9_]*' | head -1) echo "HIGH|$file|$line_num|Integer comparison: $var" count_issue "HIGH" ((count++)) [ "$count" -ge 10 ] && break done < <(grep -rn '\[ \$[a-zA-Z_][a-zA-Z0-9_]* -\(lt\|gt\|le\|ge\|eq\|ne\) [0-9]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) total=$(grep -r '\[ \$[a-zA-Z_][a-zA-Z0-9_]* -\(lt\|gt\|le\|ge\|eq\|ne\) [0-9]' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) echo "Total found: $total (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 6: Missing common-functions.sh (HIGH - command not found) #============================================================================== echo "[6/8] Checking: Missing common-functions.sh..." { echo "## CHECK 6: Missing common-functions.sh sourcing" echo "Severity: HIGH" echo "Issue: Uses functions without sourcing = command not found" echo "" while read -r file; do if ! grep -q "source.*common-functions.sh" "$file"; then echo "HIGH|$file|N/A|Uses common functions without sourcing" count_issue "HIGH" fi done < <(grep -l 'cecho\|print_info\|print_warning\|print_error' "$TOOLKIT_PATH"/modules/**/*.sh 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 7: exit in libraries (HIGH - terminates parent script) #============================================================================== echo "[7/8] Checking: exit in library files..." { echo "## CHECK 7: exit in sourced libraries" echo "Severity: HIGH" echo "Issue: Should use 'return' not 'exit'" echo "" while read -r file; do while IFS=: read -r line_num line_content; do # Skip comments if ! echo "$line_content" | grep -q "^[[:space:]]*#"; then if echo "$line_content" | grep -qE '(^|[[:space:]])exit[[:space:]]'; then echo "HIGH|$file|$line_num|Library uses exit" count_issue "HIGH" fi fi done < <(grep -n '\' "$file" 2>/dev/null | grep -v "^[[:space:]]*#" | head -5) done < <(find "$TOOLKIT_PATH/lib" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 8: bc command usage (LOW - external dependency) #============================================================================== echo "[8/11] Checking: bc command usage..." { echo "## CHECK 8: bc command usage" echo "Severity: LOW" echo "Issue: Requires bc package - not installed on all systems" echo "" while IFS=: read -r file line_num line_content; do # Skip comments if ! echo "$line_content" | grep -q "^[[:space:]]*#"; then echo "LOW|$file|$line_num|Uses bc command" count_issue "LOW" fi done < <(grep -rn ' bc\b' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) total_bc=$(grep -r ' bc\b' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | wc -l) echo "Total found: $total_bc (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 9: Hardcoded /var/cpanel paths (MEDIUM - breaks multi-panel) #============================================================================== echo "[9/11] Checking: Hardcoded /var/cpanel paths..." { echo "## CHECK 9: Hardcoded /var/cpanel paths" echo "Severity: MEDIUM" echo "Issue: Hardcoded paths break on non-cPanel systems" echo "" while IFS=: read -r file line_num line_content; do # Skip if using $SYS variables OR if it's a fallback value in ${VAR:-/var/cpanel} if ! echo "$line_content" | grep -qE '(\$SYS|:-/var/cpanel)'; then # Skip comments if ! echo "$line_content" | grep -q "^[[:space:]]*#"; then echo "MEDIUM|$file|$line_num|Hardcoded /var/cpanel path" count_issue "MEDIUM" fi fi done < <(grep -rn '/var/cpanel' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | head -10) total_cpanel=$(grep -r '/var/cpanel' "$TOOLKIT_PATH" --include="*.sh" --exclude="toolkit-qa-check.sh" 2>/dev/null | grep -vE '(\$SYS|:-/var/cpanel)' | grep -v '^[[:space:]]*#' | wc -l) echo "Total found: $total_cpanel (showing first 10)" echo "" } >> "$REPORT" #============================================================================== # CHECK 10: Undefined color variables (LOW - cosmetic issue) #============================================================================== echo "[10/11] Checking: Undefined color variables..." { echo "## CHECK 10: Undefined color variables" echo "Severity: LOW" echo "Issue: Uses color variables without defining them" echo "" while read -r file; do # Skip if sources common-functions, defines colors, OR is common-functions.sh itself if [[ "$file" != *"common-functions.sh" ]] && ! grep -q "source.*common-functions" "$file" && ! grep -q "^RED=" "$file"; then # Check if uses color variables if grep -q '\${RED}\|\${GREEN}\|\${BLUE}\|\${YELLOW}\|\${CYAN}\|\${MAGENTA}\|\${NC}' "$file"; then line_num=$(grep -n '\${RED}\|\${GREEN}' "$file" | head -1 | cut -d: -f1) echo "LOW|$file|$line_num|Uses color vars without definition" count_issue "LOW" fi fi done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # CHECK 11: Bash syntax validation (CRITICAL - prevents execution) #============================================================================== echo "[11/11] Checking: Bash syntax errors..." { echo "## CHECK 11: Bash syntax validation" echo "Severity: CRITICAL" echo "Issue: Syntax errors prevent script from running" echo "" while read -r file; do if ! bash -n "$file" 2>/dev/null; then errors=$(bash -n "$file" 2>&1 | head -1) echo "CRITICAL|$file|N/A|Syntax: $errors" count_issue "CRITICAL" fi done < <(find "$TOOLKIT_PATH" -name "*.sh" 2>/dev/null) echo "" } >> "$REPORT" #============================================================================== # PERFORMANCE CHECKS (INFO level - not counted as issues) #============================================================================== echo "" echo "═══════════════════════════════════════════════════════════════" echo "PERFORMANCE & OPTIMIZATION CHECKS (Informational)" echo "═══════════════════════════════════════════════════════════════" echo "" { echo "" echo "═══════════════════════════════════════════════════════════════" echo "PERFORMANCE ANALYSIS" echo "═══════════════════════════════════════════════════════════════" echo "" } >> "$REPORT" # PERF 1: cat file | grep (inefficient) echo "[PERF-1] Inefficient cat | grep patterns..." { echo "## PERF-1: Inefficient pipe usage" echo "Severity: INFO" echo "Pattern: cat file | grep (should be: grep '' file)" echo "" count=0 while IFS=: read -r file line_num line_content; do echo "INFO|$file|$line_num|cat | grep pattern" ((count++)) [ "$count" -ge 5 ] && break done < <(grep -rn 'cat.*|.*grep' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) total=$(grep -r 'cat.*|.*grep' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) echo "Found: $total instances (showing first 5)" echo "Optimization: Replace 'cat file | grep pattern' with 'grep pattern file'" echo "" } >> "$REPORT" # PERF 2: Repeated file reads echo "[PERF-2] Multiple zcat on same file..." { echo "## PERF-2: Repeated decompression" echo "Severity: INFO" echo "Pattern: Multiple zcat/gunzip calls (expensive)" echo "" zcat_files=$(grep -rh 'zcat [^|]*' "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | \ grep -oE 'zcat [^ ]+' | sort | uniq -c | sort -rn | head -3) if [ -n "$zcat_files" ]; then echo "$zcat_files" echo "" echo "Optimization: Decompress once, store in variable or temp file" else echo "None found" fi echo "" } >> "$REPORT" # PERF 3: Subshells in loops (expensive) echo "[PERF-3] Expensive patterns in loops..." { echo "## PERF-3: Subshells in loops" echo "Severity: INFO" echo "Pattern: \$(...) or \`...\` inside while/for loops" echo "" # Find loops with subshells (sample only) count=0 while read -r file; do # Get line numbers of loops grep -n 'while.*do\|for.*do' "$file" 2>/dev/null | while IFS=: read -r loop_line rest; do # Check next 10 lines for subshells start=$loop_line end=$((loop_line + 10)) if sed -n "${start},${end}p" "$file" 2>/dev/null | grep -q '\$(\|`'; then echo "INFO|$file|$loop_line|Subshell in loop (potential slowdown)" ((count++)) [ "$count" -ge 5 ] && break 2 fi done done < <(find "$TOOLKIT_PATH" -name "*.sh" -type f 2>/dev/null | head -20) echo "Note: Review loops for expensive nested operations" echo "" } >> "$REPORT" # PERF 4: Inefficient string operations echo "[PERF-4] String operations that could use bash builtins..." { echo "## PERF-4: Inefficient string operations" echo "Severity: INFO" echo "Pattern: sed/awk/cut when bash builtins work" echo "" # Sample sed replacements count=0 while IFS=: read -r file line_num line_content; do if echo "$line_content" | grep -q "sed 's/"; then echo "INFO|$file|$line_num|sed replacement (consider \${var//pattern/replacement})" ((count++)) [ "$count" -ge 5 ] && break fi done < <(grep -rn "sed 's/" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null) total_sed=$(grep -r "sed 's/" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) echo "Found: $total_sed sed substitutions" echo "Note: Many are justified, but consider bash builtins for simple cases" echo "" } >> "$REPORT" # PERF 5: Multiple grep on same large file echo "[PERF-5] File read optimization opportunities..." { echo "## PERF-5: Repeated file access" echo "Severity: INFO" echo "Pattern: Same file accessed multiple times" echo "" # Check for common heavy files echo "Common files that might benefit from caching:" for common_file in "/etc/csf/csf.conf" "/var/log/messages" "/etc/passwd"; do count=$(grep -r "$common_file" "$TOOLKIT_PATH" --include="*.sh" 2>/dev/null | wc -l) if [ "$count" -gt 5 ]; then echo " $common_file: $count references (consider caching)" fi done echo "" echo "Optimization: Cache frequently-read files in variables" echo "" } >> "$REPORT" #============================================================================== # SUMMARY #============================================================================== read crit high med low < "$TEMP_COUNTS" total=$((crit + high + med + low)) { echo "═══════════════════════════════════════════════════════════════" echo "SUMMARY" echo "═══════════════════════════════════════════════════════════════" echo "Total Issues: $total" echo " CRITICAL: $crit" echo " HIGH: $high" echo " MEDIUM: $med" echo " LOW: $low" echo "" echo "Files Scanned: $(find "$TOOLKIT_PATH" -name "*.sh" 2>/dev/null | wc -l)" echo "Report: $REPORT" echo "═══════════════════════════════════════════════════════════════" } | tee -a "$REPORT" echo "" echo "DETAILED BREAKDOWN BY SEVERITY:" echo "" # Group and display by severity echo "CRITICAL ISSUES ($crit):" grep "^CRITICAL|" "$REPORT" | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done echo "" echo "HIGH ISSUES ($high):" grep "^HIGH|" "$REPORT" | head -15 | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done if [ "$high" -gt 15 ]; then echo " ... and $((high - 15)) more (see $REPORT)" fi echo "" echo "MEDIUM ISSUES ($med):" grep "^MEDIUM|" "$REPORT" | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done echo "" echo "LOW ISSUES ($low):" grep "^LOW|" "$REPORT" | while IFS='|' read -r sev file line issue; do printf " %s:%s - %s\n" "$file" "$line" "$issue" done # Cleanup rm -f "$TEMP_COUNTS" echo "" echo "Full report saved to: $REPORT" echo "Exit code: $total" exit $total