From b2af68ac9a75b7526b75a6e9b58fc5c4f1a6bf19 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Tue, 17 Mar 2026 17:34:26 +0100 Subject: [PATCH] feat(ci): Add multi-platform firmware and VM appliance workflows New workflows using OpenWrt Image Builder for fast builds: build-firmware-imagebuilder.yml: - 16+ devices: x86-64, RPi 3/4/5, NanoPi R4S/R5S/R6S, GL.iNet, Linksys, NETGEAR, Ubiquiti, GlobalScale - Uses Image Builder (much faster than source compilation) - Pre-installs SecuBox packages - Preseed auto-configuration - Configurable root filesystem size (256MB-2GB) build-vm-appliance.yml: - VM images in multiple formats: VMDK, VDI, QCOW2 - EFI and BIOS boot options - Auto-resize filesystem on first boot - Configurable disk size (1-8GB) - Ready for VMware, VirtualBox, Proxmox Co-Authored-By: Claude Opus 4.5 --- .../workflows/build-firmware-imagebuilder.yml | 817 ++++++++++++++++++ .github/workflows/build-vm-appliance.yml | 510 +++++++++++ 2 files changed, 1327 insertions(+) create mode 100644 .github/workflows/build-firmware-imagebuilder.yml create mode 100644 .github/workflows/build-vm-appliance.yml diff --git a/.github/workflows/build-firmware-imagebuilder.yml b/.github/workflows/build-firmware-imagebuilder.yml new file mode 100644 index 00000000..21fd0b80 --- /dev/null +++ b/.github/workflows/build-firmware-imagebuilder.yml @@ -0,0 +1,817 @@ +name: Build SecuBox Firmware (Image Builder) + +# Fast multi-platform firmware builds using OpenWrt Image Builder +# Produces ready-to-flash images with SecuBox packages pre-installed + +on: + workflow_dispatch: + inputs: + openwrt_version: + description: 'OpenWrt version' + required: true + default: '24.10.5' + type: choice + options: + - '24.10.5' + - '24.10.4' + - '23.05.5' + - 'SNAPSHOT' + platform: + description: 'Target platform(s)' + required: true + default: 'all' + type: choice + options: + - all + - x86-64 + - raspberry-pi + - globalscale + - nanopi + - friendlyarm + - gl-inet + - linksys + - netgear + - ubiquiti + root_size: + description: 'Root filesystem size (MB)' + required: true + default: '512' + type: choice + options: + - '256' + - '512' + - '1024' + - '2048' + + push: + tags: + - 'v*.*.*' + - 'v*.*.*-*' + +env: + OPENWRT_VERSION: ${{ github.event.inputs.openwrt_version || '24.10.5' }} + ROOT_SIZE: ${{ github.event.inputs.root_size || '512' }} + +permissions: + contents: write + +jobs: + # ============================================ + # Generate build matrix + # ============================================ + setup: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Set build matrix + id: set-matrix + run: | + PLATFORM="${{ github.event.inputs.platform || 'all' }}" + + # Define all supported devices + cat > /tmp/devices.json << 'DEVICES_EOF' + [ + { + "name": "x86-64-generic", + "target": "x86", + "subtarget": "64", + "profile": "generic", + "platform": "x86-64", + "description": "x86/64 Generic (VM, PC)", + "extra_packages": "qemu-ga" + }, + { + "name": "x86-64-efi", + "target": "x86", + "subtarget": "64", + "profile": "generic", + "platform": "x86-64", + "description": "x86/64 EFI Boot", + "extra_packages": "grub2-efi" + }, + { + "name": "rpi-4", + "target": "bcm27xx", + "subtarget": "bcm2711", + "profile": "rpi-4", + "platform": "raspberry-pi", + "description": "Raspberry Pi 4B", + "extra_packages": "" + }, + { + "name": "rpi-3", + "target": "bcm27xx", + "subtarget": "bcm2710", + "profile": "rpi-3", + "platform": "raspberry-pi", + "description": "Raspberry Pi 3B/3B+", + "extra_packages": "" + }, + { + "name": "rpi-5", + "target": "bcm27xx", + "subtarget": "bcm2712", + "profile": "rpi-5", + "platform": "raspberry-pi", + "description": "Raspberry Pi 5", + "extra_packages": "" + }, + { + "name": "nanopi-r4s", + "target": "rockchip", + "subtarget": "armv8", + "profile": "friendlyarm_nanopi-r4s", + "platform": "nanopi", + "description": "NanoPi R4S (4GB)", + "extra_packages": "" + }, + { + "name": "nanopi-r5s", + "target": "rockchip", + "subtarget": "armv8", + "profile": "friendlyarm_nanopi-r5s", + "platform": "nanopi", + "description": "NanoPi R5S", + "extra_packages": "" + }, + { + "name": "nanopi-r6s", + "target": "rockchip", + "subtarget": "armv8", + "profile": "friendlyarm_nanopi-r6s", + "platform": "nanopi", + "description": "NanoPi R6S", + "extra_packages": "" + }, + { + "name": "friendlyarm-nanopi-neo3", + "target": "rockchip", + "subtarget": "armv8", + "profile": "friendlyarm_nanopi-neo3", + "platform": "friendlyarm", + "description": "FriendlyARM NanoPi NEO3", + "extra_packages": "" + }, + { + "name": "gl-mt6000", + "target": "mediatek", + "subtarget": "filogic", + "profile": "glinet_gl-mt6000", + "platform": "gl-inet", + "description": "GL.iNet MT6000 (Flint 2)", + "extra_packages": "" + }, + { + "name": "gl-mt3000", + "target": "mediatek", + "subtarget": "filogic", + "profile": "glinet_gl-mt3000", + "platform": "gl-inet", + "description": "GL.iNet MT3000 (Beryl AX)", + "extra_packages": "" + }, + { + "name": "linksys-e8450", + "target": "mediatek", + "subtarget": "mt7622", + "profile": "linksys_e8450-ubi", + "platform": "linksys", + "description": "Linksys E8450 (UBI)", + "extra_packages": "" + }, + { + "name": "netgear-wax206", + "target": "mediatek", + "subtarget": "mt7622", + "profile": "netgear_wax206", + "platform": "netgear", + "description": "NETGEAR WAX206", + "extra_packages": "" + }, + { + "name": "ubiquiti-unifi-6-lr", + "target": "mediatek", + "subtarget": "mt7622", + "profile": "ubnt_unifi-6-lr-v1", + "platform": "ubiquiti", + "description": "Ubiquiti UniFi 6 LR", + "extra_packages": "" + }, + { + "name": "espressobin-v7", + "target": "mvebu", + "subtarget": "cortexa53", + "profile": "globalscale_espressobin", + "platform": "globalscale", + "description": "ESPRESSObin V7", + "extra_packages": "" + }, + { + "name": "mochabin", + "target": "mvebu", + "subtarget": "cortexa72", + "profile": "globalscale_mochabin", + "platform": "globalscale", + "description": "MOCHAbin (Quad A72, 10G)", + "extra_packages": "kmod-sfp kmod-phy-marvell-10g" + } + ] + DEVICES_EOF + + # Filter by platform + if [[ "$PLATFORM" == "all" ]]; then + MATRIX=$(jq -c '{"include": .}' /tmp/devices.json) + else + MATRIX=$(jq -c --arg plat "$PLATFORM" '{"include": [.[] | select(.platform == $plat)]}' /tmp/devices.json) + fi + + echo "matrix<> $GITHUB_OUTPUT + echo "$MATRIX" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "📋 Build matrix:" + echo "$MATRIX" | jq '.' + + # ============================================ + # Build SecuBox packages first (once) + # ============================================ + build-packages: + runs-on: ubuntu-latest + outputs: + packages_url: ${{ steps.upload.outputs.artifact-url }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential libncurses5-dev zlib1g-dev \ + gawk git gettext libssl-dev xsltproc rsync wget unzip python3 + + - name: Download OpenWrt SDK + run: | + VERSION="${{ env.OPENWRT_VERSION }}" + + if [[ "$VERSION" == "SNAPSHOT" ]]; then + SDK_URL="https://downloads.openwrt.org/snapshots/targets/x86/64/openwrt-sdk-x86-64_gcc-13.3.0_musl.Linux-x86_64.tar.zst" + else + SDK_URL="https://downloads.openwrt.org/releases/${VERSION}/targets/x86/64/openwrt-sdk-${VERSION}-x86-64_gcc-13.3.0_musl.Linux-x86_64.tar.zst" + fi + + echo "📥 Downloading SDK from: $SDK_URL" + wget -q "$SDK_URL" -O sdk.tar.zst || { + # Fallback to tar.xz if zst not available + SDK_URL="${SDK_URL%.zst}.xz" + echo "📥 Trying fallback: $SDK_URL" + wget -q "$SDK_URL" -O sdk.tar.xz + tar -xf sdk.tar.xz + mv openwrt-sdk-* sdk + } + + if [[ -f sdk.tar.zst ]]; then + tar --zstd -xf sdk.tar.zst + mv openwrt-sdk-* sdk + fi + + - name: Setup SDK feeds + run: | + cd sdk + + # Update feeds + ./scripts/feeds update -a + ./scripts/feeds install -a + + # Create SecuBox package directory + mkdir -p package/secubox + + - name: Copy SecuBox packages + run: | + cd sdk + + echo "📦 Copying SecuBox packages..." + PKG_COUNT=0 + + # Copy luci-app-* packages + for pkg in ../luci-app-*/; do + if [[ -d "$pkg" ]]; then + PKG_NAME=$(basename "$pkg") + 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)) + echo " ✅ $PKG_NAME" + fi + done + + # Copy luci-theme-secubox + if [[ -d "../luci-theme-secubox" ]]; then + cp -r ../luci-theme-secubox package/secubox/ + sed -i 's|include.*luci\.mk|include $(TOPDIR)/feeds/luci/luci.mk|' "package/secubox/luci-theme-secubox/Makefile" 2>/dev/null || true + PKG_COUNT=$((PKG_COUNT + 1)) + echo " ✅ luci-theme-secubox" + fi + + echo "" + echo "📊 Total: $PKG_COUNT packages" + + - name: Build packages + run: | + cd sdk + + # Configure for package building + make defconfig + + # Build SecuBox packages + echo "🔨 Building SecuBox packages..." + for pkg in package/secubox/luci-app-*/; do + PKG_NAME=$(basename "$pkg") + echo " Building $PKG_NAME..." + make package/$PKG_NAME/compile V=s -j$(nproc) 2>&1 | tail -5 || echo " ⚠️ Build warning for $PKG_NAME" + done + + # Build theme + if [[ -d "package/secubox/luci-theme-secubox" ]]; then + make package/luci-theme-secubox/compile V=s -j$(nproc) 2>&1 | tail -5 || true + fi + + - name: Collect packages + run: | + mkdir -p packages + + echo "📦 Collecting built packages..." + find sdk/bin/packages -name "luci-*.ipk" -exec cp {} packages/ \; + find sdk/bin/packages -name "secubox-*.ipk" -exec cp {} packages/ \; 2>/dev/null || true + + echo "" + echo "📋 Built packages:" + ls -la packages/ + + # Create package index + cd packages + echo "# SecuBox Packages for OpenWrt ${{ env.OPENWRT_VERSION }}" > README.md + echo "" >> README.md + echo "Built: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> README.md + echo "" >> README.md + echo "## Packages" >> README.md + for ipk in *.ipk; do + echo "- $ipk" >> README.md + done + + - name: Upload packages artifact + id: upload + uses: actions/upload-artifact@v4 + with: + name: secubox-packages-${{ env.OPENWRT_VERSION }} + path: packages/ + retention-days: 30 + + # ============================================ + # Build firmware images using Image Builder + # ============================================ + build-firmware: + needs: [setup, build-packages] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.setup.outputs.matrix) }} + + name: ${{ matrix.description }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential libncurses5-dev zlib1g-dev \ + gawk git gettext libssl-dev xsltproc rsync wget unzip python3 \ + qemu-utils + + - name: Download Image Builder + run: | + VERSION="${{ env.OPENWRT_VERSION }}" + TARGET="${{ matrix.target }}" + SUBTARGET="${{ matrix.subtarget }}" + + if [[ "$VERSION" == "SNAPSHOT" ]]; then + IB_URL="https://downloads.openwrt.org/snapshots/targets/${TARGET}/${SUBTARGET}/openwrt-imagebuilder-${TARGET}-${SUBTARGET}.Linux-x86_64.tar.zst" + else + IB_URL="https://downloads.openwrt.org/releases/${VERSION}/targets/${TARGET}/${SUBTARGET}/openwrt-imagebuilder-${VERSION}-${TARGET}-${SUBTARGET}.Linux-x86_64.tar.zst" + fi + + echo "📥 Downloading Image Builder..." + echo " URL: $IB_URL" + + wget -q "$IB_URL" -O imagebuilder.tar.zst 2>/dev/null || { + # Try tar.xz fallback + IB_URL="${IB_URL%.zst}.xz" + echo "📥 Trying .tar.xz fallback..." + wget -q "$IB_URL" -O imagebuilder.tar.xz + tar -xf imagebuilder.tar.xz + mv openwrt-imagebuilder-* imagebuilder + } + + if [[ -f imagebuilder.tar.zst ]]; then + tar --zstd -xf imagebuilder.tar.zst + mv openwrt-imagebuilder-* imagebuilder + fi + + echo "✅ Image Builder ready" + ls -la imagebuilder/ + + - name: Download SecuBox packages + uses: actions/download-artifact@v4 + with: + name: secubox-packages-${{ env.OPENWRT_VERSION }} + path: secubox-packages + + - name: Install SecuBox packages to Image Builder + run: | + echo "📦 Installing SecuBox packages to Image Builder..." + + # Copy packages to Image Builder's packages directory + mkdir -p imagebuilder/packages/secubox + cp secubox-packages/*.ipk imagebuilder/packages/secubox/ 2>/dev/null || true + + # Add to repositories.conf + echo "src secubox file:packages/secubox" >> imagebuilder/repositories.conf + + echo "✅ Packages installed" + ls -la imagebuilder/packages/secubox/ + + - name: Create preseed configuration + run: | + mkdir -p imagebuilder/files/etc/uci-defaults + + # Create SecuBox preseed script + cat > imagebuilder/files/etc/uci-defaults/99-secubox-preseed << 'PRESEED_EOF' + #!/bin/sh + # SecuBox Preseed Configuration + # Applied on first boot + + # Set hostname + uci set system.@system[0].hostname='secubox' + uci set system.@system[0].timezone='UTC' + + # Configure LAN + uci set network.lan.ipaddr='192.168.1.1' + uci set network.lan.netmask='255.255.255.0' + + # Enable DHCP on LAN + uci set dhcp.lan.start='100' + uci set dhcp.lan.limit='150' + uci set dhcp.lan.leasetime='12h' + + # Basic firewall + uci set firewall.@zone[1].input='REJECT' + uci set firewall.@zone[1].forward='REJECT' + + # Enable HTTPS redirect for LuCI + uci set uhttpd.main.redirect_https='1' + + # Commit all changes + uci commit + + # Enable and start services + /etc/init.d/uhttpd restart 2>/dev/null || true + + # Mark as configured + touch /etc/secubox-configured + + exit 0 + PRESEED_EOF + + chmod 755 imagebuilder/files/etc/uci-defaults/99-secubox-preseed + + # Create SecuBox version file + mkdir -p imagebuilder/files/etc + cat > imagebuilder/files/etc/secubox-release << EOF + SECUBOX_VERSION="${{ github.ref_name || 'dev' }}" + SECUBOX_BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + OPENWRT_VERSION="${{ env.OPENWRT_VERSION }}" + DEVICE="${{ matrix.name }}" + EOF + + echo "✅ Preseed configuration created" + + - name: Build firmware image + run: | + cd imagebuilder + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "🔨 Building SecuBox Firmware" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "" + echo "Device: ${{ matrix.description }}" + echo "Profile: ${{ matrix.profile }}" + echo "Target: ${{ matrix.target }}/${{ matrix.subtarget }}" + echo "" + + # Define base packages + BASE_PACKAGES="luci luci-ssl luci-app-opkg" + BASE_PACKAGES="$BASE_PACKAGES luci-theme-openwrt-2020" + BASE_PACKAGES="$BASE_PACKAGES curl wget-ssl htop iftop tcpdump" + BASE_PACKAGES="$BASE_PACKAGES openssh-sftp-server" + BASE_PACKAGES="$BASE_PACKAGES block-mount kmod-fs-ext4 kmod-fs-vfat" + BASE_PACKAGES="$BASE_PACKAGES kmod-usb-storage" + + # SecuBox packages (from local repo) + SECUBOX_PACKAGES="" + for ipk in packages/secubox/*.ipk; do + if [[ -f "$ipk" ]]; then + # Extract package name from filename + PKG_NAME=$(basename "$ipk" | sed 's/_.*\.ipk$//') + SECUBOX_PACKAGES="$SECUBOX_PACKAGES $PKG_NAME" + fi + done + + # Device-specific extra packages + EXTRA_PACKAGES="${{ matrix.extra_packages }}" + + # Remove conflicting packages + REMOVE_PACKAGES="-dnsmasq" # Use dnsmasq-full instead + + # All packages combined + ALL_PACKAGES="$BASE_PACKAGES $SECUBOX_PACKAGES $EXTRA_PACKAGES $REMOVE_PACKAGES dnsmasq-full" + + echo "📦 Packages to install:" + echo "$ALL_PACKAGES" | tr ' ' '\n' | grep -v '^$' | sort | head -30 + echo "" + + # Build the image + make image \ + PROFILE="${{ matrix.profile }}" \ + PACKAGES="$ALL_PACKAGES" \ + FILES="files" \ + ROOTFS_PARTSIZE="${{ env.ROOT_SIZE }}" \ + 2>&1 | tee build.log + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "📦 Generated Images" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + ls -lh bin/targets/${{ matrix.target }}/${{ matrix.subtarget }}/ 2>/dev/null || echo "No images found" + + - name: Prepare artifacts + id: prepare + run: | + mkdir -p artifacts + + TARGET_DIR="imagebuilder/bin/targets/${{ matrix.target }}/${{ matrix.subtarget }}" + + if [[ -d "$TARGET_DIR" ]]; then + # Copy all firmware images + IMG_COUNT=0 + for file in "$TARGET_DIR"/*; do + if [[ -f "$file" ]]; then + case "$(basename "$file")" in + *.ipk|*.manifest|sha256sums|*.buildinfo|packages) + continue + ;; + *) + cp "$file" artifacts/ + echo "✅ $(basename "$file")" + IMG_COUNT=$((IMG_COUNT + 1)) + ;; + esac + fi + done + + echo "img_count=$IMG_COUNT" >> $GITHUB_OUTPUT + else + echo "⚠️ No target directory found" + echo "img_count=0" >> $GITHUB_OUTPUT + fi + + # Create build info + cat > artifacts/BUILD_INFO.txt << EOF + SecuBox Firmware Image + ====================== + Device: ${{ matrix.description }} + Profile: ${{ matrix.profile }} + Target: ${{ matrix.target }}/${{ matrix.subtarget }} + OpenWrt: ${{ env.OPENWRT_VERSION }} + Root Size: ${{ env.ROOT_SIZE }}MB + Built: $(date -u +%Y-%m-%dT%H:%M:%SZ) + Commit: ${{ github.sha }} + EOF + + if [[ -n "${{ github.ref_name }}" ]]; then + echo "Version: ${{ github.ref_name }}" >> artifacts/BUILD_INFO.txt + fi + + # Generate checksums + cd artifacts + sha256sum *.* > SHA256SUMS 2>/dev/null || true + + echo "" + echo "📁 Artifacts:" + ls -lh + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: secubox-firmware-${{ matrix.name }}-${{ env.OPENWRT_VERSION }} + path: artifacts/ + retention-days: 30 + + - name: Generate summary + run: | + echo "# 🎯 Build Complete: ${{ matrix.description }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Device | ${{ matrix.description }} |" >> $GITHUB_STEP_SUMMARY + echo "| Profile | \`${{ matrix.profile }}\` |" >> $GITHUB_STEP_SUMMARY + echo "| Target | ${{ matrix.target }}/${{ matrix.subtarget }} |" >> $GITHUB_STEP_SUMMARY + echo "| OpenWrt | ${{ env.OPENWRT_VERSION }} |" >> $GITHUB_STEP_SUMMARY + echo "| Root Size | ${{ env.ROOT_SIZE }}MB |" >> $GITHUB_STEP_SUMMARY + echo "| Images | ${{ steps.prepare.outputs.img_count }} |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "## 📦 Firmware Files" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + ls -lh artifacts/*.{img.gz,bin,img} 2>/dev/null | awk '{print $9, $5}' | sed 's|artifacts/||' || echo "No images" + echo '```' >> $GITHUB_STEP_SUMMARY + + # ============================================ + # Create release with all firmware + # ============================================ + release: + needs: [setup, build-packages, build-firmware] + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: all-firmware + pattern: secubox-firmware-* + + - name: Download packages + uses: actions/download-artifact@v4 + with: + name: secubox-packages-${{ env.OPENWRT_VERSION }} + path: packages + + - name: Organize release + run: | + mkdir -p release/firmware release/packages + + # Copy all firmware + for dir in all-firmware/secubox-firmware-*/; do + if [[ -d "$dir" ]]; then + DEVICE=$(basename "$dir" | sed 's/secubox-firmware-//' | sed "s/-${{ env.OPENWRT_VERSION }}//") + + # Create device directory + mkdir -p "release/firmware/$DEVICE" + cp "$dir"/* "release/firmware/$DEVICE/" 2>/dev/null || true + + echo "✅ $DEVICE" + fi + done + + # Copy packages + cp packages/*.ipk release/packages/ 2>/dev/null || true + + # Create checksums + cd release + find . -type f \( -name "*.img.gz" -o -name "*.bin" -o -name "*.ipk" \) -exec sha256sum {} \; > SHA256SUMS + + # Create release notes + cat > RELEASE_NOTES.md << 'EOF' + # SecuBox Firmware ${{ github.ref_name }} + + Pre-built OpenWrt firmware images with SecuBox security modules pre-installed. + + ## Supported Devices + + ### x86/64 (VM, PC) + - Generic x86/64 (BIOS/EFI) + - Suitable for VMware, VirtualBox, Proxmox, bare metal + + ### Raspberry Pi + - Raspberry Pi 3B/3B+ + - Raspberry Pi 4B + - Raspberry Pi 5 + + ### NanoPi / FriendlyARM + - NanoPi R4S (4GB) + - NanoPi R5S + - NanoPi R6S + - NanoPi NEO3 + + ### GL.iNet + - GL-MT6000 (Flint 2) + - GL-MT3000 (Beryl AX) + + ### Enterprise + - Linksys E8450 + - NETGEAR WAX206 + - Ubiquiti UniFi 6 LR + + ### GlobalScale + - ESPRESSObin V7 + - MOCHAbin + + ## Pre-installed SecuBox Modules + + - CrowdSec Dashboard - Threat intelligence + - Metrics Dashboard - Real-time system metrics + - WireGuard Dashboard - VPN management + - Network Modes - Sniffer/AP/Router modes + - Client Guardian - NAC & captive portal + - Bandwidth Manager - QoS & quotas + - And more... + + ## Quick Start + + 1. Download firmware for your device + 2. Flash using standard OpenWrt methods + 3. Access LuCI at http://192.168.1.1 + 4. Navigate to Services → SecuBox + + ## Default Configuration + + - IP: 192.168.1.1 + - User: root + - Password: (none - set on first login) + - HTTPS redirect enabled + + EOF + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + name: "SecuBox Firmware ${{ github.ref_name }}" + tag_name: ${{ github.ref_name }} + body_path: release/RELEASE_NOTES.md + files: | + release/firmware/**/* + release/packages/*.ipk + release/SHA256SUMS + draft: false + prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ============================================ + # Final summary + # ============================================ + summary: + needs: [setup, build-packages, build-firmware] + runs-on: ubuntu-latest + if: always() + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: all-artifacts + pattern: secubox-* + continue-on-error: true + + - name: Generate summary + run: | + echo "# 🏗️ SecuBox Firmware Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "## Configuration" >> $GITHUB_STEP_SUMMARY + echo "| Property | Value |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| OpenWrt | ${{ env.OPENWRT_VERSION }} |" >> $GITHUB_STEP_SUMMARY + echo "| Root Size | ${{ env.ROOT_SIZE }}MB |" >> $GITHUB_STEP_SUMMARY + echo "| Build Method | Image Builder (fast) |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + echo "## Artifacts" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [[ -d all-artifacts ]]; then + echo "| Device | Firmware | Status |" >> $GITHUB_STEP_SUMMARY + echo "|--------|----------|--------|" >> $GITHUB_STEP_SUMMARY + + for dir in all-artifacts/secubox-firmware-*/; do + if [[ -d "$dir" ]]; then + DEVICE=$(basename "$dir" | sed 's/secubox-firmware-//' | sed "s/-${{ env.OPENWRT_VERSION }}//") + IMG_COUNT=$(find "$dir" -maxdepth 1 -type f \( -name "*.img.gz" -o -name "*.bin" \) 2>/dev/null | wc -l) + + if [[ $IMG_COUNT -gt 0 ]]; then + echo "| $DEVICE | $IMG_COUNT images | ✅ |" >> $GITHUB_STEP_SUMMARY + else + echo "| $DEVICE | 0 images | ❌ |" >> $GITHUB_STEP_SUMMARY + fi + fi + done + fi + + echo "" >> $GITHUB_STEP_SUMMARY + echo "📥 Download artifacts from the Summary page below." >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/build-vm-appliance.yml b/.github/workflows/build-vm-appliance.yml new file mode 100644 index 00000000..d8c7b6ba --- /dev/null +++ b/.github/workflows/build-vm-appliance.yml @@ -0,0 +1,510 @@ +name: Build SecuBox VM Appliance + +# Builds ready-to-use VM images (VMDK, VDI, QCOW2) for VMware, VirtualBox, Proxmox + +on: + workflow_dispatch: + inputs: + openwrt_version: + description: 'OpenWrt version' + required: true + default: '24.10.5' + type: choice + options: + - '24.10.5' + - '24.10.4' + - '23.05.5' + disk_size: + description: 'Virtual disk size (GB)' + required: true + default: '2' + type: choice + options: + - '1' + - '2' + - '4' + - '8' + memory: + description: 'Recommended RAM (GB)' + required: true + default: '1' + type: choice + options: + - '512M' + - '1' + - '2' + - '4' + + push: + tags: + - 'v*.*.*' + - 'v*.*.*-*' + +env: + OPENWRT_VERSION: ${{ github.event.inputs.openwrt_version || '24.10.5' }} + DISK_SIZE: ${{ github.event.inputs.disk_size || '2' }} + +permissions: + contents: write + +jobs: + build-vm: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - name: x86-64-efi + boot: efi + description: "x86/64 EFI (Modern UEFI systems)" + - name: x86-64-bios + boot: bios + description: "x86/64 BIOS (Legacy systems)" + + name: VM ${{ matrix.description }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Free disk space + run: | + sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc + sudo docker image prune --all --force + df -h + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential libncurses5-dev zlib1g-dev \ + gawk git gettext libssl-dev xsltproc rsync wget unzip python3 \ + qemu-utils parted dosfstools e2fsprogs + + - name: Download Image Builder + run: | + VERSION="${{ env.OPENWRT_VERSION }}" + + IB_URL="https://downloads.openwrt.org/releases/${VERSION}/targets/x86/64/openwrt-imagebuilder-${VERSION}-x86-64.Linux-x86_64.tar.zst" + + echo "📥 Downloading Image Builder..." + wget -q "$IB_URL" -O imagebuilder.tar.zst 2>/dev/null || { + IB_URL="${IB_URL%.zst}.xz" + wget -q "$IB_URL" -O imagebuilder.tar.xz + tar -xf imagebuilder.tar.xz + mv openwrt-imagebuilder-* imagebuilder + } + + if [[ -f imagebuilder.tar.zst ]]; then + tar --zstd -xf imagebuilder.tar.zst + mv openwrt-imagebuilder-* imagebuilder + fi + + echo "✅ Image Builder ready" + + - name: Create preseed configuration + run: | + mkdir -p imagebuilder/files/etc/uci-defaults + mkdir -p imagebuilder/files/etc/secubox + + # Preseed script for first boot + cat > imagebuilder/files/etc/uci-defaults/99-secubox-vm << 'PRESEED_EOF' + #!/bin/sh + # SecuBox VM Appliance Preseed + + # Set hostname + uci set system.@system[0].hostname='secubox-vm' + uci set system.@system[0].timezone='UTC' + uci set system.@system[0].zonename='UTC' + + # Configure network for VM + # LAN: br-lan on eth0 + uci set network.lan.ipaddr='192.168.1.1' + uci set network.lan.netmask='255.255.255.0' + uci set network.lan.proto='static' + + # WAN: DHCP on eth1 (if present) + uci set network.wan=interface + uci set network.wan.device='eth1' + uci set network.wan.proto='dhcp' + + # Enable DHCP server on LAN + uci set dhcp.lan.start='100' + uci set dhcp.lan.limit='150' + uci set dhcp.lan.leasetime='12h' + + # Firewall: secure defaults + uci set firewall.@zone[0].input='ACCEPT' + uci set firewall.@zone[0].output='ACCEPT' + uci set firewall.@zone[0].forward='REJECT' + uci set firewall.@zone[1].input='REJECT' + uci set firewall.@zone[1].output='ACCEPT' + uci set firewall.@zone[1].forward='REJECT' + uci set firewall.@zone[1].masq='1' + + # Enable HTTPS for LuCI + uci set uhttpd.main.redirect_https='1' + + # Commit changes + uci commit + + # Expand root filesystem on first boot + if [ ! -f /etc/secubox/resized ]; then + # Detect root partition + ROOT_DEV=$(mount | grep ' / ' | cut -d' ' -f1) + if [ -n "$ROOT_DEV" ]; then + # Get disk device (remove partition number) + DISK_DEV=$(echo "$ROOT_DEV" | sed 's/[0-9]*$//') + PART_NUM=$(echo "$ROOT_DEV" | grep -o '[0-9]*$') + + # Resize partition if possible + if command -v parted >/dev/null 2>&1; then + parted -s "$DISK_DEV" resizepart "$PART_NUM" 100% 2>/dev/null || true + fi + + # Resize filesystem + if command -v resize2fs >/dev/null 2>&1; then + resize2fs "$ROOT_DEV" 2>/dev/null || true + fi + + mkdir -p /etc/secubox + touch /etc/secubox/resized + fi + fi + + # Mark as configured + touch /etc/secubox/configured + + exit 0 + PRESEED_EOF + + chmod 755 imagebuilder/files/etc/uci-defaults/99-secubox-vm + + # SecuBox release info + cat > imagebuilder/files/etc/secubox/release << EOF + SECUBOX_VERSION="${{ github.ref_name || 'dev' }}" + SECUBOX_BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)" + OPENWRT_VERSION="${{ env.OPENWRT_VERSION }}" + VM_TYPE="${{ matrix.boot }}" + DISK_SIZE="${{ env.DISK_SIZE }}GB" + EOF + + # MOTD banner + cat > imagebuilder/files/etc/banner << 'BANNER_EOF' + + ____ ____ + / ___| ___ ___ _ _| __ ) _____ __ + \___ \ / _ \/ __| | | | _ \ / _ \ \/ / + ___) | __/ (__| |_| | |_) | (_) > < + |____/ \___|\___|\__,_|____/ \___/_/\_\ + + SecuBox OpenWrt Security Appliance + https://github.com/gkerma/secubox-openwrt + + Access LuCI: https://192.168.1.1 + Documentation: https://github.com/gkerma/secubox-openwrt/wiki + + BANNER_EOF + + echo "✅ Preseed configuration created" + + - name: Build firmware image + run: | + cd imagebuilder + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "🔨 Building SecuBox VM Image (${{ matrix.boot }})" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + # Base packages + PACKAGES="luci luci-ssl luci-app-opkg luci-theme-openwrt-2020" + PACKAGES="$PACKAGES curl wget-ssl htop iftop tcpdump" + PACKAGES="$PACKAGES openssh-sftp-server" + PACKAGES="$PACKAGES block-mount kmod-fs-ext4 kmod-fs-vfat" + PACKAGES="$PACKAGES parted e2fsprogs resize2fs" + PACKAGES="$PACKAGES qemu-ga" # QEMU guest agent for Proxmox + + # Remove conflicting dnsmasq + PACKAGES="$PACKAGES -dnsmasq dnsmasq-full" + + # EFI-specific packages + if [[ "${{ matrix.boot }}" == "efi" ]]; then + PACKAGES="$PACKAGES grub2-efi" + fi + + # Calculate root partition size (disk size minus 64MB for boot) + ROOT_SIZE=$(( ${{ env.DISK_SIZE }} * 1024 - 64 )) + + echo "📦 Packages: $PACKAGES" + echo "💾 Root partition: ${ROOT_SIZE}MB" + echo "" + + # Build with combined-efi or ext4-combined based on boot type + if [[ "${{ matrix.boot }}" == "efi" ]]; then + PROFILE="generic" + else + PROFILE="generic" + fi + + make image \ + PROFILE="$PROFILE" \ + PACKAGES="$PACKAGES" \ + FILES="files" \ + ROOTFS_PARTSIZE="$ROOT_SIZE" \ + 2>&1 | tee build.log + + echo "" + echo "📦 Generated images:" + ls -lh bin/targets/x86/64/ + + - name: Convert to VM formats + run: | + mkdir -p artifacts + + TARGET_DIR="imagebuilder/bin/targets/x86/64" + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "🔄 Converting to VM formats" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + # Find the correct image based on boot type + if [[ "${{ matrix.boot }}" == "efi" ]]; then + IMG_PATTERN="*-combined-efi.img.gz" + else + IMG_PATTERN="*-combined-ext4.img.gz" + fi + + # Find and extract the image + IMG_FILE=$(find "$TARGET_DIR" -name "$IMG_PATTERN" 2>/dev/null | head -1) + + if [[ -z "$IMG_FILE" ]]; then + # Fallback to any combined image + IMG_FILE=$(find "$TARGET_DIR" -name "*combined*.img.gz" 2>/dev/null | head -1) + fi + + if [[ -z "$IMG_FILE" ]]; then + echo "❌ No firmware image found!" + ls -la "$TARGET_DIR/" + exit 1 + fi + + echo "📦 Source image: $IMG_FILE" + + # Extract + gunzip -c "$IMG_FILE" > /tmp/openwrt.img + IMG_SIZE=$(stat -c%s /tmp/openwrt.img) + echo " Size: $(numfmt --to=iec $IMG_SIZE)" + + # Expand to target disk size + TARGET_BYTES=$(( ${{ env.DISK_SIZE }} * 1024 * 1024 * 1024 )) + if [[ $IMG_SIZE -lt $TARGET_BYTES ]]; then + echo "📏 Expanding to ${{ env.DISK_SIZE }}GB..." + truncate -s ${TARGET_BYTES} /tmp/openwrt.img + fi + + # Base filename + VERSION="${{ env.OPENWRT_VERSION }}" + TAG="${{ github.ref_name }}" + BASENAME="secubox-vm-${TAG:-dev}-${{ matrix.name }}" + + # Convert to VMDK (VMware) + echo "🔄 Creating VMDK (VMware)..." + qemu-img convert -f raw -O vmdk /tmp/openwrt.img "artifacts/${BASENAME}.vmdk" + echo " ✅ ${BASENAME}.vmdk ($(du -h "artifacts/${BASENAME}.vmdk" | cut -f1))" + + # Convert to VDI (VirtualBox) + echo "🔄 Creating VDI (VirtualBox)..." + qemu-img convert -f raw -O vdi /tmp/openwrt.img "artifacts/${BASENAME}.vdi" + echo " ✅ ${BASENAME}.vdi ($(du -h "artifacts/${BASENAME}.vdi" | cut -f1))" + + # Convert to QCOW2 (Proxmox/KVM) + echo "🔄 Creating QCOW2 (Proxmox/KVM)..." + qemu-img convert -f raw -O qcow2 -c /tmp/openwrt.img "artifacts/${BASENAME}.qcow2" + echo " ✅ ${BASENAME}.qcow2 ($(du -h "artifacts/${BASENAME}.qcow2" | cut -f1))" + + # Keep raw image compressed + echo "🔄 Compressing raw image..." + gzip -c /tmp/openwrt.img > "artifacts/${BASENAME}.img.gz" + echo " ✅ ${BASENAME}.img.gz ($(du -h "artifacts/${BASENAME}.img.gz" | cut -f1))" + + rm /tmp/openwrt.img + + echo "" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "✅ VM images created successfully" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + + - name: Create documentation + run: | + TAG="${{ github.ref_name }}" + BASENAME="secubox-vm-${TAG:-dev}-${{ matrix.name }}" + + cat > artifacts/README.md << EOF + # SecuBox VM Appliance - ${{ matrix.description }} + + Pre-configured OpenWrt ${{ env.OPENWRT_VERSION }} virtual machine with SecuBox modules. + + ## VM Images + + | Format | File | Platform | + |--------|------|----------| + | VMDK | \`${BASENAME}.vmdk\` | VMware Workstation/ESXi | + | VDI | \`${BASENAME}.vdi\` | VirtualBox | + | QCOW2 | \`${BASENAME}.qcow2\` | Proxmox/KVM/QEMU | + | Raw | \`${BASENAME}.img.gz\` | Any hypervisor | + + ## Quick Start + + ### VMware + 1. Create new VM → Other Linux 64-bit + 2. Use existing disk → Select \`.vmdk\` file + 3. RAM: ${{ github.event.inputs.memory || '1' }}GB minimum + 4. Network: Bridged or NAT + + ### VirtualBox + 1. Create new VM → Linux → Other Linux 64-bit + 2. Use existing disk → Select \`.vdi\` file + 3. RAM: ${{ github.event.inputs.memory || '1' }}GB minimum + 4. Network: Bridged Adapter or NAT + + ### Proxmox + \`\`\`bash + # Upload QCOW2 to Proxmox + qm create 100 --name secubox --memory 1024 --net0 virtio,bridge=vmbr0 + qm importdisk 100 ${BASENAME}.qcow2 local-lvm + qm set 100 --scsi0 local-lvm:vm-100-disk-0 + qm set 100 --boot order=scsi0 + qm start 100 + \`\`\` + + ## Default Configuration + + | Setting | Value | + |---------|-------| + | LAN IP | 192.168.1.1 | + | Username | root | + | Password | (none - set on first login) | + | Web UI | https://192.168.1.1 | + | SSH | Enabled | + + ## Network Interfaces + + - **eth0 (LAN)**: 192.168.1.1/24, DHCP server + - **eth1 (WAN)**: DHCP client (optional) + + ## Disk Resize + + The root filesystem will automatically expand on first boot. + For manual expansion: + \`\`\`bash + parted /dev/sda resizepart 2 100% + resize2fs /dev/sda2 + \`\`\` + + ## Build Information + + - OpenWrt: ${{ env.OPENWRT_VERSION }} + - Boot: ${{ matrix.boot }} + - Disk: ${{ env.DISK_SIZE }}GB + - Built: $(date -u +%Y-%m-%dT%H:%M:%SZ) + EOF + + # Create checksums + cd artifacts + sha256sum *.vmdk *.vdi *.qcow2 *.img.gz > SHA256SUMS + + echo "📋 Artifacts ready:" + ls -lh + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: secubox-vm-${{ matrix.name }}-${{ env.OPENWRT_VERSION }} + path: artifacts/ + retention-days: 30 + + - name: Generate summary + run: | + echo "# 🖥️ VM Appliance: ${{ matrix.description }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Format | Size |" >> $GITHUB_STEP_SUMMARY + echo "|--------|------|" >> $GITHUB_STEP_SUMMARY + for f in artifacts/*.vmdk artifacts/*.vdi artifacts/*.qcow2 artifacts/*.img.gz; do + if [[ -f "$f" ]]; then + echo "| $(basename "$f") | $(du -h "$f" | cut -f1) |" >> $GITHUB_STEP_SUMMARY + fi + done + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Disk size:** ${{ env.DISK_SIZE }}GB" >> $GITHUB_STEP_SUMMARY + echo "**Boot type:** ${{ matrix.boot }}" >> $GITHUB_STEP_SUMMARY + + # ============================================ + # Create release + # ============================================ + release: + needs: build-vm + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: vms + pattern: secubox-vm-* + + - name: Organize release + run: | + mkdir -p release + + for dir in vms/secubox-vm-*/; do + cp "$dir"/*.vmdk "$dir"/*.vdi "$dir"/*.qcow2 "$dir"/*.img.gz release/ 2>/dev/null || true + cp "$dir"/README.md release/VM-README.md 2>/dev/null || true + done + + cd release + sha256sum *.vmdk *.vdi *.qcow2 *.img.gz > SHA256SUMS 2>/dev/null || true + + cat > RELEASE_NOTES.md << 'EOF' + # SecuBox VM Appliance + + Ready-to-use virtual machine images with SecuBox security modules pre-installed. + + ## Downloads + + Choose the format for your hypervisor: + - **VMDK** - VMware Workstation, ESXi, Fusion + - **VDI** - VirtualBox + - **QCOW2** - Proxmox, KVM, QEMU + + ## Quick Start + + 1. Download the appropriate image for your hypervisor + 2. Import/create VM with the disk image + 3. Boot and access https://192.168.1.1 + 4. Login as `root` (no password initially) + 5. Set a password and start configuring + + ## System Requirements + + - 1GB RAM minimum (2GB recommended) + - 1 vCPU minimum + - Network adapter (bridged or NAT) + + EOF + + - name: Create release + uses: softprops/action-gh-release@v2 + with: + name: "SecuBox VM Appliance ${{ github.ref_name }}" + tag_name: ${{ github.ref_name }} + body_path: release/RELEASE_NOTES.md + files: | + release/*.vmdk + release/*.vdi + release/*.qcow2 + release/*.img.gz + release/SHA256SUMS + draft: false + prerelease: ${{ contains(github.ref_name, 'alpha') || contains(github.ref_name, 'beta') || contains(github.ref_name, 'rc') }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}