Initial commit - Phase 3/4
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
86
scripts/apply-cpu-cores.sh
Executable file
86
scripts/apply-cpu-cores.sh
Executable file
@@ -0,0 +1,86 @@
|
||||
#!/bin/bash
|
||||
# Apply CPU cores defaults to cmdline.txt (fallback for manual installations)
|
||||
# Used by dangerous-pi-cpu-cores.service
|
||||
#
|
||||
# NOTE: When using the official pi-gen image, maxcpus is already set during build.
|
||||
# This script only runs as a fallback for manual installations.
|
||||
#
|
||||
# On Pi Zero 2 W (and other ARM devices), CPU hotplug via sysfs does NOT work.
|
||||
# Instead, we use the maxcpus=N kernel parameter in cmdline.txt.
|
||||
# This script sets the default maxcpus for Pi Zero 2 W if not already configured.
|
||||
|
||||
LOG_TAG="dangerous-pi-cpu-cores"
|
||||
|
||||
log() {
|
||||
logger -t "$LOG_TAG" "$1"
|
||||
echo "$1"
|
||||
}
|
||||
|
||||
# Find cmdline.txt (location varies by Pi OS version)
|
||||
find_cmdline() {
|
||||
if [ -f /boot/firmware/cmdline.txt ]; then
|
||||
echo "/boot/firmware/cmdline.txt"
|
||||
elif [ -f /boot/cmdline.txt ]; then
|
||||
echo "/boot/cmdline.txt"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
CMDLINE_FILE=$(find_cmdline)
|
||||
|
||||
if [ -z "$CMDLINE_FILE" ]; then
|
||||
log "ERROR: Could not find cmdline.txt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if maxcpus is already configured
|
||||
if grep -q "maxcpus=" "$CMDLINE_FILE"; then
|
||||
CURRENT=$(grep -o 'maxcpus=[0-9]*' "$CMDLINE_FILE" | cut -d= -f2)
|
||||
log "maxcpus already configured: $CURRENT cores"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Determine default cores based on Pi model
|
||||
get_default_cores() {
|
||||
local model=""
|
||||
local total_cores=$(nproc --all)
|
||||
|
||||
if [ -f /proc/device-tree/model ]; then
|
||||
model=$(cat /proc/device-tree/model | tr -d '\0')
|
||||
fi
|
||||
|
||||
# Pi Zero 2 W has 4 cores but should default to 2 for thermal management
|
||||
if echo "$model" | grep -q "Pi Zero 2"; then
|
||||
echo 2
|
||||
# Single-core Pis - no need to set maxcpus
|
||||
elif echo "$model" | grep -q "Pi Zero W\|Pi Zero$\|Pi 1\|Pi Model A\|Pi Model B"; then
|
||||
echo "$total_cores" # Return total (no restriction needed)
|
||||
# All other multi-core Pis use all cores by default
|
||||
else
|
||||
echo "$total_cores"
|
||||
fi
|
||||
}
|
||||
|
||||
DEFAULT_CORES=$(get_default_cores)
|
||||
TOTAL_CORES=$(nproc --all)
|
||||
|
||||
# Only set maxcpus if we want fewer than total cores
|
||||
if [ "$DEFAULT_CORES" -lt "$TOTAL_CORES" ]; then
|
||||
log "Setting default maxcpus=$DEFAULT_CORES for this device"
|
||||
|
||||
# Backup original
|
||||
cp "$CMDLINE_FILE" "${CMDLINE_FILE}.bak"
|
||||
|
||||
# Append maxcpus to cmdline
|
||||
CURRENT_CMDLINE=$(cat "$CMDLINE_FILE" | tr -d '\n')
|
||||
echo "$CURRENT_CMDLINE maxcpus=$DEFAULT_CORES" > "${CMDLINE_FILE}.new"
|
||||
mv "${CMDLINE_FILE}.new" "$CMDLINE_FILE"
|
||||
|
||||
log "Updated $CMDLINE_FILE with maxcpus=$DEFAULT_CORES"
|
||||
log "NOTE: This will take effect on next reboot"
|
||||
else
|
||||
log "No maxcpus restriction needed (using all $TOTAL_CORES cores)"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
198
scripts/audit-build-scripts.sh
Executable file
198
scripts/audit-build-scripts.sh
Executable file
@@ -0,0 +1,198 @@
|
||||
#!/bin/bash
|
||||
# Audit build scripts for common issues
|
||||
# Usage: ./scripts/audit-build-scripts.sh
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
echo "=== Auditing Build Scripts ==="
|
||||
echo ""
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
WARNINGS=0
|
||||
ERRORS=0
|
||||
|
||||
# Function to report issues
|
||||
warn() {
|
||||
echo -e "${YELLOW}WARNING:${NC} $1"
|
||||
((WARNINGS++))
|
||||
}
|
||||
|
||||
error() {
|
||||
echo -e "${RED}ERROR:${NC} $1"
|
||||
((ERRORS++))
|
||||
}
|
||||
|
||||
ok() {
|
||||
echo -e "${GREEN}✓${NC} $1"
|
||||
}
|
||||
|
||||
echo "1. Checking for commands used before installation..."
|
||||
echo "------------------------------------------------"
|
||||
|
||||
# Check PM3 stage
|
||||
PM3_SCRIPT="$PROJECT_DIR/pi-gen/stagePM3/01-proxmark3/00-run-chroot.sh"
|
||||
if [ -f "$PM3_SCRIPT" ]; then
|
||||
# Check if cmake is used before apt-get install
|
||||
if grep -q "cmake" "$PM3_SCRIPT"; then
|
||||
if ! grep -B50 "cmake" "$PM3_SCRIPT" | grep -q "apt-get install.*cmake"; then
|
||||
warn "PM3: cmake used but may not be installed"
|
||||
else
|
||||
ok "PM3: cmake installation verified"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check for git before installation
|
||||
if grep -q "git clone" "$PM3_SCRIPT"; then
|
||||
if ! grep -B50 "git clone" "$PM3_SCRIPT" | grep -q "apt-get install.*git"; then
|
||||
warn "PM3: git used but may not be installed"
|
||||
else
|
||||
ok "PM3: git installation verified"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check DangerousPi stage
|
||||
DTPI_SCRIPT="$PROJECT_DIR/pi-gen/stageDangerousPi/03-dangerous-pi/00-run-chroot.sh"
|
||||
if [ -f "$DTPI_SCRIPT" ]; then
|
||||
# Check for pip3
|
||||
if grep -q "pip3 install" "$DTPI_SCRIPT"; then
|
||||
if ! grep -B50 "pip3 install" "$DTPI_SCRIPT" | grep -q "apt-get install.*python3-pip\|command -v pip3"; then
|
||||
error "DangerousPi: pip3 used but not installed"
|
||||
else
|
||||
ok "DangerousPi: pip3 installation check present"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "2. Checking for missing directory creation..."
|
||||
echo "-------------------------------------------"
|
||||
|
||||
# Check WiFi AP script
|
||||
WIFI_SCRIPT="$PROJECT_DIR/pi-gen/stageDangerousPi/01-Wireless-AP/00-run-chroot.sh"
|
||||
if [ -f "$WIFI_SCRIPT" ]; then
|
||||
# Check for directory writes
|
||||
DIRS_WRITTEN=$(grep -n "cat >" "$WIFI_SCRIPT" | grep -oE '/etc/[^/]+(/[^/]+)*/' | sort -u)
|
||||
for dir in $DIRS_WRITTEN; do
|
||||
dir_parent=$(dirname "$dir")
|
||||
if ! grep -q "mkdir -p $dir_parent\|mkdir -p $dir" "$WIFI_SCRIPT"; then
|
||||
warn "WiFi: Writing to $dir without mkdir -p"
|
||||
else
|
||||
ok "WiFi: $dir directory creation verified"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "3. Checking for undefined variables..."
|
||||
echo "------------------------------------"
|
||||
|
||||
for script in "$PROJECT_DIR"/pi-gen/stage*/*/00-run*.sh; do
|
||||
if [ -f "$script" ]; then
|
||||
stage=$(basename $(dirname $(dirname "$script")))
|
||||
substage=$(basename $(dirname "$script"))
|
||||
|
||||
# Check for common undefined vars
|
||||
if grep -q '\$ROOTFS_DIR' "$script" && [ "$(basename "$script")" != "00-run.sh" ]; then
|
||||
warn "$stage/$substage: Uses \$ROOTFS_DIR in chroot script (should be in 00-run.sh)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "4. Checking for missing error handling..."
|
||||
echo "---------------------------------------"
|
||||
|
||||
for script in "$PROJECT_DIR"/pi-gen/stage*/*/00-run*.sh; do
|
||||
if [ -f "$script" ]; then
|
||||
stage=$(basename $(dirname $(dirname "$script")))
|
||||
substage=$(basename $(dirname "$script"))
|
||||
|
||||
# Check if script has #!/bin/bash -e
|
||||
if ! head -1 "$script" | grep -q "bash -e"; then
|
||||
warn "$stage/$substage: Missing 'set -e' or '#!/bin/bash -e'"
|
||||
else
|
||||
ok "$stage/$substage: Error handling enabled"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "5. Checking for sudo usage (not allowed in chroot)..."
|
||||
echo "---------------------------------------------------"
|
||||
|
||||
for script in "$PROJECT_DIR"/pi-gen/stage*/*/00-run*.sh; do
|
||||
if [ -f "$script" ]; then
|
||||
stage=$(basename $(dirname $(dirname "$script")))
|
||||
substage=$(basename $(dirname "$script"))
|
||||
|
||||
if grep -q "sudo " "$script"; then
|
||||
error "$stage/$substage: Contains 'sudo' commands (not allowed in chroot)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
ok "No sudo commands found in stage scripts"
|
||||
|
||||
echo ""
|
||||
echo "6. Checking configuration consistency..."
|
||||
echo "--------------------------------------"
|
||||
|
||||
CONFIG="$PROJECT_DIR/pi-gen/config"
|
||||
if [ -f "$CONFIG" ]; then
|
||||
USERNAME=$(grep "FIRST_USER_NAME=" "$CONFIG" | cut -d'"' -f2)
|
||||
echo "Configured username: $USERNAME"
|
||||
|
||||
if [ "$USERNAME" != "pi" ]; then
|
||||
warn "Username is '$USERNAME' (not standard 'pi')"
|
||||
echo " This is OK if intentional, but verify all scripts handle it correctly"
|
||||
else
|
||||
ok "Using standard 'pi' username"
|
||||
fi
|
||||
|
||||
# Check if QCOW2 is enabled
|
||||
if grep -q "USE_QCOW2=1" "$CONFIG"; then
|
||||
ok "QCOW2 caching enabled for fast rebuilds"
|
||||
else
|
||||
warn "QCOW2 caching disabled - rebuilds will be slow"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "7. Checking for required EXPORT_IMAGE markers..."
|
||||
echo "----------------------------------------------"
|
||||
|
||||
for stage in "$PROJECT_DIR"/pi-gen/stage{PM3,DangerousPi}; do
|
||||
if [ -d "$stage" ]; then
|
||||
stage_name=$(basename "$stage")
|
||||
if [ -f "$stage/EXPORT_IMAGE" ]; then
|
||||
ok "$stage_name: EXPORT_IMAGE present"
|
||||
else
|
||||
error "$stage_name: Missing EXPORT_IMAGE marker"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Audit Summary ==="
|
||||
echo "Warnings: $WARNINGS"
|
||||
echo "Errors: $ERRORS"
|
||||
echo ""
|
||||
|
||||
if [ $ERRORS -gt 0 ]; then
|
||||
echo -e "${RED}BUILD SCRIPTS HAVE ERRORS - REVIEW REQUIRED${NC}"
|
||||
exit 1
|
||||
elif [ $WARNINGS -gt 0 ]; then
|
||||
echo -e "${YELLOW}BUILD SCRIPTS HAVE WARNINGS - REVIEW RECOMMENDED${NC}"
|
||||
exit 0
|
||||
else
|
||||
echo -e "${GREEN}ALL CHECKS PASSED${NC}"
|
||||
exit 0
|
||||
fi
|
||||
39
scripts/build-status.sh
Executable file
39
scripts/build-status.sh
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
# Quick build status checker
|
||||
# Usage: ./scripts/build-status.sh
|
||||
|
||||
# Colors
|
||||
GREEN='\033[0;32m'
|
||||
BLUE='\033[0;34m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m'
|
||||
|
||||
if ! docker ps --filter name=pigen_work --format "{{.Status}}" &>/dev/null; then
|
||||
echo -e "${RED}Docker not available${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STATUS=$(docker ps --filter name=pigen_work --format "{{.Status}}")
|
||||
|
||||
if [ -z "$STATUS" ]; then
|
||||
echo -e "${YELLOW}No build running${NC}"
|
||||
echo "Start a build with: ./build-image.sh full"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo -e "${GREEN}Build Status:${NC} $STATUS"
|
||||
echo ""
|
||||
echo -e "${BLUE}Current Stage:${NC}"
|
||||
docker logs pigen_work 2>&1 | grep -E "^\[.*\] Begin /pi-gen/(stage[^/]+)" | tail -1
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}Recent Progress (last 10 steps):${NC}"
|
||||
docker logs pigen_work 2>&1 | grep -E "^\[.*\] (Begin|End)" | tail -10
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}Latest Activity:${NC}"
|
||||
docker logs --tail 3 pigen_work 2>&1
|
||||
|
||||
echo ""
|
||||
echo -e "Watch live: ${YELLOW}docker logs -f pigen_work${NC}"
|
||||
90
scripts/configure-nginx.sh
Normal file
90
scripts/configure-nginx.sh
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
# Configure nginx for Dangerous Pi HTTP or HTTPS mode
|
||||
# Switches nginx configuration based on HTTPS_ENABLED setting
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
ENV_FILE="/opt/dangerous-pi/.env"
|
||||
NGINX_SITES_ENABLED="/etc/nginx/sites-enabled"
|
||||
HTTP_CONFIG="/etc/nginx/sites-available/dangerous-pi.conf"
|
||||
HTTPS_CONFIG="/opt/dangerous-pi/nginx/dangerous-pi-https.conf"
|
||||
CERT_FILE="/opt/dangerous-pi/ssl/dangerous-pi.crt"
|
||||
KEY_FILE="/opt/dangerous-pi/ssl/dangerous-pi.key"
|
||||
|
||||
# Source environment file to get HTTPS_ENABLED
|
||||
HTTPS_ENABLED="false"
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
# shellcheck source=/dev/null
|
||||
source "$ENV_FILE"
|
||||
fi
|
||||
|
||||
echo "Configuring nginx for Dangerous Pi..."
|
||||
echo " HTTPS_ENABLED=$HTTPS_ENABLED"
|
||||
|
||||
if [ "$HTTPS_ENABLED" = "true" ]; then
|
||||
echo ""
|
||||
echo "HTTPS mode requested."
|
||||
|
||||
# Check if certificate exists, generate if not
|
||||
if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then
|
||||
echo "SSL certificate not found, generating..."
|
||||
/opt/dangerous-pi/scripts/generate-ssl-cert.sh
|
||||
fi
|
||||
|
||||
# Verify certificate exists after generation attempt
|
||||
if [ ! -f "$CERT_FILE" ] || [ ! -f "$KEY_FILE" ]; then
|
||||
echo "ERROR: SSL certificate generation failed!"
|
||||
echo "Falling back to HTTP mode."
|
||||
HTTPS_ENABLED="false"
|
||||
else
|
||||
# Check if HTTPS config exists
|
||||
if [ ! -f "$HTTPS_CONFIG" ]; then
|
||||
echo "ERROR: HTTPS nginx config not found at $HTTPS_CONFIG"
|
||||
echo "Falling back to HTTP mode."
|
||||
HTTPS_ENABLED="false"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Remove existing symlink
|
||||
rm -f "$NGINX_SITES_ENABLED/dangerous-pi.conf"
|
||||
|
||||
if [ "$HTTPS_ENABLED" = "true" ]; then
|
||||
# Use HTTPS config
|
||||
ln -sf "$HTTPS_CONFIG" "$NGINX_SITES_ENABLED/dangerous-pi.conf"
|
||||
echo ""
|
||||
echo "HTTPS mode enabled!"
|
||||
echo " Config: $HTTPS_CONFIG"
|
||||
echo " Certificate: $CERT_FILE"
|
||||
echo ""
|
||||
echo "Access the device at:"
|
||||
echo " https://192.168.4.1/"
|
||||
echo " https://dangerous-pi.local/"
|
||||
echo ""
|
||||
echo "Note: Browser will show certificate warning for self-signed cert."
|
||||
else
|
||||
# Use HTTP-only config
|
||||
if [ -f "$HTTP_CONFIG" ]; then
|
||||
ln -sf "$HTTP_CONFIG" "$NGINX_SITES_ENABLED/dangerous-pi.conf"
|
||||
echo ""
|
||||
echo "HTTP-only mode enabled."
|
||||
echo " Config: $HTTP_CONFIG"
|
||||
echo ""
|
||||
echo "Access the device at:"
|
||||
echo " http://192.168.4.1/"
|
||||
echo " http://dangerous-pi.local/"
|
||||
else
|
||||
echo "ERROR: HTTP nginx config not found at $HTTP_CONFIG"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test nginx configuration
|
||||
echo ""
|
||||
echo "Testing nginx configuration..."
|
||||
nginx -t
|
||||
|
||||
echo ""
|
||||
echo "Nginx configuration complete."
|
||||
echo "Run 'sudo systemctl reload nginx' to apply changes."
|
||||
114
scripts/generate-ssl-cert.sh
Normal file
114
scripts/generate-ssl-cert.sh
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/bin/bash
|
||||
# Generate self-signed SSL certificate for Dangerous Pi
|
||||
# Supports first-boot auto-generation or manual regeneration
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration
|
||||
CERT_DIR="/opt/dangerous-pi/ssl"
|
||||
CERT_FILE="$CERT_DIR/dangerous-pi.crt"
|
||||
KEY_FILE="$CERT_DIR/dangerous-pi.key"
|
||||
DAYS_VALID=3650 # 10 years
|
||||
|
||||
# Certificate subject
|
||||
SUBJECT="/CN=Dangerous Pi/O=Dangerous Pi/C=US"
|
||||
|
||||
# Subject Alternative Names (SANs)
|
||||
# - The AP gateway IP
|
||||
# - mDNS hostname
|
||||
# - Bare hostname
|
||||
# - Localhost for development
|
||||
SANS="IP:192.168.4.1,DNS:dangerous-pi.local,DNS:dangerous-pi,DNS:localhost,IP:127.0.0.1"
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [OPTIONS]"
|
||||
echo ""
|
||||
echo "Generate self-signed SSL certificate for Dangerous Pi HTTPS."
|
||||
echo ""
|
||||
echo "Options:"
|
||||
echo " --force Regenerate certificate even if one exists"
|
||||
echo " --cert-dir Specify certificate directory (default: $CERT_DIR)"
|
||||
echo " --help Show this help message"
|
||||
echo ""
|
||||
echo "The certificate will be valid for:"
|
||||
echo " - 192.168.4.1 (AP gateway IP)"
|
||||
echo " - dangerous-pi.local (mDNS hostname)"
|
||||
echo " - dangerous-pi (bare hostname)"
|
||||
echo " - localhost"
|
||||
}
|
||||
|
||||
FORCE=false
|
||||
|
||||
# Parse arguments
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case $1 in
|
||||
--force)
|
||||
FORCE=true
|
||||
shift
|
||||
;;
|
||||
--cert-dir)
|
||||
CERT_DIR="$2"
|
||||
CERT_FILE="$CERT_DIR/dangerous-pi.crt"
|
||||
KEY_FILE="$CERT_DIR/dangerous-pi.key"
|
||||
shift 2
|
||||
;;
|
||||
--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Create SSL directory if it doesn't exist
|
||||
mkdir -p "$CERT_DIR"
|
||||
|
||||
# Check if certificate already exists
|
||||
if [ -f "$CERT_FILE" ] && [ "$FORCE" = false ]; then
|
||||
echo "Certificate already exists at $CERT_FILE"
|
||||
echo "Use --force to regenerate."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Generating self-signed SSL certificate for Dangerous Pi..."
|
||||
echo " Certificate: $CERT_FILE"
|
||||
echo " Private key: $KEY_FILE"
|
||||
echo " Validity: $DAYS_VALID days"
|
||||
echo ""
|
||||
|
||||
# Generate private key and certificate with SANs
|
||||
# Using EC P-256 for better performance on Raspberry Pi
|
||||
openssl req -x509 -nodes -days "$DAYS_VALID" \
|
||||
-newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
|
||||
-keyout "$KEY_FILE" \
|
||||
-out "$CERT_FILE" \
|
||||
-subj "$SUBJECT" \
|
||||
-addext "subjectAltName=$SANS" \
|
||||
-addext "basicConstraints=CA:FALSE" \
|
||||
-addext "keyUsage=digitalSignature,keyEncipherment" \
|
||||
-addext "extendedKeyUsage=serverAuth"
|
||||
|
||||
# Set secure permissions
|
||||
# Private key: owner read/write only
|
||||
chmod 600 "$KEY_FILE"
|
||||
# Certificate: world readable
|
||||
chmod 644 "$CERT_FILE"
|
||||
|
||||
# Set ownership to root
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
chown root:root "$KEY_FILE" "$CERT_FILE"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "SSL certificate generated successfully!"
|
||||
echo ""
|
||||
echo "Certificate details:"
|
||||
openssl x509 -in "$CERT_FILE" -noout -subject -dates -ext subjectAltName 2>/dev/null || \
|
||||
openssl x509 -in "$CERT_FILE" -noout -subject -dates
|
||||
echo ""
|
||||
echo "To enable HTTPS, set HTTPS_ENABLED=true in /opt/dangerous-pi/.env"
|
||||
echo "Then restart the services: sudo systemctl restart dangerous-pi-nginx-config nginx"
|
||||
55
scripts/install-udev-rules.sh
Executable file
55
scripts/install-udev-rules.sh
Executable file
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
# Install udev rules for Proxmark3 device detection
|
||||
# Run with sudo
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
UDEV_RULES_SRC="$PROJECT_ROOT/udev/77-dangerous-pi-pm3.rules"
|
||||
UDEV_RULES_DEST="/etc/udev/rules.d/77-dangerous-pi-pm3.rules"
|
||||
|
||||
echo "Installing Proxmark3 udev rules for Dangerous Pi..."
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo "ERROR: This script must be run as root (use sudo)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if source file exists
|
||||
if [ ! -f "$UDEV_RULES_SRC" ]; then
|
||||
echo "ERROR: Source udev rules file not found: $UDEV_RULES_SRC"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy rules file
|
||||
echo "Copying udev rules to $UDEV_RULES_DEST..."
|
||||
cp "$UDEV_RULES_SRC" "$UDEV_RULES_DEST"
|
||||
chmod 644 "$UDEV_RULES_DEST"
|
||||
|
||||
# Reload udev rules
|
||||
echo "Reloading udev rules..."
|
||||
udevadm control --reload-rules
|
||||
udevadm trigger
|
||||
|
||||
# Add user to plugdev group if not already a member
|
||||
if ! groups $SUDO_USER | grep -q '\bplugdev\b'; then
|
||||
echo "Adding user $SUDO_USER to plugdev group..."
|
||||
usermod -a -G plugdev $SUDO_USER
|
||||
echo "NOTE: User must log out and back in for group membership to take effect"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✓ Udev rules installed successfully!"
|
||||
echo ""
|
||||
echo "The following rules are now active:"
|
||||
echo " - Proxmark3 RDV4/Easy devices will be accessible at /dev/ttyACM*"
|
||||
echo " - Devices will have permissions 0666 (readable/writable by all)"
|
||||
echo " - Symlinks created: /dev/proxmark3-*"
|
||||
echo " - Events logged to system logger"
|
||||
echo ""
|
||||
echo "To test, connect a Proxmark3 device and run:"
|
||||
echo " ls -l /dev/ttyACM*"
|
||||
echo " ls -l /dev/proxmark3-*"
|
||||
echo ""
|
||||
657
scripts/preflight-check.sh
Executable file
657
scripts/preflight-check.sh
Executable file
@@ -0,0 +1,657 @@
|
||||
#!/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
|
||||
137
scripts/serial_transfer.py
Normal file
137
scripts/serial_transfer.py
Normal file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Reliable file transfer over serial console.
|
||||
Bypasses screen to avoid escaping issues.
|
||||
"""
|
||||
|
||||
import serial
|
||||
import base64
|
||||
import hashlib
|
||||
import time
|
||||
import sys
|
||||
import re
|
||||
|
||||
def send_command(ser, cmd, wait=0.3):
|
||||
"""Send a command and wait for output."""
|
||||
ser.write(f"{cmd}\n".encode())
|
||||
time.sleep(wait)
|
||||
response = ser.read(ser.in_waiting or 1).decode('utf-8', errors='replace')
|
||||
return response
|
||||
|
||||
def wait_for_prompt(ser, timeout=5):
|
||||
"""Wait for shell prompt."""
|
||||
start = time.time()
|
||||
buffer = ""
|
||||
while time.time() - start < timeout:
|
||||
if ser.in_waiting:
|
||||
buffer += ser.read(ser.in_waiting).decode('utf-8', errors='replace')
|
||||
if buffer.rstrip().endswith('$') or buffer.rstrip().endswith('#'):
|
||||
return buffer
|
||||
time.sleep(0.1)
|
||||
return buffer
|
||||
|
||||
def transfer_file(serial_port, local_file, remote_path, baud=115200):
|
||||
"""Transfer a file reliably over serial."""
|
||||
|
||||
# Read local file
|
||||
with open(local_file, 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
# Compute checksums
|
||||
local_md5 = hashlib.md5(content).hexdigest()
|
||||
b64_content = base64.b64encode(content).decode()
|
||||
b64_md5 = hashlib.md5(b64_content.encode()).hexdigest()
|
||||
|
||||
print(f"File size: {len(content)} bytes")
|
||||
print(f"Base64 size: {len(b64_content)} bytes")
|
||||
print(f"File MD5: {local_md5}")
|
||||
print(f"Base64 MD5: {b64_md5}")
|
||||
|
||||
# Open serial port
|
||||
print(f"\nOpening {serial_port}...")
|
||||
ser = serial.Serial(serial_port, baud, timeout=1)
|
||||
time.sleep(0.5)
|
||||
|
||||
# Send initial enter to get prompt
|
||||
ser.write(b'\n')
|
||||
time.sleep(0.5)
|
||||
ser.read(ser.in_waiting) # Clear buffer
|
||||
|
||||
# Clear target file
|
||||
print("Initializing remote file...")
|
||||
send_command(ser, f"> /tmp/recv.b64", wait=0.5)
|
||||
|
||||
# Use very small chunks to avoid corruption
|
||||
chunk_size = 60
|
||||
chunks = [b64_content[i:i+chunk_size] for i in range(0, len(b64_content), chunk_size)]
|
||||
|
||||
print(f"Sending {len(chunks)} chunks...")
|
||||
|
||||
errors = 0
|
||||
for i, chunk in enumerate(chunks):
|
||||
# Use printf for better escaping than echo
|
||||
cmd = f"printf '%s' '{chunk}' >> /tmp/recv.b64"
|
||||
ser.write(f"{cmd}\n".encode())
|
||||
time.sleep(0.15) # Small delay between chunks
|
||||
|
||||
# Read any response
|
||||
if ser.in_waiting:
|
||||
ser.read(ser.in_waiting)
|
||||
|
||||
if (i + 1) % 100 == 0:
|
||||
print(f" Sent {i+1}/{len(chunks)} chunks ({100*(i+1)//len(chunks)}%)")
|
||||
# Give Pi time to process
|
||||
time.sleep(0.5)
|
||||
|
||||
print("All chunks sent. Waiting for completion...")
|
||||
time.sleep(2)
|
||||
|
||||
# Verify base64 file checksum
|
||||
print("\nVerifying base64 checksum...")
|
||||
ser.read(ser.in_waiting) # Clear buffer
|
||||
send_command(ser, f"md5sum /tmp/recv.b64", wait=1)
|
||||
response = wait_for_prompt(ser, timeout=5)
|
||||
print(f"Response: {response}")
|
||||
|
||||
if b64_md5 in response:
|
||||
print("✓ Base64 checksum matches!")
|
||||
else:
|
||||
print("✗ Base64 checksum mismatch!")
|
||||
# Extract the md5 from response
|
||||
match = re.search(r'([a-f0-9]{32})', response)
|
||||
if match:
|
||||
print(f" Expected: {b64_md5}")
|
||||
print(f" Got: {match.group(1)}")
|
||||
return False
|
||||
|
||||
# Decode to target location
|
||||
print(f"\nDecoding to {remote_path}...")
|
||||
send_command(ser, f"base64 -d /tmp/recv.b64 > {remote_path}", wait=1)
|
||||
time.sleep(1)
|
||||
|
||||
# Verify final file
|
||||
print("Verifying final file...")
|
||||
ser.read(ser.in_waiting)
|
||||
send_command(ser, f"md5sum {remote_path}", wait=1)
|
||||
response = wait_for_prompt(ser, timeout=5)
|
||||
print(f"Response: {response}")
|
||||
|
||||
if local_md5 in response:
|
||||
print("✓ File transfer successful!")
|
||||
return True
|
||||
else:
|
||||
print("✗ Final file checksum mismatch!")
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 3:
|
||||
print("Usage: serial_transfer.py <local_file> <remote_path> [serial_port]")
|
||||
print("Example: serial_transfer.py wifi_manager.py /opt/dangerous-pi/app/backend/managers/wifi_manager.py")
|
||||
sys.exit(1)
|
||||
|
||||
local_file = sys.argv[1]
|
||||
remote_path = sys.argv[2]
|
||||
serial_port = sys.argv[3] if len(sys.argv) > 3 else "/dev/ttyUSB1"
|
||||
|
||||
success = transfer_file(serial_port, local_file, remote_path)
|
||||
sys.exit(0 if success else 1)
|
||||
Reference in New Issue
Block a user