- Lyrion: Default media_path changed from /srv/media to /mnt/MUSIC - PhotoPrism: Default originals_path changed from /srv/photoprism/originals to /mnt/PHOTO These paths reflect the actual mount points used for external media storage. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
863 lines
22 KiB
Bash
Executable File
863 lines
22 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 /mnt/MUSIC)"
|
|
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)"
|
|
# Extra media paths (space-separated list of host:container pairs or just paths)
|
|
# Example: /mnt/usb:/mnt/usb /mnt/sdb1
|
|
extra_media_paths="$(uci_get extra_media_paths || echo '')"
|
|
}
|
|
|
|
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 via Debian package)
|
|
if [ -d "$LXC_ROOTFS" ] && [ -x "$LXC_ROOTFS/usr/sbin/squeezeboxserver" ] && [ -f "$LXC_CONFIG" ]; then
|
|
log_info "LXC rootfs already exists with Lyrion installed"
|
|
return 0
|
|
fi
|
|
|
|
# Check for incomplete installation (Debian exists but Lyrion not installed)
|
|
if [ -d "$LXC_ROOTFS" ] && [ -f "$LXC_ROOTFS/etc/debian_version" ] && [ ! -x "$LXC_ROOTFS/usr/sbin/squeezeboxserver" ]; then
|
|
log_warn "Incomplete installation detected (Debian downloaded but Lyrion not installed)"
|
|
log_info "Cleaning up incomplete rootfs..."
|
|
rm -rf "$LXC_PATH/$LXC_NAME"
|
|
fi
|
|
|
|
# Clean up old Alpine-based installation if present
|
|
if [ -d "$LXC_ROOTFS" ] && [ -f "$LXC_ROOTFS/etc/alpine-release" ]; then
|
|
log_warn "Old Alpine-based installation detected, replacing with Debian..."
|
|
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 (Debian-based)
|
|
lxc_create_debian_rootfs || return 1
|
|
fi
|
|
|
|
# Verify Lyrion was actually installed
|
|
if [ ! -x "$LXC_ROOTFS/usr/sbin/squeezeboxserver" ]; then
|
|
log_error "Lyrion installation failed - squeezeboxserver 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_debian_rootfs() {
|
|
local arch="arm64"
|
|
local debian_version="bookworm"
|
|
local rootfs="$LXC_ROOTFS"
|
|
|
|
# Detect architecture
|
|
case "$(uname -m)" in
|
|
x86_64) arch="amd64" ;;
|
|
aarch64) arch="arm64" ;;
|
|
armv7l) arch="armhf" ;;
|
|
*) arch="amd64" ;;
|
|
esac
|
|
|
|
log_info "Creating Debian $debian_version ($arch) rootfs..."
|
|
|
|
ensure_dir "$rootfs"
|
|
|
|
# Check if debootstrap is available
|
|
if ! command -v debootstrap >/dev/null 2>&1; then
|
|
log_info "Installing debootstrap..."
|
|
ensure_packages debootstrap || return 1
|
|
fi
|
|
|
|
# Create minimal Debian rootfs
|
|
log_info "Running debootstrap (this may take several minutes)..."
|
|
debootstrap --arch="$arch" --variant=minbase "$debian_version" "$rootfs" http://deb.debian.org/debian || {
|
|
log_error "Failed to create Debian rootfs"
|
|
return 1
|
|
}
|
|
|
|
# Configure DNS
|
|
echo "nameserver 8.8.8.8" > "$rootfs/etc/resolv.conf"
|
|
|
|
# Install Lyrion in the container
|
|
cat > "$rootfs/tmp/setup-lyrion.sh" << 'SETUP'
|
|
#!/bin/bash
|
|
set -e
|
|
export DEBIAN_FRONTEND=noninteractive
|
|
export LANG=en_US.UTF-8
|
|
export LC_ALL=en_US.UTF-8
|
|
|
|
echo "Updating package lists..."
|
|
apt-get update
|
|
|
|
echo "Installing Lyrion Music Server..."
|
|
# Add Lyrion repository
|
|
apt-get install -y gnupg curl ca-certificates locales
|
|
|
|
# Generate locale
|
|
echo "en_US.UTF-8 UTF-8" > /etc/locale.gen
|
|
locale-gen
|
|
|
|
# Add Lyrion repository key and source
|
|
curl -fsSL https://downloads.lms-community.org/LyrionMusicServer.gpg -o /etc/apt/keyrings/lyrionmusicserver.gpg
|
|
echo "deb [signed-by=/etc/apt/keyrings/lyrionmusicserver.gpg] https://downloads.lms-community.org/repo/apt stable main" > /etc/apt/sources.list.d/lyrionmusicserver.list
|
|
|
|
apt-get update
|
|
apt-get install -y lyrionmusicserver
|
|
|
|
# Install additional audio codecs
|
|
apt-get install -y --no-install-recommends \
|
|
flac \
|
|
lame \
|
|
sox \
|
|
faad \
|
|
libio-socket-ssl-perl
|
|
|
|
# Create directories with proper permissions
|
|
mkdir -p /config/prefs/plugin /config/cache /music /var/log/lyrion
|
|
chown -R nobody:nogroup /config /var/log/lyrion
|
|
|
|
# Create startup script
|
|
cat > /opt/init.sh << 'START'
|
|
#!/bin/bash
|
|
export LANG=en_US.UTF-8
|
|
export LC_ALL=en_US.UTF-8
|
|
|
|
# Ensure directories exist with proper permissions
|
|
mkdir -p /config/prefs /config/cache /var/log/lyrion /music
|
|
chown -R nobody:nogroup /config /var/log/lyrion
|
|
chmod -R 777 /config /var/log/lyrion
|
|
|
|
# Create default prefs if not exists
|
|
if [ ! -f /config/prefs/server.prefs ]; then
|
|
cat > /config/prefs/server.prefs << PREFS
|
|
---
|
|
mediadirs:
|
|
- /music
|
|
httpport: 9000
|
|
cliport: 9090
|
|
PREFS
|
|
chown nobody:nogroup /config/prefs/server.prefs
|
|
fi
|
|
|
|
# Run Lyrion (squeezeboxserver drops privileges to nobody when run as root)
|
|
exec /usr/sbin/squeezeboxserver \
|
|
--prefsdir /config/prefs \
|
|
--cachedir /config/cache \
|
|
--logdir /var/log/lyrion \
|
|
--httpport 9000 \
|
|
--cliport 9090
|
|
START
|
|
chmod +x /opt/init.sh
|
|
|
|
# Clean up
|
|
apt-get clean
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
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" /bin/bash /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 - share host network namespace
|
|
# Needed for Squeezebox UDP 3483 broadcast discovery
|
|
lxc.net.0.type = none
|
|
|
|
# Mounts (no cgroup:mixed - incompatible with cgroup v2)
|
|
lxc.mount.auto = proc:mixed sys:ro
|
|
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
|
|
EOF
|
|
|
|
# Add extra media paths if configured
|
|
if [ -n "$extra_media_paths" ]; then
|
|
for path_spec in $extra_media_paths; do
|
|
local host_path container_path
|
|
if echo "$path_spec" | grep -q ':'; then
|
|
host_path="${path_spec%%:*}"
|
|
container_path="${path_spec#*:}"
|
|
else
|
|
host_path="$path_spec"
|
|
container_path="$path_spec"
|
|
fi
|
|
# Remove leading slash for container path (LXC relative mount)
|
|
container_path="${container_path#/}"
|
|
if [ -d "$host_path" ]; then
|
|
echo "lxc.mount.entry = $host_path $container_path none bind,ro,create=dir 0 0" >> "$LXC_CONFIG"
|
|
log_info "Added media path: $host_path -> /$container_path"
|
|
else
|
|
log_info "Skipping non-existent path: $host_path"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
cat >> "$LXC_CONFIG" << EOF
|
|
|
|
# Capabilities
|
|
lxc.cap.drop = sys_admin sys_module mac_admin mac_override
|
|
|
|
# cgroups limits (cgroup2 format)
|
|
lxc.cgroup2.memory.max = $mem_bytes
|
|
|
|
# cgroup v2 compatibility
|
|
lxc.seccomp.profile =
|
|
lxc.autodev = 1
|
|
|
|
# Init - Debian-based with squeezeboxserver (drops privileges to nobody internally)
|
|
lxc.init.cmd = /opt/init.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
|