- Add --httpaddr 0.0.0.0 to squeezeboxserver startup args - Set httpaddr in default server.prefs - Improve DNS config with multiple nameservers and search domain Fixes Squeezebox devices on WAN network (192.168.1.x) getting "connection reset by peer" when trying to stream from Lyrion bound only to LAN IP (192.168.255.1). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
870 lines
22 KiB
Bash
Executable File
870 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 (use local gateway + fallback)
|
|
cat > "$rootfs/etc/resolv.conf" << 'RESOLV'
|
|
nameserver 1.1.1.1
|
|
nameserver 8.8.8.8
|
|
search lan
|
|
RESOLV
|
|
|
|
# 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
|
|
httpaddr: 0.0.0.0
|
|
httpport: 9000
|
|
cliport: 9090
|
|
PREFS
|
|
chown nobody:nogroup /config/prefs/server.prefs
|
|
fi
|
|
|
|
# Run Lyrion (squeezeboxserver drops privileges to nobody when run as root)
|
|
# Listen on all interfaces (0.0.0.0) to allow WAN devices to stream
|
|
exec /usr/sbin/squeezeboxserver \
|
|
--prefsdir /config/prefs \
|
|
--cachedir /config/cache \
|
|
--logdir /var/log/lyrion \
|
|
--httpaddr 0.0.0.0 \
|
|
--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
|