- Add luci-app-vm for LXC container management dashboard - Status bar with total/running/stopped containers, disk usage - Container cards with Start/Stop/Restart, Snapshot, Export - RPCD handler with 10 methods - Fix Vortex Firewall statistics tracking - Replace x47 multiplier with unique_ips metric - Read blocks from BIND RPZ log via stats file - RPCD now returns unique_ips count - Add c3box-vm-builder.sh for portable VM creation - Downloads OpenWrt x86-64 image - Injects SecuBox configuration - Converts to VMDK/VDI/OVA formats Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
403 lines
10 KiB
Bash
Executable File
403 lines
10 KiB
Bash
Executable File
#!/bin/sh
|
|
# SecuBox VM Manager RPCD Handler
|
|
# Manages LXC containers and VM exports
|
|
|
|
. /lib/functions.sh
|
|
. /usr/share/libubox/jshn.sh
|
|
|
|
LXC_PATH="/srv/lxc"
|
|
|
|
# Get list of all containers
|
|
get_containers() {
|
|
local containers=""
|
|
for dir in "$LXC_PATH"/*/; do
|
|
[ -d "$dir" ] || continue
|
|
local name=$(basename "$dir")
|
|
[ -f "$dir/config" ] || continue
|
|
containers="$containers $name"
|
|
done
|
|
echo "$containers"
|
|
}
|
|
|
|
# Get container state
|
|
get_container_state() {
|
|
local name="$1"
|
|
lxc-info -n "$name" -s 2>/dev/null | awk '{print $2}'
|
|
}
|
|
|
|
# Get container info
|
|
get_container_info() {
|
|
local name="$1"
|
|
local state=$(get_container_state "$name")
|
|
local config="$LXC_PATH/$name/config"
|
|
local rootfs_size="0"
|
|
local memory="0"
|
|
local arch=""
|
|
local init_cmd=""
|
|
|
|
if [ -f "$config" ]; then
|
|
arch=$(grep "lxc.arch" "$config" | cut -d= -f2 | tr -d ' ')
|
|
init_cmd=$(grep "lxc.init.cmd" "$config" | cut -d= -f2 | tr -d ' ')
|
|
memory=$(grep "lxc.cgroup2.memory.max" "$config" | cut -d= -f2 | tr -d ' ')
|
|
[ -z "$memory" ] && memory=$(grep "lxc.cgroup.memory.limit_in_bytes" "$config" | cut -d= -f2 | tr -d ' ')
|
|
fi
|
|
|
|
if [ -d "$LXC_PATH/$name/rootfs" ]; then
|
|
rootfs_size=$(du -sm "$LXC_PATH/$name/rootfs" 2>/dev/null | cut -f1)
|
|
fi
|
|
|
|
json_add_object "$name"
|
|
json_add_string "name" "$name"
|
|
json_add_string "state" "${state:-stopped}"
|
|
json_add_string "arch" "${arch:-aarch64}"
|
|
json_add_int "rootfs_mb" "${rootfs_size:-0}"
|
|
json_add_string "memory" "${memory:-unlimited}"
|
|
json_add_string "init_cmd" "$init_cmd"
|
|
json_close_object
|
|
}
|
|
|
|
# List all containers
|
|
method_list() {
|
|
json_init
|
|
json_add_array "containers"
|
|
|
|
for name in $(get_containers); do
|
|
local state=$(get_container_state "$name")
|
|
local rootfs_size=$(du -sm "$LXC_PATH/$name/rootfs" 2>/dev/null | cut -f1)
|
|
|
|
json_add_object
|
|
json_add_string "name" "$name"
|
|
json_add_string "state" "${state:-stopped}"
|
|
json_add_int "rootfs_mb" "${rootfs_size:-0}"
|
|
json_close_object
|
|
done
|
|
|
|
json_close_array
|
|
json_dump
|
|
}
|
|
|
|
# Get status summary
|
|
method_status() {
|
|
local total=0
|
|
local running=0
|
|
local stopped=0
|
|
|
|
for name in $(get_containers); do
|
|
total=$((total + 1))
|
|
local state=$(get_container_state "$name")
|
|
if [ "$state" = "RUNNING" ]; then
|
|
running=$((running + 1))
|
|
else
|
|
stopped=$((stopped + 1))
|
|
fi
|
|
done
|
|
|
|
# Get disk usage
|
|
local disk_used=$(du -sm "$LXC_PATH" 2>/dev/null | cut -f1)
|
|
local disk_free=$(df -m /srv 2>/dev/null | tail -1 | awk '{print $4}')
|
|
|
|
json_init
|
|
json_add_int "total" "$total"
|
|
json_add_int "running" "$running"
|
|
json_add_int "stopped" "$stopped"
|
|
json_add_int "disk_used_mb" "${disk_used:-0}"
|
|
json_add_int "disk_free_mb" "${disk_free:-0}"
|
|
json_add_string "lxc_path" "$LXC_PATH"
|
|
json_dump
|
|
}
|
|
|
|
# Get detailed container info
|
|
method_info() {
|
|
local name="$1"
|
|
|
|
[ -d "$LXC_PATH/$name" ] || {
|
|
json_init
|
|
json_add_string "error" "Container not found"
|
|
json_dump
|
|
return
|
|
}
|
|
|
|
json_init
|
|
get_container_info "$name"
|
|
|
|
# Add extra details for single container
|
|
local pid=""
|
|
local ips=""
|
|
|
|
if [ "$(get_container_state "$name")" = "RUNNING" ]; then
|
|
pid=$(lxc-info -n "$name" -p 2>/dev/null | awk '{print $2}')
|
|
ips=$(lxc-info -n "$name" -i 2>/dev/null | awk '{print $2}' | tr '\n' ',')
|
|
fi
|
|
|
|
json_add_string "pid" "$pid"
|
|
json_add_string "ips" "$ips"
|
|
|
|
# Config file contents
|
|
if [ -f "$LXC_PATH/$name/config" ]; then
|
|
local config=$(cat "$LXC_PATH/$name/config" | head -50)
|
|
json_add_string "config" "$config"
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Get container logs
|
|
method_logs() {
|
|
local name="$1"
|
|
local lines="${2:-50}"
|
|
|
|
json_init
|
|
|
|
# Try various log sources
|
|
local log=""
|
|
if [ -f "/var/log/lxc/$name.log" ]; then
|
|
log=$(tail -n "$lines" "/var/log/lxc/$name.log" 2>/dev/null)
|
|
elif [ -f "/tmp/$name.log" ]; then
|
|
log=$(tail -n "$lines" "/tmp/$name.log" 2>/dev/null)
|
|
else
|
|
log="No logs available for container: $name"
|
|
fi
|
|
|
|
json_add_string "name" "$name"
|
|
json_add_string "logs" "$log"
|
|
json_dump
|
|
}
|
|
|
|
# Start container
|
|
method_start() {
|
|
local name="$1"
|
|
|
|
json_init
|
|
|
|
if [ ! -d "$LXC_PATH/$name" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Container not found"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
lxc-start -n "$name" 2>/tmp/lxc-start-$name.log
|
|
local rc=$?
|
|
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Container $name started"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "$(cat /tmp/lxc-start-$name.log 2>/dev/null || echo 'Start failed')"
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Stop container
|
|
method_stop() {
|
|
local name="$1"
|
|
|
|
json_init
|
|
|
|
if [ ! -d "$LXC_PATH/$name" ]; then
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Container not found"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
lxc-stop -n "$name" 2>&1
|
|
local rc=$?
|
|
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "message" "Container $name stopped"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Stop failed"
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Restart container
|
|
method_restart() {
|
|
local name="$1"
|
|
|
|
lxc-stop -n "$name" 2>/dev/null
|
|
sleep 1
|
|
method_start "$name"
|
|
}
|
|
|
|
# Create snapshot
|
|
method_snapshot() {
|
|
local name="$1"
|
|
local snap_name="${2:-snapshot-$(date +%Y%m%d-%H%M%S)}"
|
|
local snap_dir="$LXC_PATH/$name/snapshots"
|
|
|
|
json_init
|
|
|
|
mkdir -p "$snap_dir"
|
|
|
|
# Stop if running
|
|
local was_running=0
|
|
if [ "$(get_container_state "$name")" = "RUNNING" ]; then
|
|
was_running=1
|
|
lxc-stop -n "$name" 2>/dev/null
|
|
sleep 1
|
|
fi
|
|
|
|
# Create snapshot
|
|
tar -czf "$snap_dir/$snap_name.tar.gz" -C "$LXC_PATH/$name" rootfs config 2>/dev/null
|
|
local rc=$?
|
|
|
|
# Restart if was running
|
|
[ $was_running -eq 1 ] && lxc-start -n "$name" 2>/dev/null
|
|
|
|
if [ $rc -eq 0 ]; then
|
|
json_add_boolean "success" 1
|
|
json_add_string "snapshot" "$snap_name"
|
|
json_add_string "path" "$snap_dir/$snap_name.tar.gz"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Snapshot failed"
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Export container to VMDK/OVA (for VM builder)
|
|
method_export() {
|
|
local name="$1"
|
|
local format="${2:-tar}"
|
|
local output_dir="${3:-/tmp/vm-export}"
|
|
|
|
json_init
|
|
|
|
mkdir -p "$output_dir"
|
|
|
|
# For now, just create a tar archive
|
|
# Full VMDK/OVA conversion would require qemu-img on the host
|
|
local output="$output_dir/$name-export.tar.gz"
|
|
|
|
tar -czf "$output" -C "$LXC_PATH" "$name" 2>/dev/null
|
|
|
|
if [ -f "$output" ]; then
|
|
local size=$(ls -lh "$output" | awk '{print $5}')
|
|
json_add_boolean "success" 1
|
|
json_add_string "format" "tar.gz"
|
|
json_add_string "path" "$output"
|
|
json_add_string "size" "$size"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "Export failed"
|
|
fi
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Main dispatcher
|
|
case "$1" in
|
|
list)
|
|
case "$2" in
|
|
status)
|
|
method_status
|
|
;;
|
|
list)
|
|
method_list
|
|
;;
|
|
info)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
method_info "$name"
|
|
;;
|
|
logs)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
json_get_var lines lines
|
|
method_logs "$name" "$lines"
|
|
;;
|
|
start|stop|restart|snapshot|export)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
case "$2" in
|
|
start) method_start "$name" ;;
|
|
stop) method_stop "$name" ;;
|
|
restart) method_restart "$name" ;;
|
|
snapshot)
|
|
json_get_var snap_name snap_name
|
|
method_snapshot "$name" "$snap_name"
|
|
;;
|
|
export)
|
|
json_get_var format format
|
|
method_export "$name" "$format"
|
|
;;
|
|
esac
|
|
;;
|
|
*)
|
|
echo '{"status":"invalid_method"}'
|
|
;;
|
|
esac
|
|
;;
|
|
call)
|
|
case "$2" in
|
|
status)
|
|
method_status
|
|
;;
|
|
list)
|
|
method_list
|
|
;;
|
|
info)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
method_info "$name"
|
|
;;
|
|
logs)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
json_get_var lines lines
|
|
method_logs "$name" "$lines"
|
|
;;
|
|
start)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
method_start "$name"
|
|
;;
|
|
stop)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
method_stop "$name"
|
|
;;
|
|
restart)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
method_restart "$name"
|
|
;;
|
|
snapshot)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
json_get_var snap_name snap_name
|
|
method_snapshot "$name" "$snap_name"
|
|
;;
|
|
export)
|
|
read -r input
|
|
json_load "$input"
|
|
json_get_var name name
|
|
json_get_var format format
|
|
method_export "$name" "$format"
|
|
;;
|
|
*)
|
|
echo '{"error":"unknown_method"}'
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
exit 0
|