secubox-openwrt/secubox-tools/local-build.sh
CyberMind-FR c739104bca feat: add local build tool that replicates GitHub Actions workflows
- 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>
2025-12-24 11:37:26 +01:00

794 lines
23 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 --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 "$@"