- Add build_progress RPCD method to track image build status
- Fix handleBuild() to handle RPC expect array unwrapping
- The expect: { devices: [] } unwraps the array, so data IS the array
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
430 lines
11 KiB
Bash
Executable File
430 lines
11 KiB
Bash
Executable File
#!/bin/sh
|
|
#
|
|
# RPCD handler for SecuBox Cloner
|
|
#
|
|
|
|
. /usr/share/libubox/jshn.sh
|
|
|
|
CLONE_DIR="/srv/secubox/clone"
|
|
TFTP_ROOT="/srv/tftp"
|
|
TOKENS_DIR="/var/run/secubox/clone-tokens"
|
|
STATE_FILE="/var/run/secubox/cloner.state"
|
|
|
|
# Detect device type
|
|
detect_device() {
|
|
local board_name=""
|
|
[ -f /tmp/sysinfo/board_name ] && board_name=$(cat /tmp/sysinfo/board_name)
|
|
[ -z "$board_name" ] && board_name=$(uci -q get system.@system[0].hostname 2>/dev/null)
|
|
|
|
case "$board_name" in
|
|
*mochabin*|*MOCHAbin*|globalscale,mochabin) echo "mochabin" ;;
|
|
*espressobin*ultra*) echo "espressobin-ultra" ;;
|
|
*espressobin*) echo "espressobin-v7" ;;
|
|
*x86*|*generic*) echo "x86-64" ;;
|
|
*) echo "unknown" ;;
|
|
esac
|
|
}
|
|
|
|
get_lan_ip() {
|
|
uci -q get network.lan.ipaddr 2>/dev/null || echo "192.168.255.1"
|
|
}
|
|
|
|
do_status() {
|
|
local device_type lan_ip hostname tftp_enabled
|
|
local has_image image_size image_name token_count clone_count
|
|
|
|
json_init
|
|
|
|
# Device info
|
|
device_type=$(detect_device)
|
|
lan_ip=$(get_lan_ip)
|
|
hostname=$(uci -q get system.@system[0].hostname || echo "secubox")
|
|
|
|
json_add_string "device_type" "$device_type"
|
|
json_add_string "lan_ip" "$lan_ip"
|
|
json_add_string "hostname" "$hostname"
|
|
|
|
# TFTP status
|
|
tftp_enabled=$(uci -q get dhcp.@dnsmasq[0].enable_tftp)
|
|
json_add_boolean "tftp_running" "$([ "$tftp_enabled" = "1" ] && echo 1 || echo 0)"
|
|
json_add_string "tftp_root" "$TFTP_ROOT"
|
|
|
|
# Image status
|
|
has_image=0
|
|
image_size=""
|
|
image_name=""
|
|
if [ -f "$TFTP_ROOT/secubox-clone.img" ]; then
|
|
has_image=1
|
|
image_size=$(ls -lh "$TFTP_ROOT/secubox-clone.img" 2>/dev/null | awk '{print $5}')
|
|
image_name="secubox-clone.img"
|
|
fi
|
|
json_add_boolean "has_image" "$has_image"
|
|
json_add_string "image_size" "${image_size:-0}"
|
|
json_add_string "image_name" "${image_name:-}"
|
|
|
|
# Token count
|
|
token_count=0
|
|
[ -d "$TOKENS_DIR" ] && token_count=$(ls "$TOKENS_DIR"/*.json 2>/dev/null | wc -l)
|
|
json_add_int "token_count" "$token_count"
|
|
|
|
# Clone count (from master-link peer-list)
|
|
clone_count=0
|
|
if [ -x /usr/lib/secubox/master-link.sh ]; then
|
|
clone_count=$(/usr/lib/secubox/master-link.sh peer-list 2>/dev/null | grep -c "^[0-9]" || echo 0)
|
|
fi
|
|
json_add_int "clone_count" "$clone_count"
|
|
|
|
# Build state
|
|
if [ -f "$STATE_FILE" ]; then
|
|
. "$STATE_FILE"
|
|
json_add_string "last_build" "${BUILD_TIME:-}"
|
|
else
|
|
json_add_string "last_build" ""
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
do_list_images() {
|
|
local img name device_type
|
|
|
|
json_init
|
|
json_add_array "images"
|
|
|
|
# All TFTP-ready images
|
|
for img in "$TFTP_ROOT"/secubox-clone*.img; do
|
|
[ -f "$img" ] || continue
|
|
name=$(basename "$img")
|
|
# Extract device type from filename
|
|
device_type=$(echo "$name" | sed -n 's/secubox-clone-\(.*\)\.img/\1/p')
|
|
[ -z "$device_type" ] && device_type="mochabin"
|
|
json_add_object ""
|
|
json_add_string "name" "$name"
|
|
json_add_string "path" "$img"
|
|
json_add_string "size" "$(ls -lh "$img" | awk '{print $5}')"
|
|
json_add_string "device" "$device_type"
|
|
json_add_boolean "tftp_ready" 1
|
|
json_close_object
|
|
done
|
|
|
|
# Clone directory images (not yet in TFTP)
|
|
if [ -d "$CLONE_DIR" ]; then
|
|
for img in "$CLONE_DIR"/*.img "$CLONE_DIR"/*.img.gz; do
|
|
[ -f "$img" ] || continue
|
|
name=$(basename "$img")
|
|
# Skip if already in TFTP
|
|
[ -f "$TFTP_ROOT/${name%.gz}" ] && continue
|
|
device_type=$(echo "$name" | sed -n 's/secubox-clone-\(.*\)\.img.*/\1/p')
|
|
[ -z "$device_type" ] && device_type="unknown"
|
|
json_add_object ""
|
|
json_add_string "name" "$name"
|
|
json_add_string "path" "$img"
|
|
json_add_string "size" "$(ls -lh "$img" | awk '{print $5}')"
|
|
json_add_string "device" "$device_type"
|
|
json_add_boolean "tftp_ready" 0
|
|
json_close_object
|
|
done
|
|
fi
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
do_list_tokens() {
|
|
local tf token created used auto
|
|
|
|
json_init
|
|
json_add_array "tokens"
|
|
|
|
if [ -d "$TOKENS_DIR" ]; then
|
|
for tf in "$TOKENS_DIR"/*.json; do
|
|
[ -f "$tf" ] || continue
|
|
token=$(jsonfilter -i "$tf" -e '@.token' 2>/dev/null)
|
|
created=$(jsonfilter -i "$tf" -e '@.created' 2>/dev/null)
|
|
used=$(jsonfilter -i "$tf" -e '@.used' 2>/dev/null)
|
|
auto=$(jsonfilter -i "$tf" -e '@.auto_approve' 2>/dev/null)
|
|
|
|
json_add_object ""
|
|
json_add_string "token" "$token"
|
|
json_add_string "token_short" "${token:0:16}..."
|
|
json_add_string "created" "$created"
|
|
json_add_boolean "used" "$([ "$used" = "true" ] && echo 1 || echo 0)"
|
|
json_add_boolean "auto_approve" "$([ "$auto" = "true" ] && echo 1 || echo 0)"
|
|
json_close_object
|
|
done
|
|
fi
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
do_list_clones() {
|
|
local peer_ip peer_name peer_status
|
|
|
|
json_init
|
|
json_add_array "clones"
|
|
|
|
# Get peer list from WireGuard interfaces (most reliable source)
|
|
# Each wg peer is a potential clone
|
|
for wg in /etc/config/network; do
|
|
# Get WireGuard peers from UCI
|
|
uci -q show network 2>/dev/null | grep "\.public_key=" | while read -r line; do
|
|
peer_name=$(echo "$line" | cut -d'.' -f2)
|
|
# Skip if not a wireguard peer
|
|
echo "$peer_name" | grep -q "^wg" || continue
|
|
peer_ip=$(uci -q get "network.${peer_name}.endpoint_host" 2>/dev/null)
|
|
[ -n "$peer_ip" ] || continue
|
|
json_add_object ""
|
|
json_add_string "info" "$peer_name ($peer_ip)"
|
|
json_add_string "name" "$peer_name"
|
|
json_add_string "ip" "$peer_ip"
|
|
json_add_string "status" "active"
|
|
json_close_object
|
|
done
|
|
break # Only need to run once
|
|
done
|
|
|
|
# Also check master-link peer-list if available
|
|
if [ -x /usr/lib/secubox/master-link.sh ]; then
|
|
/usr/lib/secubox/master-link.sh peer-list 2>/dev/null | grep "^[0-9]" > /tmp/cloner_peers.tmp 2>/dev/null
|
|
while read -r line; do
|
|
[ -n "$line" ] || continue
|
|
json_add_object ""
|
|
json_add_string "info" "$line"
|
|
json_add_string "status" "mesh"
|
|
json_close_object
|
|
done < /tmp/cloner_peers.tmp 2>/dev/null
|
|
rm -f /tmp/cloner_peers.tmp
|
|
fi
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
do_generate_token() {
|
|
local input auto_approve token token_file
|
|
|
|
read input
|
|
auto_approve=$(echo "$input" | jsonfilter -e '@.auto_approve' 2>/dev/null)
|
|
|
|
mkdir -p "$TOKENS_DIR"
|
|
token=$(head -c 32 /dev/urandom | sha256sum | cut -d' ' -f1)
|
|
token_file="$TOKENS_DIR/${token}.json"
|
|
|
|
cat > "$token_file" <<EOF
|
|
{
|
|
"token": "$token",
|
|
"created": "$(date -Iseconds)",
|
|
"ttl": 86400,
|
|
"auto_approve": $([ "$auto_approve" = "true" ] && echo "true" || echo "false"),
|
|
"type": "clone",
|
|
"used": false
|
|
}
|
|
EOF
|
|
|
|
json_init
|
|
json_add_boolean "success" 1
|
|
json_add_string "token" "$token"
|
|
json_add_string "token_short" "${token:0:16}..."
|
|
json_dump
|
|
}
|
|
|
|
do_build_image() {
|
|
local input device_type
|
|
|
|
read input
|
|
device_type=$(echo "$input" | jsonfilter -e '@.device_type' 2>/dev/null)
|
|
|
|
json_init
|
|
|
|
if [ -x /usr/sbin/secubox-cloner ]; then
|
|
if [ -n "$device_type" ]; then
|
|
(/usr/sbin/secubox-cloner build "$device_type" 2>&1 > /tmp/cloner-build.log) &
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Build started for $device_type"
|
|
else
|
|
(/usr/sbin/secubox-cloner build 2>&1 > /tmp/cloner-build.log) &
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Build started for current device"
|
|
fi
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "message" "secubox-cloner not installed"
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
do_list_devices() {
|
|
json_init
|
|
json_add_array "devices"
|
|
|
|
json_add_object ""
|
|
json_add_string "id" "mochabin"
|
|
json_add_string "name" "Globalscale MOCHAbin"
|
|
json_add_string "cpu" "Cortex-A72"
|
|
json_close_object
|
|
|
|
json_add_object ""
|
|
json_add_string "id" "espressobin-v7"
|
|
json_add_string "name" "Globalscale ESPRESSObin v7"
|
|
json_add_string "cpu" "Cortex-A53"
|
|
json_close_object
|
|
|
|
json_add_object ""
|
|
json_add_string "id" "espressobin-ultra"
|
|
json_add_string "name" "Globalscale ESPRESSObin Ultra"
|
|
json_add_string "cpu" "Cortex-A53"
|
|
json_close_object
|
|
|
|
json_add_object ""
|
|
json_add_string "id" "x86-64"
|
|
json_add_string "name" "Generic x86-64"
|
|
json_add_string "cpu" "x86_64"
|
|
json_close_object
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
do_tftp_start() {
|
|
json_init
|
|
|
|
uci -q set dhcp.@dnsmasq[0].enable_tftp='1'
|
|
uci -q set dhcp.@dnsmasq[0].tftp_root="$TFTP_ROOT"
|
|
uci commit dhcp
|
|
/etc/init.d/dnsmasq restart 2>/dev/null
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "TFTP server started"
|
|
json_dump
|
|
}
|
|
|
|
do_tftp_stop() {
|
|
json_init
|
|
|
|
uci -q set dhcp.@dnsmasq[0].enable_tftp='0'
|
|
uci commit dhcp
|
|
/etc/init.d/dnsmasq restart 2>/dev/null
|
|
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "TFTP server stopped"
|
|
json_dump
|
|
}
|
|
|
|
do_delete_token() {
|
|
local input token
|
|
|
|
read input
|
|
token=$(echo "$input" | jsonfilter -e '@.token' 2>/dev/null)
|
|
|
|
json_init
|
|
if [ -n "$token" ] && [ -f "$TOKENS_DIR/${token}.json" ]; then
|
|
rm -f "$TOKENS_DIR/${token}.json"
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Token deleted"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "message" "Token not found"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
do_delete_image() {
|
|
local input name
|
|
|
|
read input
|
|
name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
|
|
|
|
json_init
|
|
if [ -n "$name" ]; then
|
|
rm -f "$CLONE_DIR/$name" "$TFTP_ROOT/$name" 2>/dev/null
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Image deleted"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "message" "Image not found"
|
|
fi
|
|
json_dump
|
|
}
|
|
|
|
do_build_progress() {
|
|
local building=0 progress=0 stage="" log_tail=""
|
|
|
|
json_init
|
|
|
|
# Check if build is running
|
|
if pgrep -f "secubox-cloner build" >/dev/null 2>&1; then
|
|
building=1
|
|
# Parse log for progress
|
|
if [ -f /tmp/cloner-build.log ]; then
|
|
log_tail=$(tail -5 /tmp/cloner-build.log 2>/dev/null | tr '\n' ' ' | cut -c1-200)
|
|
# Estimate progress from log content
|
|
if grep -q "Downloading" /tmp/cloner-build.log 2>/dev/null; then
|
|
stage="downloading"
|
|
progress=20
|
|
elif grep -q "Compiling\|Building" /tmp/cloner-build.log 2>/dev/null; then
|
|
stage="building"
|
|
progress=50
|
|
elif grep -q "Packaging\|Creating" /tmp/cloner-build.log 2>/dev/null; then
|
|
stage="packaging"
|
|
progress=80
|
|
else
|
|
stage="initializing"
|
|
progress=10
|
|
fi
|
|
fi
|
|
elif [ -f /tmp/cloner-build.log ]; then
|
|
# Build finished - check result
|
|
if grep -q "Build complete\|Successfully" /tmp/cloner-build.log 2>/dev/null; then
|
|
stage="complete"
|
|
progress=100
|
|
elif grep -q "Error\|Failed\|error:" /tmp/cloner-build.log 2>/dev/null; then
|
|
stage="failed"
|
|
progress=0
|
|
fi
|
|
log_tail=$(tail -5 /tmp/cloner-build.log 2>/dev/null | tr '\n' ' ' | cut -c1-200)
|
|
fi
|
|
|
|
json_add_boolean "building" "$building"
|
|
json_add_int "progress" "$progress"
|
|
json_add_string "stage" "$stage"
|
|
json_add_string "log" "$log_tail"
|
|
json_dump
|
|
}
|
|
|
|
case "$1" in
|
|
list)
|
|
echo '{'
|
|
echo '"status":{},'
|
|
echo '"list_images":{},'
|
|
echo '"list_tokens":{},'
|
|
echo '"list_clones":{},'
|
|
echo '"list_devices":{},'
|
|
echo '"build_progress":{},'
|
|
echo '"generate_token":{"auto_approve":"Boolean"},'
|
|
echo '"build_image":{"device_type":"String"},'
|
|
echo '"tftp_start":{},'
|
|
echo '"tftp_stop":{},'
|
|
echo '"delete_token":{"token":"String"},'
|
|
echo '"delete_image":{"name":"String"}'
|
|
echo '}'
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
status) do_status ;;
|
|
list_images) do_list_images ;;
|
|
list_tokens) do_list_tokens ;;
|
|
list_clones) do_list_clones ;;
|
|
list_devices) do_list_devices ;;
|
|
build_progress) do_build_progress ;;
|
|
generate_token) do_generate_token ;;
|
|
build_image) do_build_image ;;
|
|
tftp_start) do_tftp_start ;;
|
|
tftp_stop) do_tftp_stop ;;
|
|
delete_token) do_delete_token ;;
|
|
delete_image) do_delete_image ;;
|
|
esac
|
|
;;
|
|
esac
|