#!/bin/bash # Comprehensive Pre-Flight Validation for Dangerous Pi Builds # Catches errors BEFORE wasting hours on failed builds # Usage: ./scripts/preflight-check.sh # Don't use set -e - we want to collect ALL errors, not stop on first one set +e SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" PI_GEN_DIR="/home/work/pi-gen-builder" # Colors RED='\033[0;31m' YELLOW='\033[1;33m' GREEN='\033[0;32m' BLUE='\033[0;34m' NC='\033[0m' # No Color ERRORS=0 WARNINGS=0 CHECKS_PASSED=0 error() { echo -e "${RED}✗ ERROR:${NC} $1" ((ERRORS++)) } warn() { echo -e "${YELLOW}⚠ WARNING:${NC} $1" ((WARNINGS++)) } pass() { echo -e "${GREEN}✓${NC} $1" ((CHECKS_PASSED++)) } info() { echo -e "${BLUE}ℹ${NC} $1" } section() { echo "" echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" echo -e "${BLUE}$1${NC}" echo -e "${BLUE}═══════════════════════════════════════════════════════${NC}" } echo "" echo "╔════════════════════════════════════════════════════════════╗" echo "║ DANGEROUS PI BUILD PRE-FLIGHT VALIDATION ║" echo "╚════════════════════════════════════════════════════════════╝" echo "" # ============================================================================ section "1. PI-GEN ENVIRONMENT CHECKS" # ============================================================================ # Check if pi-gen directory exists if [ -d "$PI_GEN_DIR" ]; then pass "Pi-gen directory exists: $PI_GEN_DIR" else error "Pi-gen directory not found: $PI_GEN_DIR" fi # Check Docker availability if command -v docker &> /dev/null; then if docker ps &> /dev/null; then pass "Docker is available and accessible" else error "Docker command exists but cannot connect (check permissions)" fi else error "Docker is not installed or not in PATH" fi # Check qemu registration if [ -f "/proc/sys/fs/binfmt_misc/qemu-aarch64" ]; then if grep -q "enabled" /proc/sys/fs/binfmt_misc/qemu-aarch64; then pass "qemu-aarch64 is registered and enabled" else warn "qemu-aarch64 exists but may not be enabled" fi else warn "qemu-aarch64 not registered (will be registered during build)" fi # ============================================================================ section "2. CONFIGURATION FILE VALIDATION" # ============================================================================ CONFIG_FILE="$PROJECT_DIR/pi-gen/config" if [ -f "$CONFIG_FILE" ]; then pass "Config file exists: $CONFIG_FILE" # Check required variables for var in IMG_NAME RELEASE STAGE_LIST FIRST_USER_NAME; do if grep -q "^$var=" "$CONFIG_FILE"; then value=$(grep "^$var=" "$CONFIG_FILE" | cut -d'=' -f2- | tr -d '"') pass "Config has $var=$value" else error "Config missing required variable: $var" fi done # Check QCOW2 setting if grep -q "USE_QCOW2=1" "$CONFIG_FILE"; then pass "QCOW2 caching enabled (fast incremental builds)" else warn "QCOW2 caching disabled (slow rebuilds)" fi else error "Config file not found: $CONFIG_FILE" fi # ============================================================================ section "3. STAGE STRUCTURE VALIDATION" # ============================================================================ # Get list of custom stages from config CUSTOM_STAGES=$(grep "^STAGE_LIST=" "$CONFIG_FILE" | cut -d'"' -f2 | grep -oE 'stage[A-Za-z0-9_]+' | grep -v '^stage[0-9]$') for stage in $CUSTOM_STAGES; do STAGE_DIR="$PROJECT_DIR/pi-gen/$stage" if [ ! -d "$STAGE_DIR" ]; then error "Stage directory does not exist: $STAGE_DIR" continue fi info "Checking stage: $stage" # Check for prerun.sh (CRITICAL - this was the bug!) if [ -f "$STAGE_DIR/prerun.sh" ]; then pass " Has prerun.sh" # Check if prerun.sh contains copy_previous if grep -q "copy_previous" "$STAGE_DIR/prerun.sh"; then pass " prerun.sh calls copy_previous" else error " prerun.sh exists but doesn't call copy_previous" info " This will cause: 'Unable to chroot' error" fi # Check if executable if [ -x "$STAGE_DIR/prerun.sh" ]; then pass " prerun.sh is executable" else warn " prerun.sh is not executable (should be chmod +x)" fi else # Only error if this is not stage0 (stage0 doesn't need prerun.sh) if [ "$stage" != "stage0" ]; then error " MISSING prerun.sh (CRITICAL)" info " This will cause: 'No such file or directory' for rootfs" info " Fix: Create prerun.sh with copy_previous function" fi fi # Check for EXPORT_IMAGE if [ -f "$STAGE_DIR/EXPORT_IMAGE" ]; then pass " Has EXPORT_IMAGE" # Validate EXPORT_IMAGE syntax if grep -q "IMG_SUFFIX" "$STAGE_DIR/EXPORT_IMAGE"; then pass " EXPORT_IMAGE has IMG_SUFFIX" else warn " EXPORT_IMAGE missing IMG_SUFFIX" fi else warn " No EXPORT_IMAGE (stage won't create image)" fi # Check for substages substage_count=$(find "$STAGE_DIR" -maxdepth 1 -type d -name '[0-9][0-9]*' | wc -l) if [ $substage_count -eq 0 ]; then warn " No substages found (stage does nothing)" else pass " Found $substage_count substage(s)" fi done # ============================================================================ section "4. BUILD SCRIPT SYNTAX VALIDATION" # ============================================================================ info "Checking all stage scripts for syntax errors..." for script in "$PROJECT_DIR"/pi-gen/stage*/*/[0-9][0-9]-*.sh; do if [ -f "$script" ]; then if bash -n "$script" 2>/dev/null; then pass " $(basename $(dirname $(dirname "$script")))/$(basename $(dirname "$script"))/$(basename "$script")" else error " Syntax error in: $script" bash -n "$script" 2>&1 | sed 's/^/ /' fi fi done # Check prerun.sh files for script in "$PROJECT_DIR"/pi-gen/stage*/prerun.sh; do if [ -f "$script" ]; then if bash -n "$script" 2>/dev/null; then pass " $(basename $(dirname "$script"))/prerun.sh" else error " Syntax error in: $script" fi fi done # ============================================================================ section "5. DEPENDENCY CHECKS IN SCRIPTS" # ============================================================================ info "Checking if commands are installed before use..." # Check PM3 stage for cmake before use PM3_SCRIPT="$PROJECT_DIR/pi-gen/stage*/01-proxmark3/00-run-chroot.sh" if ls $PM3_SCRIPT 2>/dev/null | head -1 | xargs -I {} bash -c ' script="$1" if grep -q "cmake" "$script"; then if grep -B50 "cmake" "$script" | grep -q "apt-get install.*cmake"; then echo "PASS" else echo "FAIL" fi else echo "SKIP" fi ' _ {} | grep -q "PASS"; then pass " PM3: cmake installed before use" elif ls $PM3_SCRIPT 2>/dev/null | head -1 | xargs -I {} bash -c ' script="$1" if grep -q "cmake" "$script"; then if grep -B50 "cmake" "$script" | grep -q "apt-get install.*cmake"; then echo "PASS" else echo "FAIL" fi else echo "SKIP" fi ' _ {} | grep -q "FAIL"; then warn " PM3: cmake may not be installed before use" fi # Check dangerous-pi stage for pip3 DTPI_SCRIPT="$PROJECT_DIR/pi-gen/stage*/*/04-dangerous-pi/00-run-chroot.sh" if ls $DTPI_SCRIPT 2>/dev/null | head -1 | xargs -I {} bash -c ' script="$1" if grep -q "pip3 install" "$script"; then if grep -B20 "pip3 install" "$script" | grep -qE "apt-get install.*python3-pip|command -v pip3"; then echo "PASS" else echo "FAIL" fi else echo "SKIP" fi ' _ {} | grep -q "PASS"; then pass " Dangerous-Pi: pip3 check before use" elif ls $DTPI_SCRIPT 2>/dev/null | head -1 | xargs -I {} bash -c ' script="$1" if grep -q "pip3 install" "$script"; then if grep -B20 "pip3 install" "$script" | grep -qE "apt-get install.*python3-pip|command -v pip3"; then echo "PASS" else echo "FAIL" fi else echo "SKIP" fi ' _ {} | grep -q "FAIL"; then error " Dangerous-Pi: pip3 used without installation check" fi # Check PM3 dependencies by analyzing source info "Analyzing PM3 source for required dependencies..." PM3_SCRIPT_PATH="$PROJECT_DIR/pi-gen/stageDangerousPi/02-pm3-install/00-run-chroot-compile.sh" if [ -f "$PM3_SCRIPT_PATH" ]; then # Clone PM3 temporarily to analyze (lightweight, just for analysis) PM3_TEMP="/tmp/pm3-preflight-check" if [ ! -d "$PM3_TEMP" ]; then info "Cloning Proxmark3 for dependency analysis..." git clone --depth 1 --quiet https://github.com/RfidResearchGroup/proxmark3 "$PM3_TEMP" 2>/dev/null fi if [ -d "$PM3_TEMP" ]; then # Header to package mapping (common Debian packages) declare -A HEADER_TO_PKG=( ["lz4frame.h"]="liblz4-dev" ["lz4.h"]="liblz4-dev" ["readline/readline.h"]="libreadline-dev" ["readline.h"]="libreadline-dev" ["libusb.h"]="libusb-1.0-0-dev" ["libusb-1.0/libusb.h"]="libusb-1.0-0-dev" ["pthread.h"]="libc6-dev" ["zlib.h"]="zlib1g-dev" ["bzlib.h"]="libbz2-dev" ["jansson.h"]="libjansson-dev" ["bluez/bluetooth.h"]="libbluetooth-dev" ) # Find all #include statements for system headers FOUND_HEADERS=$(grep -rh "^#include <" "$PM3_TEMP/client" 2>/dev/null | \ grep -oE '<[^>]+>' | tr -d '<>' | sort -u) # Check if required packages are installed for header in $FOUND_HEADERS; do pkg="${HEADER_TO_PKG[$header]}" if [ -n "$pkg" ]; then # Check if this package is in our install script if grep -q "$pkg" "$PM3_SCRIPT_PATH"; then pass " PM3: $pkg (for $header)" else error " PM3: Missing $pkg (required for #include <$header>)" info " Add '$pkg' to apt-get install in stageDangerousPi/02-pm3-install/00-run-chroot-compile.sh" fi fi done # Also check for essential build tools ESSENTIAL_DEPS=("cmake" "build-essential" "git" "pkg-config") for dep in "${ESSENTIAL_DEPS[@]}"; do if grep -q "$dep" "$PM3_SCRIPT_PATH"; then pass " PM3: $dep (build tool)" else error " PM3: Missing $dep (essential build tool)" info " Add '$dep' to apt-get install in stageDangerousPi/02-pm3-install/00-run-chroot-compile.sh" fi done else warn " Could not clone PM3 for analysis - using basic checks only" # Fallback to basic check for dep in cmake build-essential git; do if grep -q "$dep" "$PM3_SCRIPT_PATH"; then pass " PM3: $dep" else error " PM3: Missing $dep" fi done fi fi # ============================================================================ section "6. DIRECTORY CREATION VALIDATION" # ============================================================================ info "Checking for mkdir -p before file writes to non-standard dirs..." WIFI_SCRIPT="$PROJECT_DIR/pi-gen/stage*/*/01-Wireless-AP/00-run-chroot.sh" if [ -f "$(ls $WIFI_SCRIPT 2>/dev/null | head -1)" ]; then # Check /etc/network/interfaces.d if grep -q "cat.*>.*\/etc\/network\/interfaces.d" "$(ls $WIFI_SCRIPT 2>/dev/null | head -1)"; then if grep -q "mkdir -p /etc/network/interfaces.d" "$(ls $WIFI_SCRIPT 2>/dev/null | head -1)"; then pass " WiFi: /etc/network/interfaces.d created before use" else error " WiFi: /etc/network/interfaces.d not created (will fail)" fi fi # Check lighttpd directories if grep -q "cat.*>.*lighttpd" "$(ls $WIFI_SCRIPT 2>/dev/null | head -1)"; then if grep -q "mkdir -p.*lighttpd.*conf-" "$(ls $WIFI_SCRIPT 2>/dev/null | head -1)"; then pass " WiFi: lighttpd config directories created" else warn " WiFi: lighttpd directories may not exist" fi fi fi # ============================================================================ section "7. SOURCE FILE AVAILABILITY" # ============================================================================ # Check if source files exist for dir in app systemd scripts; do if [ -d "$PROJECT_DIR/$dir" ]; then pass "Source directory exists: $dir/" else error "Source directory missing: $dir/" fi done if [ -f "$PROJECT_DIR/requirements.txt" ]; then pass "requirements.txt exists" else error "requirements.txt missing" fi # ============================================================================ section "8. BUILD SCRIPT VALIDATION" # ============================================================================ BUILD_SCRIPT="$PROJECT_DIR/build-image.sh" if [ -f "$BUILD_SCRIPT" ]; then pass "build-image.sh exists" if [ -x "$BUILD_SCRIPT" ]; then pass "build-image.sh is executable" else warn "build-image.sh is not executable" fi # Check if it uses our custom stages if grep -q "stagePM3\|stageDangerousPi\|stage3\|stage4" "$BUILD_SCRIPT"; then pass "build-image.sh references custom stages" else warn "build-image.sh may not reference custom stages" fi else error "build-image.sh not found" fi # ============================================================================ section "9. PACKAGE NAME VALIDATION" # ============================================================================ info "Validating package names against Debian repositories..." # Create temporary container to query package database TEMP_CONTAINER="preflight-pkg-check-$$" docker run -d --name "$TEMP_CONTAINER" debian:trixie sleep 3600 > /dev/null 2>&1 docker exec "$TEMP_CONTAINER" apt-get update > /dev/null 2>&1 # Extract all package names from apt-get install commands ALL_PACKAGES=$(grep -rh "apt-get install" "$PROJECT_DIR/pi-gen/stage"* 2>/dev/null | \ grep -v "^#" | \ sed 's/.*apt-get install//' | \ sed 's/-y//g; s/--no-install-recommends//g; s/--break-system-packages//g' | \ tr '\\' ' ' | \ tr '\n' ' ' | \ tr -s ' ' | \ grep -oE '[a-z0-9][a-z0-9+.-]+' | \ sort -u) PACKAGE_ERRORS=0 for pkg in $ALL_PACKAGES; do # Skip common flags, keywords, and commands if echo "$pkg" | grep -qE '^(y|no|install|apt|get|update|upgrade|break|system|packages)$'; then continue fi # Check if package exists in Debian repos if docker exec "$TEMP_CONTAINER" apt-cache show "$pkg" > /dev/null 2>&1; then pass " Package exists: $pkg" else error " Package NOT FOUND in Debian repos: $pkg" info " Check for typos or hallucinated package names!" ((PACKAGE_ERRORS++)) fi done # Cleanup docker rm -f "$TEMP_CONTAINER" > /dev/null 2>&1 if [ $PACKAGE_ERRORS -eq 0 ]; then pass "All package names validated successfully" else error "Found $PACKAGE_ERRORS invalid package names" info " Common mistakes: python-swig (should be: swig), libbluez-dev (should be: libbluetooth-dev)" fi # ============================================================================ section "10. FILE REFERENCE VALIDATION" # ============================================================================ info "Checking for references to missing files..." # Check for file copies in 00-run.sh scripts (outside chroot) for script in "$PROJECT_DIR"/pi-gen/stage*/*/00-run.sh; do if [ -f "$script" ]; then STAGE_DIR=$(dirname "$script") # Look for cp commands with source files while read -r line; do # Extract source path from cp commands if echo "$line" | grep -q "^cp "; then SRC=$(echo "$line" | awk '{print $2}' | sed 's/"//g') # Skip if it's a flag, variable, or special path if echo "$SRC" | grep -qE '^-|^\$|^\*|^/tmp|^/etc|^/var'; then continue fi # Check if file exists relative to stage dir if [ -f "$STAGE_DIR/$SRC" ] || [ -d "$STAGE_DIR/$SRC" ]; then pass " Found: $SRC (in $(basename $(dirname "$script")))" else warn " Missing file reference: $SRC in $script" fi fi done < <(grep -E "^cp " "$script" 2>/dev/null) fi done # ============================================================================ section "11. SERVICE FILE SYNTAX VALIDATION" # ============================================================================ info "Validating systemd service files..." # Find all .service files in stage directories for service_file in "$PROJECT_DIR"/pi-gen/stage*/*/*.service "$PROJECT_DIR"/systemd/*.service; do if [ -f "$service_file" ]; then BASENAME=$(basename "$service_file") # Check for required sections if grep -q "^\[Unit\]" "$service_file" && \ grep -q "^\[Service\]" "$service_file" && \ grep -q "^\[Install\]" "$service_file"; then pass " Service file structure valid: $BASENAME" else error " Service file missing required sections: $BASENAME" info " Required: [Unit], [Service], [Install]" fi # Check for ExecStart if grep -q "^ExecStart=" "$service_file"; then pass " Has ExecStart: $BASENAME" else error " Missing ExecStart in: $BASENAME" fi # Check for invalid __PLACEHOLDERS__ that weren't substituted # Allow __DANGEROUS_PI_USER__ as it's intentionally substituted during build UNSUBBED=$(grep "__.*__" "$service_file" 2>/dev/null | grep -v "__DANGEROUS_PI_USER__" || true) if [ -n "$UNSUBBED" ]; then warn " Found un-substituted placeholder in: $BASENAME" echo "$UNSUBBED" | sed 's/^/ /' fi fi done # ============================================================================ section "12. ENVIRONMENT VARIABLE CHECKS" # ============================================================================ info "Checking for undefined environment variables in scripts..." for script in "$PROJECT_DIR"/pi-gen/stage*/*/[0-9][0-9]-*.sh; do if [ -f "$script" ]; then SCRIPT_NAME=$(basename "$script") # Look for variable usage USED_VARS=$(grep -oE '\$\{?[A-Z_][A-Z0-9_]*\}?' "$script" 2>/dev/null | \ sed 's/[${}]//g' | sort -u) for var in $USED_VARS; do # Check if variable is defined in script or is a known variable if grep -q "^$var=" "$script" || \ grep -q "export $var=" "$script" || \ grep -q "$var=" "$script" || \ echo "$var" | grep -qE '^(ROOTFS_DIR|SCRIPT_DIR|DEFAULT_USER|DEFAULT_HOME|PATH|HOME|USER|HTTP|PM3_ROOT|BOOT_DIR|CONFIG_FILE|CMDLINE_FILE)$'; then # Variable is defined, standard, or from external system (lighttpd config, PM3 build) continue else warn " Potentially undefined variable: \$$var in $SCRIPT_NAME" fi done fi done # ============================================================================ section "13. DISK SPACE CHECK" # ============================================================================ info "Checking available disk space..." REQUIRED_GB=15 AVAILABLE_KB=$(df /home/work/pi-gen-builder 2>/dev/null | tail -1 | awk '{print $4}') AVAILABLE_GB=$((AVAILABLE_KB / 1024 / 1024)) if [ $AVAILABLE_GB -ge $REQUIRED_GB ]; then pass "Sufficient disk space: ${AVAILABLE_GB}GB available (${REQUIRED_GB}GB required)" else error "Insufficient disk space: ${AVAILABLE_GB}GB available (${REQUIRED_GB}GB required)" info " Pi-gen builds require ~15GB for image + cache" info " Free up space or build will fail mid-download" fi # ============================================================================ section "14. NETWORK CONNECTIVITY CHECK" # ============================================================================ info "Testing network connectivity to required repositories..." # Test Debian repos if curl -s --connect-timeout 5 http://deb.debian.org/debian/dists/trixie/Release > /dev/null 2>&1; then pass "Can reach deb.debian.org" else error "Cannot reach deb.debian.org (Debian package repository)" info " Check internet connection or firewall" fi # Test Raspberry Pi repos if curl -s --connect-timeout 5 http://archive.raspberrypi.com/debian/dists/trixie/Release > /dev/null 2>&1; then pass "Can reach archive.raspberrypi.com" else error "Cannot reach archive.raspberrypi.com (Raspberry Pi repository)" fi # Test GitHub (for PM3 clone) if curl -s --connect-timeout 5 https://github.com > /dev/null 2>&1; then pass "Can reach github.com" else error "Cannot reach github.com (needed for Proxmark3 clone)" fi # Test PM3 repo specifically if git ls-remote https://github.com/RfidResearchGroup/proxmark3 > /dev/null 2>&1; then pass "Proxmark3 repository is accessible" else error "Cannot access Proxmark3 repository" info " URL: https://github.com/RfidResearchGroup/proxmark3" fi # ============================================================================ section "VALIDATION SUMMARY" # ============================================================================ echo "" echo "┌────────────────────────────────────────┐" echo "│ VALIDATION RESULTS │" echo "├────────────────────────────────────────┤" printf "│ ${GREEN}Checks Passed:${NC} %-4d │\n" $CHECKS_PASSED printf "│ ${YELLOW}Warnings:${NC} %-4d │\n" $WARNINGS printf "│ ${RED}Errors:${NC} %-4d │\n" $ERRORS echo "└────────────────────────────────────────┘" echo "" if [ $ERRORS -gt 0 ]; then echo -e "${RED}╔════════════════════════════════════════════════════════════╗${NC}" echo -e "${RED}║ BUILD WILL FAIL - FIX ERRORS BEFORE ATTEMPTING BUILD ║${NC}" echo -e "${RED}╚════════════════════════════════════════════════════════════╝${NC}" echo "" echo "Do NOT start a build until all errors are fixed." echo "Each error listed above will cause build failure." exit 1 elif [ $WARNINGS -gt 0 ]; then echo -e "${YELLOW}╔════════════════════════════════════════════════════════════╗${NC}" echo -e "${YELLOW}║ WARNINGS DETECTED - REVIEW BEFORE BUILD ║${NC}" echo -e "${YELLOW}╚════════════════════════════════════════════════════════════╝${NC}" echo "" echo "Warnings may indicate potential issues but won't necessarily fail the build." exit 0 else echo -e "${GREEN}╔════════════════════════════════════════════════════════════╗${NC}" echo -e "${GREEN}║ ALL CHECKS PASSED - READY TO BUILD ║${NC}" echo -e "${GREEN}╚════════════════════════════════════════════════════════════╝${NC}" echo "" echo "Build environment is validated. Safe to proceed with:" echo " ./build-image.sh full" exit 0 fi