Validated secubox-image.sh and secubox-sysupgrade.sh scripts: - Fixed curl redirect issue: ASU API returns 301 redirects - Added -L flag to 9 curl calls across both scripts - Verified all device profiles valid (mochabin, espressobin, x86-64) - Confirmed POSIX sh compatibility for sysupgrade script - Validated first-boot script syntax Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
674 lines
21 KiB
Bash
Executable File
674 lines
21 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# secubox-image.sh - Build SecuBox firmware images via OpenWrt ASU API
|
|
#
|
|
# Uses the Attended SysUpgrade server (firmware-selector.openwrt.org backend)
|
|
# to build custom OpenWrt images with all required packages, maximum rootfs,
|
|
# and a first-boot script that installs SecuBox packages from the feed.
|
|
#
|
|
# Usage:
|
|
# ./secubox-image.sh build [device] # Build via ASU API
|
|
# ./secubox-image.sh firmware-selector [dev] # Print config for web UI paste
|
|
# ./secubox-image.sh status <hash> # Check build status
|
|
# ./secubox-image.sh download <hash> # Download completed build
|
|
#
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
ASU_URL="https://sysupgrade.openwrt.org"
|
|
OPENWRT_VERSION="24.10.5"
|
|
OUTPUT_DIR="$SCRIPT_DIR/build/images"
|
|
DEFAULT_DEVICE="mochabin"
|
|
FEED_URL="https://github.com/gkerma/secubox-openwrt/releases/latest/download"
|
|
RESIZE_TARGET="" # e.g. "16G" — resize image after download
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
log_info() { echo -e "${GREEN}[INFO]${NC} $*" >&2; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*" >&2; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
log_step() { echo -e "${CYAN}[STEP]${NC} $*" >&2; }
|
|
|
|
# Device profiles: device_name -> target:profile
|
|
declare -A DEVICES=(
|
|
["mochabin"]="mvebu/cortexa72:globalscale_mochabin"
|
|
["espressobin-v7"]="mvebu/cortexa53:globalscale_espressobin"
|
|
["espressobin-ultra"]="mvebu/cortexa53:globalscale_espressobin-ultra"
|
|
["x86-64"]="x86/64:generic"
|
|
)
|
|
|
|
# Device-specific extra packages
|
|
declare -A DEVICE_PACKAGES=(
|
|
["mochabin"]="kmod-sfp kmod-phy-marvell-10g"
|
|
["espressobin-v7"]=""
|
|
["espressobin-ultra"]="kmod-mt76 kmod-mac80211"
|
|
["x86-64"]=""
|
|
)
|
|
|
|
# Base packages included in the firmware image (official OpenWrt repos)
|
|
BASE_PACKAGES=(
|
|
# LuCI
|
|
luci luci-ssl luci-theme-bootstrap luci-app-firewall
|
|
|
|
# DNS
|
|
dnsmasq-full -dnsmasq
|
|
|
|
# Networking essentials
|
|
curl wget-ssl ca-certificates openssl-util
|
|
rsync diffutils
|
|
|
|
# WireGuard VPN
|
|
wireguard-tools luci-proto-wireguard kmod-wireguard
|
|
|
|
# Reverse proxy & cache
|
|
haproxy squid
|
|
|
|
# MQTT
|
|
mosquitto-client-ssl
|
|
|
|
# Container runtimes
|
|
docker dockerd containerd
|
|
lxc lxc-common lxc-attach lxc-start lxc-stop lxc-destroy
|
|
|
|
# Storage / filesystem tools
|
|
block-mount kmod-fs-ext4 e2fsprogs parted losetup
|
|
|
|
# Shell utilities
|
|
nano htop lsblk
|
|
|
|
# Attended sysupgrade client (for future upgrades)
|
|
owut attendedsysupgrade-common
|
|
)
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: secubox-image.sh <command> [options]
|
|
|
|
Commands:
|
|
build [device] Build firmware image via ASU API (default: mochabin)
|
|
firmware-selector [dev] Print packages + script for firmware-selector.openwrt.org
|
|
status <hash> Check build status
|
|
download <hash> Download completed build
|
|
|
|
Devices:
|
|
mochabin MOCHAbin (Cortex-A72, 10G) — default
|
|
espressobin-v7 ESPRESSObin V7 (Cortex-A53)
|
|
espressobin-ultra ESPRESSObin Ultra (Cortex-A53, WiFi)
|
|
x86-64 Generic x86_64
|
|
|
|
Options:
|
|
-v, --version VERSION OpenWrt version (default: 24.10.5)
|
|
-o, --output DIR Output directory (default: secubox-tools/build/images/)
|
|
-r, --resize SIZE Resize image after download (e.g., 16G for eMMC)
|
|
-h, --help Show this help
|
|
EOF
|
|
}
|
|
|
|
# Parse device profile into target and profile
|
|
parse_device() {
|
|
local device="${1:-$DEFAULT_DEVICE}"
|
|
|
|
if [[ -z "${DEVICES[$device]+x}" ]]; then
|
|
log_error "Unknown device: $device"
|
|
log_info "Available: ${!DEVICES[*]}"
|
|
return 1
|
|
fi
|
|
|
|
local spec="${DEVICES[$device]}"
|
|
TARGET="${spec%%:*}"
|
|
PROFILE="${spec##*:}"
|
|
DEVICE="$device"
|
|
|
|
log_info "Device: $DEVICE ($TARGET / $PROFILE)"
|
|
}
|
|
|
|
# Build the full package list for a device
|
|
get_packages() {
|
|
local device="${1:-$DEFAULT_DEVICE}"
|
|
local pkgs=("${BASE_PACKAGES[@]}")
|
|
|
|
# Add device-specific packages
|
|
local extras="${DEVICE_PACKAGES[$device]:-}"
|
|
if [[ -n "$extras" ]]; then
|
|
for pkg in $extras; do
|
|
pkgs+=("$pkg")
|
|
done
|
|
fi
|
|
|
|
echo "${pkgs[*]}"
|
|
}
|
|
|
|
# Generate the first-boot defaults script
|
|
generate_defaults() {
|
|
cat <<'DEFAULTS'
|
|
#!/bin/sh
|
|
# SecuBox First Boot Setup — runs once after flash/sysupgrade
|
|
|
|
FEED_URL="https://github.com/gkerma/secubox-openwrt/releases/latest/download"
|
|
LOG="/tmp/secubox-setup.log"
|
|
|
|
log() { echo "[$(date +%T)] $*" | tee -a "$LOG"; logger -t secubox-setup "$*"; }
|
|
log "SecuBox first-boot setup starting..."
|
|
|
|
# --- Step 1: Resize root to fill storage ---
|
|
log "Resizing root partition..."
|
|
ROOT_DEV=$(awk '$2=="/" {print $1}' /proc/mounts)
|
|
if [ -n "$ROOT_DEV" ]; then
|
|
DISK=$(echo "$ROOT_DEV" | sed 's/p\?[0-9]*$//')
|
|
[ "$DISK" = "$ROOT_DEV" ] && DISK=$(echo "$ROOT_DEV" | sed 's/[0-9]*$//')
|
|
PART_NUM=$(echo "$ROOT_DEV" | grep -o '[0-9]*$')
|
|
if command -v parted >/dev/null 2>&1; then
|
|
parted -s "$DISK" resizepart "$PART_NUM" 100% 2>/dev/null
|
|
resize2fs "$ROOT_DEV" 2>/dev/null
|
|
log "Root resized: $(df -h / | tail -1 | awk '{print $2}')"
|
|
else
|
|
log "parted not available, skipping resize"
|
|
fi
|
|
fi
|
|
|
|
# --- Step 2: Add SecuBox package feed ---
|
|
log "Adding SecuBox feed..."
|
|
FEED_CONF="/etc/opkg/customfeeds.conf"
|
|
grep -q "secubox" "$FEED_CONF" 2>/dev/null || \
|
|
echo "src/gz secubox $FEED_URL" >> "$FEED_CONF"
|
|
opkg update >>"$LOG" 2>&1
|
|
|
|
# --- Step 3: Install SecuBox packages ---
|
|
log "Installing SecuBox core packages..."
|
|
CORE_PKGS="secubox-core secubox-app secubox-p2p luci-app-secubox luci-theme-secubox"
|
|
CORE_PKGS="$CORE_PKGS luci-app-secubox-admin luci-app-secubox-portal luci-app-system-hub"
|
|
CORE_PKGS="$CORE_PKGS luci-app-service-registry secubox-master-link luci-app-master-link"
|
|
for pkg in $CORE_PKGS; do
|
|
opkg install "$pkg" >>"$LOG" 2>&1 || log "WARN: $pkg failed"
|
|
done
|
|
|
|
log "Installing all SecuBox packages from feed..."
|
|
opkg list 2>/dev/null | awk '/^(secubox-|luci-app-secubox|luci-app-master|luci-app-service|luci-app-auth|luci-app-bandwidth|luci-app-cdn|luci-app-client|luci-app-crowdsec|luci-app-cyber|luci-app-dns|luci-app-exposure|luci-app-gitea|luci-app-glances|luci-app-haproxy|luci-app-hexo|luci-app-jitsi|luci-app-ksm|luci-app-local|luci-app-lyrion|luci-app-magic|luci-app-mail|luci-app-media|luci-app-meta|luci-app-mitmproxy|luci-app-mmpm|luci-app-mqtt|luci-app-ndpid|luci-app-netd|luci-app-network|luci-app-next|luci-app-ollama|luci-app-pico|luci-app-simplex|luci-app-stream|luci-app-system-hub|luci-app-tor|luci-app-traffic|luci-app-vhost|luci-app-wireguard-dash|luci-app-zigbee|luci-secubox)/{print $1}' | \
|
|
while read -r pkg; do
|
|
opkg install "$pkg" >>"$LOG" 2>&1 || true
|
|
done
|
|
|
|
# --- Step 4: Enable core services ---
|
|
for svc in secubox-core; do
|
|
[ -x "/etc/init.d/$svc" ] && /etc/init.d/$svc enable
|
|
done
|
|
|
|
# --- Step 5: Ensure sysupgrade config preserves SecuBox data ---
|
|
SYSUPGRADE_CONF="/etc/sysupgrade.conf"
|
|
for path in /etc/config/ /etc/secubox/ /etc/opkg/customfeeds.conf /srv/; do
|
|
grep -q "^${path}$" "$SYSUPGRADE_CONF" 2>/dev/null || echo "$path" >> "$SYSUPGRADE_CONF"
|
|
done
|
|
|
|
INSTALLED=$(opkg list-installed 2>/dev/null | grep -c secubox || echo 0)
|
|
log "SecuBox setup complete — $INSTALLED packages installed"
|
|
log "Disk usage: $(df -h / | tail -1 | awk '{print $3 "/" $2 " (" $5 ")"}')"
|
|
exit 0
|
|
DEFAULTS
|
|
}
|
|
|
|
# Build JSON request for ASU API
|
|
build_request_json() {
|
|
local device="${1:-$DEFAULT_DEVICE}"
|
|
parse_device "$device"
|
|
|
|
local packages
|
|
packages=$(get_packages "$device")
|
|
|
|
# Write defaults to temp file for python to read
|
|
local defaults_file
|
|
defaults_file=$(mktemp)
|
|
generate_defaults > "$defaults_file"
|
|
|
|
# Build JSON package array
|
|
local pkg_json=""
|
|
for pkg in $packages; do
|
|
[[ -n "$pkg_json" ]] && pkg_json="$pkg_json,"
|
|
pkg_json="$pkg_json\"$pkg\""
|
|
done
|
|
|
|
# Encode defaults as JSON string
|
|
local defaults_encoded
|
|
defaults_encoded=$(python3 -c "
|
|
import json, sys
|
|
with open(sys.argv[1]) as f:
|
|
print(json.dumps(f.read()))
|
|
" "$defaults_file")
|
|
rm -f "$defaults_file"
|
|
|
|
# Build complete JSON via python for correctness
|
|
python3 -c "
|
|
import json, sys
|
|
data = {
|
|
'profile': '$PROFILE',
|
|
'target': '$TARGET',
|
|
'version': '$OPENWRT_VERSION',
|
|
'packages': '$packages'.split(),
|
|
'defaults': json.loads(sys.argv[1]),
|
|
'rootfs_size_mb': 1024,
|
|
'diff_packages': True,
|
|
'client': 'secubox-image/1.0'
|
|
}
|
|
print(json.dumps(data, indent=2))
|
|
" "$defaults_encoded"
|
|
}
|
|
|
|
# POST build request to ASU API
|
|
api_build() {
|
|
local json_file="$1"
|
|
log_step "Submitting build request to ASU..."
|
|
|
|
local response
|
|
response=$(curl -sL -w "\n%{http_code}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "@$json_file" \
|
|
"$ASU_URL/api/v1/build")
|
|
|
|
local http_code
|
|
http_code=$(echo "$response" | tail -1)
|
|
local body
|
|
body=$(echo "$response" | sed '$d')
|
|
|
|
case "$http_code" in
|
|
200)
|
|
log_info "Build completed (cached)"
|
|
echo "$body"
|
|
;;
|
|
202)
|
|
log_info "Build queued"
|
|
echo "$body"
|
|
;;
|
|
400)
|
|
log_error "Bad request:"
|
|
echo "$body" >&2
|
|
return 1
|
|
;;
|
|
422)
|
|
log_error "Validation error:"
|
|
echo "$body" >&2
|
|
return 1
|
|
;;
|
|
500)
|
|
log_error "Server error:"
|
|
echo "$body" >&2
|
|
return 1
|
|
;;
|
|
*)
|
|
log_error "Unexpected HTTP $http_code:"
|
|
echo "$body" >&2
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Poll build status until complete
|
|
poll_build() {
|
|
local request_hash="$1"
|
|
local max_wait=600 # 10 minutes
|
|
local interval=10
|
|
local elapsed=0
|
|
|
|
log_step "Waiting for build to complete (hash: $request_hash)..."
|
|
|
|
while [ $elapsed -lt $max_wait ]; do
|
|
local response
|
|
response=$(curl -sL -w "\n%{http_code}" "$ASU_URL/api/v1/build/$request_hash")
|
|
local http_code
|
|
http_code=$(echo "$response" | tail -1)
|
|
local body
|
|
body=$(echo "$response" | sed '$d')
|
|
|
|
if [ "$http_code" = "200" ]; then
|
|
# Check if build has images (= complete)
|
|
local detail
|
|
detail=$(echo "$body" | python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("detail",""))' 2>/dev/null || echo "")
|
|
|
|
if [ "$detail" = "done" ] || [ "$detail" = "" ]; then
|
|
# Check for images to confirm completion
|
|
local img_count
|
|
img_count=$(echo "$body" | python3 -c 'import sys,json; print(len(json.load(sys.stdin).get("images",[])))' 2>/dev/null || echo "0")
|
|
if [ "$img_count" -gt 0 ] 2>/dev/null; then
|
|
log_info "Build complete!"
|
|
echo "$body"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
case "$detail" in
|
|
"queued"|"building")
|
|
printf "\r Waiting... %ds elapsed (%s) " "$elapsed" "$detail" >&2
|
|
;;
|
|
*)
|
|
# Response has no images but detail is not queued/building
|
|
log_info "Build finished (detail: $detail)"
|
|
echo "$body"
|
|
return 0
|
|
;;
|
|
esac
|
|
elif [ "$http_code" = "202" ]; then
|
|
printf "\r Waiting... %ds elapsed (building) " "$elapsed" >&2
|
|
elif [ "$http_code" = "404" ]; then
|
|
log_error "Build not found: $request_hash"
|
|
return 1
|
|
else
|
|
log_error "Unexpected HTTP $http_code"
|
|
echo "$body" >&2
|
|
return 1
|
|
fi
|
|
|
|
sleep "$interval"
|
|
elapsed=$((elapsed + interval))
|
|
done
|
|
|
|
echo ""
|
|
log_error "Build timed out after ${max_wait}s"
|
|
return 1
|
|
}
|
|
|
|
# Download the built image
|
|
download_image() {
|
|
local build_response="$1"
|
|
local output_dir="${2:-$OUTPUT_DIR}"
|
|
|
|
mkdir -p "$output_dir"
|
|
|
|
# Save response to temp file for python to parse
|
|
local resp_file
|
|
resp_file=$(mktemp)
|
|
echo "$build_response" > "$resp_file"
|
|
|
|
# Extract image name: prefer ext4-sdcard, then sysupgrade, then any sdcard
|
|
local image_name
|
|
image_name=$(python3 -c "
|
|
import json, sys
|
|
with open(sys.argv[1]) as f:
|
|
d = json.load(f)
|
|
images = d.get('images', [])
|
|
# Prefer ext4-sdcard (best for resize)
|
|
for img in images:
|
|
if 'ext4' in img.get('name', '') and 'sdcard' in img.get('name', ''):
|
|
print(img['name']); sys.exit()
|
|
# Then sysupgrade
|
|
for img in images:
|
|
if 'sysupgrade' in img.get('name', '') or 'sysupgrade' in img.get('type', ''):
|
|
print(img['name']); sys.exit()
|
|
# Then any sdcard
|
|
for img in images:
|
|
if 'sdcard' in img.get('name', '') or 'sdcard' in img.get('type', ''):
|
|
print(img['name']); sys.exit()
|
|
# Fallback: first image
|
|
if images:
|
|
print(images[0]['name'])
|
|
" "$resp_file" 2>/dev/null)
|
|
|
|
# Extract request_hash (used as store directory) and expected sha256
|
|
local request_hash expected_sha256
|
|
read -r request_hash expected_sha256 < <(python3 -c "
|
|
import json, sys
|
|
with open(sys.argv[1]) as f:
|
|
d = json.load(f)
|
|
rh = d.get('request_hash', '')
|
|
# Find sha256 for the selected image
|
|
sha = ''
|
|
for img in d.get('images', []):
|
|
if img.get('name', '') == sys.argv[2]:
|
|
sha = img.get('sha256', '')
|
|
break
|
|
print(rh, sha)
|
|
" "$resp_file" "$image_name" 2>/dev/null)
|
|
|
|
rm -f "$resp_file"
|
|
|
|
if [[ -z "$image_name" ]]; then
|
|
log_warn "No images found in build response"
|
|
return 1
|
|
fi
|
|
|
|
if [[ -z "$request_hash" ]]; then
|
|
log_error "No request_hash in build response"
|
|
return 1
|
|
fi
|
|
|
|
# ASU store URL uses request_hash as directory
|
|
local download_url="$ASU_URL/store/$request_hash/$image_name"
|
|
local filename="$image_name"
|
|
local output_file="$output_dir/$filename"
|
|
|
|
log_step "Downloading: $filename"
|
|
log_info "URL: $download_url"
|
|
curl -#L -o "$output_file" "$download_url" || {
|
|
log_error "Download failed"
|
|
return 1
|
|
}
|
|
|
|
# Verify size is non-zero
|
|
local file_size
|
|
file_size=$(stat -c%s "$output_file" 2>/dev/null || echo 0)
|
|
if [[ "$file_size" -eq 0 ]]; then
|
|
log_error "Downloaded file is empty"
|
|
rm -f "$output_file"
|
|
return 1
|
|
fi
|
|
|
|
# Verify SHA256 if available
|
|
local sha256
|
|
sha256=$(sha256sum "$output_file" | awk '{print $1}')
|
|
log_info "Downloaded: $output_file"
|
|
log_info "SHA256: $sha256"
|
|
log_info "Size: $(du -h "$output_file" | awk '{print $1}')"
|
|
|
|
if [[ -n "$expected_sha256" && "$sha256" != "$expected_sha256" ]]; then
|
|
log_warn "SHA256 mismatch! Expected: $expected_sha256"
|
|
elif [[ -n "$expected_sha256" ]]; then
|
|
log_info "SHA256 verified OK"
|
|
fi
|
|
|
|
echo "$output_file"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Commands
|
|
# =============================================================================
|
|
|
|
cmd_build() {
|
|
local device="${1:-$DEFAULT_DEVICE}"
|
|
|
|
# Check dependencies
|
|
for cmd in curl python3; do
|
|
command -v "$cmd" >/dev/null 2>&1 || {
|
|
log_error "Required: $cmd"
|
|
return 1
|
|
}
|
|
done
|
|
|
|
echo -e "${BOLD}SecuBox Image Builder${NC}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
|
|
# Generate request
|
|
log_step "Generating build request for $device..."
|
|
local json
|
|
json=$(build_request_json "$device")
|
|
|
|
local json_file
|
|
json_file=$(mktemp)
|
|
echo "$json" > "$json_file"
|
|
trap "rm -f $json_file" EXIT
|
|
|
|
local pkg_count
|
|
pkg_count=$(echo "$json" | python3 -c 'import sys,json; print(len(json.load(sys.stdin)["packages"]))' 2>/dev/null || echo "?")
|
|
log_info "OpenWrt: $OPENWRT_VERSION"
|
|
log_info "Rootfs: 1024 MB (maximum)"
|
|
log_info "Packages: $pkg_count official + SecuBox feed at boot"
|
|
|
|
# Submit build
|
|
local response
|
|
response=$(api_build "$json_file") || return 1
|
|
|
|
# Check if response already has images (cached build) or needs polling
|
|
local has_images request_hash
|
|
has_images=$(echo "$response" | python3 -c 'import sys,json; print(len(json.load(sys.stdin).get("images",[])))' 2>/dev/null || echo "0")
|
|
request_hash=$(echo "$response" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("request_hash",""))' 2>/dev/null || echo "")
|
|
|
|
local image_file=""
|
|
local build_result="$response"
|
|
|
|
if [[ "$has_images" -gt 0 ]]; then
|
|
# Cached build — already has images, skip polling
|
|
image_file=$(download_image "$build_result") || return 1
|
|
elif [[ -n "$request_hash" ]]; then
|
|
# Need to poll for completion
|
|
build_result=$(poll_build "$request_hash") || return 1
|
|
echo "" >&2
|
|
image_file=$(download_image "$build_result") || return 1
|
|
else
|
|
log_error "No images or request hash in response"
|
|
echo "$response" >&2
|
|
return 1
|
|
fi
|
|
|
|
# Resize image if requested (for full eMMC utilization)
|
|
if [[ -n "$RESIZE_TARGET" && -n "$image_file" ]]; then
|
|
local resize_script="$SCRIPT_DIR/resize-openwrt-image.sh"
|
|
if [[ ! -x "$resize_script" ]]; then
|
|
log_error "Resize script not found: $resize_script"
|
|
log_info "Image saved without resize: $image_file"
|
|
return 1
|
|
fi
|
|
|
|
local resized_file="${image_file%.img.gz}-${RESIZE_TARGET}.img.gz"
|
|
log_step "Resizing image to $RESIZE_TARGET for full eMMC..."
|
|
|
|
# resize-openwrt-image.sh requires root for loop devices
|
|
local sudo_cmd=""
|
|
if [ "$(id -u)" -ne 0 ]; then
|
|
sudo_cmd="sudo"
|
|
log_info "Root required for resize (loop devices). Running with sudo..."
|
|
fi
|
|
|
|
$sudo_cmd "$resize_script" "$image_file" "$RESIZE_TARGET" "$resized_file" || {
|
|
log_error "Resize failed"
|
|
log_info "To resize manually: sudo $resize_script $image_file $RESIZE_TARGET $resized_file"
|
|
log_info "Original image: $image_file"
|
|
return 1
|
|
}
|
|
|
|
log_info "Resized image: $resized_file"
|
|
log_info "Original image: $image_file"
|
|
echo "$resized_file"
|
|
else
|
|
echo "$image_file"
|
|
fi
|
|
}
|
|
|
|
cmd_firmware_selector() {
|
|
local device="${1:-$DEFAULT_DEVICE}"
|
|
parse_device "$device"
|
|
|
|
local packages
|
|
packages=$(get_packages "$device")
|
|
|
|
echo -e "${BOLD}SecuBox Firmware Selector Configuration${NC}"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo -e "${CYAN}1. Open:${NC} https://firmware-selector.openwrt.org/"
|
|
echo -e "${CYAN}2. Select version:${NC} $OPENWRT_VERSION"
|
|
echo -e "${CYAN}3. Search device:${NC} $DEVICE"
|
|
echo -e "${CYAN}4. Click:${NC} 'Customize installed packages and/or first boot script'"
|
|
echo ""
|
|
echo -e "${BOLD}═══ PACKAGES (paste into 'Installed Packages' field) ═══${NC}"
|
|
echo ""
|
|
echo "$packages"
|
|
echo ""
|
|
echo -e "${BOLD}═══ SCRIPT (paste into 'Script to run on first boot' field) ═══${NC}"
|
|
echo ""
|
|
generate_defaults
|
|
echo ""
|
|
echo -e "${BOLD}═══ ROOTFS SIZE ═══${NC}"
|
|
echo ""
|
|
echo "Set 'Root filesystem partition size' to: 1024 MB"
|
|
echo ""
|
|
echo -e "${CYAN}5. Click:${NC} 'Request Build'"
|
|
echo -e "${CYAN}6. Download:${NC} SYSUPGRADE or SDCARD image"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
}
|
|
|
|
cmd_status() {
|
|
local hash="$1"
|
|
[[ -z "$hash" ]] && { log_error "Usage: $0 status <hash>"; return 1; }
|
|
|
|
local response
|
|
response=$(curl -sL "$ASU_URL/api/v1/build/$hash")
|
|
echo "$response" | python3 -m json.tool 2>/dev/null || echo "$response"
|
|
}
|
|
|
|
cmd_download() {
|
|
local hash="$1"
|
|
[[ -z "$hash" ]] && { log_error "Usage: $0 download <hash>"; return 1; }
|
|
|
|
local response
|
|
response=$(curl -sL "$ASU_URL/api/v1/build/$hash")
|
|
download_image "$response"
|
|
}
|
|
|
|
# =============================================================================
|
|
# Main
|
|
# =============================================================================
|
|
|
|
# Parse global options
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-v|--version)
|
|
OPENWRT_VERSION="$2"
|
|
shift 2
|
|
;;
|
|
-o|--output)
|
|
OUTPUT_DIR="$2"
|
|
shift 2
|
|
;;
|
|
-r|--resize)
|
|
RESIZE_TARGET="$2"
|
|
shift 2
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
done
|
|
|
|
case "${1:-}" in
|
|
build)
|
|
shift
|
|
cmd_build "${1:-$DEFAULT_DEVICE}"
|
|
;;
|
|
firmware-selector|fs)
|
|
shift
|
|
cmd_firmware_selector "${1:-$DEFAULT_DEVICE}"
|
|
;;
|
|
status)
|
|
shift
|
|
cmd_status "${1:-}"
|
|
;;
|
|
download)
|
|
shift
|
|
cmd_download "${1:-}"
|
|
;;
|
|
help|--help|-h|"")
|
|
usage
|
|
;;
|
|
*)
|
|
log_error "Unknown command: $1"
|
|
usage >&2
|
|
exit 1
|
|
;;
|
|
esac
|