#!/bin/sh # SecuBox Jellyfin Media Server manager # LXC-based deployment with HAProxy, Firewall, Mesh P2P integration VERSION="3.0.0" CONFIG="jellyfin" CONTAINER="jellyfin" LXC_PATH="/srv/lxc/jellyfin" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' log() { echo -e "${GREEN}[JELLYFIN]${NC} $1"; } warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } error() { echo -e "${RED}[ERROR]${NC} $1"; } # ============================================================================ # Configuration Helpers # ============================================================================ uci_get() { uci -q get ${CONFIG}.$1; } uci_set() { uci -q set ${CONFIG}.$1="$2"; } require_root() { [ "$(id -u)" -eq 0 ] || { error "Root required"; exit 1; } } defaults() { data_path="$(uci_get main.data_path)" [ -z "$data_path" ] && data_path="/srv/jellyfin" media_path="$(uci_get main.media_path)" [ -z "$media_path" ] && media_path="/srv/SHARE" port="$(uci_get main.port)" [ -z "$port" ] && port="8096" ip_address="$(uci_get main.ip_address)" [ -z "$ip_address" ] && ip_address="192.168.255.31" domain="$(uci_get network.domain)" [ -z "$domain" ] && domain="jellyfin.secubox.local" } ensure_dir() { [ -d "$1" ] || mkdir -p "$1"; } # ============================================================================ # LXC Helpers # ============================================================================ lxc_running() { lxc-info -n "$CONTAINER" 2>/dev/null | grep -q "State:.*RUNNING" } lxc_exists() { [ -d "$LXC_PATH/rootfs" ] } create_lxc_config() { defaults cat > "$LXC_PATH/config" << EOF lxc.uts.name = jellyfin lxc.rootfs.path = dir:${LXC_PATH}/rootfs lxc.net.0.type = veth lxc.net.0.link = br-lan lxc.net.0.flags = up lxc.net.0.ipv4.address = ${ip_address}/24 lxc.net.0.ipv4.gateway = 192.168.255.1 lxc.mount.auto = proc:mixed sys:ro cgroup:mixed lxc.mount.entry = ${media_path} srv/SHARE none bind,ro 0 0 lxc.mount.entry = ${data_path}/config config none bind 0 0 lxc.mount.entry = ${data_path}/cache cache none bind 0 0 lxc.cap.drop = sys_module mac_admin mac_override sys_time lxc.seccomp.profile = lxc.tty.max = 0 lxc.pty.max = 256 lxc.cgroup2.memory.max = 2048000000 lxc.init.cmd = /opt/start-jellyfin.sh EOF } create_startup_script() { cat > "$LXC_PATH/rootfs/opt/start-jellyfin.sh" << 'EOF' #!/bin/bash export PATH=/usr/lib/jellyfin-ffmpeg:$PATH export LD_LIBRARY_PATH=/usr/lib/jellyfin-ffmpeg/lib:$LD_LIBRARY_PATH echo "Starting Jellyfin..." exec /jellyfin/jellyfin \ --datadir=/config \ --cachedir=/cache \ --webdir=/jellyfin/jellyfin-web EOF chmod +x "$LXC_PATH/rootfs/opt/start-jellyfin.sh" } # ============================================================================ # HAProxy Integration # ============================================================================ configure_haproxy() { local haproxy_enabled=$(uci_get network.haproxy) [ "$haproxy_enabled" != "1" ] && { log "HAProxy integration disabled in UCI"; return 0; } if ! command -v haproxyctl >/dev/null 2>&1; then warn "haproxyctl not found, skipping HAProxy configuration" return 0 fi defaults # Check if backend already exists if uci -q get haproxy.jellyfin_web >/dev/null 2>&1; then log "HAProxy backend jellyfin_web already exists, updating..." uci set haproxy.jellyfin_web.server="media ${ip_address}:${port} weight 100 check" else log "Creating HAProxy backend..." uci set haproxy.jellyfin_web=backend uci set haproxy.jellyfin_web.name='jellyfin_web' uci set haproxy.jellyfin_web.mode='http' uci set haproxy.jellyfin_web.balance='roundrobin' uci set haproxy.jellyfin_web.enabled='1' uci set haproxy.jellyfin_web.server="media ${ip_address}:${port} weight 100 check" fi # Check if vhost already exists local vhost_name=$(echo "$domain" | tr '.' '_') if ! uci -q get haproxy.${vhost_name} >/dev/null 2>&1; then log "Creating HAProxy vhost for $domain..." uci set haproxy.${vhost_name}=vhost uci set haproxy.${vhost_name}.domain="$domain" uci set haproxy.${vhost_name}.backend='jellyfin_web' uci set haproxy.${vhost_name}.ssl='1' uci set haproxy.${vhost_name}.ssl_redirect='1' uci set haproxy.${vhost_name}.acme='1' uci set haproxy.${vhost_name}.waf_bypass='1' uci set haproxy.${vhost_name}.enabled='1' fi uci commit haproxy haproxyctl generate 2>/dev/null haproxyctl reload 2>/dev/null log "HAProxy configured for $domain -> ${ip_address}:${port}" } # ============================================================================ # Installation # ============================================================================ cmd_install() { require_root log "Installing Jellyfin LXC container..." defaults ensure_dir "$LXC_PATH" ensure_dir "$LXC_PATH/rootfs/srv/SHARE" ensure_dir "$LXC_PATH/rootfs/config" ensure_dir "$LXC_PATH/rootfs/cache" ensure_dir "$LXC_PATH/rootfs/opt" ensure_dir "$data_path/config" ensure_dir "$data_path/cache" if [ ! -f "$LXC_PATH/rootfs/jellyfin/jellyfin" ]; then log "Jellyfin rootfs not found. Please extract from Docker image:" echo " docker pull jellyfin/jellyfin:latest" echo " docker create --name temp-jellyfin jellyfin/jellyfin:latest" echo " docker export temp-jellyfin > /tmp/jellyfin.tar" echo " tar -xf /tmp/jellyfin.tar -C $LXC_PATH/rootfs/" echo " docker rm temp-jellyfin" return 1 fi create_lxc_config create_startup_script # Symlink ffmpeg if needed if [ -d "$LXC_PATH/rootfs/usr/lib/jellyfin-ffmpeg" ]; then ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg "$LXC_PATH/rootfs/usr/bin/ffmpeg" 2>/dev/null ln -sf /usr/lib/jellyfin-ffmpeg/ffprobe "$LXC_PATH/rootfs/usr/bin/ffprobe" 2>/dev/null fi uci_set main.enabled '1' uci commit ${CONFIG} configure_haproxy log "Jellyfin LXC installed successfully!" log "Start with: jellyfinctl start" } # ============================================================================ # Service Control # ============================================================================ cmd_start() { require_root if lxc_running; then log "Jellyfin already running" return 0 fi if ! lxc_exists; then error "Jellyfin not installed. Run 'jellyfinctl install' first" return 1 fi defaults create_lxc_config log "Starting Jellyfin LXC..." lxc-start -n "$CONTAINER" -d sleep 5 if lxc_running; then log "Jellyfin started at http://${ip_address}:${port}" else error "Failed to start Jellyfin" return 1 fi } cmd_stop() { require_root if ! lxc_running; then log "Jellyfin is not running" return 0 fi log "Stopping Jellyfin..." lxc-stop -n "$CONTAINER" log "Jellyfin stopped" } cmd_restart() { cmd_stop sleep 2 cmd_start } # ============================================================================ # Status # ============================================================================ cmd_status() { defaults echo "" echo "========================================" echo " Jellyfin Media Server v$VERSION (LXC)" echo "========================================" echo "" local enabled=$(uci_get main.enabled) echo "Configuration:" echo " Enabled: $([ "$enabled" = "1" ] && echo -e "${GREEN}Yes${NC}" || echo -e "${RED}No${NC}")" echo " IP Address: $ip_address" echo " Port: $port" echo " Data: $data_path" echo " Media: $media_path" echo " Domain: $domain" echo "" echo "Container:" if lxc_running; then echo -e " Status: ${GREEN}Running${NC}" local pid=$(lxc-info -n "$CONTAINER" 2>/dev/null | grep PID | awk '{print $2}') echo " PID: $pid" # Health check local health=$(curl -s "http://${ip_address}:${port}/health" 2>/dev/null) if [ "$health" = "Healthy" ]; then echo -e " Health: ${GREEN}Healthy${NC}" else echo -e " Health: ${YELLOW}Starting...${NC}" fi elif lxc_exists; then echo -e " Status: ${YELLOW}Stopped${NC}" else echo -e " Status: ${RED}Not installed${NC}" fi echo "" # Storage if [ -d "$data_path" ]; then local disk=$(du -sh "$data_path" 2>/dev/null | cut -f1) echo "Storage:" echo " Data size: ${disk:-unknown}" fi echo "" } # ============================================================================ # Logs & Shell # ============================================================================ cmd_logs() { defaults if [ -f "$data_path/config/log/log_$(date +%Y%m%d).log" ]; then tail "${@:--100}" "$data_path/config/log/log_$(date +%Y%m%d).log" else ls -la "$data_path/config/log/" 2>/dev/null || echo "No logs found" fi } cmd_shell() { if ! lxc_running; then error "Container not running" return 1 fi lxc-attach -n "$CONTAINER" -- /bin/bash 2>/dev/null || lxc-attach -n "$CONTAINER" -- /bin/sh } # ============================================================================ # Main # ============================================================================ show_help() { cat << EOF Jellyfin Media Server Control v$VERSION (LXC) Usage: jellyfinctl [options] Commands: install Install LXC container start Start Jellyfin stop Stop Jellyfin restart Restart Jellyfin status Show status logs [-f] [--tail N] Show logs shell Open shell inside container configure-haproxy Configure HAProxy vhost Examples: jellyfinctl install jellyfinctl start jellyfinctl status jellyfinctl logs -100 EOF } case "${1:-}" in install) shift; cmd_install "$@" ;; start) shift; cmd_start "$@" ;; stop) shift; cmd_stop "$@" ;; restart) shift; cmd_restart "$@" ;; status) shift; cmd_status "$@" ;; logs) shift; cmd_logs "$@" ;; shell) shift; cmd_shell "$@" ;; configure-haproxy) configure_haproxy ;; service-run) cmd_start ;; service-stop) cmd_stop ;; help|--help|-h|'') show_help ;; *) error "Unknown command: $1"; show_help >&2; exit 1 ;; esac exit 0