secubox-openwrt/secubox-tools/local-build.sh
2026-01-23 05:44:38 +01:00

2447 lines
81 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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

#!/bin/bash
#
# 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.
# NOTE: secubox-app-* wrappers are PKGARCH:=all (shell scripts) and CAN be built in SDK.
OPENWRT_ONLY_PACKAGES=(
"netifyd" # C++ native binary
"crowdsec" # Go binary
"crowdsec-firewall-bouncer" # Go binary
"nodogsplash" # C native binary
)
# 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 FORCE=1 2>/dev/null
cd - > /dev/null
print_success "SDK feeds configured"
return 0
}
# Synchronize packages from package/secubox to local-feed
# This ensures local-feed always matches the canonical source
sync_packages_to_local_feed() {
local feed_dir="./local-feed"
local pkg_src="$REPO_ROOT/package/secubox"
print_info "Syncing from $pkg_src to $feed_dir"
# Create local-feed if it doesn't exist
mkdir -p "$feed_dir"
# Sync packages from package/secubox/ to local-feed
if [[ -d "$pkg_src" ]]; then
print_info "Syncing secubox core packages..."
for pkg in "$pkg_src"/*/; do
if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then
local pkg_name=$(basename "$pkg")
echo " 📦 $pkg_name"
rm -rf "$feed_dir/$pkg_name"
cp -r "$pkg" "$feed_dir/"
# 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
fi
# Sync luci-app-* packages from repo root
print_info "Syncing LuCI app packages..."
for pkg in "$REPO_ROOT"/luci-app-*/; do
if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then
local pkg_name=$(basename "$pkg")
echo " 📁 $pkg_name"
rm -rf "$feed_dir/$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"
fi
done
# Sync luci-theme-* packages from repo root
print_info "Syncing LuCI theme packages..."
for pkg in "$REPO_ROOT"/luci-theme-*/; do
if [[ -d "$pkg" && -f "${pkg}Makefile" ]]; then
local pkg_name=$(basename "$pkg")
echo " 🎨 $pkg_name"
rm -rf "$feed_dir/$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"
fi
done
# Count packages
local pkg_count=$(ls -d "$feed_dir"/*/ 2>/dev/null | wc -l)
print_success "Synchronized $pkg_count packages to local-feed"
}
# 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 FORCE=1 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 FORCE=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
}
# Embed built packages into luci-app-secubox-bonus as local feed
embed_local_feed() {
print_header "Embedding Local Package Feed"
local feed_dir="$SCRIPT_DIR/../package/secubox/luci-app-secubox-bonus/root/www/secubox-feed"
local pkg_ext="${PKG_EXT:-ipk}"
local src_dir="$BUILD_DIR/$ARCH"
# Clean and create feed directory
rm -rf "$feed_dir"
mkdir -p "$feed_dir"
# Check if we have packages to embed
if [[ ! -d "$src_dir" ]] || [[ -z "$(ls -A "$src_dir"/*.${pkg_ext} 2>/dev/null)" ]]; then
print_warning "No packages found to embed in local feed"
return 0
fi
# Copy all built packages
print_info "Copying packages to local feed..."
cp "$src_dir"/*.${pkg_ext} "$feed_dir/" 2>/dev/null || true
local pkg_count=$(ls -1 "$feed_dir"/*.${pkg_ext} 2>/dev/null | wc -l)
print_info "Copied $pkg_count packages"
# Generate Packages index for opkg
print_info "Generating Packages index..."
(
cd "$feed_dir"
for pkg in *.${pkg_ext}; do
[[ -f "$pkg" ]] || continue
# Extract control file from package
local control=""
if [[ "$pkg_ext" == "ipk" ]]; then
control=$(tar -xzOf "$pkg" ./control.tar.gz 2>/dev/null | tar -xzOf - ./control 2>/dev/null || \
ar -p "$pkg" control.tar.gz 2>/dev/null | tar -xzOf - ./control 2>/dev/null || \
ar -p "$pkg" control.tar.zst 2>/dev/null | zstd -d 2>/dev/null | tar -xOf - ./control 2>/dev/null || true)
fi
if [[ -n "$control" ]]; then
echo "$control"
echo "Filename: $pkg"
echo "Size: $(stat -c%s "$pkg")"
echo "SHA256sum: $(sha256sum "$pkg" | cut -d' ' -f1)"
echo ""
fi
done > Packages
# Create compressed index
gzip -k Packages 2>/dev/null || true
)
# Generate apps-local.json for appstore UI
print_info "Generating local apps manifest..."
generate_local_apps_json "$feed_dir"
print_success "Local feed embedded with $pkg_count packages"
echo " Feed location: /www/secubox-feed/"
echo " opkg config: src/gz secubox file:///www/secubox-feed"
return 0
}
# Generate apps-local.json from built packages
generate_local_apps_json() {
local feed_dir="$1"
local json_file="$feed_dir/apps-local.json"
local pkg_ext="${PKG_EXT:-ipk}"
cat > "$json_file" << 'HEADER'
{
"feed_url": "/secubox-feed",
"generated": "TIMESTAMP",
"packages": [
HEADER
sed -i "s/TIMESTAMP/$(date -Iseconds)/" "$json_file"
local first=true
for pkg in "$feed_dir"/*.${pkg_ext}; do
[[ -f "$pkg" ]] || continue
local filename=$(basename "$pkg")
local name=$(echo "$filename" | sed 's/_[0-9].*$//')
local version=$(echo "$filename" | sed 's/^[^_]*_//; s/_[^_]*$//')
local size=$(stat -c%s "$pkg")
# Determine category and description based on package name
local category="utility"
local description=""
local icon=""
local luci_app=""
case "$name" in
luci-app-crowdsec*)
category="security"; icon="shield"; description="CrowdSec security monitoring";;
luci-app-mitmproxy*)
category="security"; icon="lock"; description="HTTPS proxy and traffic inspection";;
luci-app-bandwidth*)
category="network"; icon="activity"; description="Bandwidth monitoring and control";;
luci-app-traffic*)
category="network"; icon="filter"; description="Traffic shaping and QoS";;
luci-app-client*)
category="network"; icon="users"; description="Client management and monitoring";;
luci-app-network*)
category="network"; icon="wifi"; description="Network configuration";;
luci-app-wireguard*)
category="vpn"; icon="shield"; description="WireGuard VPN dashboard";;
luci-app-vhost*)
category="network"; icon="server"; description="Virtual host management";;
luci-app-secubox*)
category="system"; icon="box"; description="SecuBox system component";;
luci-app-zigbee*)
category="iot"; icon="radio"; description="Zigbee device management";;
luci-app-mqtt*)
category="iot"; icon="message-square"; description="MQTT bridge";;
luci-app-ndpid*)
category="security"; icon="eye"; description="Deep packet inspection";;
luci-app-netdata*)
category="monitoring"; icon="bar-chart-2"; description="System monitoring dashboard";;
luci-app-system*)
category="system"; icon="settings"; description="System management";;
luci-app-cdn*)
category="network"; icon="globe"; description="CDN caching";;
luci-app-ksm*)
category="system"; icon="cpu"; description="Kernel memory management";;
luci-app-media*)
category="media"; icon="film"; description="Media streaming";;
luci-app-magicmirror*)
category="iot"; icon="monitor"; description="Smart mirror display";;
luci-app-auth*)
category="security"; icon="key"; description="Authentication management";;
luci-theme-*)
category="theme"; icon="palette"; description="LuCI theme";;
secubox-app-*)
category="secubox"; icon="package"; description="SecuBox backend service";;
secubox-core*)
category="system"; icon="box"; description="SecuBox core components";;
*)
category="utility"; icon="package"; description="SecuBox package";;
esac
# Check if this package has a corresponding luci-app
if [[ "$name" =~ ^secubox-app- ]]; then
local app_name="${name#secubox-app-}"
luci_app="luci-app-${app_name}"
fi
if [[ "$first" == "true" ]]; then
first=false
else
echo "," >> "$json_file"
fi
cat >> "$json_file" << ENTRY
{
"name": "$name",
"version": "$version",
"filename": "$filename",
"size": $size,
"category": "$category",
"icon": "$icon",
"description": "$description",
"installed": false,
"luci_app": $([ -n "$luci_app" ] && echo "\"$luci_app\"" || echo "null")
}
ENTRY
done
cat >> "$json_file" << 'FOOTER'
]
}
FOOTER
print_success "Generated apps-local.json"
}
# 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 FORCE=1
# 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
embed_local_feed || return 1
print_header "Build Complete!"
print_success "Packages available in: $BUILD_DIR/$ARCH/"
print_info "Local feed embedded in luci-app-secubox-bonus"
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"
# Fix: Create rsync symlink in staging_dir for OpenWrt build environment
# OpenWrt uses a restricted PATH and doesn't see system rsync
if command -v rsync &>/dev/null; then
mkdir -p staging_dir/host/bin
if [[ ! -L staging_dir/host/bin/rsync ]]; then
ln -sf "$(command -v rsync)" staging_dir/host/bin/rsync
print_success "Created rsync symlink in staging_dir/host/bin/"
fi
else
print_warning "rsync not found on system - some builds may fail"
fi
# 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=16384
# 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
# Container/LXC support
CONFIG_PACKAGE_kmod-veth=y
CONFIG_PACKAGE_kmod-br-netfilter=y
CONFIG_PACKAGE_kmod-nf-conntrack-netlink=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 FORCE=1 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 <device>"
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 <device>"
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
# Fix: Ensure rsync is available in OpenWrt staging directory
# OpenWrt build uses restricted PATH and may not see system rsync
if command -v rsync &>/dev/null; then
mkdir -p "$OPENWRT_DIR/staging_dir/host/bin"
if [[ ! -e "$OPENWRT_DIR/staging_dir/host/bin/rsync" ]]; then
ln -sf "$(command -v rsync)" "$OPENWRT_DIR/staging_dir/host/bin/rsync"
print_success "Created rsync symlink in staging_dir"
fi
fi
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
}
# Build native packages using full OpenWrt toolchain (without firmware)
run_toolchain_build() {
local device="${1:-espressobin-v7}"
local packages=("${@:2}")
print_header "Building Native Packages (Toolchain)"
# Parse device profile for architecture
parse_device_profile "$device" || return 1
# Check dependencies
check_dependencies
# Setup OpenWrt environment
download_openwrt_source || return 1
# Fix: Ensure rsync is available in OpenWrt staging directory
if command -v rsync &>/dev/null; then
mkdir -p "$OPENWRT_DIR/staging_dir/host/bin"
if [[ ! -e "$OPENWRT_DIR/staging_dir/host/bin/rsync" ]]; then
ln -sf "$(command -v rsync)" "$OPENWRT_DIR/staging_dir/host/bin/rsync"
print_success "Created rsync symlink in staging_dir"
fi
fi
setup_openwrt_feeds || return 1
copy_secubox_to_openwrt || return 1
# Generate minimal config for package building
print_header "Generating Package Build Configuration"
cd "$OPENWRT_DIR"
# Start with minimal config
cat > .config << EOF
# Target configuration for $FW_TARGET/$FW_SUBTARGET
CONFIG_TARGET_${FW_TARGET}=y
CONFIG_TARGET_${FW_TARGET}_${FW_SUBTARGET}=y
CONFIG_TARGET_MULTI_PROFILE=y
CONFIG_TARGET_ALL_PROFILES=y
# Build packages only (no firmware)
CONFIG_ALL_NONSHARED=n
CONFIG_ALL_KMODS=n
CONFIG_ALL=n
# Enable SecuBox native packages
CONFIG_PACKAGE_secubox-app-cs-firewall-bouncer=m
CONFIG_PACKAGE_secubox-app-crowdsec=m
CONFIG_PACKAGE_secubox-app-ndpid=m
CONFIG_PACKAGE_secubox-app-netifyd=m
CONFIG_PACKAGE_secubox-app-nodogsplash=m
# Required dependencies
CONFIG_PACKAGE_nftables=y
CONFIG_PACKAGE_golang=y
# Disable GDB to speed up build
CONFIG_GDB=n
EOF
# Expand config
make defconfig
# Download sources
print_info "Downloading package sources..."
make download -j$(nproc) V=s 2>&1 | grep -v "^make\[" || true
# Build prerequisite targets first
# This builds tools, toolchain, and target preparation in one step
print_header "Building Prerequisites (Tools + Toolchain)"
print_info "This may take 1-2 hours on first run..."
# Build tools, toolchain, and target preparation (needed for packages)
if ! make tools/install toolchain/install target/compile V=s -j$(nproc) 2>&1 | tee build-prereqs.log; then
print_warning "Prerequisites build had issues, continuing..."
fi
print_success "Prerequisites built"
# Build Go compiler for host (needed for Go packages)
print_header "Building Go Compiler (for Go packages)"
if ! make package/feeds/packages/lang/golang/host/compile V=s -j$(nproc) 2>&1 | tee build-golang.log; then
print_warning "Go compiler build had issues, some packages may fail"
fi
print_success "Go compiler ready"
# Build specific packages or all native SecuBox packages
print_header "Compiling Native Packages"
local native_packages=(
"secubox-app-cs-firewall-bouncer"
"secubox-app-crowdsec"
"secubox-app-ndpid"
"secubox-app-netifyd"
"secubox-app-nodogsplash"
)
if [[ ${#packages[@]} -gt 0 ]]; then
native_packages=("${packages[@]}")
fi
local built=()
local failed=()
for pkg in "${native_packages[@]}"; do
print_info "Building: $pkg"
if make package/$pkg/compile V=s -j$(nproc) 2>&1 | tee -a build-$pkg.log; then
built+=("$pkg")
print_success "Built: $pkg"
else
failed+=("$pkg")
print_error "Failed: $pkg"
fi
done
# Collect built packages
print_header "Collecting Built Packages"
local output_dir="$BUILD_DIR/toolchain-$ARCH"
mkdir -p "$output_dir"
# Find and copy IPK packages
find "$OPENWRT_DIR/bin/packages" -name "*.ipk" -exec cp {} "$output_dir/" \; 2>/dev/null || true
find "$OPENWRT_DIR/bin/targets" -name "*.ipk" -exec cp {} "$output_dir/" \; 2>/dev/null || true
local pkg_count=$(find "$output_dir" -name "*.ipk" 2>/dev/null | wc -l)
cd - > /dev/null
# Summary
print_header "Toolchain Build Summary"
print_success "Built: ${#built[@]} packages: ${built[*]}"
if [[ ${#failed[@]} -gt 0 ]]; then
print_error "Failed: ${#failed[@]} packages: ${failed[*]}"
fi
print_success "Total IPK packages: $pkg_count"
print_success "Location: $output_dir/"
return 0
}
# Show usage
show_usage() {
cat << EOF
SecuBox Local Build Tool
Replicates GitHub Actions workflows for local development
USAGE:
$0 <command> [options]
COMMANDS:
validate Run validation only (lint, syntax checks)
build Build all packages for x86_64
build <package> Build single package
build --arch <arch> Build for specific architecture
build-toolchain <device> Build native packages (Go/C++) using full toolchain
build-firmware <device> Build full firmware image for device
debug-firmware <device> 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 and local-feed
sync Sync packages from package/secubox to local-feed
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|mitmproxy)
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 <device>"
print_info "Available devices: ${!DEVICE_PROFILES[*]}"
exit 1
fi
run_firmware_build "$device"
;;
build-toolchain)
local device="${1:-espressobin-v7}"
shift || true
run_toolchain_build "$device" "$@"
;;
debug-firmware)
local device="$1"
if [[ -z "$device" ]]; then
print_error "Device not specified"
print_info "Usage: $0 debug-firmware <device>"
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" "./local-feed"
print_success "All build directories cleaned (SDK, build, OpenWrt source, cache, local-feed)"
;;
sync)
print_header "Synchronizing packages to local-feed"
sync_packages_to_local_feed
print_success "Packages synchronized to local-feed"
;;
help|--help|-h)
show_usage
;;
*)
print_error "Unknown command: $command"
echo ""
show_usage
exit 1
;;
esac
}
# Run main
main "$@"