Converted secubox-app-jellyfin, secubox-app-mailserver, and added secubox-app-roundcube to use LXC containers instead of Docker. Changes: - jellyfinctl: Now uses LXC at 192.168.255.31 - mailserverctl: New controller for Alpine LXC with Postfix/Dovecot - roundcubectl: New package for Roundcube webmail LXC All controllers support: - Bootstrap Alpine rootfs using static apk - LXC configuration generation - HAProxy integration with waf_bypass - Start/stop/status commands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
363 lines
9.8 KiB
Bash
363 lines
9.8 KiB
Bash
#!/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 <command> [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
|