#!/bin/bash # # local-build.sh - Local build script for SecuBox packages # Replicates GitHub Actions workflows for local testing # # Usage: # ./local-build.sh validate # Run validation only # ./local-build.sh build # Build all packages (x86_64) # ./local-build.sh build luci-app-system-hub # Build single package # ./local-build.sh build secubox-core # Build SecuBox Core package # ./local-build.sh build netifyd # Build netifyd DPI engine # ./local-build.sh build --arch aarch64 # Build for specific architecture # ./local-build.sh full # Validate + Build # set -e # Exit on error # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' # No Color # Normalize important directories SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) REPO_ROOT=$(cd "$SCRIPT_DIR/.." && pwd) # Configuration # Available versions: 25.12.0-rc1 (default), 24.10.5 (stable LTS), 23.05.5, SNAPSHOT OPENWRT_VERSION="${OPENWRT_VERSION:-24.10.5}" SDK_DIR="${SDK_DIR:-./sdk}" BUILD_DIR="${BUILD_DIR:-./build}" CACHE_DIR="${CACHE_DIR:-./cache}" OPENWRT_DIR="${OPENWRT_DIR:-./openwrt}" # Default architecture ARCH="aarch64_cortex-a72" ARCH_NAME="aarch64_cortex-a72" SDK_PATH="mvebu/cortexa72" # Device profiles for firmware building declare -A DEVICE_PROFILES=( ["espressobin-v7"]="mvebu:cortexa53:globalscale_espressobin:ESPRESSObin V7 (1-2GB DDR4)" ["espressobin-ultra"]="mvebu:cortexa53:globalscale_espressobin-ultra:ESPRESSObin Ultra (PoE, WiFi)" # ["sheeva64"]="mvebu:cortexa53:globalscale_sheeva64:Sheeva64 (Plug computer)" # Disabled ["mochabin"]="mvebu:cortexa72:globalscale_mochabin:MOCHAbin (Quad-core A72, 10G)" ["x86-64"]="x86:64:generic:x86_64 Generic PC" ) # Packages that must be built in the OpenWrt buildroot (toolchain) instead of the SDK. # These packages compile native code and need system libraries not available in SDK. OPENWRT_ONLY_PACKAGES=( "netifyd" "crowdsec" "secubox-app-crowdsec" "secubox-app-netifyd" "secubox-app-ndpid" "secubox-app-nodogsplash" "secubox-app-mitmproxy" "mitmproxy" "nodogsplash" ) # Helper functions is_openwrt_only_pkg() { local target="$1" for pkg in "${OPENWRT_ONLY_PACKAGES[@]}"; do if [[ "$pkg" == "$target" ]]; then return 0 fi done return 1 } print_header() { echo "" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${CYAN}$1${NC}" echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" } print_success() { echo -e "${GREEN}✅ $1${NC}" } print_error() { echo -e "${RED}❌ $1${NC}" } print_warning() { echo -e "${YELLOW}⚠️ $1${NC}" } print_info() { echo -e "${BLUE}ℹ️ $1${NC}" } # Architecture mapping set_architecture() { case "$1" in x86-64|x86_64) ARCH="x86-64" ARCH_NAME="x86_64" SDK_PATH="x86/64" ;; aarch64-cortex-a53|aarch64_cortex-a53) ARCH="aarch64-cortex-a53" ARCH_NAME="aarch64_cortex-a53" SDK_PATH="mvebu/cortexa53" ;; aarch64-cortex-a72|aarch64_cortex-a72) ARCH="aarch64-cortex-a72" ARCH_NAME="aarch64_cortex-a72" SDK_PATH="mvebu/cortexa72" ;; aarch64-generic|aarch64_generic) ARCH="aarch64-generic" ARCH_NAME="aarch64_generic" SDK_PATH="armsr/armv8" ;; mips-24kc|mips_24kc) ARCH="mips-24kc" ARCH_NAME="mips_24kc" SDK_PATH="ath79/generic" ;; mipsel-24kc|mipsel_24kc) ARCH="mipsel-24kc" ARCH_NAME="mipsel_24kc" SDK_PATH="ramips/mt7621" ;; *) print_error "Unknown architecture: $1" print_info "Supported architectures: x86-64, aarch64-cortex-a53, aarch64-cortex-a72, aarch64-generic, mips-24kc, mipsel-24kc" exit 1 ;; esac print_info "Architecture: $ARCH ($ARCH_NAME) - SDK: $SDK_PATH" } # Check dependencies check_dependencies() { print_header "Checking Dependencies" local missing_deps=() # Build tools for cmd in make gcc g++ git wget curl tar xz jq ninja; do if ! command -v "$cmd" &> /dev/null; then missing_deps+=("$cmd") fi done # Validation tools for cmd in shellcheck node; do if ! command -v "$cmd" &> /dev/null; then print_warning "$cmd not found (optional, needed for validation)" fi done if [ ${#missing_deps[@]} -gt 0 ]; then print_error "Missing required dependencies: ${missing_deps[*]}" echo "" echo "Install them with:" echo " sudo apt-get install -y build-essential clang flex bison g++ gawk \\" echo " gcc-multilib g++-multilib gettext git libncurses5-dev \\" echo " libssl-dev python3-setuptools python3-dev rsync \\" echo " swig unzip zlib1g-dev file wget curl jq ninja-build" echo "" echo "For validation tools:" echo " sudo apt-get install -y shellcheck nodejs" exit 1 fi print_success "All required dependencies found" } # Validation functions (from test-validate.yml) validate_makefiles() { print_header "Validating Makefiles" local errors=0 # Validate luci-app-* packages for makefile in ../luci-app-*/Makefile; do if [[ -f "$makefile" ]]; then local pkg=$(dirname "$makefile" | xargs basename) echo " 🔍 Checking $pkg..." # Required fields local required_fields=("PKG_NAME" "PKG_VERSION" "PKG_RELEASE" "PKG_LICENSE") for field in "${required_fields[@]}"; do if ! grep -q "^${field}:=" "$makefile"; then print_error "Missing: $field in $pkg" errors=$((errors + 1)) fi done # Check for include statements if ! grep -q "include.*luci.mk\|include.*package.mk" "$makefile"; then print_error "Missing include statement in $pkg" errors=$((errors + 1)) fi fi done # Validate luci-theme-* packages for makefile in ../luci-theme-*/Makefile; do if [[ -f "$makefile" ]]; then local pkg=$(dirname "$makefile" | xargs basename) echo " 🔍 Checking $pkg..." # Required fields local required_fields=("PKG_NAME" "PKG_VERSION" "PKG_RELEASE" "PKG_LICENSE") for field in "${required_fields[@]}"; do if ! grep -q "^${field}:=" "$makefile"; then print_error "Missing: $field in $pkg" errors=$((errors + 1)) fi done # Check for include statements if ! grep -q "include.*luci.mk\|include.*package.mk" "$makefile"; then print_error "Missing include statement in $pkg" errors=$((errors + 1)) fi fi done if [[ $errors -gt 0 ]]; then print_error "Found $errors Makefile errors" return 1 fi print_success "All Makefiles valid" return 0 } validate_json() { print_header "Validating JSON Files" local errors=0 while IFS= read -r jsonfile; do echo " 🔍 Checking $(basename "$jsonfile")..." if ! jq empty "$jsonfile" 2>/dev/null; then print_error "Invalid JSON: $jsonfile" errors=$((errors + 1)) fi done < <(find .. -name "*.json" -type f ! -path "*/node_modules/*" ! -path "*/sdk/*" ! -path "*/build/*") if [[ $errors -gt 0 ]]; then print_error "Found $errors JSON errors" return 1 fi print_success "All JSON files valid" return 0 } validate_javascript() { print_header "Validating JavaScript Files" if ! command -v node &> /dev/null; then print_warning "Node.js not found, skipping JavaScript validation" return 0 fi local errors=0 while IFS= read -r jsfile; do echo " 🔍 Checking $(basename "$jsfile")..." if ! node --check "$jsfile" 2>/dev/null; then print_error "Syntax error in: $jsfile" errors=$((errors + 1)) fi done < <(find .. -name "*.js" -type f ! -path "*/node_modules/*" ! -path "*/sdk/*" ! -path "*/build/*") if [[ $errors -gt 0 ]]; then print_error "Found $errors JavaScript errors" return 1 fi print_success "All JavaScript files valid" return 0 } validate_shellscripts() { print_header "Validating Shell Scripts" if ! command -v shellcheck &> /dev/null; then print_warning "shellcheck not found, skipping shell script validation" return 0 fi local warnings=0 # Check RPCD scripts while IFS= read -r script; do echo " 🔍 Checking $(basename "$script")..." if ! shellcheck -s sh "$script" 2>/dev/null; then warnings=$((warnings + 1)) fi done < <(find .. -path "*/rpcd/*" -type f ! -path "*/sdk/*" ! -path "*/build/*" 2>/dev/null) # Check init scripts while IFS= read -r script; do echo " 🔍 Checking $(basename "$script")..." if ! shellcheck -s sh "$script" 2>/dev/null; then warnings=$((warnings + 1)) fi done < <(find .. -path "*/init.d/*" -type f ! -path "*/sdk/*" ! -path "*/build/*" 2>/dev/null) if [[ $warnings -gt 0 ]]; then print_warning "Found $warnings shellcheck warnings (non-blocking)" fi print_success "Shell script validation complete" return 0 } check_file_permissions() { print_header "Checking File Permissions" local errors=0 # RPCD scripts should be executable while IFS= read -r script; do if [[ ! -x "$script" ]]; then print_warning "Not executable: $script (fixing...)" chmod +x "$script" errors=$((errors + 1)) fi done < <(find .. -path "*/usr/libexec/rpcd/*" -type f ! -path "*/sdk/*" ! -path "*/build/*" 2>/dev/null) # Init scripts should be executable while IFS= read -r script; do if [[ ! -x "$script" ]]; then print_warning "Not executable: $script (fixing...)" chmod +x "$script" errors=$((errors + 1)) fi done < <(find .. -path "*/etc/init.d/*" -type f ! -path "*/sdk/*" ! -path "*/build/*" 2>/dev/null) if [[ $errors -gt 0 ]]; then print_warning "Fixed $errors permission issues" fi print_success "File permissions checked" return 0 } # Download and setup SDK download_sdk() { print_header "Downloading OpenWrt SDK" local base_url="https://downloads.openwrt.org/releases/${OPENWRT_VERSION}/targets/${SDK_PATH}" print_info "OpenWrt version: $OPENWRT_VERSION" print_info "Architecture: $ARCH" print_info "SDK URL: $base_url" # Check cache if [[ -d "$SDK_DIR" && -f "$SDK_DIR/.sdk_ready" ]]; then local cached_version=$(cat "$SDK_DIR/.sdk_ready") if [[ "$cached_version" == "${OPENWRT_VERSION}-${ARCH}" ]]; then print_success "Using cached SDK: ${OPENWRT_VERSION}-${ARCH}" return 0 else print_info "Cached SDK version mismatch, re-downloading..." rm -rf "$SDK_DIR" fi fi # Find SDK filename echo " 📥 Fetching SDK list..." local sdk_file sdk_file=$(curl -sL --retry 3 --retry-delay 5 "$base_url/" | grep -oP 'openwrt-sdk[^"<>]+\.tar\.(xz|zst)' | head -1) if [[ -z "$sdk_file" ]]; then print_error "Could not find SDK at $base_url" return 1 fi print_info "Downloading: $sdk_file" # Download SDK mkdir -p "$CACHE_DIR" local sdk_archive="$CACHE_DIR/$sdk_file" if [[ ! -f "$sdk_archive" ]]; then echo " Downloading SDK (this may take several minutes)..." if wget --retry-connrefused --waitretry=5 --timeout=60 --progress=dot:mega \ "${base_url}/${sdk_file}" -O "$sdk_archive" 2>&1 | grep --line-buffered '%'; then print_success "Download complete" else # Fallback for older wget versions wget --retry-connrefused --waitretry=5 --timeout=60 \ "${base_url}/${sdk_file}" -O "$sdk_archive" fi else print_info "Using cached archive: $sdk_file" fi # Extract SDK print_info "Extracting SDK..." rm -rf "$SDK_DIR" mkdir -p "$SDK_DIR" tar -xf "$sdk_archive" -C "$SDK_DIR" --strip-components=1 # Mark SDK as ready echo "${OPENWRT_VERSION}-${ARCH}" > "$SDK_DIR/.sdk_ready" print_success "SDK downloaded and extracted" return 0 } # Setup SDK feeds setup_sdk_feeds() { print_header "Setting up SDK Feeds" cd "$SDK_DIR" # Remove unwanted feeds from feeds.conf.default if [[ -f "feeds.conf.default" ]]; then sed -i '/telephony/d' feeds.conf.default sed -i '/routing/d' feeds.conf.default print_success "Removed telephony and routing from feeds.conf.default" fi # Create local feed for SecuBox packages outside of SDK local local_feed_dir="$(pwd)/../local-feed" mkdir -p "$local_feed_dir" # Determine correct branch based on OpenWrt version local branch if [[ "$OPENWRT_VERSION" == "SNAPSHOT" ]]; then branch="master" elif [[ "$OPENWRT_VERSION" =~ ^25\. ]]; then branch="openwrt-25.12" elif [[ "$OPENWRT_VERSION" =~ ^24\. ]]; then branch="openwrt-24.10" elif [[ "$OPENWRT_VERSION" =~ ^23\. ]]; then branch="openwrt-23.05" else branch="openwrt-23.05" # fallback fi print_info "Using branch: $branch for OpenWrt $OPENWRT_VERSION" # Use GitHub mirrors + local feed cat > feeds.conf << FEEDS src-git packages https://github.com/openwrt/packages.git;$branch src-git luci https://github.com/openwrt/luci.git;$branch src-link secubox $local_feed_dir FEEDS print_info "feeds.conf configured with local SecuBox feed at $local_feed_dir" # Update feeds echo "🔄 Updating feeds..." local feeds_ok=0 local required_feeds=3 for feed in packages luci secubox; do echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Updating feed: $feed" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" local feed_success=0 for attempt in 1 2 3; do echo "Attempt $attempt of 3..." if ./scripts/feeds update "$feed" 2>&1 | tee "feed-update-${feed}.log"; then if [[ -d "feeds/$feed" ]]; then print_success "$feed updated successfully" feeds_ok=$((feeds_ok + 1)) feed_success=1 break else print_warning "Feed directory not created, retrying..." fi else print_warning "Update command failed, retrying..." fi sleep $((10 * attempt)) done if [[ $feed_success -eq 0 ]]; then print_error "Failed to update $feed after 3 attempts" return 1 fi done echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "📊 Feeds Status: $feeds_ok/$required_feeds updated" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" if [[ $feeds_ok -lt $required_feeds ]]; then print_error "Not all required feeds were updated successfully" return 1 fi # Install feeds echo "" echo "📦 Installing feeds..." if ! ./scripts/feeds install -a 2>&1 | tee feed-install.log; then print_warning "Feed installation had errors, checking if critical..." fi # Note: We skip Lua header installation and manual dependency builds # Our SecuBox packages are PKGARCH:=all (scripts only) - no compilation needed # lucihttp and cgi-io dependencies will be disabled in .config echo "" echo "ℹ️ Dependencies will be handled via .config (pre-built packages preferred)" echo " Our packages are PKGARCH:=all (scripts) - no lucihttp compilation needed" # Verify feeds echo "" echo "🔍 Verifying feed installation..." for feed in packages luci secubox; do if [[ -d "feeds/$feed" ]]; then local feed_size=$(du -sh "feeds/$feed" 2>/dev/null | cut -f1) print_success "feeds/$feed exists ($feed_size)" else if [[ "$feed" == "secubox" ]]; then print_warning "feeds/$feed is empty (will be populated)" else print_error "feeds/$feed is missing!" return 1 fi fi done # Verify luci.mk if [[ ! -f "feeds/luci/luci.mk" ]]; then print_warning "luci.mk not found, creating fallback..." mkdir -p feeds/luci cat > feeds/luci/luci.mk << 'LUCI_MK' # Minimal LuCI build system fallback LUCI_PKGARCH:=all define Package/Default SECTION:=luci CATEGORY:=LuCI SUBMENU:=3. Applications PKGARCH:=all endef LUCI_MK fi # Final cleanup rm -f feeds/telephony.index feeds/routing.index 2>/dev/null || true rm -rf feeds/telephony feeds/routing 2>/dev/null || true make defconfig 2>/dev/null cd - > /dev/null print_success "SDK feeds configured" return 0 } # Copy packages to SDK feed copy_packages() { local single_package="$1" print_header "Copying Packages to SecuBox Feed" cd "$SDK_DIR" # Use the local feed directory (outside SDK) local feed_dir="../local-feed" mkdir -p "$feed_dir" local -a core_pkg_names=() if [[ -n "$single_package" ]]; then print_info "Copying single package: $single_package" # Check in root directory first (luci-app-*, luci-theme-*) if [[ -d "../../$single_package" && -f "../../${single_package}/Makefile" ]]; then echo " 📁 $single_package" cp -r "../../$single_package" "$feed_dir/" # Fix Makefile include path for LuCI packages if [[ "$single_package" =~ ^luci- ]]; then sed -i 's|include.*luci\.mk|include $(TOPDIR)/feeds/luci/luci.mk|' "$feed_dir/$single_package/Makefile" echo " ✓ Fixed Makefile include path" fi # Check in package/secubox/ directory (secubox-app-*, secubox-*) elif [[ -d "../../package/secubox/$single_package" && -f "../../package/secubox/${single_package}/Makefile" ]]; then echo " 📦 $single_package" cp -r "../../package/secubox/$single_package" "$feed_dir/" core_pkg_names+=("$single_package") # Fix Makefile include paths for feed structure if grep -q "../../lang/golang/golang-package.mk" "$feed_dir/$single_package/Makefile" 2>/dev/null; then sed -i 's|include.*../../lang/golang/golang-package.mk|include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk|' "$feed_dir/$single_package/Makefile" echo " ✓ Fixed golang package include path" fi else print_error "Package $single_package not found or missing Makefile" cd - > /dev/null return 1 fi else print_info "Copying all packages" # Copy luci-app-* packages for pkg in ../../luci-app-*/; do if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then local pkg_name=$(basename "$pkg") echo " 📁 $pkg_name" cp -r "$pkg" "$feed_dir/" # Fix Makefile include path for feed structure sed -i 's|include.*luci\.mk|include $(TOPDIR)/feeds/luci/luci.mk|' "$feed_dir/$pkg_name/Makefile" echo " ✓ Fixed Makefile include path" fi done # Copy luci-theme-* packages for pkg in ../../luci-theme-*/; do if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then local pkg_name=$(basename "$pkg") echo " 📁 $pkg_name" cp -r "$pkg" "$feed_dir/" # Fix Makefile include path for feed structure sed -i 's|include.*luci\.mk|include $(TOPDIR)/feeds/luci/luci.mk|' "$feed_dir/$pkg_name/Makefile" echo " ✓ Fixed Makefile include path" fi done # Copy luci-app-* packages from package/secubox/ (e.g., luci-app-secubox-admin) for pkg in ../../package/secubox/luci-app-*/; do if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then local pkg_name=$(basename "$pkg") echo " 📁 $pkg_name (SecuBox LuCI)" cp -r "$pkg" "$feed_dir/" # Fix Makefile include path for feed structure sed -i 's|include.*luci\.mk|include $(TOPDIR)/feeds/luci/luci.mk|' "$feed_dir/$pkg_name/Makefile" echo " ✓ Fixed Makefile include path" fi done # Copy secubox-app-* packages (backend services) for pkg in ../../package/secubox/secubox-app-*/; do if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then local pkg_name=$(basename "$pkg") echo " 📦 $pkg_name (SecuBox App)" cp -r "$pkg" "$feed_dir/" core_pkg_names+=("$pkg_name") # Fix Makefile include paths for feed structure if grep -q "../../lang/golang/golang-package.mk" "$feed_dir/$pkg_name/Makefile" 2>/dev/null; then sed -i 's|include.*../../lang/golang/golang-package.mk|include $(TOPDIR)/feeds/packages/lang/golang/golang-package.mk|' "$feed_dir/$pkg_name/Makefile" echo " ✓ Fixed golang package include path" fi fi done # Copy other core packages (non-LuCI, non-secubox-app) for pkg in ../../package/secubox/*/; do if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then local pkg_name=$(basename "$pkg") # Skip if already copied (luci-app-*, luci-theme-*, secubox-app-*) if [[ ! "$pkg_name" =~ ^luci-app- ]] && \ [[ ! "$pkg_name" =~ ^luci-theme- ]] && \ [[ ! "$pkg_name" =~ ^secubox-app- ]] && \ [[ "$pkg_name" != ".appstore" ]]; then echo " 📁 $pkg_name (Core)" cp -r "$pkg" "$feed_dir/" core_pkg_names+=("$pkg_name") fi fi done fi echo "" print_info "Packages in feed:" ls -d "$feed_dir/luci-app-"*/ 2>/dev/null || true ls -d "$feed_dir/luci-theme-"*/ 2>/dev/null || true ls -d "$feed_dir/secubox-app-"*/ 2>/dev/null || true # Update the secubox feed echo "" echo "🔄 Updating SecuBox feed index..." ./scripts/feeds update secubox # Install packages from secubox feed echo "" echo "📦 Installing packages from SecuBox feed..." if [[ -n "$single_package" ]]; then echo " Installing $single_package..." ./scripts/feeds install "$single_package" else # Install luci-app-* packages for pkg in "$feed_dir"/luci-app-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo " Installing $pkg_name..." ./scripts/feeds install "$pkg_name" 2>&1 | grep -v "WARNING:" || true fi done # Install luci-theme-* packages for pkg in "$feed_dir"/luci-theme-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo " Installing $pkg_name..." ./scripts/feeds install "$pkg_name" 2>&1 | grep -v "WARNING:" || true fi done # Install secubox-app-* packages for pkg in "$feed_dir"/secubox-app-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo " Installing $pkg_name..." ./scripts/feeds install "$pkg_name" 2>&1 | grep -v "WARNING:" || true fi done # Install secubox core packages for pkg_name in "${core_pkg_names[@]}"; do local pkg_path="$feed_dir/$pkg_name" if [[ -d "$pkg_path" ]]; then echo " Installing $pkg_name..." # For netifyd, ensure we're using SecuBox feed (not packages feed which has old version) if [[ "$pkg_name" == "netifyd" ]]; then ./scripts/feeds uninstall netifyd 2>&1 | grep -v "WARNING:" || true ./scripts/feeds install -p secubox netifyd 2>&1 | grep -v "WARNING:" || true else ./scripts/feeds install "$pkg_name" 2>&1 | grep -v "WARNING:" || true fi fi done fi cd - > /dev/null print_success "Packages copied and installed to feed" return 0 } # Configure packages configure_packages() { local single_package="$1" print_header "Configuring Packages" cd "$SDK_DIR" echo "⚙️ Enabling packages..." if [[ -n "$single_package" ]]; then # Enable only the specified package if [[ -d "feeds/secubox/$single_package" ]]; then echo "CONFIG_PACKAGE_${single_package}=m" >> .config print_success "$single_package enabled" else print_error "Package $single_package not found in feed" cd - > /dev/null return 1 fi else # Enable all SecuBox packages from feed (luci-app-*) for pkg in feeds/secubox/luci-app-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo "CONFIG_PACKAGE_${pkg_name}=m" >> .config print_success "$pkg_name enabled" fi done # Enable all SecuBox theme packages from feed (luci-theme-*) for pkg in feeds/secubox/luci-theme-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo "CONFIG_PACKAGE_${pkg_name}=m" >> .config print_success "$pkg_name enabled" fi done # Enable all SecuBox app packages from feed (secubox-app-*) for pkg in feeds/secubox/secubox-app-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo "CONFIG_PACKAGE_${pkg_name}=m" >> .config print_success "$pkg_name enabled" fi done fi # Disable problematic packages that fail to compile in SDK # Our SecuBox packages are PKGARCH:=all (scripts) so they don't need these echo "" echo "⚠️ Disabling packages that fail in SDK environment..." echo "# CONFIG_PACKAGE_lucihttp is not set" >> .config echo "# CONFIG_PACKAGE_cgi-io is not set" >> .config print_info "lucihttp and cgi-io disabled (fail to compile: missing lua.h)" # Enable use of pre-built packages from feeds echo "CONFIG_DEVEL=y" >> .config echo "CONFIG_AUTOREBUILD=y" >> .config echo "CONFIG_AUTOREMOVE=y" >> .config echo "CONFIG_FEED_packages=y" >> .config echo "CONFIG_FEED_luci=y" >> .config make defconfig 2>/dev/null cd - > /dev/null print_success "Packages configured" return 0 } # Build packages build_packages() { local single_package="$1" print_header "Building Packages" cd "$SDK_DIR" # Detect package format based on OpenWrt version local pkg_ext if [[ "$OPENWRT_VERSION" =~ ^25\. ]] || [[ "$OPENWRT_VERSION" == "SNAPSHOT" ]]; then pkg_ext="apk" print_info "Building for OpenWrt $OPENWRT_VERSION (apk format)" else pkg_ext="ipk" print_info "Building for OpenWrt $OPENWRT_VERSION (ipk format)" fi # Export for later use export PKG_EXT="$pkg_ext" local built=0 local failed=0 local built_list="" local failed_list="" # Determine which packages to build local packages_to_build=() if [[ -n "$single_package" ]]; then if [[ -d "feeds/secubox/$single_package" ]]; then packages_to_build=("$single_package") else print_error "Package $single_package not found in feed" cd - > /dev/null return 1 fi else # Build luci-app-* packages for pkg in feeds/secubox/luci-app-*/; do [[ -d "$pkg" ]] && packages_to_build+=("$(basename "$pkg")") done # Build luci-theme-* packages for pkg in feeds/secubox/luci-theme-*/; do [[ -d "$pkg" ]] && packages_to_build+=("$(basename "$pkg")") done # Build core secubox packages (secubox-app, nodogsplash, netifyd, etc.) for pkg in feeds/secubox/secubox-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") if is_openwrt_only_pkg "$pkg_name"; then print_info "Skipping $pkg_name (requires OpenWrt buildroot)" continue fi packages_to_build+=("$pkg_name") fi done for pkg in feeds/secubox/nodogsplash/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") if is_openwrt_only_pkg "$pkg_name"; then print_info "Skipping $pkg_name (requires OpenWrt buildroot)" continue fi packages_to_build+=("$pkg_name") fi done for pkg in feeds/secubox/netifyd/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") if is_openwrt_only_pkg "$pkg_name"; then print_info "Skipping $pkg_name (requires OpenWrt buildroot)" continue fi packages_to_build+=("$pkg_name") fi done fi # Build packages for pkg_name in "${packages_to_build[@]}"; do echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "📦 Building: $pkg_name" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" # Show package contents for debugging echo "📁 Package contents:" ls -la "feeds/secubox/$pkg_name" # Build with timeout (10 minutes per package) local build_log="/tmp/build-${pkg_name}.log" # Build from feed (skip dependency checks for architecture-independent packages) # These packages are just JavaScript/shell scripts - no compilation needed if timeout 600 make "package/feeds/secubox/${pkg_name}/compile" V=s -j1 NO_DEPS=1 > "$build_log" 2>&1; then # Check if package was created (.apk or .ipk) local pkg_file=$(find bin -name "${pkg_name}*.${pkg_ext}" 2>/dev/null | head -1) if [[ -n "$pkg_file" ]]; then print_success "Built: $pkg_name" echo " → $pkg_file" built=$((built + 1)) built_list="${built_list}${pkg_name}," else print_warning "No .${pkg_ext} generated for $pkg_name" echo "📋 Last 50 lines of build log:" tail -50 "$build_log" failed=$((failed + 1)) failed_list="${failed_list}${pkg_name}," fi else print_error "Build failed: $pkg_name" echo "📋 Last 100 lines of build log:" tail -100 "$build_log" failed=$((failed + 1)) failed_list="${failed_list}${pkg_name}," fi echo "" done cd - > /dev/null echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "📊 Build Summary" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_success "Built: $built packages" if [[ $failed -gt 0 ]]; then print_error "Failed: $failed packages" fi echo "" echo "Built: $built_list" if [[ -n "$failed_list" ]]; then echo "Failed: $failed_list" fi return 0 } # Collect artifacts collect_artifacts() { print_header "Collecting Artifacts" mkdir -p "$BUILD_DIR/$ARCH" # Use package extension from build step local pkg_ext="${PKG_EXT:-ipk}" print_info "Package format: .${pkg_ext}" # Find and copy package files (.apk or .ipk) find "$SDK_DIR/bin" -name "luci-app-*.${pkg_ext}" -exec cp {} "$BUILD_DIR/$ARCH/" \; 2>/dev/null || true find "$SDK_DIR/bin" -name "luci-theme-*.${pkg_ext}" -exec cp {} "$BUILD_DIR/$ARCH/" \; 2>/dev/null || true # Also collect any SecuBox related packages find "$SDK_DIR/bin" -name "*secubox*.${pkg_ext}" -exec cp {} "$BUILD_DIR/$ARCH/" \; 2>/dev/null || true find "$SDK_DIR/bin" -name "netifyd*.${pkg_ext}" -exec cp {} "$BUILD_DIR/$ARCH/" \; 2>/dev/null || true # Count local pkg_count=$(find "$BUILD_DIR/$ARCH" -name "*.${pkg_ext}" 2>/dev/null | wc -l) echo "" print_info "Built packages for $ARCH:" ls -la "$BUILD_DIR/$ARCH/" 2>/dev/null || echo "No packages" # Create checksums if [[ $pkg_count -gt 0 ]]; then cd "$BUILD_DIR/$ARCH" sha256sum ./*.${pkg_ext} > SHA256SUMS cd - > /dev/null fi echo "" print_success "Total: $pkg_count packages" return 0 } # Run validation run_validation() { print_header "Running Validation" local failed=0 validate_makefiles || failed=$((failed + 1)) validate_json || failed=$((failed + 1)) validate_javascript || failed=$((failed + 1)) validate_shellscripts || failed=$((failed + 1)) check_file_permissions || failed=$((failed + 1)) if [[ $failed -gt 0 ]]; then print_error "Validation failed with $failed error(s)" return 1 fi print_success "All validations passed!" return 0 } # Run build using OpenWrt buildroot (for packages that need system libraries like netifyd) run_build_openwrt() { local single_package="$1" print_header "Building $single_package with OpenWrt Buildroot" print_info "This package requires system libraries not available in SDK" echo "" check_dependencies download_openwrt_source || return 1 setup_openwrt_feeds || return 1 copy_secubox_to_openwrt || return 1 cd "$OPENWRT_DIR" # Map shorthand names to actual directory names in package/secubox/ declare -A DIR_NAME_MAP=( ["nodogsplash"]="secubox-app-nodogsplash" ["ndpid"]="secubox-app-ndpid" ["netifyd"]="secubox-app-netifyd" ["crowdsec"]="secubox-app-crowdsec" ["mitmproxy"]="secubox-app-mitmproxy" ) # Map directory names to actual package names (PKG_NAME in Makefile) # Only needed when directory name differs from PKG_NAME declare -A PKG_NAME_MAP=( ["secubox-app-ndpid"]="ndpid" ["secubox-app-netifyd"]="secubox-netifyd" ["secubox-app-crowdsec"]="secubox-crowdsec" ["secubox-app-nodogsplash"]="secubox-app-nodogsplash" ["secubox-app-mitmproxy"]="secubox-app-mitmproxy" ) # Resolve directory name (handle shorthand like "nodogsplash" -> "secubox-app-nodogsplash") local dir_name="${DIR_NAME_MAP[$single_package]:-$single_package}" # Get actual package name (for config and finding .ipk) local pkg_name="${PKG_NAME_MAP[$dir_name]:-$dir_name}" print_info "Input: $single_package -> Directory: $dir_name -> Package: $pkg_name" # Update feeds print_header "Installing Package from Feeds" ./scripts/feeds update -a # For netifyd, remove old version from packages feed first if [[ "$single_package" == "netifyd" ]]; then ./scripts/feeds uninstall netifyd 2>/dev/null || true fi # For Go packages (crowdsec, etc.), install golang build infrastructure first if [[ "$dir_name" =~ ^(crowdsec|secubox-app-crowdsec)$ ]] || \ grep -q "golang-package.mk" "../package/secubox/$dir_name/Makefile" 2>/dev/null; then print_info "Installing Go language support for $dir_name..." ./scripts/feeds install -a golang fi ./scripts/feeds install -p secubox "$dir_name" # Configure build for target architecture (mochabin = mvebu/cortexa72) print_header "Configuring Build" # Set target configuration based on ARCH case "$ARCH" in aarch64_cortex-a72|aarch64-cortex-a72) echo "CONFIG_TARGET_mvebu=y" >> .config echo "CONFIG_TARGET_mvebu_cortexa72=y" >> .config ;; aarch64_cortex-a53|aarch64-cortex-a53) echo "CONFIG_TARGET_mvebu=y" >> .config echo "CONFIG_TARGET_mvebu_cortexa53=y" >> .config ;; x86-64|x86_64) echo "CONFIG_TARGET_x86=y" >> .config echo "CONFIG_TARGET_x86_64=y" >> .config ;; esac # Enable the package (use actual package name, not directory name) echo "CONFIG_PACKAGE_${pkg_name}=m" >> .config make defconfig # Build dependencies first (for packages like netifyd that need system libraries) print_header "Building Dependencies" print_info "Downloading and building required system libraries..." make package/libs/toolchain/compile V=s 2>&1 | grep -v "^make\[" || true # Build package print_header "Building Package: $dir_name ($pkg_name)" print_info "This may take several minutes on first build..." echo "" # Build from SecuBox feed (package/secubox/...) if make package/secubox/"$dir_name"/compile V=s; then print_success "Package built successfully" # Find and display built package (search by actual package name) local pkg_file=$(find bin/packages bin/targets -name "${pkg_name}*.ipk" -o -name "${pkg_name}_*.ipk" 2>/dev/null | head -1) if [[ -z "$pkg_file" ]]; then # Try alternative search patterns pkg_file=$(find bin -name "*${pkg_name}*.ipk" 2>/dev/null | head -1) fi if [[ -n "$pkg_file" ]]; then echo "" echo "📦 Built package:" ls -lh "$pkg_file" echo "" # Copy to build directory mkdir -p "$BUILD_DIR/$ARCH" cp "$pkg_file" "$BUILD_DIR/$ARCH/" print_success "Package copied to: $BUILD_DIR/$ARCH/" else print_warning "Package file not found in bin/, checking build directory..." # The package might be in targets instead of packages pkg_file=$(find bin/targets -name "${pkg_name}*.ipk" 2>/dev/null | head -1) if [[ -n "$pkg_file" ]]; then echo "📦 Built package:" ls -lh "$pkg_file" mkdir -p "$BUILD_DIR/$ARCH" cp "$pkg_file" "$BUILD_DIR/$ARCH/" print_success "Package copied to: $BUILD_DIR/$ARCH/" fi fi else print_error "Package build failed" return 1 fi cd - > /dev/null print_info "Syncing OpenWrt packages into firmware tree..." ARCH_NAME="$ARCH_NAME" "$REPO_ROOT/secubox-tools/sync-openwrt-packages.sh" || print_warning "Package sync script failed" return 0 } # Run build run_build() { local single_package="$1" # Packages that are OpenWrt buildroot only if [[ -n "$single_package" ]] && is_openwrt_only_pkg "$single_package"; then run_build_openwrt "$single_package" return $? fi check_dependencies download_sdk || return 1 setup_sdk_feeds || return 1 copy_packages "$single_package" || return 1 configure_packages "$single_package" || return 1 build_packages "$single_package" || return 1 collect_artifacts || return 1 print_header "Build Complete!" print_success "Packages available in: $BUILD_DIR/$ARCH/" return 0 } # ============================================ # Firmware Image Building Functions # ============================================ # Parse device profile parse_device_profile() { local device="$1" if [[ -z "${DEVICE_PROFILES[$device]}" ]]; then print_error "Unknown device: $device" print_info "Available devices: ${!DEVICE_PROFILES[*]}" return 1 fi local profile="${DEVICE_PROFILES[$device]}" IFS=':' read -r TARGET SUBTARGET PROFILE_NAME DESCRIPTION <<< "$profile" export FW_TARGET="$TARGET" export FW_SUBTARGET="$SUBTARGET" export FW_PROFILE="$PROFILE_NAME" export FW_DESCRIPTION="$DESCRIPTION" export FW_DEVICE="$device" return 0 } # Download OpenWrt source download_openwrt_source() { print_header "Downloading OpenWrt Source" if [[ -d "$OPENWRT_DIR/.git" ]]; then print_info "OpenWrt source already exists, checking version..." cd "$OPENWRT_DIR" local current_version=$(git describe --tags 2>/dev/null || echo "unknown") if [[ "$current_version" == "v${OPENWRT_VERSION}" ]]; then print_success "Using existing OpenWrt $OPENWRT_VERSION" cd - > /dev/null return 0 else print_info "Version mismatch (current: $current_version), re-cloning..." cd - > /dev/null rm -rf "$OPENWRT_DIR" fi fi print_info "Cloning OpenWrt $OPENWRT_VERSION..." if [[ "$OPENWRT_VERSION" == "SNAPSHOT" ]]; then git clone --depth 1 https://github.com/openwrt/openwrt.git "$OPENWRT_DIR" else git clone --depth 1 --branch "v${OPENWRT_VERSION}" \ https://github.com/openwrt/openwrt.git "$OPENWRT_DIR" fi print_success "OpenWrt source downloaded" return 0 } # Setup OpenWrt feeds for firmware build setup_openwrt_feeds() { print_header "Setting up OpenWrt Feeds" cd "$OPENWRT_DIR" # Remove unwanted feeds if [[ -f "feeds.conf.default" ]]; then sed -i '/telephony/d' feeds.conf.default sed -i '/routing/d' feeds.conf.default print_success "Removed telephony and routing from feeds.conf.default" fi # Update feeds print_info "Updating feeds (this may take a few minutes)..." if ! ./scripts/feeds update -a 2>&1 | tee feed-update.log; then print_warning "Feed update had errors, continuing..." fi # Install feeds print_info "Installing feeds..." if ! ./scripts/feeds install -a 2>&1 | tee feed-install.log; then print_warning "Feed install had warnings, checking directories..." fi # Note: Skipping Lua header installation # Our packages are PKGARCH:=all (scripts only) - no compilation needed # Verify feeds for feed in packages luci; do if [[ -d "feeds/$feed" ]]; then local feed_size=$(du -sh "feeds/$feed" 2>/dev/null | cut -f1) print_success "feeds/$feed ($feed_size)" else print_error "feeds/$feed missing!" cd - > /dev/null return 1 fi done cd - > /dev/null print_success "OpenWrt feeds configured" return 0 } # Copy SecuBox packages to OpenWrt copy_secubox_to_openwrt() { print_header "Copying SecuBox Packages to OpenWrt" cd "$OPENWRT_DIR" mkdir -p package/secubox local pkg_count=0 # Copy luci-app-* packages for pkg in ../../luci-app-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo " ✅ $pkg_name" cp -r "$pkg" package/secubox/ # Fix Makefile include path if [[ -f "package/secubox/$pkg_name/Makefile" ]]; then sed -i 's|include.*luci\.mk|include $(TOPDIR)/feeds/luci/luci.mk|' \ "package/secubox/$pkg_name/Makefile" fi pkg_count=$((pkg_count + 1)) fi done # Copy luci-theme-* packages for pkg in ../../luci-theme-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo " ✅ $pkg_name" cp -r "$pkg" package/secubox/ # Fix Makefile include path if [[ -f "package/secubox/$pkg_name/Makefile" ]]; then sed -i 's|include.*luci\.mk|include $(TOPDIR)/feeds/luci/luci.mk|' \ "package/secubox/$pkg_name/Makefile" fi pkg_count=$((pkg_count + 1)) fi done # Copy secubox-app-* helper packages for pkg in ../../package/secubox/secubox-app-*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo " ✅ $pkg_name" cp -r "$pkg" package/secubox/ pkg_count=$((pkg_count + 1)) fi done # Copy additional core packages (non-LuCI / non-app store) for pkg in ../../package/secubox/*/; do if [[ -d "$pkg" ]]; then local pkg_name=$(basename "$pkg") echo " ✅ $pkg_name" cp -r "$pkg" package/secubox/ pkg_count=$((pkg_count + 1)) fi done rm -f package/secubox/luci-app-secubox-netifyd/root/etc/config/netifyd >/dev/null 2>&1 || true cd - > /dev/null print_success "Copied $pkg_count SecuBox packages" return 0 } # Generate firmware configuration generate_firmware_config() { print_header "Generating Firmware Configuration" cd "$OPENWRT_DIR" print_info "Device: $FW_DESCRIPTION" print_info "Target: $FW_TARGET/$FW_SUBTARGET" print_info "Profile: $FW_PROFILE" # Base configuration cat > .config << EOF # Target CONFIG_TARGET_${FW_TARGET}=y CONFIG_TARGET_${FW_TARGET}_${FW_SUBTARGET}=y CONFIG_TARGET_${FW_TARGET}_${FW_SUBTARGET}_DEVICE_${FW_PROFILE}=y # Image building (REQUIRED for firmware generation) CONFIG_TARGET_MULTI_PROFILE=n CONFIG_TARGET_ALL_PROFILES=n CONFIG_TARGET_PER_DEVICE_ROOTFS=y # Image settings CONFIG_TARGET_ROOTFS_SQUASHFS=y CONFIG_TARGET_ROOTFS_EXT4FS=y CONFIG_TARGET_KERNEL_PARTSIZE=32 CONFIG_TARGET_ROOTFS_PARTSIZE=512 # Disable GDB in toolchain (fixes build issues) # CONFIG_GDB is not set CONFIG_BUILD_LOG=y # Package conflict resolution # CONFIG_PACKAGE_lucihttp is not set (fails in SDK) # CONFIG_PACKAGE_cgi-io is not set (fails in SDK) CONFIG_AUTOREMOVE=y # Base packages CONFIG_PACKAGE_luci=y CONFIG_PACKAGE_luci-ssl=y CONFIG_PACKAGE_luci-app-opkg=y CONFIG_PACKAGE_luci-theme-openwrt-2020=y CONFIG_PACKAGE_luci-theme-secubox=y # DNS Server (fix conflict: use dnsmasq-full only) # CONFIG_PACKAGE_dnsmasq is not set CONFIG_PACKAGE_dnsmasq-full=y # Networking essentials CONFIG_PACKAGE_curl=y CONFIG_PACKAGE_wget-ssl=y CONFIG_PACKAGE_iptables=y CONFIG_PACKAGE_ip6tables=y # USB support CONFIG_PACKAGE_kmod-usb-core=y CONFIG_PACKAGE_kmod-usb3=y CONFIG_PACKAGE_kmod-usb-storage=y # Filesystem CONFIG_PACKAGE_kmod-fs-ext4=y CONFIG_PACKAGE_kmod-fs-vfat=y # SecuBox packages - Core CONFIG_PACKAGE_secubox-app=y CONFIG_PACKAGE_luci-app-secubox=y CONFIG_PACKAGE_luci-app-system-hub=y CONFIG_PACKAGE_luci-app-secubox-admin=y # SecuBox packages - Security & Monitoring # CONFIG_PACKAGE_luci-app-crowdsec-dashboard is not set (requires crowdsec backend - compile fails) CONFIG_PACKAGE_luci-app-netdata-dashboard=y CONFIG_PACKAGE_crowdsec=y CONFIG_PACKAGE_secubox-app-crowdsec=y # SecuBox packages - Network Intelligence CONFIG_PACKAGE_netifyd=y CONFIG_PACKAGE_luci-app-secubox-netifyd=y CONFIG_PACKAGE_luci-app-network-modes=y # SecuBox packages - VPN & Access Control CONFIG_PACKAGE_luci-app-wireguard-dashboard=y CONFIG_PACKAGE_luci-app-client-guardian=y # CONFIG_PACKAGE_luci-app-auth-guardian is not set (not stable yet) # SecuBox packages - Bandwidth & Traffic CONFIG_PACKAGE_luci-app-bandwidth-manager=y CONFIG_PACKAGE_luci-app-media-flow=y # SecuBox packages - Performance & Services CONFIG_PACKAGE_luci-app-cdn-cache=y CONFIG_PACKAGE_luci-app-vhost-manager=y # SecuBox packages - Disabled (require compilation/not ready) # CONFIG_PACKAGE_luci-app-ksm-manager is not set (not stable) # CONFIG_PACKAGE_luci-app-traffic-shaper is not set (not stable) # WireGuard CONFIG_PACKAGE_wireguard-tools=y CONFIG_PACKAGE_kmod-wireguard=y CONFIG_PACKAGE_qrencode=y EOF # Device-specific packages case "$FW_DEVICE" in mochabin) cat >> .config << EOF # MOCHAbin specific - 10G networking CONFIG_PACKAGE_kmod-sfp=y CONFIG_PACKAGE_kmod-phy-marvell-10g=y EOF ;; espressobin-ultra) cat >> .config << EOF # WiFi support CONFIG_PACKAGE_kmod-mt76=y CONFIG_PACKAGE_kmod-mac80211=y EOF ;; esac # Run defconfig make defconfig 2>/dev/null cd - > /dev/null print_success "Configuration generated" return 0 } # Verify firmware configuration verify_firmware_config() { print_header "Verifying Firmware Configuration" cd "$OPENWRT_DIR" # Check device profile if grep -q "CONFIG_TARGET_${FW_TARGET}_${FW_SUBTARGET}_DEVICE_${FW_PROFILE}=y" .config; then print_success "Device profile correctly configured" else print_error "Device profile not found in .config!" print_info "Searching for available profiles..." find "target/$FW_TARGET/$FW_SUBTARGET" -name "*.mk" -exec grep -l "DEVICE_NAME" {} \; 2>/dev/null | head -5 cd - > /dev/null return 1 fi # Check image generation if grep -q "CONFIG_TARGET_ROOTFS_SQUASHFS=y" .config; then print_success "SQUASHFS image generation enabled" fi if grep -q "CONFIG_TARGET_ROOTFS_EXT4FS=y" .config; then print_success "EXT4 image generation enabled" fi # Show relevant config echo "" print_info "Device configuration:" grep "^CONFIG_TARGET_" .config | head -10 cd - > /dev/null print_success "Configuration verified" return 0 } # Build firmware image build_firmware_image() { print_header "Building Firmware Image" cd "$OPENWRT_DIR" print_info "Device: $FW_DESCRIPTION" print_info "Target: $FW_TARGET/$FW_SUBTARGET" print_info "Profile: $FW_PROFILE" print_info "CPU Cores: $(nproc)" echo "" local start_time=$(date +%s) # Download packages first print_info "Downloading packages..." if ! make download -j$(nproc) V=s; then print_warning "Parallel download failed, retrying single-threaded..." make download -j1 V=s fi echo "" print_header "Compiling Firmware (This may take 1-2 hours)" echo "" # Create necessary directories to avoid opkg lock file errors # Find all root directories and ensure tmp subdirectories exist find build_dir -type d -name "root.orig-*" -exec mkdir -p {}/tmp \; 2>/dev/null || true find build_dir -type d -name "root-*" -exec mkdir -p {}/tmp \; 2>/dev/null || true # Build with explicit PROFILE if make -j$(nproc) PROFILE="$FW_PROFILE" V=s 2>&1 | tee build.log; then local end_time=$(date +%s) local duration=$((end_time - start_time)) local minutes=$((duration / 60)) local seconds=$((duration % 60)) print_success "Build completed in ${minutes}m ${seconds}s" else print_error "Parallel build failed, retrying single-threaded..." # Ensure staging directories exist before retry find build_dir -type d -name "root.orig-*" -exec mkdir -p {}/tmp \; 2>/dev/null || true find build_dir -type d -name "root-*" -exec mkdir -p {}/tmp \; 2>/dev/null || true if make -j1 PROFILE="$FW_PROFILE" V=s 2>&1 | tee build-retry.log; then local end_time=$(date +%s) local duration=$((end_time - start_time)) local minutes=$((duration / 60)) local seconds=$((duration % 60)) print_success "Build completed in ${minutes}m ${seconds}s (after retry)" else print_error "Build failed!" echo "" echo "Last 50 lines of build log:" tail -50 build-retry.log cd - > /dev/null return 1 fi fi cd - > /dev/null return 0 } # Collect firmware artifacts collect_firmware_artifacts() { print_header "Collecting Firmware Artifacts" local target_dir="$OPENWRT_DIR/bin/targets/$FW_TARGET/$FW_SUBTARGET" local output_dir="$BUILD_DIR/firmware/$FW_DEVICE" mkdir -p "$output_dir" # Find and copy firmware images local img_count=0 if [[ -d "$target_dir" ]]; then echo "🔍 Target directory: $target_dir" echo "" echo "📂 All files in target directory:" ls -lh "$target_dir" 2>/dev/null | grep -v "^total" || echo " (empty)" echo "" echo "📦 Copying firmware images..." while IFS= read -r file; do case "$(basename "$file")" in *.ipk|*.manifest|*.json|sha256sums|*.buildinfo|packages) continue ;; *) cp "$file" "$output_dir/" print_success "$(basename "$file") ($(du -h "$file" | cut -f1))" img_count=$((img_count + 1)) ;; esac done < <(find "$target_dir" -maxdepth 1 -type f 2>/dev/null) else print_error "Target directory not found: $target_dir" fi if [[ $img_count -eq 0 ]]; then echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print_warning "No firmware images found!" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Diagnostic information echo "🔍 Diagnostic Information:" echo "" if [[ -d "$OPENWRT_DIR/bin/targets" ]]; then echo "📂 Available targets:" find "$OPENWRT_DIR/bin/targets" -type d -mindepth 2 -maxdepth 2 2>/dev/null | sed 's|.*/bin/targets/||' || echo " (none)" echo "" fi if [[ -f "$OPENWRT_DIR/build.log" ]]; then echo "📋 Checking build log for errors..." if grep -i "error\|failed\|cannot" "$OPENWRT_DIR/build.log" | tail -10 | grep -v "warning" > /tmp/fw-errors.txt 2>/dev/null && [[ -s /tmp/fw-errors.txt ]]; then echo "Recent errors found:" cat /tmp/fw-errors.txt rm -f /tmp/fw-errors.txt else echo " No obvious errors in build log" fi echo "" fi if [[ -d "$target_dir" ]]; then local all_files=$(find "$target_dir" -type f 2>/dev/null | wc -l) echo "🎯 Target directory analysis:" echo " Total files: $all_files" if [[ $all_files -gt 0 ]]; then echo " File types:" find "$target_dir" -type f 2>/dev/null -exec basename {} \; | sed 's/.*\./ ./' | sort -u fi fi echo "" print_warning "This usually means:" echo " 1. Device profile was not properly selected" echo " 2. Build completed but only packages were built, not images" echo " 3. Device profile name doesn't match OpenWrt $OPENWRT_VERSION" echo "" print_info "To debug:" echo " 1. Check: $OPENWRT_DIR/.config for CONFIG_TARGET settings" echo " 2. Review: $OPENWRT_DIR/build.log for errors" echo " 3. Verify profile exists: find $OPENWRT_DIR/target/$FW_TARGET/$FW_SUBTARGET -name '*.mk'" echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" fi # Copy packages mkdir -p "$output_dir/packages" find "$OPENWRT_DIR/bin/packages" -name "luci-app-*.ipk" -exec cp {} "$output_dir/packages/" \; 2>/dev/null || true find "$OPENWRT_DIR/bin/packages" -name "*secubox*.ipk" -exec cp {} "$output_dir/packages/" \; 2>/dev/null || true find "$OPENWRT_DIR/bin/packages" -name "netifyd*.ipk" -exec cp {} "$output_dir/packages/" \; 2>/dev/null || true local pkg_count=$(find "$output_dir/packages" -name "*.ipk" 2>/dev/null | wc -l) # Generate checksums cd "$output_dir" sha256sum *.* > SHA256SUMS 2>/dev/null || true if [[ -d packages && -n "$(ls -A packages 2>/dev/null)" ]]; then (cd packages && sha256sum *.ipk > SHA256SUMS 2>/dev/null || true) fi cd - > /dev/null # Create build info cat > "$output_dir/BUILD_INFO.txt" << EOF SecuBox Firmware Build ====================== Device: $FW_DESCRIPTION Profile: $FW_PROFILE Target: $FW_TARGET/$FW_SUBTARGET OpenWrt: $OPENWRT_VERSION Built: $(date -u +%Y-%m-%dT%H:%M:%SZ) Firmware Images: $img_count SecuBox Packages: $pkg_count EOF echo "" print_success "Firmware images: $img_count" print_success "SecuBox packages: $pkg_count" print_success "Artifacts saved to: $output_dir" echo "" print_info "Contents:" ls -lh "$output_dir" return 0 } # Debug firmware configuration debug_firmware_build() { local device="$1" if [[ -z "$device" ]]; then print_error "Device not specified" print_info "Usage: $0 debug-firmware " print_info "Available devices: ${!DEVICE_PROFILES[*]}" return 1 fi # Parse device profile parse_device_profile "$device" || return 1 print_header "Firmware Build Debug Information" echo "Device Configuration:" echo " Device: $FW_DEVICE" echo " Description: $FW_DESCRIPTION" echo " Target: $FW_TARGET" echo " Subtarget: $FW_SUBTARGET" echo " Profile: $FW_PROFILE" echo "" if [[ -d "$OPENWRT_DIR" ]]; then print_info "OpenWrt source exists at: $OPENWRT_DIR" if [[ -f "$OPENWRT_DIR/.config" ]]; then echo "" echo "Current .config settings:" grep "^CONFIG_TARGET_" "$OPENWRT_DIR/.config" | head -20 echo "" echo "Checking device profile..." if grep -q "CONFIG_TARGET_${FW_TARGET}_${FW_SUBTARGET}_DEVICE_${FW_PROFILE}=y" "$OPENWRT_DIR/.config"; then print_success "Device profile is configured" else print_error "Device profile NOT configured!" fi echo "" echo "Available device profiles for $FW_TARGET/$FW_SUBTARGET:" find "$OPENWRT_DIR/target/$FW_TARGET/$FW_SUBTARGET" -name "*.mk" 2>/dev/null | \ xargs grep -l "DEVICE_NAME" 2>/dev/null | head -10 else print_warning "No .config file found - run build-firmware first" fi echo "" if [[ -d "$OPENWRT_DIR/bin/targets/$FW_TARGET/$FW_SUBTARGET" ]]; then echo "Build output directory exists:" echo " Path: $OPENWRT_DIR/bin/targets/$FW_TARGET/$FW_SUBTARGET" echo " Files:" ls -lh "$OPENWRT_DIR/bin/targets/$FW_TARGET/$FW_SUBTARGET" 2>/dev/null | grep -v "^total" | head -20 else print_warning "Build output directory doesn't exist yet" fi else print_warning "OpenWrt source not downloaded yet" print_info "Run: $0 build-firmware $device" fi return 0 } # Run firmware build run_firmware_build() { local device="$1" if [[ -z "$device" ]]; then print_error "Device not specified" print_info "Usage: $0 build-firmware " print_info "Available devices: ${!DEVICE_PROFILES[*]}" return 1 fi # Parse device profile parse_device_profile "$device" || return 1 # Check dependencies check_dependencies # Build firmware download_openwrt_source || return 1 setup_openwrt_feeds || return 1 copy_secubox_to_openwrt || return 1 generate_firmware_config || return 1 verify_firmware_config || return 1 build_firmware_image || return 1 collect_firmware_artifacts || return 1 print_header "Firmware Build Complete!" print_success "Device: $FW_DESCRIPTION" print_success "Location: $BUILD_DIR/firmware/$FW_DEVICE/" return 0 } # Show usage show_usage() { cat << EOF SecuBox Local Build Tool Replicates GitHub Actions workflows for local development USAGE: $0 [options] COMMANDS: validate Run validation only (lint, syntax checks) build Build all packages for x86_64 build Build single package build --arch Build for specific architecture build-firmware Build full firmware image for device debug-firmware Debug firmware build (check config without building) full Run validation then build clean Clean build directories clean-all Clean all build directories including OpenWrt source help Show this help message PACKAGES: SDK packages (scripts only, fast build): luci-app-* LuCI application packages luci-theme-* LuCI theme packages Toolchain packages (native code, requires full OpenWrt build): ndpid nDPId DPI engine (shorthand for secubox-app-ndpid) netifyd Netifyd DPI engine (shorthand for secubox-app-netifyd) nodogsplash Captive portal (shorthand for secubox-app-nodogsplash) crowdsec CrowdSec IPS (shorthand for secubox-app-crowdsec) mitmproxy mitmproxy HTTPS proxy (shorthand for secubox-app-mitmproxy) secubox-app-* Full directory names also accepted ARCHITECTURES (for package building): aarch64-cortex-a72 ARM Cortex-A72 (MOCHAbin, RPi4) (default) aarch64-cortex-a53 ARM Cortex-A53 (ESPRESSObin) x86-64 PC, VMs aarch64-generic Generic ARM64 mips-24kc MIPS 24Kc (TP-Link) mipsel-24kc MIPS LE (Xiaomi, GL.iNet) DEVICES (for firmware building): espressobin-v7 ESPRESSObin V7 (1-2GB DDR4) espressobin-ultra ESPRESSObin Ultra (PoE, WiFi) mochabin MOCHAbin (Quad-core A72, 10G) x86-64 x86_64 Generic PC EXAMPLES: # Validate all packages $0 validate # Build all SDK packages for default architecture (mochabin) $0 build # Build single LuCI package (SDK - fast) $0 build luci-app-system-hub # Build nDPId DPI engine (toolchain - native code) $0 build ndpid # Build Netifyd DPI engine (toolchain) $0 build netifyd # Build Nodogsplash captive portal (toolchain) $0 build nodogsplash # Build CrowdSec IPS (toolchain - Go) $0 build crowdsec # Build mitmproxy HTTPS proxy (toolchain - binary download) $0 build mitmproxy # Build using full directory name $0 build secubox-app-ndpid # Build netifyd LuCI app (SDK - scripts only) $0 build luci-app-secubox-netifyd # Build for specific architecture $0 build ndpid --arch x86-64 # Build firmware image for MOCHAbin $0 build-firmware mochabin # Build firmware image for ESPRESSObin V7 $0 build-firmware espressobin-v7 # Debug firmware build configuration $0 debug-firmware mochabin # Full validation and build $0 full # Clean build artifacts $0 clean # Clean everything including OpenWrt source $0 clean-all ENVIRONMENT VARIABLES: OPENWRT_VERSION OpenWrt version (default: 24.10.5) SDK_DIR SDK directory (default: ./sdk) BUILD_DIR Build output directory (default: ./build) CACHE_DIR Download cache directory (default: ./cache) OPENWRT_DIR OpenWrt source directory for firmware builds (default: ./openwrt) EOF } # Main script main() { # Change to script directory cd "$(dirname "$0")" local command="${1:-help}" shift || true case "$command" in validate) run_validation ;; build) local single_package="" local arch_specified=false while [[ $# -gt 0 ]]; do case "$1" in --arch) set_architecture "$2" arch_specified=true shift 2 ;; luci-app-*|luci-theme-*|secubox-app-*|secubox-*|netifyd|ndpid|nodogsplash|crowdsec) single_package="$1" shift ;; *) print_error "Unknown option: $1" show_usage exit 1 ;; esac done run_build "$single_package" ;; build-firmware) local device="$1" if [[ -z "$device" ]]; then print_error "Device not specified" print_info "Usage: $0 build-firmware " print_info "Available devices: ${!DEVICE_PROFILES[*]}" exit 1 fi run_firmware_build "$device" ;; debug-firmware) local device="$1" if [[ -z "$device" ]]; then print_error "Device not specified" print_info "Usage: $0 debug-firmware " print_info "Available devices: ${!DEVICE_PROFILES[*]}" exit 1 fi debug_firmware_build "$device" ;; full) run_validation && run_build ;; clean) print_header "Cleaning Build Directories" rm -rf "$SDK_DIR" "$BUILD_DIR" print_success "Build directories cleaned" ;; clean-all) print_header "Cleaning All Build Directories" rm -rf "$SDK_DIR" "$BUILD_DIR" "$OPENWRT_DIR" "$CACHE_DIR" print_success "All build directories cleaned (SDK, build, OpenWrt source, cache)" ;; help|--help|-h) show_usage ;; *) print_error "Unknown command: $command" echo "" show_usage exit 1 ;; esac } # Run main main "$@"