secubox-openwrt/secubox-tools/local-build.sh
CyberMind-FR b7b51a6749 fix(bonus): Fix secubox-app-bonus build and opkg feed installation
- Fix recursive inclusion bug where secubox-app-bonus was including itself
  causing 1GB package size (now 7.5MB with 73 packages)
- Fix Packages index generation to strip Source/SourceName/SourceDateEpoch/URL
  fields that caused opkg parsing issues
- Add rebuild_bonus_package() to local-build.sh for proper feed embedding
- Update secubox-feed install command to handle local dependencies from files
  (workaround for opkg signature bug with file:// URLs)
- Clean up libc dependency stripping in Packages generation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 19:46:27 +01:00

2889 lines
98 KiB
Bash
Executable File
Raw 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=(
# C/C++ native binaries
"netifyd" # C++ native binary (Netify DPI)
"secubox-app-netifyd" # C++ native binary wrapper
"ndpid" # C++ native binary (nDPI)
"secubox-app-ndpid" # C++ native binary wrapper
"nodogsplash" # C native binary (captive portal)
"secubox-app-nodogsplash" # C native binary wrapper (needs microhttpd)
# Go binaries
"crowdsec" # Go binary
"secubox-app-crowdsec" # Go binary wrapper
"crowdsec-firewall-bouncer" # Go binary
"secubox-app-cs-firewall-bouncer" # Go binary wrapper
# Python/special packages
"secubox-app-metablogizer" # Python dependencies
"luci-app-tor" # Requires tor daemon compilation
)
# 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"
}
# Deploy packages to router
deploy_packages() {
local router="$1"
local packages="$2"
local ssh_opts="-o StrictHostKeyChecking=no -o ConnectTimeout=10"
# Test connectivity
print_info "Testing connection to router..."
if ! ssh $ssh_opts root@$router "echo 'Connected'" 2>/dev/null; then
print_error "Cannot connect to router at $router"
return 1
fi
# Find packages to deploy
local pkg_dir="$SDK_DIR/bin/packages/$ARCH_NAME/secubox"
local target_pkg_dir="$SDK_DIR/bin/targets/$SDK_PATH/packages"
if [[ -n "$packages" ]]; then
# Deploy specific packages
print_info "Deploying specific packages: $packages"
for pkg in $packages; do
local ipk=$(find "$pkg_dir" "$target_pkg_dir" -name "${pkg}*.ipk" 2>/dev/null | head -1)
if [[ -n "$ipk" ]]; then
print_info "Deploying $(basename "$ipk")..."
scp $ssh_opts "$ipk" root@$router:/tmp/
ssh $ssh_opts root@$router "opkg install /tmp/$(basename "$ipk") --force-reinstall 2>&1"
else
print_warning "Package not found: $pkg"
fi
done
else
# Deploy all recently built packages
print_info "Deploying all packages from SDK..."
# Find all IPK files built today
local today=$(date +%Y%m%d)
local ipks=$(find "$pkg_dir" -name "*.ipk" -mtime 0 2>/dev/null)
if [[ -z "$ipks" ]]; then
print_warning "No recently built packages found"
print_info "Run 'local-build.sh build <package>' first"
return 1
fi
# Copy packages
print_info "Copying packages to router..."
for ipk in $ipks; do
scp $ssh_opts "$ipk" root@$router:/tmp/
done
# Install packages
print_info "Installing packages..."
ssh $ssh_opts root@$router "opkg install /tmp/*.ipk --force-reinstall 2>&1" || true
fi
# Sync feed to router
print_info "Syncing package feed to router..."
local feed_pkg="$SDK_DIR/bin/packages/$ARCH_NAME/secubox"
if [[ -d "$feed_pkg" ]]; then
ssh $ssh_opts root@$router "mkdir -p /www/secubox-feed"
scp $ssh_opts "$feed_pkg"/*.ipk root@$router:/www/secubox-feed/ 2>/dev/null || true
# Generate Packages index
ssh $ssh_opts root@$router "cd /www/secubox-feed && \
rm -f Packages Packages.gz && \
for ipk in *.ipk; do \
[ -f \"\$ipk\" ] && tar -xzf \"\$ipk\" ./control.tar.gz && \
tar -xzf control.tar.gz ./control && \
cat control >> Packages && echo '' >> Packages && \
rm -f control control.tar.gz; \
done && \
gzip -k Packages 2>/dev/null || true"
print_success "Feed synced to /www/secubox-feed"
fi
print_success "Deployment complete"
}
# 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
# Clean old versions, keep only latest
clean_old_ipk_versions "$BUILD_DIR/$ARCH" "$pkg_ext"
# Count after cleanup
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
}
# Clean old IPK versions, keep only the latest for each package
clean_old_ipk_versions() {
local feed_dir="$1"
local pkg_ext="${2:-ipk}"
print_info "Cleaning old package versions..."
# Get list of all packages
local packages=()
for pkg in "$feed_dir"/*."$pkg_ext"; do
[[ -f "$pkg" ]] || continue
local basename=$(basename "$pkg")
# Extract package name (everything before first underscore)
local name=$(echo "$basename" | sed 's/_[0-9].*$//')
packages+=("$name")
done
# Get unique package names
local unique_packages=($(printf '%s\n' "${packages[@]}" | sort -u))
local removed=0
for name in "${unique_packages[@]}"; do
# Find all versions of this package, sorted by modification time (newest first)
local versions=($(ls -t "$feed_dir/${name}_"*."$pkg_ext" 2>/dev/null))
# Keep only the latest (first in the list), remove the rest
if [[ ${#versions[@]} -gt 1 ]]; then
for ((i=1; i<${#versions[@]}; i++)); do
echo " Removing old: $(basename "${versions[$i]}")"
rm -f "${versions[$i]}"
removed=$((removed + 1))
done
fi
done
if [[ $removed -gt 0 ]]; then
print_success "Removed $removed old package versions"
else
print_info "No old versions to remove"
fi
}
# Note: We intentionally do NOT generate Packages.sig for local feeds
# opkg will skip signature verification if no .sig file exists
# This avoids "Failed to decode signature" errors for local/offline feeds
generate_packages_sig() {
local feed_dir="$1"
# Remove any existing .sig file to ensure opkg skips signature verification
rm -f "$feed_dir/Packages.sig" 2>/dev/null || true
print_info "Local feed configured without signature (opkg will skip verification)"
}
# Strip libc dependency from IPK control file and repack
# This is needed because opkg reads the control file from inside the IPK
strip_libc_from_ipk() {
local ipk_file="$1"
# Get absolute path
ipk_file="$(cd "$(dirname "$ipk_file")" && pwd)/$(basename "$ipk_file")"
local tmp_dir=$(mktemp -d)
local orig_dir=$(pwd)
cd "$tmp_dir" || return 1
# Try tar format first (newer OpenWrt), then ar format (older)
if ! tar -xzf "$ipk_file" 2>/dev/null; then
if ! ar -x "$ipk_file" 2>/dev/null; then
cd "$orig_dir"
rm -rf "$tmp_dir"
return 1
fi
fi
# Extract and modify control file
local control_archive=""
if [[ -f control.tar.gz ]]; then
control_archive="control.tar.gz"
mkdir -p control_dir
tar -xzf control.tar.gz -C control_dir
elif [[ -f control.tar.zst ]]; then
control_archive="control.tar.zst"
mkdir -p control_dir
zstd -d control.tar.zst -o control.tar 2>/dev/null
tar -xf control.tar -C control_dir
rm -f control.tar
else
cd "$orig_dir"
rm -rf "$tmp_dir"
return 1
fi
# Strip libc from control file and remove blank lines
if [[ -f control_dir/control ]]; then
sed -i \
-e 's/^Depends: libc$//' \
-e 's/^Depends: libc, /Depends: /g' \
-e 's/, libc$//g' \
-e 's/, libc,/,/g' \
control_dir/control
# Remove any blank lines (critical for opkg to parse correctly)
sed -i '/^$/d' control_dir/control
fi
# Repack control archive
rm -f control.tar.gz control.tar.zst
tar -czf control.tar.gz -C control_dir .
rm -rf control_dir
# Repack IPK (tar.gz format) - order matters: debian-binary, control, data
rm -f "$ipk_file"
tar -czf "$ipk_file" debian-binary control.tar.gz data.tar.*
cd "$orig_dir"
rm -rf "$tmp_dir"
return 0
}
# Embed built packages into secubox-app-bonus as local feed
embed_local_feed() {
print_header "Embedding Local Package Feed"
local feed_dir="$SCRIPT_DIR/../package/secubox/secubox-app-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 EXCEPT secubox-app-bonus itself (avoid recursive inclusion)
print_info "Copying packages to local feed..."
for pkg in "$src_dir"/*.${pkg_ext}; do
[[ -f "$pkg" ]] || continue
local basename=$(basename "$pkg")
# Skip secubox-app-bonus to avoid recursive inclusion (package including itself)
if [[ "$basename" =~ ^secubox-app-bonus_ ]]; then
print_info "Skipping $basename (avoid recursive inclusion)"
continue
fi
cp "$pkg" "$feed_dir/"
done
# Clean old versions, keep only latest
clean_old_ipk_versions "$feed_dir" "$pkg_ext"
# Strip libc from all IPK control files
print_info "Stripping libc from IPK control files..."
local stripped=0
for pkg in "$feed_dir"/*.${pkg_ext}; do
[[ -f "$pkg" ]] || continue
if strip_libc_from_ipk "$pkg"; then
stripped=$((stripped + 1))
fi
done
print_success "Processed $stripped packages"
local pkg_count=$(ls -1 "$feed_dir"/*.${pkg_ext} 2>/dev/null | wc -l)
print_info "Final package count: $pkg_count"
# Generate Packages index for opkg
print_info "Generating Packages index..."
rm -f "$feed_dir/Packages" "$feed_dir/Packages.gz"
for pkg in "$feed_dir"/*.${pkg_ext}; do
[[ -f "$pkg" ]] || continue
local pkg_basename=$(basename "$pkg")
# Extract control file from package (IPK is tar.gz containing control.tar.gz)
local control=""
if [[ "$pkg_ext" == "ipk" ]]; then
control=$(tar -xOzf "$pkg" control.tar.gz 2>/dev/null | tar -xOzf - ./control 2>/dev/null || true)
fi
if [[ -n "$control" ]]; then
# Strip Source/SourceName/SourceDateEpoch/URL fields (cause opkg parsing issues)
echo "$control" | grep -v "^Source:\|^SourceName:\|^SourceDateEpoch:\|^URL:" >> "$feed_dir/Packages"
echo "Filename: $pkg_basename" >> "$feed_dir/Packages"
echo "Size: $(stat -c%s "$pkg")" >> "$feed_dir/Packages"
echo "" >> "$feed_dir/Packages"
fi
done
# Create compressed index
gzip -kf "$feed_dir/Packages" 2>/dev/null || true
print_info "Generated Packages with $(grep -c '^Package:' "$feed_dir/Packages" 2>/dev/null || echo 0) packages"
# Strip libc dependency from all packages
# The SDK adds libc to all packages, but for local feeds without libc
# this causes opkg to fail with "incompatible architectures" error
print_info "Stripping libc dependencies from packages..."
sed -i \
-e 's/^Depends: libc$/Depends:/g' \
-e 's/^Depends: libc, /Depends: /g' \
-e 's/, libc$//g' \
-e 's/, libc,/,/g' \
-e 's/^Depends:$/Depends:/g' \
"$feed_dir/Packages"
# Clean up any empty or malformed Depends lines
sed -i \
-e 's/^Depends: ,/Depends: /g' \
-e 's/, ,/, /g' \
-e 's/,$//' \
"$feed_dir/Packages"
# Regenerate compressed index after modification
gzip -kf "$feed_dir/Packages" 2>/dev/null || true
# Do NOT create Packages.sig - opkg will skip signature verification if absent
# Creating an empty or invalid sig causes opkg to discard the package list
rm -f "$feed_dir/Packages.sig" 2>/dev/null || true
print_info "Local feed configured without signature (opkg will skip verification)"
# 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 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"
["metablogizer"]="secubox-app-metablogizer"
["tor"]="secubox-app-tor"
["luci-app-tor"]="luci-app-tor"
["cs-firewall-bouncer"]="secubox-app-cs-firewall-bouncer"
)
# 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"
["secubox-app-metablogizer"]="secubox-app-metablogizer"
["secubox-app-tor"]="secubox-app-tor"
["secubox-app-cs-firewall-bouncer"]="secubox-app-cs-firewall-bouncer"
["luci-app-tor"]="luci-app-tor"
)
# 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
}
# Rebuild secubox-app-bonus with populated local feed
rebuild_bonus_package() {
print_header "Rebuilding secubox-app-bonus with Local Feed"
local pkg_ext="${PKG_EXT:-ipk}"
local bonus_pkg="secubox-app-bonus"
# Sync the updated secubox-app-bonus to local-feed
print_info "Syncing secubox-app-bonus to local-feed..."
local src_dir="$SCRIPT_DIR/../package/secubox/$bonus_pkg"
local feed_dir="$SDK_DIR/../local-feed/$bonus_pkg"
if [[ ! -d "$src_dir" ]]; then
print_error "Source directory not found: $src_dir"
return 1
fi
rsync -av --delete "$src_dir/" "$feed_dir/"
print_success "Synced to local-feed"
# Update the feed and install the package
cd "$SDK_DIR"
print_info "Updating feeds..."
./scripts/feeds update secubox
./scripts/feeds install "$bonus_pkg" 2>&1 | grep -v "WARNING:" || true
# Enable and rebuild the package
echo "CONFIG_PACKAGE_${bonus_pkg}=m" >> .config
make defconfig FORCE=1 2>/dev/null
print_info "Building $bonus_pkg..."
local build_log="/tmp/build-${bonus_pkg}.log"
if timeout 600 make "package/feeds/secubox/${bonus_pkg}/compile" V=s -j1 NO_DEPS=1 FORCE=1 > "$build_log" 2>&1; then
local pkg_file=$(find bin -name "${bonus_pkg}*.${pkg_ext}" 2>/dev/null | head -1)
if [[ -n "$pkg_file" ]]; then
print_success "Built: $bonus_pkg"
echo "$pkg_file"
# Copy to build directory (but NOT into the feed - avoid recursive inclusion)
mkdir -p "$BUILD_DIR/$ARCH"
cp "$pkg_file" "$BUILD_DIR/$ARCH/"
# NOTE: We do NOT copy secubox-app-bonus into its own feed directory
# This would cause infinite size growth (package including itself)
# Regenerate Packages index (without secubox-app-bonus)
print_info "Regenerating Packages index..."
(
cd "$feed_src_dir"
rm -f Packages Packages.gz
for pkg in *.${pkg_ext}; do
[[ -f "$pkg" ]] || continue
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 ""
fi
done > Packages
# Strip libc dependencies
sed -i \
-e 's/^Depends: libc$/Depends:/g' \
-e 's/^Depends: libc, /Depends: /g' \
-e 's/, libc$//g' \
-e 's/, libc,/,/g' \
Packages
gzip -kf Packages 2>/dev/null || true
rm -f Packages.sig 2>/dev/null || true
)
print_success "Packages index regenerated"
else
print_warning "No .${pkg_ext} generated for $bonus_pkg"
tail -50 "$build_log"
return 1
fi
else
print_error "Build failed: $bonus_pkg"
tail -100 "$build_log"
return 1
fi
cd - > /dev/null
print_success "secubox-app-bonus rebuilt with local feed"
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
rebuild_bonus_package || return 1
print_header "Build Complete!"
print_success "Packages available in: $BUILD_DIR/$ARCH/"
print_info "Local feed embedded in secubox-app-bonus with Packages index"
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
sync-feed Clean old IPKs and regenerate feed (Packages, Packages.sig, apps-local.json)
deploy [router] [packages] Deploy packages to router (default: 192.168.255.1)
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|metablogizer|tor|cs-firewall-bouncer)
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"
;;
sync-feed|regenerate-feed)
print_header "Regenerating Local Feed"
local feed_dir="$SCRIPT_DIR/../package/secubox/secubox-app-bonus/root/www/secubox-feed"
local pkg_ext="ipk"
if [[ ! -d "$feed_dir" ]]; then
print_error "Feed directory not found: $feed_dir"
exit 1
fi
# Clean old versions
clean_old_ipk_versions "$feed_dir" "$pkg_ext"
# Regenerate Packages index
print_info "Regenerating Packages index..."
(
cd "$feed_dir"
rm -f Packages Packages.gz Packages.sig
for pkg in *.${pkg_ext}; do
[[ -f "$pkg" ]] || continue
local control=""
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)
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
gzip -k Packages 2>/dev/null || true
)
# Strip libc dependency from all packages
print_info "Stripping libc dependencies..."
sed -i \
-e 's/^Depends: libc$/Depends:/g' \
-e 's/^Depends: libc, /Depends: /g' \
-e 's/, libc$//g' \
-e 's/, libc,/,/g' \
"$feed_dir/Packages"
# Clean up any empty or malformed Depends lines
sed -i \
-e 's/^Depends: ,/Depends: /g' \
-e 's/, ,/, /g' \
-e 's/,$//' \
"$feed_dir/Packages"
gzip -kf "$feed_dir/Packages" 2>/dev/null || true
# Generate Packages.sig
generate_packages_sig "$feed_dir"
# Regenerate apps-local.json
PKG_EXT="$pkg_ext" generate_local_apps_json "$feed_dir"
local pkg_count=$(ls -1 "$feed_dir"/*.${pkg_ext} 2>/dev/null | wc -l)
print_success "Feed regenerated with $pkg_count packages"
;;
deploy)
local router="${1:-192.168.255.1}"
local packages="$2"
print_header "Deploying Packages to Router ($router)"
deploy_packages "$router" "$packages"
;;
help|--help|-h)
show_usage
;;
*)
print_error "Unknown command: $command"
echo ""
show_usage
exit 1
;;
esac
}
# Run main
main "$@"