#!/bin/bash # ============================================================= # PLEX32 — Config Backup Script # Creates tar archives of Plex and Tautulli configs # Copies to NAS08 via NFS as single files # Keeps last 3 backups, deletes older ones # Sends HTML email report in KingDezigns style # Location: /usr/scripts/plex32/plex32_backup.sh # Schedule: Every 3 days at 2:00 AM (cron) # ============================================================= # --- Config --- HOSTNAME="plex32" PLEX_SRC="/opt/plex/config" TAUTULLI_SRC="/opt/tautulli/config" NFS_DEST="/mnt/plex/config" TAUTULLI_LOC="backups/tautulli" PLEX_LOC="backups/plex" TMP_DIR="/tmp/plex32-backup" LOG_DIR="/var/log/plex32-backup" LOG_FILE="$LOG_DIR/backup-$(date +%Y%m%d).log" RETENTION_COUNT=3 COMPOSE_DIR="/home/rufusking/docker/plex" DATESTAMP=$(date '+%Y-%m-%d') TIMESTAMP=$(date '+%B %d, %Y at %I:%M %p') PLEX_ARCHIVE="plex-config-${DATESTAMP}.tar.gz" TAUTULLI_ARCHIVE="tautulli-config-${DATESTAMP}.tar.gz" SMTP_SERVER="smtppro.zoho.com" SMTP_PORT=465 SMTP_USER="rufus.king@kingdezigns.com" SMTP_PASS="$(cat /etc/plex32-smtp-pass 2>/dev/null)" EMAIL_TO="rufus.king@kingdezigns.com" EMAIL_FROM="rufus.king@kingdezigns.com" # --- Setup --- mkdir -p "$LOG_DIR" "$TMP_DIR" START_TIME=$(date +%s) exec > >(tee -a "$LOG_FILE") 2>&1 echo "[$(date '+%Y-%m-%d %H:%M:%S')] PLEX32 backup started" # --- Check NFS mount --- if ! mountpoint -q "$NFS_DEST"; then echo "[ERROR] NFS mount not available: $NFS_DEST" exit 1 fi # --- Stop containers --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Stopping Plex and Tautulli containers..." cd "$COMPOSE_DIR" && docker compose down STOP_STATUS=$? if [ $STOP_STATUS -ne 0 ]; then echo "[ERROR] Failed to stop containers cleanly" fi # --- Tar Plex config --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating Plex config archive..." PLEX_START=$(date +%s) tar -czf "$TMP_DIR/$PLEX_ARCHIVE" \ --exclude="config/Library/Application Support/Plex Media Server/Cache" \ --exclude="config/Library/Application Support/Plex Media Server/Logs" \ --exclude="config/Library/Application Support/Plex Media Server/Crash Reports" \ -C /opt/plex config 2>&1 PLEX_TAR_STATUS=$? PLEX_END=$(date +%s) PLEX_DURATION=$((PLEX_END - PLEX_START)) if [ $PLEX_TAR_STATUS -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Plex archive created in ${PLEX_DURATION}s" else echo "[ERROR] Plex tar failed with status $PLEX_TAR_STATUS" fi # --- Tar Tautulli config --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Creating Tautulli config archive..." TAUTULLI_START=$(date +%s) tar -czf "$TMP_DIR/$TAUTULLI_ARCHIVE" \ -C /opt/tautulli config 2>&1 TAUTULLI_TAR_STATUS=$? TAUTULLI_END=$(date +%s) TAUTULLI_DURATION=$((TAUTULLI_END - TAUTULLI_START)) if [ $TAUTULLI_TAR_STATUS -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tautulli archive created in ${TAUTULLI_DURATION}s" else echo "[ERROR] Tautulli tar failed with status $TAUTULLI_TAR_STATUS" fi # --- Restart containers --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Restarting containers..." cd "$COMPOSE_DIR" && docker compose up -d START_STATUS=$? if [ $START_STATUS -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Containers restarted successfully" else echo "[ERROR] Failed to restart containers on first attempt — retrying in 15 seconds..." sleep 15 cd "$COMPOSE_DIR" && docker compose up -d START_STATUS=$? if [ $START_STATUS -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Containers restarted successfully on retry" else echo "[ERROR] Containers failed to restart after retry — manual intervention required" fi fi # --- Verify containers are actually running --- sleep 5 PLEX_RUNNING=$(docker ps --filter "name=plex" --filter "status=running" --format "{{.Names}}" | grep -c "^plex$") TAUTULLI_RUNNING=$(docker ps --filter "name=tautulli" --filter "status=running" --format "{{.Names}}" | grep -c "^tautulli$") if [ "$PLEX_RUNNING" -eq 1 ]; then PLEX_CONTAINER_STATUS="✅ RUNNING" PLEX_CONTAINER_COLOR="#166534" PLEX_CONTAINER_BG="#f0fdf4" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Plex container verified running" else PLEX_CONTAINER_STATUS="❌ NOT RUNNING — MANUAL INTERVENTION REQUIRED" PLEX_CONTAINER_COLOR="#b91c1c" PLEX_CONTAINER_BG="#fff1f2" echo "[ERROR] Plex container is NOT running after backup" fi if [ "$TAUTULLI_RUNNING" -eq 1 ]; then TAUTULLI_CONTAINER_STATUS="✅ RUNNING" TAUTULLI_CONTAINER_COLOR="#166534" TAUTULLI_CONTAINER_BG="#f0fdf4" echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tautulli container verified running" else TAUTULLI_CONTAINER_STATUS="❌ NOT RUNNING — MANUAL INTERVENTION REQUIRED" TAUTULLI_CONTAINER_COLOR="#b91c1c" TAUTULLI_CONTAINER_BG="#fff1f2" echo "[ERROR] Tautulli container is NOT running after backup" fi # --- Copy Plex archive to NAS08 --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Copying Plex archive to NAS08..." COPY_PLEX_START=$(date +%s) cp "$TMP_DIR/$PLEX_ARCHIVE" "$NFS_DEST/$PLEX_LOC/" PLEX_COPY_STATUS=$? COPY_PLEX_END=$(date +%s) PLEX_COPY_DURATION=$((COPY_PLEX_END - COPY_PLEX_START)) if [ $PLEX_COPY_STATUS -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Plex archive copied in ${PLEX_COPY_DURATION}s" else echo "[ERROR] Plex archive copy failed" fi # --- Copy Tautulli archive to NAS08 --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Copying Tautulli archive to NAS08..." COPY_TAUTULLI_START=$(date +%s) cp "$TMP_DIR/$TAUTULLI_ARCHIVE" "$NFS_DEST/$TAUTULLI_LOC/" TAUTULLI_COPY_STATUS=$? COPY_TAUTULLI_END=$(date +%s) TAUTULLI_COPY_DURATION=$((COPY_TAUTULLI_END - COPY_TAUTULLI_START)) if [ $TAUTULLI_COPY_STATUS -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Tautulli archive copied in ${TAUTULLI_COPY_DURATION}s" else echo "[ERROR] Tautulli archive copy failed" fi # --- Rotate old backups — keep last 3 --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Rotating old backups..." ls -t "$NFS_DEST/$PLEX_LOC"/plex-config-*.tar.gz 2>/dev/null | tail -n +$((RETENTION_COUNT + 1)) | xargs rm -f ls -t "$NFS_DEST/$TAUTULLI_LOC"/tautulli-config-*.tar.gz 2>/dev/null | tail -n +$((RETENTION_COUNT + 1)) | xargs rm -f # --- Cleanup temp files --- rm -f "$TMP_DIR/$PLEX_ARCHIVE" "$TMP_DIR/$TAUTULLI_ARCHIVE" # --- Sizes and storage --- PLEX_ARCHIVE_SIZE=$(ls -lh "$NFS_DEST/$PLEX_LOC/$PLEX_ARCHIVE" 2>/dev/null | awk '{print $5}') TAUTULLI_ARCHIVE_SIZE=$(ls -lh "$NFS_DEST/$TAUTULLI_LOC/$TAUTULLI_ARCHIVE" 2>/dev/null | awk '{print $5}') PLEX_BACKUP_COUNT=$(ls "$NFS_DEST/$PLEX_LOC"/plex-config-*.tar.gz 2>/dev/null | wc -l) TAUTULLI_BACKUP_COUNT=$(ls "$NFS_DEST/$TAUTULLI_LOC"/tautulli-config-*.tar.gz 2>/dev/null | wc -l) # Get accurate NAS08 disk stats via SSH (avoids NFS dataset quota confusion) NAS08_DISK_INFO=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=5 rufusking@192.168.150.35 \ "df -h /export/kingdezigns-all | awk 'NR==2{print \$3\" used / \"\$2\" total / \"\$4\" free\"}'" 2>/dev/null) if [ -z "$NAS08_DISK_INFO" ]; then # Fallback to NFS mount stats if SSH fails NAS08_DISK_INFO=$(df -h "$NFS_DEST" 2>/dev/null | awk 'NR==2{print $3" used / "$2" total / "$4" free"}') fi # List of current backups on NAS08 PLEX_BACKUP_LIST=$(ls -lh "$NFS_DEST/$PLEX_LOC"/plex-config-*.tar.gz 2>/dev/null | \ awk '{print $NF" ("$5")"}' | xargs -I{} basename {} 2>/dev/null | \ while read -r line; do echo "$line"; done) TAUTULLI_BACKUP_LIST=$(ls -lh "$NFS_DEST/$TAUTULLI_LOC"/tautulli-config-*.tar.gz 2>/dev/null | \ awk '{print $NF" ("$5")"}' | xargs -I{} basename {} 2>/dev/null | \ while read -r line; do echo "$line"; done) # Build backup list HTML rows PLEX_LIST_ROWS="" while IFS= read -r f; do SIZE=$(ls -lh "$NFS_DEST/$PLEX_LOC/$f" 2>/dev/null | awk '{print $5}') PLEX_LIST_ROWS="${PLEX_LIST_ROWS}
|