New secubox-app-smbfs package for SMB/CIFS remote directory management with smbfsctl CLI (add/remove/mount/umount/test/status), UCI config, auto-mount init script, and Jellyfin/Lyrion media path integration. Glances LXC: host bind mounts (/rom, /overlay, /boot, /srv), Docker socket fix (symlink loop), fs plugin @exit_after patch, hostname/OS identity, pre-generated /etc/mtab. KISS READMEs for secubox-app-jellyfin and luci-app-jellyfin. Planning files updated with Domoticz IoT, AI Gateway strategy, App Store P2P emancipation, and v2 roadmap items. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
443 lines
12 KiB
Bash
443 lines
12 KiB
Bash
#!/bin/sh
|
|
# SecuBox Glances manager - LXC container support
|
|
# Copyright (C) 2025-2026 CyberMind.fr
|
|
|
|
CONFIG="glances"
|
|
LXC_NAME="glances"
|
|
OPKG_UPDATED=0
|
|
|
|
# Paths
|
|
LXC_PATH="/srv/lxc"
|
|
LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs"
|
|
LXC_CONFIG="$LXC_PATH/$LXC_NAME/config"
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
Usage: glancesctl <command>
|
|
|
|
Commands:
|
|
install Install prerequisites and create LXC container
|
|
check Run prerequisite checks
|
|
update Update Glances in container
|
|
status Show container status
|
|
logs Show Glances logs (use -f to follow)
|
|
shell Open shell in container
|
|
service-run Internal: run container under procd
|
|
service-stop Stop container
|
|
|
|
Web Interface: http://<router-ip>:61208
|
|
API Endpoint: http://<router-ip>:61209
|
|
EOF
|
|
}
|
|
|
|
require_root() { [ "$(id -u)" -eq 0 ] || { echo "Root required" >&2; exit 1; }; }
|
|
|
|
log_info() { echo "[INFO] $*"; }
|
|
log_warn() { echo "[WARN] $*" >&2; }
|
|
log_error() { echo "[ERROR] $*" >&2; }
|
|
|
|
uci_get() { uci -q get ${CONFIG}.$1; }
|
|
uci_set() { uci set ${CONFIG}.$1="$2" && uci commit ${CONFIG}; }
|
|
|
|
# Load configuration with defaults
|
|
load_config() {
|
|
web_port="$(uci_get main.web_port || echo 61208)"
|
|
api_port="$(uci_get main.api_port || echo 61209)"
|
|
web_host="$(uci_get main.web_host || echo 0.0.0.0)"
|
|
refresh_rate="$(uci_get main.refresh_rate || echo 3)"
|
|
memory_limit="$(uci_get main.memory_limit || echo 128M)"
|
|
|
|
# Monitoring options
|
|
monitor_docker="$(uci_get monitoring.monitor_docker || echo 1)"
|
|
monitor_network="$(uci_get monitoring.monitor_network || echo 1)"
|
|
monitor_diskio="$(uci_get monitoring.monitor_diskio || echo 1)"
|
|
monitor_sensors="$(uci_get monitoring.monitor_sensors || echo 1)"
|
|
|
|
# Alert thresholds
|
|
cpu_warning="$(uci_get alerts.cpu_warning || echo 70)"
|
|
cpu_critical="$(uci_get alerts.cpu_critical || echo 90)"
|
|
mem_warning="$(uci_get alerts.mem_warning || echo 70)"
|
|
mem_critical="$(uci_get alerts.mem_critical || echo 90)"
|
|
}
|
|
|
|
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
|
|
|
|
has_lxc() {
|
|
command -v lxc-start >/dev/null 2>&1 && \
|
|
command -v lxc-stop >/dev/null 2>&1
|
|
}
|
|
|
|
# Ensure required packages are installed
|
|
ensure_packages() {
|
|
require_root
|
|
for pkg in "$@"; do
|
|
if ! opkg list-installed | grep -q "^$pkg "; then
|
|
if [ "$OPKG_UPDATED" -eq 0 ]; then
|
|
opkg update || return 1
|
|
OPKG_UPDATED=1
|
|
fi
|
|
opkg install "$pkg" || return 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
# =============================================================================
|
|
# LXC CONTAINER FUNCTIONS
|
|
# =============================================================================
|
|
|
|
lxc_check_prereqs() {
|
|
log_info "Checking LXC prerequisites..."
|
|
ensure_packages lxc lxc-common lxc-attach lxc-start lxc-stop lxc-destroy || return 1
|
|
|
|
if [ ! -d /sys/fs/cgroup ]; then
|
|
log_error "cgroups not mounted at /sys/fs/cgroup"
|
|
return 1
|
|
fi
|
|
|
|
log_info "LXC ready"
|
|
}
|
|
|
|
lxc_create_rootfs() {
|
|
load_config
|
|
|
|
if [ -d "$LXC_ROOTFS" ] && [ -x "$LXC_ROOTFS/usr/local/bin/glances" ]; then
|
|
log_info "LXC rootfs already exists with Glances"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Creating LXC rootfs for Glances..."
|
|
ensure_dir "$LXC_PATH/$LXC_NAME"
|
|
|
|
lxc_create_docker_rootfs || return 1
|
|
lxc_create_config || return 1
|
|
|
|
log_info "LXC rootfs created successfully"
|
|
}
|
|
|
|
lxc_create_docker_rootfs() {
|
|
local rootfs="$LXC_ROOTFS"
|
|
local image="nicolargo/glances"
|
|
local tag="latest-full"
|
|
local registry="registry-1.docker.io"
|
|
local arch
|
|
|
|
# Detect architecture for Docker manifest
|
|
case "$(uname -m)" in
|
|
x86_64) arch="amd64" ;;
|
|
aarch64) arch="arm64" ;;
|
|
armv7l) arch="arm" ;;
|
|
*) arch="amd64" ;;
|
|
esac
|
|
|
|
log_info "Extracting Glances Docker image ($arch)..."
|
|
ensure_dir "$rootfs"
|
|
|
|
# Get Docker Hub token
|
|
local token=$(wget -q -O - "https://auth.docker.io/token?service=registry.docker.io&scope=repository:$image:pull" | jsonfilter -e '@.token')
|
|
[ -z "$token" ] && { log_error "Failed to get Docker Hub token"; return 1; }
|
|
|
|
# Get manifest list
|
|
local manifest=$(wget -q -O - --header="Authorization: Bearer $token" \
|
|
--header="Accept: application/vnd.docker.distribution.manifest.list.v2+json" \
|
|
"https://$registry/v2/$image/manifests/$tag")
|
|
|
|
# Find digest for our architecture
|
|
local digest=$(echo "$manifest" | jsonfilter -e "@.manifests[@.platform.architecture='$arch'].digest")
|
|
[ -z "$digest" ] && { log_error "No manifest found for $arch"; return 1; }
|
|
|
|
# Get image manifest
|
|
local img_manifest=$(wget -q -O - --header="Authorization: Bearer $token" \
|
|
--header="Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
|
"https://$registry/v2/$image/manifests/$digest")
|
|
|
|
# Extract layers and download them
|
|
log_info "Downloading and extracting layers..."
|
|
local layers=$(echo "$img_manifest" | jsonfilter -e '@.layers[*].digest')
|
|
|
|
for layer_digest in $layers; do
|
|
log_info " Layer: ${layer_digest:7:12}..."
|
|
wget -q -O - --header="Authorization: Bearer $token" \
|
|
"https://$registry/v2/$image/blobs/$layer_digest" | \
|
|
tar xzf - -C "$rootfs" 2>&1 | grep -v "Cannot change ownership" || true
|
|
done
|
|
|
|
# Configure container
|
|
echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf"
|
|
mkdir -p "$rootfs/var/log/glances" "$rootfs/etc/glances" "$rootfs/tmp"
|
|
mkdir -p "$rootfs/host" "$rootfs/rom" "$rootfs/overlay" "$rootfs/boot" "$rootfs/srv"
|
|
mkdir -p "$rootfs/run"
|
|
touch "$rootfs/run/docker.sock"
|
|
|
|
# Ensure /bin/sh exists
|
|
if [ ! -x "$rootfs/bin/sh" ]; then
|
|
if [ -x "$rootfs/bin/dash" ]; then
|
|
ln -sf dash "$rootfs/bin/sh"
|
|
elif [ -x "$rootfs/bin/bash" ]; then
|
|
ln -sf bash "$rootfs/bin/sh"
|
|
elif [ -x "$rootfs/usr/bin/dash" ]; then
|
|
mkdir -p "$rootfs/bin"
|
|
ln -sf /usr/bin/dash "$rootfs/bin/sh"
|
|
fi
|
|
fi
|
|
|
|
# Patch Glances: disable @exit_after on fs plugin (multiprocessing
|
|
# fails inside LXC containers, causing all disk_usage calls to return None)
|
|
local fs_init="$rootfs/app/glances/plugins/fs/__init__.py"
|
|
if [ -f "$fs_init" ]; then
|
|
sed -i 's/^@exit_after(/#@exit_after(/' "$fs_init"
|
|
log_info "Patched Glances fs plugin for LXC compatibility"
|
|
fi
|
|
|
|
# Generate /etc/mtab from host mounts (psutil reads this for disk_partitions)
|
|
grep -E '^(/dev/|overlayfs:)' /proc/mounts | \
|
|
grep -v '/srv/docker/overlay' > "$rootfs/etc/mtab" 2>/dev/null
|
|
|
|
# Create startup script (written via file, not heredoc, to preserve shebang)
|
|
printf '%s\n' '#!/bin/sh' \
|
|
'export PATH="/usr/local/bin:/usr/bin:/bin:$PATH"' \
|
|
'export PYTHONPATH="/app:$PYTHONPATH"' \
|
|
'cd /' \
|
|
'' \
|
|
'# Set hostname from host kernel (proc:mixed exposes this)' \
|
|
'REAL_HOSTNAME=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo glances)' \
|
|
'hostname "$REAL_HOSTNAME" 2>/dev/null' \
|
|
'printf "127.0.0.1\tlocalhost %s glances\n::1\t\tlocalhost ip6-localhost\n" "$REAL_HOSTNAME" > /etc/hosts' \
|
|
'' \
|
|
'# Copy host os-release so Glances reports OpenWrt, not Alpine' \
|
|
'if [ -f /host/openwrt_release ]; then' \
|
|
" RELEASE=\$(grep DISTRIB_RELEASE /host/openwrt_release 2>/dev/null | cut -d\"'\" -f2)" \
|
|
' printf '"'"'NAME="OpenWrt"\nVERSION="%s"\nID=openwrt\nPRETTY_NAME="OpenWrt %s"\n'"'"' "$RELEASE" "$RELEASE" > /etc/os-release' \
|
|
'fi' \
|
|
'' \
|
|
'WEB_PORT="${GLANCES_WEB_PORT:-61208}"' \
|
|
'WEB_HOST="${GLANCES_WEB_HOST:-0.0.0.0}"' \
|
|
'REFRESH="${GLANCES_REFRESH:-3}"' \
|
|
'' \
|
|
'ARGS="-w -B $WEB_HOST -p $WEB_PORT -t $REFRESH --disable-autodiscover --disable-check-update"' \
|
|
'[ "$GLANCES_NO_DOCKER" = "1" ] && ARGS="$ARGS --disable-plugin docker"' \
|
|
'[ "$GLANCES_NO_SENSORS" = "1" ] && ARGS="$ARGS --disable-plugin sensors"' \
|
|
'' \
|
|
'echo "Starting Glances on ${WEB_HOST}:${WEB_PORT} host=${REAL_HOSTNAME}..."' \
|
|
'exec /venv/bin/python -m glances $ARGS' \
|
|
> "$rootfs/opt/start-glances.sh"
|
|
chmod +x "$rootfs/opt/start-glances.sh"
|
|
|
|
log_info "Glances Docker image extracted successfully"
|
|
}
|
|
|
|
lxc_create_config() {
|
|
load_config
|
|
|
|
local hostname=$(cat /proc/sys/kernel/hostname 2>/dev/null || echo glances)
|
|
local mem_bytes=$(echo "$memory_limit" | sed 's/M/000000/')
|
|
|
|
cat > "$LXC_CONFIG" << EOF
|
|
# Glances LXC Configuration — full host visibility
|
|
lxc.uts.name = $hostname
|
|
lxc.rootfs.path = dir:$LXC_ROOTFS
|
|
|
|
# Network - host network for full visibility
|
|
lxc.net.0.type = none
|
|
|
|
# Auto-mounts
|
|
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
|
|
|
|
# Host filesystem bind mounts (read-only) for disk monitoring
|
|
lxc.mount.entry = /rom rom none bind,ro,create=dir 0 0
|
|
lxc.mount.entry = /overlay overlay none bind,ro,create=dir 0 0
|
|
lxc.mount.entry = /boot boot none bind,ro,create=dir 0 0
|
|
lxc.mount.entry = /srv srv none bind,ro,create=dir 0 0
|
|
|
|
# Host info for OS identification
|
|
lxc.mount.entry = /etc/openwrt_release host/openwrt_release none bind,ro,create=file 0 0
|
|
lxc.mount.entry = /etc/openwrt_version host/openwrt_version none bind,ro,create=file 0 0
|
|
|
|
# Docker socket for container monitoring (mount at /run, not /var/run which is a symlink)
|
|
lxc.mount.entry = /var/run/docker.sock run/docker.sock none bind,create=file 0 0
|
|
|
|
# Environment variables
|
|
lxc.environment = GLANCES_WEB_PORT=$web_port
|
|
lxc.environment = GLANCES_WEB_HOST=$web_host
|
|
lxc.environment = GLANCES_REFRESH=$refresh_rate
|
|
lxc.environment = GLANCES_NO_DOCKER=$([ "$monitor_docker" = "0" ] && echo 1 || echo 0)
|
|
lxc.environment = GLANCES_NO_SENSORS=$([ "$monitor_sensors" = "0" ] && echo 1 || echo 0)
|
|
|
|
# Capabilities
|
|
lxc.cap.drop = sys_module mac_admin mac_override sys_rawio
|
|
|
|
# cgroups limits
|
|
lxc.cgroup2.memory.max = $mem_bytes
|
|
|
|
# Init
|
|
lxc.init.cmd = /opt/start-glances.sh
|
|
|
|
# Console
|
|
lxc.console.size = 1024
|
|
lxc.pty.max = 1024
|
|
EOF
|
|
|
|
log_info "LXC config created at $LXC_CONFIG"
|
|
}
|
|
|
|
lxc_stop() {
|
|
if lxc-info -n "$LXC_NAME" >/dev/null 2>&1; then
|
|
lxc-stop -n "$LXC_NAME" -k >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
|
|
lxc_run() {
|
|
load_config
|
|
lxc_stop
|
|
|
|
if [ ! -f "$LXC_CONFIG" ]; then
|
|
log_error "LXC not configured. Run 'glancesctl install' first."
|
|
return 1
|
|
fi
|
|
|
|
# Regenerate config to pick up any UCI changes
|
|
lxc_create_config
|
|
|
|
# Regenerate /etc/mtab from host mounts (psutil reads this)
|
|
grep -E '^(/dev/|overlayfs:)' /proc/mounts | \
|
|
grep -v '/srv/docker/overlay' > "$LXC_ROOTFS/etc/mtab" 2>/dev/null
|
|
|
|
log_info "Starting Glances LXC container..."
|
|
log_info "Web interface: http://0.0.0.0:$web_port"
|
|
exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONFIG"
|
|
}
|
|
|
|
lxc_status() {
|
|
load_config
|
|
echo "=== Glances Status ==="
|
|
echo ""
|
|
|
|
if lxc-info -n "$LXC_NAME" >/dev/null 2>&1; then
|
|
lxc-info -n "$LXC_NAME"
|
|
else
|
|
echo "LXC container '$LXC_NAME' not found or not configured"
|
|
fi
|
|
|
|
echo ""
|
|
echo "=== Configuration ==="
|
|
echo "Web port: $web_port"
|
|
echo "Refresh rate: ${refresh_rate}s"
|
|
echo "Memory limit: $memory_limit"
|
|
}
|
|
|
|
lxc_logs() {
|
|
if [ "$1" = "-f" ]; then
|
|
logread -f -e glances
|
|
else
|
|
logread -e glances | tail -100
|
|
fi
|
|
}
|
|
|
|
lxc_shell() {
|
|
lxc-attach -n "$LXC_NAME" -- /bin/sh
|
|
}
|
|
|
|
lxc_destroy() {
|
|
lxc_stop
|
|
if [ -d "$LXC_PATH/$LXC_NAME" ]; then
|
|
rm -rf "$LXC_PATH/$LXC_NAME"
|
|
log_info "LXC container destroyed"
|
|
fi
|
|
}
|
|
|
|
# =============================================================================
|
|
# COMMANDS
|
|
# =============================================================================
|
|
|
|
cmd_install() {
|
|
require_root
|
|
load_config
|
|
|
|
if ! has_lxc; then
|
|
log_error "LXC not available. Install lxc packages first."
|
|
exit 1
|
|
fi
|
|
|
|
log_info "Installing Glances..."
|
|
|
|
lxc_check_prereqs || exit 1
|
|
lxc_create_rootfs || exit 1
|
|
|
|
uci_set main.enabled '1'
|
|
/etc/init.d/glances enable
|
|
|
|
log_info "Glances installed."
|
|
log_info "Start with: /etc/init.d/glances start"
|
|
log_info "Web interface: http://<router-ip>:$web_port"
|
|
}
|
|
|
|
cmd_check() {
|
|
load_config
|
|
|
|
log_info "Checking prerequisites..."
|
|
if has_lxc; then
|
|
log_info "LXC: available"
|
|
lxc_check_prereqs
|
|
else
|
|
log_warn "LXC: not available"
|
|
fi
|
|
}
|
|
|
|
cmd_update() {
|
|
require_root
|
|
load_config
|
|
|
|
log_info "Updating Glances..."
|
|
lxc_destroy
|
|
lxc_create_rootfs || exit 1
|
|
|
|
if /etc/init.d/glances enabled >/dev/null 2>&1; then
|
|
/etc/init.d/glances restart
|
|
else
|
|
log_info "Update complete. Restart manually to apply."
|
|
fi
|
|
}
|
|
|
|
cmd_status() {
|
|
lxc_status
|
|
}
|
|
|
|
cmd_logs() {
|
|
lxc_logs "$@"
|
|
}
|
|
|
|
cmd_shell() {
|
|
lxc_shell
|
|
}
|
|
|
|
cmd_service_run() {
|
|
require_root
|
|
load_config
|
|
|
|
if ! has_lxc; then
|
|
log_error "LXC not available"
|
|
exit 1
|
|
fi
|
|
|
|
lxc_check_prereqs || exit 1
|
|
lxc_run
|
|
}
|
|
|
|
cmd_service_stop() {
|
|
require_root
|
|
lxc_stop
|
|
}
|
|
|
|
# Main Entry Point
|
|
case "${1:-}" in
|
|
install) shift; cmd_install "$@" ;;
|
|
check) shift; cmd_check "$@" ;;
|
|
update) shift; cmd_update "$@" ;;
|
|
status) shift; cmd_status "$@" ;;
|
|
logs) shift; cmd_logs "$@" ;;
|
|
shell) shift; cmd_shell "$@" ;;
|
|
service-run) shift; cmd_service_run "$@" ;;
|
|
service-stop) shift; cmd_service_stop "$@" ;;
|
|
help|--help|-h|'') usage ;;
|
|
*) echo "Unknown command: $1" >&2; usage >&2; exit 1 ;;
|
|
esac
|