#!/bin/bash # Build Dangerous Pi image using pi-gen # # For faster builds, you can use pre-built PM3 binaries: # PM3_TARBALL=/path/to/pm3-*.tar.gz ./build-image.sh full # # For faster package downloads, run apt-cacher-ng locally: # docker run -d -p 3142:3142 --name apt-cache sameersbn/apt-cacher-ng # APT_PROXY=http://host.docker.internal:3142 ./build-image.sh full # # To skip base OS build (stages 0-2), use a pre-baked base image: # BASE_IMAGE=/path/to/base-image.tar.gz ./build-image.sh full set -e # Colors for output GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Configuration PI_GEN_DIR="/home/work/pi-gen-builder" # Official pi-gen builder SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" DTPI_STAGE_DIR="$SCRIPT_DIR/pi-gen/stageDangerousPi" CONFIG_FILE="$SCRIPT_DIR/pi-gen/config" KEEP_CONTAINER=0 # Parse flags (after build type positional arg) for arg in "$@"; do case $arg in --keep-container) KEEP_CONTAINER=1 shift ;; esac done # Verify pi-gen directory exists if [ ! -d "$PI_GEN_DIR" ]; then echo "Error: pi-gen directory not found at $PI_GEN_DIR" echo "Please run: cd /home/work && git clone https://github.com/RPi-Distro/pi-gen.git pi-gen-builder" exit 1 fi echo -e "${GREEN}Using pi-gen at: $PI_GEN_DIR${NC}" # Build frontend build_frontend() { echo -e "${GREEN}Building frontend...${NC}" cd "$SCRIPT_DIR/app/frontend" # Check if npm is available if ! command -v npm &> /dev/null; then echo -e "${YELLOW}WARNING: npm not found, skipping frontend build${NC}" echo -e "${YELLOW}Install Node.js to build frontend: https://nodejs.org/${NC}" cd "$SCRIPT_DIR" return 1 fi # Install dependencies if needed if [ ! -d "node_modules" ]; then echo -e "${GREEN}Installing frontend dependencies...${NC}" npm install fi # Build the frontend echo -e "${GREEN}Running npm build...${NC}" npm run build echo -e "${GREEN}Frontend build complete${NC}" cd "$SCRIPT_DIR" return 0 } # Helper function to copy stages copy_stages() { cd "$PI_GEN_DIR" # Copy config cp "$CONFIG_FILE" config # Copy DangerousPi stage rm -rf stageDangerousPi cp -r "$DTPI_STAGE_DIR" . # Remove intermediate image exports (we only want the final image) rm -f stage2/EXPORT_IMAGE # Build frontend before copying if build_frontend; then echo -e "${GREEN}Frontend built successfully${NC}" else echo -e "${YELLOW}Continuing without frontend build${NC}" fi # Return to pi-gen directory cd "$PI_GEN_DIR" # Copy Dangerous Pi source files into the stage echo -e "${GREEN}Copying Dangerous Pi source files...${NC}" mkdir -p stageDangerousPi/04-dangerous-pi/files cp -r "$SCRIPT_DIR/app" stageDangerousPi/04-dangerous-pi/files/ cp -r "$SCRIPT_DIR/systemd" stageDangerousPi/04-dangerous-pi/files/ cp -r "$SCRIPT_DIR/scripts" stageDangerousPi/04-dangerous-pi/files/ cp "$SCRIPT_DIR/requirements.txt" stageDangerousPi/04-dangerous-pi/files/ # Copy dt-design-system packages (needed by frontend's file: dependency) DT_DS_DIR="$(dirname "$SCRIPT_DIR")/dt-design-system" if [ -d "$DT_DS_DIR/packages/web" ]; then echo -e "${GREEN}Copying dt-design-system packages...${NC}" mkdir -p stageDangerousPi/04-dangerous-pi/files/dt-design-system/packages cp -r "$DT_DS_DIR/packages/web" stageDangerousPi/04-dangerous-pi/files/dt-design-system/packages/ cp -r "$DT_DS_DIR/packages/tokens" stageDangerousPi/04-dangerous-pi/files/dt-design-system/packages/ cp "$DT_DS_DIR/package.json" stageDangerousPi/04-dangerous-pi/files/dt-design-system/ else echo -e "${YELLOW}WARNING: dt-design-system not found at $DT_DS_DIR${NC}" echo -e "${YELLOW}Frontend npm ci will fail without it${NC}" fi # Pass PM3_TARBALL into the stage if set if [ -n "${PM3_TARBALL:-}" ] && [ -f "$PM3_TARBALL" ]; then echo -e "${GREEN}Staging PM3 tarball: $PM3_TARBALL${NC}" mkdir -p stageDangerousPi/02-pm3-install/files cp "$PM3_TARBALL" stageDangerousPi/02-pm3-install/files/ fi } # Install pre-baked base image if BASE_IMAGE is set install_base_image() { if [ -z "${BASE_IMAGE:-}" ]; then return 1 fi if [ ! -f "$BASE_IMAGE" ]; then echo -e "${YELLOW}WARNING: BASE_IMAGE file not found: $BASE_IMAGE${NC}" return 1 fi echo -e "${GREEN}Installing pre-baked base image: $BASE_IMAGE${NC}" cd "$PI_GEN_DIR" # Extract base image into work directory as stage2 rootfs local WORK_DIR="work/${IMG_NAME:-dangerous-pi}" mkdir -p "${WORK_DIR}/stage2" tar -xzf "$BASE_IMAGE" -C "${WORK_DIR}/stage2/" echo -e "${GREEN}Base image extracted, skipping stages 0-2${NC}" # Mark stages 0-2 as SKIP touch stage0/SKIP stage1/SKIP stage2/SKIP return 0 } # Build type BUILD_TYPE="${1:-full}" case "$BUILD_TYPE" in full) echo -e "${GREEN}Starting full build (all stages)...${NC}" # Remove container but KEEP the volume for caching if docker ps -a --filter name=pigen_work --format "{{.Names}}" | grep -q pigen_work; then echo -e "${YELLOW}Removing existing container (keeping cache)...${NC}" docker rm pigen_work > /dev/null 2>&1 || true fi copy_stages # Use pre-baked base image if available, otherwise build all stages if install_base_image; then echo -e "${GREEN}Using pre-baked base image (stages 0-2 skipped)${NC}" else # Remove any SKIP files to ensure full build rm -f stage0/SKIP stage1/SKIP stage2/SKIP fi rm -f stageDangerousPi/SKIP # Full build (uses docker, no root needed) PIGEN_DOCKER_MODE=1 ./build-docker.sh ;; from-dtpi) echo -e "${GREEN}Building from DangerousPi stage (customizations only)...${NC}" copy_stages # Check for cached stage2 if ls work/*/stage2/*.qcow2 2>/dev/null | grep -q . || \ [ -d "work/dangerous-pi/stage2/rootfs" ]; then echo -e "${GREEN}Found cached base stages, creating SKIP markers...${NC}" touch stage0/SKIP stage1/SKIP stage2/SKIP echo -e "${GREEN}Will build: stageDangerousPi only (~5 min)${NC}" elif install_base_image; then echo -e "${GREEN}Using pre-baked base image${NC}" else echo -e "${YELLOW}WARNING: No cached stages found!${NC}" echo -e "${YELLOW}You need to run './build-image.sh full' first${NC}" echo -e "${YELLOW}Falling back to full build...${NC}" rm -f stage0/SKIP stage1/SKIP stage2/SKIP fi # Ensure DangerousPi stage builds rm -f stageDangerousPi/SKIP # Build with continue flag CONTINUE=1 PIGEN_DOCKER_MODE=1 ./build-docker.sh ;; clean) echo -e "${YELLOW}Removing ALL build cache and container...${NC}" if docker ps -a --filter name=pigen_work --format "{{.Names}}" | grep -q pigen_work; then docker rm -v pigen_work > /dev/null 2>&1 || true echo -e "${GREEN}Removed container and volume${NC}" else docker volume rm pigen_work > /dev/null 2>&1 || true echo -e "${GREEN}Removed volume${NC}" fi echo "" echo "Cache wiped. Next build will be from scratch." exit 0 ;; test) echo -e "${GREEN}Validating stage scripts...${NC}" # Syntax check - PM3 install bash -n "$DTPI_STAGE_DIR/02-pm3-install/00-run.sh" bash -n "$DTPI_STAGE_DIR/02-pm3-install/00-run-chroot.sh" bash -n "$DTPI_STAGE_DIR/02-pm3-install/00-run-chroot-compile.sh" # Syntax check - WiFi AP bash -n "$DTPI_STAGE_DIR/01-Wireless-AP/00-run-chroot.sh" # Syntax check - ttyd bash -n "$DTPI_STAGE_DIR/03-ttyd/00-run-chroot.sh" # Syntax check - Dangerous Pi bash -n "$DTPI_STAGE_DIR/04-dangerous-pi/00-run.sh" bash -n "$DTPI_STAGE_DIR/04-dangerous-pi/00-run-chroot.sh" bash -n "$DTPI_STAGE_DIR/04-dangerous-pi/01-run-chroot.sh" # Syntax check - OS updates if [ -f "$DTPI_STAGE_DIR/07-os-updates/00-run-chroot.sh" ]; then bash -n "$DTPI_STAGE_DIR/07-os-updates/00-run-chroot.sh" bash -n "$DTPI_STAGE_DIR/07-os-updates/01-run-chroot.sh" fi echo -e "${GREEN}All scripts pass syntax validation${NC}" # Check required files exist [ -d "$(dirname "$0")/app" ] || { echo "Error: app/ directory not found"; exit 1; } [ -f "$(dirname "$0")/requirements.txt" ] || { echo "Error: requirements.txt not found"; exit 1; } [ -d "$(dirname "$0")/systemd" ] || { echo "Error: systemd/ directory not found"; exit 1; } echo -e "${GREEN}All required files exist${NC}" ;; *) echo "Usage: $0 {full|from-dtpi|test|clean} [--keep-container]" echo "" echo "Build Commands:" echo " full - Build all stages (~15-20 min with pre-built PM3, cached base)" echo " Use for: First build or when base system needs updates" echo " Preserves cache for fast rebuilds" echo "" echo " from-dtpi - Rebuild only customizations (~5 min)" echo " Use for: Daily development, app changes" echo " Skips: stages 0-2, PM3 (uses cache)" echo " Requires: Previous 'full' build" echo "" echo "Utility Commands:" echo " test - Validate all scripts without building" echo " clean - Wipe ALL cache (next build will be from scratch)" echo "" echo "Flags:" echo " --keep-container - Keep Docker container after build (for debugging)" echo "" echo "Environment Variables:" echo " PM3_TARBALL Path to pre-built PM3 tarball (aarch64)" echo " Skips PM3 compilation (~45 min savings)" echo " Example: PM3_TARBALL=./pm3-v4.20728-aarch64-linux-cp312.tar.gz" echo "" echo " BASE_IMAGE Path to pre-baked base image tarball (stages 0-2)" echo " Skips base OS build (~30 min savings on cold build)" echo " Example: BASE_IMAGE=./base-image-trixie-arm64.tar.gz" echo "" echo " APT_PROXY URL of apt-cacher-ng proxy for faster package downloads" echo " Example: APT_PROXY=http://host.docker.internal:3142" echo "" echo "Stage Structure:" echo " stage0, stage1, stage2 -> Base Raspberry Pi OS (cached)" echo " stageDangerousPi -> PM3 + WiFi AP + ttyd + app + UPS + HTTPS + updates" exit 1 ;; esac echo -e "${GREEN}Build complete!${NC}" echo "Image location: $PI_GEN_DIR/deploy/" # Clean up container but preserve work volume for future builds if [ $KEEP_CONTAINER -eq 0 ]; then if docker ps -a --filter name=pigen_work --format "{{.Names}}" | grep -q pigen_work; then echo -e "${GREEN}Cleaning up build container (preserving cache)...${NC}" docker rm pigen_work > /dev/null 2>&1 echo -e "${GREEN}Container removed, cache preserved for fast rebuilds${NC}" fi else echo -e "${YELLOW}Container kept for debugging (--keep-container flag)${NC}" echo "Inspect with: docker exec -it pigen_work bash" echo "Remove with: docker rm -v pigen_work" fi