- Add local-build.sh: comprehensive local build system
- Validates packages (Makefiles, JSON, JavaScript, shell scripts)
- Downloads and caches OpenWrt SDK
- Builds .ipk packages locally
- Supports multiple architectures (x86-64, ARM, MIPS)
- Collects artifacts with SHA256 checksums
- Update CLAUDE.md with local build documentation
- Add Local Build section with usage examples
- Update Development Workflow to include local testing
- List supported architectures and environment variables
- Update secubox-tools/README.md
- Add comprehensive local-build.sh documentation
- Update workflow examples to include local building
- Add dependencies and installation instructions
Benefits:
- Test builds locally before CI/CD
- Faster development iteration
- Reduced GitHub Actions usage
- Offline development support
Usage:
./secubox-tools/local-build.sh validate
./secubox-tools/local-build.sh build
./secubox-tools/local-build.sh build luci-app-<name>
./secubox-tools/local-build.sh build --arch <arch>
./secubox-tools/local-build.sh full
🤖 Generated with Claude Code
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
794 lines
23 KiB
Bash
Executable File
794 lines
23 KiB
Bash
Executable File
#!/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 --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
|
||
|
||
# Configuration
|
||
OPENWRT_VERSION="${OPENWRT_VERSION:-23.05.5}"
|
||
SDK_DIR="${SDK_DIR:-./sdk}"
|
||
BUILD_DIR="${BUILD_DIR:-./build}"
|
||
CACHE_DIR="${CACHE_DIR:-./cache}"
|
||
|
||
# Default architecture
|
||
ARCH="x86-64"
|
||
ARCH_NAME="x86_64"
|
||
SDK_PATH="x86/64"
|
||
|
||
# Helper functions
|
||
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; 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"
|
||
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
|
||
|
||
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
|
||
|
||
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
|
||
|
||
# Use GitHub mirrors
|
||
cat > feeds.conf << 'FEEDS'
|
||
src-git packages https://github.com/openwrt/packages.git;openwrt-23.05
|
||
src-git luci https://github.com/openwrt/luci.git;openwrt-23.05
|
||
FEEDS
|
||
|
||
print_info "feeds.conf configured"
|
||
|
||
# Update feeds
|
||
echo "🔄 Updating feeds..."
|
||
local feeds_ok=0
|
||
local required_feeds=2
|
||
|
||
for feed in packages luci; 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
|
||
|
||
# Verify feeds
|
||
echo ""
|
||
echo "🔍 Verifying feed installation..."
|
||
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 exists ($feed_size)"
|
||
else
|
||
print_error "feeds/$feed is missing!"
|
||
return 1
|
||
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
|
||
|
||
cd - > /dev/null
|
||
|
||
print_success "SDK feeds configured"
|
||
return 0
|
||
}
|
||
|
||
# Copy packages to SDK
|
||
copy_packages() {
|
||
local single_package="$1"
|
||
|
||
print_header "Copying Packages to SDK"
|
||
|
||
if [[ -n "$single_package" ]]; then
|
||
print_info "Copying single package: $single_package"
|
||
|
||
if [[ -d "../$single_package" && -f "../${single_package}/Makefile" ]]; then
|
||
echo " 📁 $single_package"
|
||
cp -r "../$single_package" "$SDK_DIR/package/"
|
||
else
|
||
print_error "Package $single_package not found or missing Makefile"
|
||
return 1
|
||
fi
|
||
else
|
||
print_info "Copying all 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" "$SDK_DIR/package/"
|
||
fi
|
||
done
|
||
fi
|
||
|
||
echo ""
|
||
print_info "Packages in SDK:"
|
||
ls -d "$SDK_DIR/package/luci-app-"*/ 2>/dev/null || echo "None"
|
||
|
||
print_success "Packages copied"
|
||
return 0
|
||
}
|
||
|
||
# Configure packages
|
||
configure_packages() {
|
||
print_header "Configuring Packages"
|
||
|
||
cd "$SDK_DIR"
|
||
|
||
echo "⚙️ Enabling packages..."
|
||
|
||
for pkg in package/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
|
||
|
||
make defconfig
|
||
|
||
cd - > /dev/null
|
||
|
||
print_success "Packages configured"
|
||
return 0
|
||
}
|
||
|
||
# Build packages
|
||
build_packages() {
|
||
print_header "Building Packages"
|
||
|
||
cd "$SDK_DIR"
|
||
|
||
local built=0
|
||
local failed=0
|
||
local built_list=""
|
||
local failed_list=""
|
||
|
||
for pkg in package/luci-app-*/; do
|
||
[[ -d "$pkg" ]] || continue
|
||
|
||
local pkg_name=$(basename "$pkg")
|
||
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "📦 Building: $pkg_name"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
|
||
# Show package contents for debugging
|
||
echo "📁 Package contents:"
|
||
ls -la "$pkg"
|
||
|
||
# Build with timeout (10 minutes per package)
|
||
local build_log="/tmp/build-${pkg_name}.log"
|
||
|
||
if timeout 600 make "package/${pkg_name}/compile" V=s -j"$(nproc)" > "$build_log" 2>&1; then
|
||
# Check if .ipk was created
|
||
local ipk_file=$(find bin -name "${pkg_name}*.ipk" 2>/dev/null | head -1)
|
||
|
||
if [[ -n "$ipk_file" ]]; then
|
||
print_success "Built: $pkg_name"
|
||
echo " → $ipk_file"
|
||
built=$((built + 1))
|
||
built_list="${built_list}${pkg_name},"
|
||
else
|
||
print_warning "No .ipk 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"
|
||
|
||
# Find and copy .ipk files
|
||
find "$SDK_DIR/bin" -name "luci-app-*.ipk" -exec cp {} "$BUILD_DIR/$ARCH/" \; 2>/dev/null || true
|
||
|
||
# Also collect any SecuBox related packages
|
||
find "$SDK_DIR/bin" -name "*secubox*.ipk" -exec cp {} "$BUILD_DIR/$ARCH/" \; 2>/dev/null || true
|
||
|
||
# Count
|
||
local pkg_count=$(find "$BUILD_DIR/$ARCH" -name "*.ipk" 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 ./*.ipk > SHA256SUMS
|
||
cd - > /dev/null
|
||
fi
|
||
|
||
echo ""
|
||
print_success "Total: $pkg_count packages"
|
||
|
||
return 0
|
||
}
|
||
|
||
# Run validation
|
||
run_validation() {
|
||
print_header "Running Validation"
|
||
|
||
local failed=0
|
||
|
||
validate_makefiles || failed=$((failed + 1))
|
||
validate_json || failed=$((failed + 1))
|
||
validate_javascript || failed=$((failed + 1))
|
||
validate_shellscripts || failed=$((failed + 1))
|
||
check_file_permissions || failed=$((failed + 1))
|
||
|
||
if [[ $failed -gt 0 ]]; then
|
||
print_error "Validation failed with $failed error(s)"
|
||
return 1
|
||
fi
|
||
|
||
print_success "All validations passed!"
|
||
return 0
|
||
}
|
||
|
||
# Run build
|
||
run_build() {
|
||
local single_package="$1"
|
||
|
||
check_dependencies
|
||
download_sdk || return 1
|
||
setup_sdk_feeds || return 1
|
||
copy_packages "$single_package" || return 1
|
||
configure_packages || return 1
|
||
build_packages || return 1
|
||
collect_artifacts || return 1
|
||
|
||
print_header "Build Complete!"
|
||
print_success "Packages available in: $BUILD_DIR/$ARCH/"
|
||
|
||
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
|
||
full Run validation then build
|
||
clean Clean build directories
|
||
help Show this help message
|
||
|
||
ARCHITECTURES:
|
||
x86-64 PC, VMs (default)
|
||
aarch64-cortex-a53 ARM Cortex-A53 (ESPRESSObin)
|
||
aarch64-cortex-a72 ARM Cortex-A72 (MOCHAbin, RPi4)
|
||
aarch64-generic Generic ARM64
|
||
mips-24kc MIPS 24Kc (TP-Link)
|
||
mipsel-24kc MIPS LE (Xiaomi, GL.iNet)
|
||
|
||
EXAMPLES:
|
||
# Validate all packages
|
||
$0 validate
|
||
|
||
# Build all packages for x86_64
|
||
$0 build
|
||
|
||
# Build single package
|
||
$0 build luci-app-system-hub
|
||
|
||
# Build for specific architecture
|
||
$0 build --arch aarch64-cortex-a72
|
||
|
||
# Full validation and build
|
||
$0 full
|
||
|
||
# Clean build artifacts
|
||
$0 clean
|
||
|
||
ENVIRONMENT VARIABLES:
|
||
OPENWRT_VERSION OpenWrt version (default: 23.05.5)
|
||
SDK_DIR SDK directory (default: ./sdk)
|
||
BUILD_DIR Build output directory (default: ./build)
|
||
CACHE_DIR Download cache directory (default: ./cache)
|
||
|
||
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-*)
|
||
single_package="$1"
|
||
shift
|
||
;;
|
||
*)
|
||
print_error "Unknown option: $1"
|
||
show_usage
|
||
exit 1
|
||
;;
|
||
esac
|
||
done
|
||
|
||
run_build "$single_package"
|
||
;;
|
||
|
||
full)
|
||
run_validation && run_build
|
||
;;
|
||
|
||
clean)
|
||
print_header "Cleaning Build Directories"
|
||
rm -rf "$SDK_DIR" "$BUILD_DIR"
|
||
print_success "Build directories cleaned"
|
||
;;
|
||
|
||
help|--help|-h)
|
||
show_usage
|
||
;;
|
||
|
||
*)
|
||
print_error "Unknown command: $command"
|
||
echo ""
|
||
show_usage
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# Run main
|
||
main "$@"
|