Files
pi-pm3/scripts/preflight-check.sh
michael 4f35df1781 Initial commit - Phase 3/4
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 13:46:22 -08:00

658 lines
25 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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*/*/03-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/stagePM3/01-proxmark3/00-run-chroot.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 stagePM3/01-proxmark3/00-run-chroot.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 stagePM3/01-proxmark3/00-run-chroot.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