secubox-openwrt/package/secubox/secubox-app-lyrion/files/usr/sbin/lyrionctl
CyberMind-FR 3c1d6d2fd0 feat(lyrion): Add WAN access checkbox for firewall rules
Add wan_access UCI option and LuCI checkbox to optionally open Lyrion
ports (9000, 9090, 3483 TCP+UDP) on the WAN interface. WAN rules are
automatically removed when the option is disabled.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 08:06:54 +01:00

890 lines
23 KiB
Bash
Executable File

#!/bin/sh
# SecuBox Lyrion manager - Multi-runtime support (Docker/LXC)
# Copyright (C) 2024 CyberMind.fr
CONFIG="lyrion"
CONTAINER_NAME="secbx-lyrion"
LXC_NAME="lyrion"
OPKG_UPDATED=0
# Paths
LXC_PATH="/srv/lxc"
LXC_ROOTFS="$LXC_PATH/$LXC_NAME/rootfs"
LXC_CONFIG="$LXC_PATH/$LXC_NAME/config"
LYRION_ROOTFS_SCRIPT="/usr/share/lyrion/create-lxc-rootfs.sh"
usage() {
cat <<'EOF'
Usage: lyrionctl <command>
Commands:
install Install prerequisites, prep folders, pull/create container
check Run prerequisite checks (storage, runtime)
update Update container image and restart service
destroy Remove container and rootfs (for reinstall)
status Show container status
logs Show container logs (use -f to follow)
shell Open shell in container
service-run Internal: run container under procd
service-stop Stop container
runtime Show detected/configured runtime
Runtime Selection:
The runtime can be configured in /etc/config/lyrion:
option runtime 'auto' # auto-detect (LXC preferred if available)
option runtime 'docker' # Force Docker
option runtime 'lxc' # Force LXC
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}.main.$1; }
uci_set() { uci set ${CONFIG}.main.$1="$2" && uci commit ${CONFIG}; }
# Load configuration with defaults
load_config() {
runtime="$(uci_get runtime || echo auto)"
image="$(uci_get image || echo ghcr.io/lms-community/lyrionmusicserver:stable)"
data_path="$(uci_get data_path || echo /srv/lyrion)"
media_path="$(uci_get media_path || echo /srv/media)"
port="$(uci_get port || echo 9000)"
timezone="$(uci_get timezone || cat /etc/TZ 2>/dev/null || echo UTC)"
memory_limit="$(uci_get memory_limit || echo 256M)"
lxc_rootfs_url="$(uci_get lxc_rootfs_url || echo '')"
wan_access="$(uci_get wan_access || echo 0)"
}
ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; }
# Firewall management - open Lyrion ports for LAN device access
firewall_ensure_rules() {
local changed=0
# Lyrion Web UI (TCP 9000)
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-Web"; then
log_info "Creating firewall rule for Lyrion Web UI (TCP $port)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-Web'
uci set firewall.@rule[-1].src='lan'
uci set firewall.@rule[-1].dest_port="$port"
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
# Lyrion CLI (TCP 9090)
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-CLI"; then
log_info "Creating firewall rule for Lyrion CLI (TCP 9090)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-CLI'
uci set firewall.@rule[-1].src='lan'
uci set firewall.@rule[-1].dest_port='9090'
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
# Slim Protocol TCP (TCP 3483) - player control
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-Slim-TCP"; then
log_info "Creating firewall rule for Slim Protocol (TCP 3483)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-Slim-TCP'
uci set firewall.@rule[-1].src='lan'
uci set firewall.@rule[-1].dest_port='3483'
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
# Slim Protocol UDP (UDP 3483) - player discovery
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-Slim-UDP"; then
log_info "Creating firewall rule for Slim Discovery (UDP 3483)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-Slim-UDP'
uci set firewall.@rule[-1].src='lan'
uci set firewall.@rule[-1].dest_port='3483'
uci set firewall.@rule[-1].proto='udp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
# WAN rules (optional, controlled by wan_access UCI option)
if [ "$wan_access" = "1" ]; then
# Lyrion Web UI on WAN
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-WAN-Web"; then
log_info "Creating WAN firewall rule for Lyrion Web UI (TCP $port)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-WAN-Web'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest_port="$port"
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
# Lyrion CLI on WAN
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-WAN-CLI"; then
log_info "Creating WAN firewall rule for Lyrion CLI (TCP 9090)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-WAN-CLI'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest_port='9090'
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
# Slim Protocol TCP on WAN
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-WAN-Slim-TCP"; then
log_info "Creating WAN firewall rule for Slim Protocol (TCP 3483)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-WAN-Slim-TCP'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest_port='3483'
uci set firewall.@rule[-1].proto='tcp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
# Slim Protocol UDP on WAN
if ! uci show firewall 2>/dev/null | grep -q "Lyrion-WAN-Slim-UDP"; then
log_info "Creating WAN firewall rule for Slim Discovery (UDP 3483)..."
uci add firewall rule
uci set firewall.@rule[-1].name='Lyrion-WAN-Slim-UDP'
uci set firewall.@rule[-1].src='wan'
uci set firewall.@rule[-1].dest_port='3483'
uci set firewall.@rule[-1].proto='udp'
uci set firewall.@rule[-1].target='ACCEPT'
uci set firewall.@rule[-1].enabled='1'
changed=1
fi
else
# Remove WAN rules if wan_access is disabled
local i=0
while uci -q get firewall.@rule[$i] >/dev/null 2>&1; do
local name=$(uci -q get firewall.@rule[$i].name)
case "$name" in
Lyrion-WAN-*)
uci delete "firewall.@rule[$i]"
changed=1
# Don't increment - array shifted after delete
continue
;;
esac
i=$((i + 1))
done
fi
if [ "$changed" = "1" ]; then
uci commit firewall
/etc/init.d/firewall reload 2>/dev/null || true
log_info "Firewall rules updated - Lyrion ports open on LAN${wan_access:+/WAN}"
fi
}
# Check if a runtime is available
has_docker() {
command -v docker >/dev/null 2>&1 && \
command -v dockerd >/dev/null 2>&1 && \
[ -S /var/run/docker.sock ]
}
has_lxc() {
command -v lxc-start >/dev/null 2>&1 && \
command -v lxc-stop >/dev/null 2>&1
}
# Detect best available runtime
detect_runtime() {
load_config
case "$runtime" in
docker)
if has_docker; then
echo "docker"
else
log_error "Docker requested but not available"
return 1
fi
;;
lxc)
if has_lxc; then
echo "lxc"
else
log_error "LXC requested but not available"
return 1
fi
;;
auto|*)
# Prefer LXC if available (lighter weight)
if has_lxc; then
echo "lxc"
elif has_docker; then
echo "docker"
else
log_error "No container runtime available (install lxc or docker)"
return 1
fi
;;
esac
}
# 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
}
# =============================================================================
# Docker Runtime Functions
# =============================================================================
docker_check_prereqs() {
log_info "Checking Docker prerequisites..."
ensure_packages dockerd docker containerd || return 1
# Enable and start Docker
/etc/init.d/dockerd enable >/dev/null 2>&1
if ! /etc/init.d/dockerd status >/dev/null 2>&1; then
/etc/init.d/dockerd start || return 1
sleep 3
fi
# Wait for Docker socket
local retry=0
while [ ! -S /var/run/docker.sock ] && [ $retry -lt 30 ]; do
sleep 1
retry=$((retry + 1))
done
[ -S /var/run/docker.sock ] || { log_error "Docker socket not available"; return 1; }
log_info "Docker ready"
}
docker_pull() {
load_config
log_info "Pulling Docker image: $image"
docker pull "$image"
}
docker_stop() {
docker stop "$CONTAINER_NAME" >/dev/null 2>&1 || true
docker rm "$CONTAINER_NAME" >/dev/null 2>&1 || true
}
docker_run() {
load_config
docker_stop
log_info "Starting Lyrion Docker container..."
exec docker run --rm \
--name "$CONTAINER_NAME" \
-p "${port}:9000" \
-p "9090:9090" \
-p "3483:3483" \
-p "3483:3483/udp" \
-v "$data_path:/config" \
-v "$media_path:/music:ro" \
-e TZ="$timezone" \
--memory="$memory_limit" \
"$image"
}
docker_status() {
docker ps -a --filter "name=$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
}
docker_logs() {
docker logs "$@" "$CONTAINER_NAME"
}
docker_shell() {
docker exec -it "$CONTAINER_NAME" /bin/sh
}
# =============================================================================
# LXC Runtime Functions
# =============================================================================
lxc_check_prereqs() {
log_info "Checking LXC prerequisites..."
# Check if LXC binaries are already available (pre-installed)
if ! has_lxc; then
log_info "LXC not found, attempting to install..."
ensure_packages lxc lxc-common lxc-attach lxc-start lxc-stop lxc-destroy || return 1
fi
# Check cgroups
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
# Check for COMPLETE installation (Lyrion installed, not just Alpine)
if [ -d "$LXC_ROOTFS" ] && [ -f "$LXC_ROOTFS/opt/lyrion/slimserver.pl" ] && [ -f "$LXC_CONFIG" ]; then
log_info "LXC rootfs already exists with Lyrion installed"
return 0
fi
# Check for incomplete installation (Alpine exists but Lyrion not installed)
if [ -d "$LXC_ROOTFS" ] && [ -f "$LXC_ROOTFS/etc/alpine-release" ] && [ ! -f "$LXC_ROOTFS/opt/lyrion/slimserver.pl" ]; then
log_warn "Incomplete installation detected (Alpine downloaded but Lyrion not installed)"
log_info "Cleaning up incomplete rootfs..."
rm -rf "$LXC_PATH/$LXC_NAME"
fi
log_info "Creating LXC rootfs for Lyrion..."
ensure_dir "$LXC_PATH/$LXC_NAME"
# Use external script if available
if [ -x "$LYRION_ROOTFS_SCRIPT" ]; then
"$LYRION_ROOTFS_SCRIPT" "$LXC_ROOTFS" || return 1
else
# Inline rootfs creation
lxc_create_alpine_rootfs || return 1
fi
# Verify Lyrion was actually installed
if [ ! -f "$LXC_ROOTFS/opt/lyrion/slimserver.pl" ]; then
log_error "Lyrion installation failed - slimserver.pl not found"
log_error "Check network connectivity and try again"
return 1
fi
# Create LXC config
lxc_create_config || return 1
log_info "LXC rootfs created successfully"
}
lxc_create_alpine_rootfs() {
local arch="aarch64"
local alpine_version="3.19"
local mirror="https://dl-cdn.alpinelinux.org/alpine"
local rootfs="$LXC_ROOTFS"
# Detect architecture
case "$(uname -m)" in
x86_64) arch="x86_64" ;;
aarch64) arch="aarch64" ;;
armv7l) arch="armv7" ;;
*) arch="x86_64" ;;
esac
log_info "Downloading Alpine Linux $alpine_version ($arch)..."
ensure_dir "$rootfs"
cd "$rootfs" || return 1
# Download Alpine minirootfs
local rootfs_url="$mirror/v$alpine_version/releases/$arch/alpine-minirootfs-$alpine_version.0-$arch.tar.gz"
wget -q -O /tmp/alpine-rootfs.tar.gz "$rootfs_url" || {
log_error "Failed to download Alpine rootfs"
return 1
}
# Extract rootfs
tar xzf /tmp/alpine-rootfs.tar.gz -C "$rootfs" || return 1
rm -f /tmp/alpine-rootfs.tar.gz
# Configure Alpine
echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf"
# Install Lyrion in the container
cat > "$rootfs/tmp/setup-lyrion.sh" << 'SETUP'
#!/bin/sh
set -e
# Update and install dependencies
apk update
apk add --no-cache \
perl \
perl-io-socket-ssl \
perl-encode \
perl-xml-parser \
perl-xml-simple \
perl-dbi \
perl-dbd-sqlite \
perl-json-xs \
perl-yaml-libyaml \
perl-crypt-openssl-rsa \
perl-ev \
perl-anyevent \
perl-gd \
perl-digest-sha1 \
perl-sub-name \
perl-html-parser \
perl-template-toolkit \
perl-file-slurp \
imagemagick \
flac \
faad2 \
sox \
lame \
curl \
wget
# Download and install Lyrion
cd /tmp
# Detect architecture for appropriate tarball
LYRION_ARCH=""
case "$(uname -m)" in
aarch64|arm*) LYRION_ARCH="arm-linux" ;;
esac
# Try ARM-specific tarball first (smaller ~60MB), then fall back to multi-arch (~126MB)
echo "Downloading Lyrion Music Server..."
LYRION_URL=""
if [ -n "$LYRION_ARCH" ]; then
LYRION_URL="https://downloads.lms-community.org/LyrionMusicServer_v9.0.3/lyrionmusicserver-9.0.3-${LYRION_ARCH}.tgz"
else
LYRION_URL="https://downloads.lms-community.org/LyrionMusicServer_v9.0.3/lyrionmusicserver-9.0.3.tgz"
fi
echo "URL: $LYRION_URL"
wget -O lyrion.tar.gz "$LYRION_URL" || {
echo "Primary download failed, trying multi-arch tarball..."
LYRION_URL="https://downloads.lms-community.org/LyrionMusicServer_v9.0.3/lyrionmusicserver-9.0.3.tgz"
wget -O lyrion.tar.gz "$LYRION_URL" || {
echo "ERROR: All download attempts failed"
exit 1
}
}
# Verify download succeeded
if [ ! -f lyrion.tar.gz ] || [ ! -s lyrion.tar.gz ]; then
echo "ERROR: Failed to download Lyrion tarball"
exit 1
fi
mkdir -p /opt/lyrion
tar xzf lyrion.tar.gz -C /opt/lyrion --strip-components=1 || {
echo "ERROR: Failed to extract Lyrion tarball"
exit 1
}
rm -f lyrion.tar.gz
# Remove conflicting bundled CPAN modules (use system modules instead)
rm -rf /opt/lyrion/CPAN/arch
rm -rf /opt/lyrion/CPAN/XML/Parser*
rm -rf /opt/lyrion/CPAN/Image
rm -rf /opt/lyrion/CPAN/DBD
rm -rf /opt/lyrion/CPAN/DBI /opt/lyrion/CPAN/DBI.pm
rm -rf /opt/lyrion/CPAN/YAML
rm -rf /opt/lyrion/CPAN/Template /opt/lyrion/CPAN/Template.pm
# Create stub Image::Scale module (artwork resizing - optional)
mkdir -p /opt/lyrion/CPAN/Image
cat > /opt/lyrion/CPAN/Image/Scale.pm << 'STUB'
package Image::Scale;
our $VERSION = '0.08';
sub new { return bless {}, shift }
sub resize { return 1 }
sub width { return 0 }
sub height { return 0 }
1;
STUB
# Create stub Devel::Peek module (runtime inspection - optional)
mkdir -p /opt/lyrion/CPAN/Devel
cat > /opt/lyrion/CPAN/Devel/Peek.pm << 'STUB'
package Devel::Peek;
use strict;
our $VERSION = '1.32';
our $ANON_GV;
{ no strict 'refs'; $ANON_GV = \*{'Devel::Peek::ANON'}; }
sub Dump { }
sub DumpArray { }
sub SvREFCNT { return 1 }
sub DeadCode { }
sub mstat { }
sub fill_mstats { }
sub SvROK { return 0 }
sub CvGV { return $ANON_GV }
1;
STUB
# Create directories with proper permissions for nobody user (uid 65534)
mkdir -p /config/prefs/plugin /config/cache /music /var/log/lyrion
chown -R 65534:65534 /config /var/log/lyrion /opt/lyrion
# Create startup script (runs as nobody via LXC init.uid/gid)
cat > /opt/lyrion/start.sh << 'START'
#!/bin/sh
cd /opt/lyrion
# Ensure directories exist (ownership set during LXC config creation)
mkdir -p /config/prefs/plugin /config/cache /var/log/lyrion 2>/dev/null || true
# Run Lyrion (already running as nobody via LXC init.uid/gid settings)
exec perl slimserver.pl \
--prefsdir /config/prefs \
--cachedir /config/cache \
--logdir /var/log/lyrion \
--httpport 9000 \
--cliport 9090
START
chmod +x /opt/lyrion/start.sh
echo "Lyrion installed successfully"
SETUP
chmod +x "$rootfs/tmp/setup-lyrion.sh"
# Run setup in chroot
log_info "Installing Lyrion in container (this may take a while)..."
chroot "$rootfs" /tmp/setup-lyrion.sh || {
log_error "Failed to install Lyrion in container"
return 1
}
rm -f "$rootfs/tmp/setup-lyrion.sh"
}
lxc_create_config() {
load_config
# Convert memory limit to bytes for cgroup2
local mem_bytes
case "$memory_limit" in
*G) mem_bytes=$(( ${memory_limit%G} * 1073741824 )) ;;
*M) mem_bytes=$(( ${memory_limit%M} * 1048576 )) ;;
*K) mem_bytes=$(( ${memory_limit%K} * 1024 )) ;;
*) mem_bytes="$memory_limit" ;;
esac
cat > "$LXC_CONFIG" << EOF
# Lyrion LXC Configuration
lxc.uts.name = $LXC_NAME
# Root filesystem
lxc.rootfs.path = dir:$LXC_ROOTFS
# Network - inherit host network (no lxc.net = share host namespace)
# Needed for Squeezebox UDP 3483 broadcast discovery
# Mounts
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
lxc.mount.entry = $data_path config none bind,create=dir 0 0
lxc.mount.entry = $media_path music none bind,ro,create=dir 0 0
# Capabilities
lxc.cap.drop = sys_admin sys_module mac_admin mac_override
# cgroups limits (cgroup2 format)
lxc.cgroup2.memory.max = $mem_bytes
# Run as nobody user (uid/gid 65534) - Lyrion must not run as root
lxc.init.uid = 65534
lxc.init.gid = 65534
# Init
lxc.init.cmd = /opt/lyrion/start.sh
# Console
lxc.console.size = 1024
lxc.pty.max = 1024
EOF
# Set ownership on data directory for nobody user
chown -R 65534:65534 "$data_path" 2>/dev/null || true
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 'lyrionctl install' first."
return 1
fi
# Ensure mount points exist
ensure_dir "$data_path"
ensure_dir "$media_path"
log_info "Starting Lyrion LXC container..."
exec lxc-start -n "$LXC_NAME" -F -f "$LXC_CONFIG"
}
lxc_status() {
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
}
lxc_logs() {
load_config
local logfile="$LXC_ROOTFS/var/log/lyrion/server.log"
# Also check container logs via lxc-attach if container is running
if lxc-info -n "$LXC_NAME" -s 2>/dev/null | grep -q "RUNNING"; then
if [ "$1" = "-f" ]; then
lxc-attach -n "$LXC_NAME" -- tail -f /var/log/lyrion/server.log
else
lxc-attach -n "$LXC_NAME" -- tail -100 /var/log/lyrion/server.log
fi
elif [ -f "$logfile" ]; then
if [ "$1" = "-f" ]; then
tail -f "$logfile"
else
tail -100 "$logfile"
fi
else
log_warn "Container not running and no log file found"
log_info "Start the service with: /etc/init.d/lyrion start"
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
}
# =============================================================================
# Main Commands
# =============================================================================
cmd_install() {
require_root
load_config
local rt=$(detect_runtime) || exit 1
log_info "Using runtime: $rt"
# Save detected runtime if auto
[ "$runtime" = "auto" ] && uci_set detected_runtime "$rt"
# Create directories
ensure_dir "$data_path"
ensure_dir "$media_path"
case "$rt" in
docker)
docker_check_prereqs || exit 1
docker_pull || exit 1
;;
lxc)
lxc_check_prereqs || exit 1
lxc_create_rootfs || exit 1
;;
esac
# Ensure firewall rules are in place
firewall_ensure_rules
uci_set enabled '1'
/etc/init.d/lyrion enable
log_info "Lyrion installed. Start with: /etc/init.d/lyrion start"
log_info "Web interface will be at: http://<router-ip>:$port"
}
cmd_check() {
load_config
log_info "Checking prerequisites..."
log_info "Configured runtime: $runtime"
local rt=$(detect_runtime)
if [ -n "$rt" ]; then
log_info "Detected runtime: $rt"
case "$rt" in
docker) docker_check_prereqs ;;
lxc) lxc_check_prereqs ;;
esac
fi
}
cmd_update() {
require_root
load_config
local rt=$(detect_runtime) || exit 1
case "$rt" in
docker)
docker_pull || exit 1
;;
lxc)
log_info "Updating LXC rootfs..."
lxc_destroy
lxc_create_rootfs || exit 1
;;
esac
if /etc/init.d/lyrion enabled >/dev/null 2>&1; then
/etc/init.d/lyrion restart
else
log_info "Update complete. Restart manually to apply."
fi
}
cmd_destroy() {
require_root
load_config
local rt=$(detect_runtime 2>/dev/null)
case "$rt" in
docker)
docker_stop
log_info "Docker container stopped. Image kept for reinstall."
log_info "To remove image: docker rmi $image"
;;
lxc)
lxc_destroy
;;
*)
# No runtime detected, but try to clean up LXC anyway
if [ -d "$LXC_PATH/$LXC_NAME" ]; then
log_info "Removing LXC rootfs..."
rm -rf "$LXC_PATH/$LXC_NAME"
log_info "LXC container destroyed"
else
log_info "No container found to destroy"
fi
;;
esac
log_info "To reinstall: lyrionctl install"
}
cmd_status() {
local rt=$(detect_runtime 2>/dev/null)
case "$rt" in
docker) docker_status ;;
lxc) lxc_status ;;
*) echo "No runtime detected" ;;
esac
}
cmd_logs() {
local rt=$(detect_runtime 2>/dev/null)
case "$rt" in
docker) docker_logs "$@" ;;
lxc) lxc_logs "$@" ;;
*) echo "No runtime detected" ;;
esac
}
cmd_shell() {
local rt=$(detect_runtime 2>/dev/null)
case "$rt" in
docker) docker_shell ;;
lxc) lxc_shell ;;
*) echo "No runtime detected" ;;
esac
}
cmd_service_run() {
require_root
load_config
# Ensure firewall rules on every start
firewall_ensure_rules
local rt=$(detect_runtime) || exit 1
case "$rt" in
docker)
docker_check_prereqs || exit 1
docker_run
;;
lxc)
lxc_check_prereqs || exit 1
lxc_run
;;
esac
}
cmd_service_stop() {
require_root
local rt=$(detect_runtime 2>/dev/null)
case "$rt" in
docker) docker_stop ;;
lxc) lxc_stop ;;
esac
}
cmd_runtime() {
load_config
echo "Configured: $runtime"
local detected=$(detect_runtime 2>/dev/null)
if [ -n "$detected" ]; then
echo "Detected: $detected"
else
echo "Detected: none"
fi
echo ""
echo "Available runtimes:"
has_docker && echo " - docker" || echo " - docker (not installed)"
has_lxc && echo " - lxc" || echo " - lxc (not installed)"
}
# =============================================================================
# Main Entry Point
# =============================================================================
case "${1:-}" in
install) shift; cmd_install "$@" ;;
check) shift; cmd_check "$@" ;;
update) shift; cmd_update "$@" ;;
destroy) shift; cmd_destroy "$@" ;;
status) shift; cmd_status "$@" ;;
logs) shift; cmd_logs "$@" ;;
shell) shift; cmd_shell "$@" ;;
service-run) shift; cmd_service_run "$@" ;;
service-stop) shift; cmd_service_stop "$@" ;;
runtime) shift; cmd_runtime "$@" ;;
help|--help|-h|'') usage ;;
*) echo "Unknown command: $1" >&2; usage >&2; exit 1 ;;
esac