#!/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 — manual intervention required" 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 --- 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}') NFS_FREE=$(df -h "$NFS_DEST" 2>/dev/null | awk 'NR==2{print $4}') 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) # --- Total duration --- END_TIME=$(date +%s) TOTAL_DURATION=$((END_TIME - START_TIME)) TOTAL_MIN=$((TOTAL_DURATION / 60)) TOTAL_SEC=$((TOTAL_DURATION % 60)) # --- Overall status --- PLEX_STATUS=$((PLEX_TAR_STATUS + PLEX_COPY_STATUS)) TAUTULLI_STATUS=$((TAUTULLI_TAR_STATUS + TAUTULLI_COPY_STATUS)) if [ $PLEX_STATUS -eq 0 ] && [ $TAUTULLI_STATUS -eq 0 ] && [ $START_STATUS -eq 0 ]; then OVERALL_STATUS="SUCCESS" STATUS_COLOR="#1a7f4b" BANNER_ICON="✅" BANNER_MSG="Backup completed successfully — Plex and Tautulli configs archived to NAS08" BADGE_COLOR="#1a7f4b" elif [ $PLEX_STATUS -ne 0 ] && [ $TAUTULLI_STATUS -ne 0 ]; then OVERALL_STATUS="FAILED" STATUS_COLOR="#b91c1c" BANNER_ICON="🔴" BANNER_MSG="Backup failed — both archive operations encountered errors" BADGE_COLOR="#b91c1c" else OVERALL_STATUS="PARTIAL" STATUS_COLOR="#b45309" BANNER_ICON="⚠️" BANNER_MSG="Backup partially completed — review individual results below" BADGE_COLOR="#b45309" fi # --- Status labels --- if [ $PLEX_STATUS -eq 0 ]; then PLEX_LABEL="✅ SUCCESS" PLEX_ROW_BG="#f0fdf4" PLEX_TEXT_COLOR="#166534" else PLEX_LABEL="❌ FAILED" PLEX_ROW_BG="#fff1f2" PLEX_TEXT_COLOR="#b91c1c" fi if [ $TAUTULLI_STATUS -eq 0 ]; then TAUTULLI_LABEL="✅ SUCCESS" TAUTULLI_ROW_BG="#f0fdf4" TAUTULLI_TEXT_COLOR="#166534" else TAUTULLI_LABEL="❌ FAILED" TAUTULLI_ROW_BG="#fff1f2" TAUTULLI_TEXT_COLOR="#b91c1c" fi if [ $START_STATUS -eq 0 ]; then RESTART_LABEL="✅ RUNNING" RESTART_COLOR="#166534" else RESTART_LABEL="❌ CHECK REQUIRED" RESTART_COLOR="#b91c1c" fi # --- Send email --- echo "[$(date '+%Y-%m-%d %H:%M:%S')] Sending email report..." python3 << PYEOF import smtplib, ssl from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText smtp_server = "$SMTP_SERVER" smtp_port = $SMTP_PORT smtp_user = "$SMTP_USER" smtp_pass = "$SMTP_PASS" email_to = "$EMAIL_TO" email_from = "$EMAIL_FROM" subject = "PLEX32 Backup — $OVERALL_STATUS — $DATESTAMP" html = """
Config Backup
🎬 plex32
$TIMESTAMP  |  VLAN 50 — Lab/Servers
$OVERALL_STATUS
$BANNER_ICON  $BANNER_MSG
Duration
${TOTAL_MIN}m ${TOTAL_SEC}s
Plex Archive
$PLEX_ARCHIVE_SIZE
Tautulli Archive
$TAUTULLI_ARCHIVE_SIZE
NAS08 Free
$NFS_FREE
📋 Backup Results
Component Archive Copies Kept Duration Status
🎬 Plex Config $PLEX_ARCHIVE $PLEX_BACKUP_COUNT / $RETENTION_COUNT ${PLEX_DURATION}s tar + ${PLEX_COPY_DURATION}s copy $PLEX_LABEL
📊 Tautulli Config $TAUTULLI_ARCHIVE $TAUTULLI_BACKUP_COUNT / $RETENTION_COUNT ${TAUTULLI_DURATION}s tar + ${TAUTULLI_COPY_DURATION}s copy $TAUTULLI_LABEL
Container Status After Backup $RESTART_LABEL
Backup Destination NAS08 (192.168.150.35) via NFS — $NFS_FREE free
💡 To Restore tar -xzf $PLEX_ARCHIVE -C /opt/plex
Config Backup  |  plex32  |  KingDezigns Infrastructure
""" msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = email_from msg["To"] = email_to msg.attach(MIMEText(html, "html")) ctx = ssl.create_default_context() with smtplib.SMTP_SSL(smtp_server, smtp_port, context=ctx, timeout=15) as s: s.login(smtp_user, smtp_pass) s.sendmail(email_from, [email_to], msg.as_string()) print("Email sent successfully") PYEOF EMAIL_STATUS=$? if [ $EMAIL_STATUS -eq 0 ]; then echo "[$(date '+%Y-%m-%d %H:%M:%S')] Email sent successfully" else echo "[ERROR] Email send failed" fi # --- Cleanup old logs --- find "$LOG_DIR" -name "backup-*.log" -mtime +$RETENTION_DAYS -delete 2>/dev/null echo "[$(date '+%Y-%m-%d %H:%M:%S')] Backup complete — Status: $OVERALL_STATUS | Duration: ${TOTAL_MIN}m ${TOTAL_SEC}s"