Files
shell/jellyfin/jellyfin.sh

600 lines
21 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
################################################################################
# Jellyfin Management Script
################################################################################
#
# Author: Peter Wood <peter@peterwood.dev>
# Description: Modern, user-friendly Jellyfin Media Server management script
# with styled output and comprehensive service control capabilities.
# Provides an interactive interface for common jellyfin operations.
#
# Features:
# - Service start/stop/restart/status operationsd
# - Styled console output with Unicode symbols
# - Service health monitoring
# - Process management and monitoring
# - Interactive menu system
#
# Related Scripts:
# - backup-jellyfin.sh: Comprehensive backup solution
# - restore-jellyfin.sh: Backup restoration utilities
# - monitor-jellyfin-backup.sh: Backup system monitoring
# - validate-jellyfin-backups.sh: Backup validation tools
# - test-jellyfin-backup.sh: Testing framework
#
# Usage:
# ./jellyfin.sh start # Start jellyfin service
# ./jellyfin.sh stop # Stop jellyfin service
# ./jellyfin.sh restart # Restart jellyfin service
# ./jellyfin.sh status # Show service status
# ./jellyfin.sh # Interactive menu
#
# Dependencies:
# - systemctl (systemd service management)
# - Jellyfin Media Server package
#
# Exit Codes:
# 0 - Success
# 1 - General error
# 2 - Service operation failure
# 3 - Invalid command or option
#
################################################################################
# 🎬 Jellyfin Media Server Management Script
# A sexy, modern script for managing Jellyfin Media Server with style
# Author: acedanger <peter@peterwood.dev>
# Version: 2.0
set -euo pipefail
# 🎨 Color definitions for sexy output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly PURPLE='\033[0;35m'
readonly CYAN='\033[0;36m'
readonly WHITE='\033[1;37m'
readonly BOLD='\033[1m'
readonly DIM='\033[2m'
readonly RESET='\033[0m'
# 🌈 Function to check if colors should be used
use_colors() {
# Check if stdout is a terminal and colors are supported
if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]] && [[ "${NO_COLOR:-}" != "1" ]]; then
return 0
else
return 1
fi
}
# 🔧 Configuration
readonly JELLYFIN_SERVICE="jellyfin"
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
# Global variables for command-line options
PORCELAIN_MODE=false
# 🎭 ASCII symbols for compatible output
readonly CHECKMARK="✓"
readonly CROSS="✗"
readonly ROCKET="▶"
readonly STOP_SIGN="■"
readonly RECYCLE="↻"
readonly INFO=""
readonly SPARKLES="✦"
# 📊 Function to print fancy headers
print_header() {
if use_colors && [[ "$PORCELAIN_MODE" != "true" ]]; then
echo -e "\n${PURPLE}${BOLD}+==============================================================+${RESET}"
echo -e "${PURPLE}${BOLD}| ${SPARKLES} JELLYFIN MEDIA SERVER ${SPARKLES} |${RESET}"
echo -e "${PURPLE}${BOLD}+==============================================================+${RESET}\n"
elif [[ "$PORCELAIN_MODE" != "true" ]]; then
echo ""
echo "+=============================================================="
echo "| ${SPARKLES} JELLYFIN MEDIA SERVER ${SPARKLES} |"
echo "+=============================================================="
echo ""
fi
}
# 🎉 Function to print completion footer
print_footer() {
if [[ "$PORCELAIN_MODE" == "true" ]]; then
return # No footer in porcelain mode
elif use_colors; then
echo -e "\n${DIM}${CYAN}\\--- Operation completed ${SPARKLES} ---/${RESET}\n"
else
echo ""
echo "\\--- Operation completed ${SPARKLES} ---/"
echo ""
fi
}
# 🎯 Function to print status with style
print_status() {
local status="$1"
local message="$2"
local color="$3"
if [[ "$PORCELAIN_MODE" == "true" ]]; then
# Porcelain mode: simple, machine-readable output
echo "${status} ${message}"
elif use_colors; then
echo -e "${color}${BOLD}[${status}]${RESET} ${message}"
else
echo "[${status}] ${message}"
fi
}
# ⏱️ Function to show loading animation
show_loading() {
local message="$1"
local pid="$2"
local spin='-\|/'
local i=0
# For non-interactive terminals, porcelain mode, or when called from other scripts,
# use a simpler approach
if ! use_colors || [[ "$PORCELAIN_MODE" == "true" ]]; then
echo "${message}..."
wait "$pid"
echo "${message}"
return
fi
# Full interactive mode with colors
echo -n "${message}"
while kill -0 "$pid" 2>/dev/null; do
i=$(( (i+1) %4 ))
echo -ne "\r⌛ ${message} ${spin:$i:1}"
sleep 0.1
done
echo -e "\r⌛ ${message}"
}
# 🚀 Enhanced start function
start_jellyfin() {
print_status "${ROCKET}" "Starting Jellyfin Media Server..." "${GREEN}"
if systemctl is-active --quiet "$JELLYFIN_SERVICE"; then
print_status "${INFO}" "Jellyfin is already running!" "${YELLOW}"
show_detailed_status
return 0
fi
sudo systemctl start "$JELLYFIN_SERVICE" &
local pid=$!
show_loading "Initializing Jellyfin Media Server" $pid
wait $pid
sleep 2 # Give it a moment to fully start
if systemctl is-active --quiet "$JELLYFIN_SERVICE"; then
print_status "${CHECKMARK}" "Jellyfin Media Server started successfully!" "${GREEN}"
print_footer
else
print_status "${CROSS}" "Failed to start Jellyfin Media Server!" "${RED}"
return 1
fi
}
# 🛑 Enhanced stop function
stop_jellyfin() {
print_status "${STOP_SIGN}" "Stopping Jellyfin Media Server..." "${YELLOW}"
if ! systemctl is-active --quiet "$JELLYFIN_SERVICE"; then
print_status "${INFO}" "Jellyfin is already stopped!" "${YELLOW}"
return 0
fi
sudo systemctl stop "$JELLYFIN_SERVICE" &``
local pid=$!
show_loading "Gracefully shutting down jellyfin" $pid
wait $pid
if ! systemctl is-active --quiet "$JELLYFIN_SERVICE"; then
print_status "${CHECKMARK}" "Jellyfin Media Server stopped successfully!" "${GREEN}"
print_footer
else
print_status "${CROSS}" "Failed to stop jellyfin Media Server!" "${RED}"
return 1
fi
}
# ♻️ Enhanced restart function
restart_jellyfin() {
print_status "${RECYCLE}" "Restarting jellyfin Media Server..." "${BLUE}"
if systemctl is-active --quiet "$JELLYFIN_SERVICE"; then
stop_jellyfin
echo ""
fi
start_jellyfin
}
# 📊 Enhanced status function with detailed info
show_detailed_status() {
local service_status
service_status=$(systemctl is-active "$JELLYFIN_SERVICE" 2>/dev/null || echo "inactive")
if [[ "$PORCELAIN_MODE" == "true" ]]; then
# Porcelain mode: simple output
echo "status ${service_status}"
if [[ "$service_status" == "active" ]]; then
local uptime
uptime=$(systemctl show "$JELLYFIN_SERVICE" --property=ActiveEnterTimestamp --value | xargs -I {} date -d {} "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "Unknown")
local memory_usage
memory_usage=$(systemctl show "$JELLYFIN_SERVICE" --property=MemoryCurrent --value 2>/dev/null || echo "0")
if [[ "$memory_usage" != "0" ]] && [[ "$memory_usage" =~ ^[0-9]+$ ]]; then
memory_usage="$(( memory_usage / 1024 / 1024 )) MB"
else
memory_usage="Unknown"
fi
echo "started ${uptime}"
echo "memory ${memory_usage}"
echo "service ${JELLYFIN_SERVICE}"
fi
return
fi
# Interactive mode with styled output
if use_colors; then
echo -e "\n${BOLD}${BLUE}+==============================================================+${RESET}"
echo -e "${BOLD}${BLUE}| SERVICE STATUS |${RESET}"
echo -e "${BOLD}${BLUE}+==============================================================+${RESET}"
else
echo ""
echo "+=============================================================="
echo "| SERVICE STATUS |"
echo "+=============================================================="
fi
case "$service_status" in
"active")
if use_colors; then
print_status "${CHECKMARK}" "Service Status: ${GREEN}${BOLD}ACTIVE${RESET}" "${GREEN}"
else
print_status "${CHECKMARK}" "Service Status: ACTIVE" ""
fi
# Get additional info
local uptime
uptime=$(systemctl show "$JELLYFIN_SERVICE" --property=ActiveEnterTimestamp --value | xargs -I {} date -d {} "+%Y-%m-%d %H:%M:%S" 2>/dev/null || echo "Unknown")
local memory_usage
memory_usage=$(systemctl show "$JELLYFIN_SERVICE" --property=MemoryCurrent --value 2>/dev/null || echo "0")
if [[ "$memory_usage" != "0" ]] && [[ "$memory_usage" =~ ^[0-9]+$ ]]; then
memory_usage="$(( memory_usage / 1024 / 1024 )) MB"
else
memory_usage="Unknown"
fi
if use_colors; then
echo -e "${DIM}${CYAN} Started: ${WHITE}${uptime}${RESET}"
echo -e "${DIM}${CYAN} Memory Usage: ${WHITE}${memory_usage}${RESET}"
echo -e "${DIM}${CYAN} Service Name: ${WHITE}${JELLYFIN_SERVICE}${RESET}"
else
echo " Started: ${uptime}"
echo " Memory Usage: ${memory_usage}"
echo " Service Name: ${JELLYFIN_SERVICE}"
fi
;;
"inactive")
if use_colors; then
print_status "${CROSS}" "Service Status: ${RED}${BOLD}INACTIVE${RESET}" "${RED}"
echo -e "${DIM}${YELLOW} Use '${SCRIPT_NAME} start' to start the service${RESET}"
else
print_status "${CROSS}" "Service Status: INACTIVE" ""
echo " Use '${SCRIPT_NAME} start' to start the service"
fi
;;
"failed")
if use_colors; then
print_status "${CROSS}" "Service Status: ${RED}${BOLD}FAILED${RESET}" "${RED}"
echo -e "${DIM}${RED} Check logs with: ${WHITE}journalctl -u ${JELLYFIN_SERVICE}${RESET}"
else
print_status "${CROSS}" "Service Status: FAILED" ""
echo " Check logs with: journalctl -u ${JELLYFIN_SERVICE}"
fi
;;
*)
if use_colors; then
print_status "${INFO}" "Service Status: ${YELLOW}${BOLD}${service_status^^}${RESET}" "${YELLOW}"
else
print_status "${INFO}" "Service Status: ${service_status^^}" ""
fi
;;
esac
# Show recent logs only in interactive mode
if [[ "$PORCELAIN_MODE" != "true" ]]; then
if use_colors; then
echo -e "\n${DIM}${CYAN}+--- Recent Service Logs (24h) ---+${RESET}"
else
echo ""
echo "+--- Recent Service Logs (24h) ---+"
fi
# Try to get logs with sudo, fall back to user permissions
local logs
if logs=$(sudo journalctl -u "$JELLYFIN_SERVICE" --no-pager -n 5 --since "24 hours ago" --output=short 2>/dev/null); then
if [[ -n "$logs" && "$logs" != "-- No entries --" ]]; then
if use_colors; then
echo -e "${DIM}${logs}${RESET}"
else
echo "${logs}"
fi
else
if use_colors; then
echo -e "${DIM}${YELLOW}No recent log entries found${RESET}"
else
echo "No recent log entries found"
fi
fi
else
# Fallback: try without sudo
logs=$(journalctl -u "$JELLYFIN_SERVICE" --no-pager -n 5 --since "24 hours ago" 2>/dev/null || echo "Unable to access logs")
if [[ "$logs" == "Unable to access logs" || "$logs" == "-- No entries --" ]]; then
if use_colors; then
echo -e "${DIM}${YELLOW}Unable to access recent logs (try: sudo journalctl -u ${JELLYFIN_SERVICE})${RESET}"
else
echo "Unable to access recent logs (try: sudo journalctl -u ${JELLYFIN_SERVICE})"
fi
else
if use_colors; then
echo -e "${DIM}${logs}${RESET}"
else
echo "${logs}"
fi
fi
fi
if use_colors; then
echo -e "${DIM}${CYAN}+----------------------------------+${RESET}"
else
echo "+----------------------------------+"
fi
fi
}
# 📋 Enhanced logs function
show_logs() {
local lines=100
local follow=false
# Parse arguments for logs command
while [[ $# -gt 0 ]]; do
case $1 in
-f|--follow)
follow=true
shift
;;
-[0-9]*|[0-9]*)
# Extract number from argument like -50 or 50
lines="${1#-}"
shift
;;
*)
# Assume it's a number of lines
if [[ "$1" =~ ^[0-9]+$ ]]; then
lines="$1"
fi
shift
;;
esac
done
if [[ "$PORCELAIN_MODE" == "true" ]]; then
# Porcelain mode: simple output without decorations
if [[ "$follow" == "true" ]]; then
sudo journalctl -u "$JELLYFIN_SERVICE" --no-pager -f --output=short-iso 2>/dev/null || \
journalctl -u "$JELLYFIN_SERVICE" --no-pager -f --output=short-iso 2>/dev/null || \
echo "Unable to access logs"
else
sudo journalctl -u "$JELLYFIN_SERVICE" --no-pager -n "$lines" --output=short-iso 2>/dev/null || \
journalctl -u "$JELLYFIN_SERVICE" --no-pager -n "$lines" --output=short-iso 2>/dev/null || \
echo "Unable to access logs"
fi
return
fi
# Interactive mode with styled output
if [[ "$follow" == "true" ]]; then
if use_colors; then
echo -e "${BOLD}${CYAN}Following Jellyfin Media Server logs (Ctrl+C to stop)...${RESET}\n"
else
echo "Following Jellyfin Media Server logs (Ctrl+C to stop)..."
echo ""
fi
sudo journalctl -u "$JELLYFIN_SERVICE" --no-pager -f --output=short 2>/dev/null || \
journalctl -u "$JELLYFIN_SERVICE" --no-pager -f --output=short 2>/dev/null || {
if use_colors; then
echo -e "${RED}Unable to access logs. Try: sudo journalctl -u ${JELLYFIN_SERVICE} -f${RESET}"
else
echo "Unable to access logs. Try: sudo journalctl -u ${JELLYFIN_SERVICE} -f"
fi
}
else
if use_colors; then
echo -e "${BOLD}${CYAN}Recent Jellyfin Media Server logs (last ${lines} lines):${RESET}\n"
else
echo "Recent Jellyfin Media Server logs (last ${lines} lines):"
echo ""
fi
local logs
if logs=$(sudo journalctl -u "$JELLYFIN_SERVICE" --no-pager -n "$lines" --output=short 2>/dev/null); then
if [[ -n "$logs" && "$logs" != "-- No entries --" ]]; then
if use_colors; then
echo -e "${DIM}${logs}${RESET}"
else
echo "${logs}"
fi
else
if use_colors; then
echo -e "${YELLOW}No log entries found${RESET}"
else
echo "No log entries found"
fi
fi
else
# Fallback: try without sudo
logs=$(journalctl -u "$JELLYFIN_SERVICE" --no-pager -n "$lines" --output=short 2>/dev/null || echo "Unable to access logs")
if [[ "$logs" == "Unable to access logs" || "$logs" == "-- No entries --" ]]; then
if use_colors; then
echo -e "${YELLOW}Unable to access logs. Try: ${WHITE}sudo journalctl -u ${JELLYFIN_SERVICE} -n ${lines}${RESET}"
else
echo "Unable to access logs. Try: sudo journalctl -u ${JELLYFIN_SERVICE} -n ${lines}"
fi
else
if use_colors; then
echo -e "${DIM}${logs}${RESET}"
else
echo "${logs}"
fi
fi
fi
fi
}
# 🔧 Show available commands
show_help() {
if use_colors; then
echo -e "${BOLD}${WHITE}Usage:${RESET} ${CYAN}${SCRIPT_NAME}${RESET} ${YELLOW}[OPTIONS] <command>${RESET}"
echo ""
echo -e "${BOLD}${WHITE}Available Commands:${RESET}"
echo -e " ${GREEN}${BOLD}start${RESET} ${ROCKET} Start jellyfin Media Server"
echo -e " ${YELLOW}${BOLD}stop${RESET} ${STOP_SIGN} Stop jellyfin Media Server"
echo -e " ${BLUE}${BOLD}restart${RESET} ${RECYCLE} Restart jellyfin Media Server"
echo -e " ${CYAN}${BOLD}status${RESET} ${INFO} Show detailed service status"
echo -e " ${PURPLE}${BOLD}logs${RESET} 📋 Show recent service logs"
echo -e " ${PURPLE}${BOLD}help${RESET} ${SPARKLES} Show this help message"
echo ""
echo -e "${BOLD}${WHITE}Options:${RESET}"
echo -e " ${WHITE}-p, --porcelain${RESET} Simple, machine-readable output"
echo ""
echo -e "${BOLD}${WHITE}Logs Command Usage:${RESET}"
echo -e " ${DIM}${SCRIPT_NAME} logs${RESET} Show last 100 log lines"
echo -e " ${DIM}${SCRIPT_NAME} logs 50${RESET} Show last 50 log lines"
echo -e " ${DIM}${SCRIPT_NAME} logs -f${RESET} Follow logs in real-time"
echo ""
echo -e "${DIM}${WHITE}Examples:${RESET}"
echo -e " ${DIM}${SCRIPT_NAME} start # Start the jellyfin service${RESET}"
echo -e " ${DIM}${SCRIPT_NAME} status --porcelain # Machine-readable status${RESET}"
echo -e " ${DIM}${SCRIPT_NAME} logs -f # Follow logs in real-time${RESET}"
else
echo "Usage: ${SCRIPT_NAME} [OPTIONS] <command>"
echo ""
echo "Available Commands:"
echo " start ${ROCKET} Start jellyfin Media Server"
echo " stop ${STOP_SIGN} Stop jellyfin Media Server"
echo " restart ${RECYCLE} Restart jellyfin Media Server"
echo " status ${INFO} Show detailed service status"
echo " logs 📋 Show recent service logs"
echo " help ${SPARKLES} Show this help message"
echo ""
echo "Options:"
echo " -p, --porcelain Simple, machine-readable output"
echo ""
echo "Logs Command Usage:"
echo " ${SCRIPT_NAME} logs Show last 100 log lines"
echo " ${SCRIPT_NAME} logs 50 Show last 50 log lines"
echo " ${SCRIPT_NAME} logs -f Follow logs in real-time"
echo ""
echo "Examples:"
echo " ${SCRIPT_NAME} start # Start the jellyfin service"
echo " ${SCRIPT_NAME} status # Show current status"
fi
echo ""
}
# 🎯 Main script logic
main() {
# Check if running as root
if [[ $EUID -eq 0 ]]; then
print_header
print_status "${CROSS}" "Don't run this script as root! Use your regular user account." "${RED}"
exit 1
fi
# Parse command line arguments
local command=""
local args=()
while [[ $# -gt 0 ]]; do
case $1 in
-p|--porcelain)
PORCELAIN_MODE=true
shift
;;
-h|--help|help)
command="help"
shift
;;
start|stop|restart|reload|status|info|logs)
command="${1,,}" # Convert to lowercase
shift
# Collect remaining arguments for the command (especially for logs)
args=("$@")
break
;;
*)
echo "Unknown option or command: $1" >&2
exit 3
;;
esac
done
# Check if no command provided
if [[ -z "$command" ]]; then
print_header
show_help
exit 1
fi
# Show header for all operations except help
if [[ "$command" != "help" ]]; then
print_header
fi
case "$command" in
"start")
start_jellyfin
;;
"stop")
stop_jellyfin
;;
"restart"|"reload")
restart_jellyfin
;;
"status"|"info")
show_detailed_status
;;
"logs")
show_logs "${args[@]}"
;;
"help")
print_header
show_help
;;
*)
print_status "${CROSS}" "Unknown command: ${RED}${BOLD}$command${RESET}" "${RED}"
echo ""
show_help
exit 1
;;
esac
}
# 🚀 Execute main function with all arguments
main "$@"