diff --git a/jellyfin/restore_corrupted_database.sh b/jellyfin/fix-jellyfin-db.sh old mode 100644 new mode 100755 similarity index 100% rename from jellyfin/restore_corrupted_database.sh rename to jellyfin/fix-jellyfin-db.sh diff --git a/jellyfin/jellyfin.sh b/jellyfin/jellyfin.sh new file mode 100755 index 0000000..4fbc589 --- /dev/null +++ b/jellyfin/jellyfin.sh @@ -0,0 +1,599 @@ +#!/bin/bash + +################################################################################ +# Jellyfin Management Script +################################################################################ +# +# Author: Peter Wood +# 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 +# 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] ${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] " + 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 "$@" diff --git a/jellyfin/restore_corrupted_database.md b/jellyfin/restore-corrupted-database.md similarity index 94% rename from jellyfin/restore_corrupted_database.md rename to jellyfin/restore-corrupted-database.md index 78981b6..d9d5fd2 100644 --- a/jellyfin/restore_corrupted_database.md +++ b/jellyfin/restore-corrupted-database.md @@ -1,24 +1,24 @@ # Jellyfin SQLite Database Repair Guide -This document explains how to use the `fix_jellyfin_db.sh` script to repair a corrupted Jellyfin `library.db` file. +This document explains how to use the `fix-jellyfin-db.sh` script to repair a corrupted Jellyfin `library.db` file. **Warning:** Repeated database corruption is a strong indicator of an underlying issue, most commonly a failing hard drive or SSD. If you have to run this script more than once, you should immediately investigate the health of your storage device using tools like `smartctl`. ## How to Use the Script 1. **Save the Script:** - Save the script content to a file named `fix_jellyfin_db.sh` on your server. + Save the script content to a file named `fix-jellyfin-db.sh` on your server. 2. **Make it Executable:** Open a terminal and navigate to the directory where you saved the file. Run the following command to make it executable: ```bash - chmod +x fix_jellyfin_db.sh + chmod +x fix-jellyfin-db.sh ``` 3. **Run the Script:** The script must be run with `sudo` because it needs to stop/start system services and modify files in `/var/lib/jellyfin/`. ```bash - sudo ./fix_jellyfin_db.sh + sudo ./fix-jellyfin-db.sh ``` The script will print its progress as it executes each step. @@ -32,7 +32,7 @@ To prevent any other process from reading or writing to the database during the ```bash systemctl stop jellyfin ``` - +q #### Step 2: Backs Up the Corrupted Database Your corrupted database is never deleted. It is copied to a new file with a timestamp, ensuring you have a fallback. ```bash