#!/bin/bash # ============================================================= # PLEX32 — Daily Update Check & Notification # KingDezigns Infrastructure # Schedule: Daily at 7:00 AM # Location: /usr/scripts/plex32/plex32_update_check.sh # ============================================================= HOSTNAME="plex32" SMTP_SERVER="smtppro.zoho.com" SMTP_PORT="465" SMTP_USER="rufus.king@kingdezigns.com" SMTP_PASS="Valerie8545" EMAIL_TO="rufus.king@kingdezigns.com" EMAIL_FROM="rufus.king@kingdezigns.com" TIMESTAMP=$(date "+%A, %B %d, %Y at %I:%M %p") DATE_SHORT=$(date "+%Y-%m-%d") LOG_DIR="/var/log/plex32-updates" LOG_FILE="$LOG_DIR/update-check-$DATE_SHORT.log" mkdir -p "$LOG_DIR" exec > >(tee -a "$LOG_FILE") 2>&1 echo "[$TIMESTAMP] Starting update check..." # ============================================================= # STEP 1 — CHECK OS/APT UPDATES # ============================================================= echo "[INFO] Checking apt updates..." apt_output=$(apt-get update -qq 2>&1) apt_check_status=$? if [ $apt_check_status -ne 0 ]; then APT_FAILED=true APT_UPDATES=0 APT_DETAILS="apt-get update failed:\n$apt_output" else APT_FAILED=false apt_list=$(apt list --upgradable 2>/dev/null | grep -v "Listing..." | grep -v "^$") APT_UPDATES=$(echo "$apt_list" | grep -c "/" || true) APT_DETAILS="$apt_list" fi echo "[INFO] APT updates available: $APT_UPDATES" # ============================================================= # STEP 2 — CHECK PLEX DOCKER IMAGE UPDATE # ============================================================= echo "[INFO] Checking Plex Docker image..." PLEX_FAILED=false PLEX_UPDATE_AVAILABLE=false PLEX_ERROR_HTML="" PLEX_PULL_OUTPUT=$(docker pull lscr.io/linuxserver/plex:latest 2>&1) PLEX_PULL_STATUS=$? PLEX_IMAGE_VERSION=$(docker inspect lscr.io/linuxserver/plex:latest --format '{{index .Config.Labels "build_version"}}' 2>/dev/null || echo "unknown") CURRENT_IMAGE_DATE=$(docker inspect --format='{{.Created}}' lscr.io/linuxserver/plex:latest 2>/dev/null | cut -d'T' -f1) if [ $PLEX_PULL_STATUS -ne 0 ]; then PLEX_FAILED=true PLEX_STATUS_TEXT="Check failed — see error detail below" echo "[ERROR] docker pull exited $PLEX_PULL_STATUS. Full output:" echo "$PLEX_PULL_OUTPUT" PLEX_ERROR_HTML="
⚠ Docker Pull Error (exit $PLEX_PULL_STATUS)
$(echo "$PLEX_PULL_OUTPUT" | sed 's/&/\&/g; s//\>/g')
" elif echo "$PLEX_PULL_OUTPUT" | grep -q "Pull complete"; then PLEX_UPDATE_AVAILABLE=true PLEX_STATUS_TEXT="Update available — new image pulled" else PLEX_STATUS_TEXT="Up to date" fi echo "[INFO] Plex status: $PLEX_STATUS_TEXT" # ============================================================= # STEP 3 — CHECK FAIL2BAN VERSION # ============================================================= echo "[INFO] Checking Fail2Ban..." F2B_UPDATE_AVAILABLE=false F2B_CANDIDATE=$(apt-cache policy fail2ban 2>/dev/null | grep Candidate | awk '{print $2}' | cut -d'-' -f1) F2B_INSTALLED=$(apt-cache policy fail2ban 2>/dev/null | grep Installed | awk '{print $2}' | cut -d'-' -f1) if [ "$F2B_CANDIDATE" != "$F2B_INSTALLED" ] && [ -n "$F2B_CANDIDATE" ]; then F2B_UPDATE_AVAILABLE=true F2B_STATUS_TEXT="Update available: $F2B_INSTALLED → $F2B_CANDIDATE" else F2B_STATUS_TEXT="Up to date ($F2B_INSTALLED)" fi echo "[INFO] Fail2Ban status: $F2B_STATUS_TEXT" # ============================================================= # STEP 4 — CHECK DOCKER ENGINE UPDATE # ============================================================= echo "[INFO] Checking Docker..." DOCKER_UPDATE_AVAILABLE=false DOCKER_CANDIDATE=$(apt-cache policy docker-ce 2>/dev/null | grep Candidate | awk '{print $2}') DOCKER_INSTALLED=$(apt-cache policy docker-ce 2>/dev/null | grep Installed | awk '{print $2}') if [ "$DOCKER_CANDIDATE" != "$DOCKER_INSTALLED" ] && [ -n "$DOCKER_CANDIDATE" ]; then DOCKER_UPDATE_AVAILABLE=true DOCKER_STATUS_TEXT="Update available: $DOCKER_INSTALLED → $DOCKER_CANDIDATE" else DOCKER_STATUS_TEXT="Up to date ($DOCKER_INSTALLED)" fi echo "[INFO] Docker status: $DOCKER_STATUS_TEXT" # ============================================================= # STEP 5 — DETERMINE OVERALL STATUS # ============================================================= ANY_FAILED=false ANY_UPDATES=false [ "$APT_FAILED" = true ] && ANY_FAILED=true [ "$PLEX_FAILED" = true ] && ANY_FAILED=true [ "$APT_UPDATES" -gt 0 ] && ANY_UPDATES=true [ "$PLEX_UPDATE_AVAILABLE" = true ] && ANY_UPDATES=true [ "$F2B_UPDATE_AVAILABLE" = true ] && ANY_UPDATES=true [ "$DOCKER_UPDATE_AVAILABLE" = true ] && ANY_UPDATES=true if [ "$ANY_FAILED" = true ]; then STATUS_COLOR="#b45309" STATUS_BADGE="⚠ CHECK FAILED" STATUS_BANNER_MSG="⚠  One or more update checks failed — manual verification required" SUBJECT="⚠ PLEX32 — Update Check Failed — $DATE_SHORT" elif [ "$ANY_UPDATES" = true ]; then STATUS_COLOR="#1d6fa8" STATUS_BADGE="🔄 UPDATES AVAILABLE" STATUS_BANNER_MSG="🔄  Updates are available for one or more components" SUBJECT="🔄 PLEX32 — Updates Available — $DATE_SHORT" else STATUS_COLOR="#1a7f4b" STATUS_BADGE="✅ ALL CURRENT" STATUS_BANNER_MSG="✅  All components are up to date — no action required" SUBJECT="✅ PLEX32 — All Systems Current — $DATE_SHORT" fi # ============================================================= # STEP 6 — BUILD COMMAND BLOCKS (click to select) # ============================================================= make_cmd_block() { local step_num="$1" local step_label="$2" local cmd="$3" local cmd_html=$(echo "$cmd" | sed 's/&/\&/g; s//\>/g') echo "
$step_num. $step_label
👆 Click inside to select all — then Ctrl+C
$cmd_html
" } UPDATE_COMMANDS_HTML="" if [ "$ANY_UPDATES" = true ]; then UPDATE_COMMANDS_HTML="
🛠 Commands to Apply Updates (run in order)
" STEP=1 if [ "$APT_UPDATES" -gt 0 ]; then CMD="sudo apt update && sudo apt upgrade -y" UPDATE_COMMANDS_HTML+=$(make_cmd_block "$STEP" "OS & Package Updates" "$CMD") STEP=$((STEP+1)) fi if [ "$PLEX_UPDATE_AVAILABLE" = true ]; then CMD="cd ~/docker/plex docker compose up -d" UPDATE_COMMANDS_HTML+=$(make_cmd_block "$STEP" "Plex Media Server (Docker)" "$CMD") STEP=$((STEP+1)) fi if [ "$DOCKER_UPDATE_AVAILABLE" = true ]; then CMD="sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin docker --version" UPDATE_COMMANDS_HTML+=$(make_cmd_block "$STEP" "Docker Engine" "$CMD") STEP=$((STEP+1)) fi if [ "$F2B_UPDATE_AVAILABLE" = true ]; then CMD="sudo apt update && sudo apt install -y fail2ban sudo systemctl restart fail2ban sudo systemctl status fail2ban" UPDATE_COMMANDS_HTML+=$(make_cmd_block "$STEP" "Fail2Ban" "$CMD") fi UPDATE_COMMANDS_HTML+="
" fi # ============================================================= # STEP 7 — BUILD APT PACKAGES TABLE # ============================================================= APT_PACKAGES_HTML="" if [ "$APT_UPDATES" -gt 0 ] && [ "$APT_FAILED" = false ]; then APT_PACKAGES_HTML='
📦 Packages Awaiting Update
' while IFS= read -r line; do [ -z "$line" ] && continue pkg=$(echo "$line" | awk '{print $1}' | cut -d'/' -f1) ver=$(echo "$line" | awk '{print $2}') APT_PACKAGES_HTML+="" done <<< "$APT_DETAILS" APT_PACKAGES_HTML+='
Package Available Version
$pkg$ver
' fi # ============================================================= # STEP 8 — BADGES # ============================================================= make_badge() { local update="$1" local failed="$2" if [ "$failed" = true ]; then echo "CHECK FAILED" elif [ "$update" = true ]; then echo "UPDATE AVAILABLE" else echo "CURRENT" fi } PLEX_BADGE=$(make_badge "$PLEX_UPDATE_AVAILABLE" "$PLEX_FAILED") F2B_BADGE=$(make_badge "$F2B_UPDATE_AVAILABLE" "false") DOCKER_BADGE=$(make_badge "$DOCKER_UPDATE_AVAILABLE" "false") # ============================================================= # STEP 9 — BUILD & SEND EMAIL # ============================================================= python3 << PYEOF import smtplib, ssl from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText apt_updates = $APT_UPDATES plex_update = "$PLEX_UPDATE_AVAILABLE" == "true" f2b_update = "$F2B_UPDATE_AVAILABLE" == "true" docker_update = "$DOCKER_UPDATE_AVAILABLE" == "true" any_failed = "$ANY_FAILED" == "true" status_color = "$STATUS_COLOR" status_badge = "$STATUS_BADGE" status_banner = "$STATUS_BANNER_MSG" subject = "$SUBJECT" plex_status = "$PLEX_STATUS_TEXT" f2b_status = "$F2B_STATUS_TEXT" docker_status = "$DOCKER_STATUS_TEXT" plex_badge = """$PLEX_BADGE""" f2b_badge = """$F2B_BADGE""" docker_badge = """$DOCKER_BADGE""" update_commands = """$UPDATE_COMMANDS_HTML""" apt_packages_html = """$APT_PACKAGES_HTML""" plex_error_html = """$PLEX_ERROR_HTML""" timestamp = "$TIMESTAMP" plex_image_ver = "$PLEX_IMAGE_VERSION" current_image_date= "$CURRENT_IMAGE_DATE" from_email = "$EMAIL_FROM" total_updates = apt_updates + (1 if plex_update else 0) + (1 if f2b_update else 0) + (1 if docker_update else 0) apt_card_color = "#1d6fa8" if apt_updates > 0 else "#111827" plex_card_color = "#1d6fa8" if plex_update else "#111827" total_card_color = "#1d6fa8" if total_updates > 0 else "#1a7f4b" other_update = f2b_update or docker_update other_card_color = "#1d6fa8" if other_update else "#111827" other_count = sum([1 if f2b_update else 0, 1 if docker_update else 0]) html = f"""
Update Monitor
🖥 PLEX32
{timestamp} | VLAN 50 — 192.168.150.45
{status_badge}
{status_banner}
Total Updates
{total_updates}
OS Packages
{apt_updates}
Plex
{'1' if plex_update else '0'}
Other
{other_count}
📋 Component Status
Component Status
🐧 Ubuntu Server (apt) {apt_updates} package(s) available
🎬 Plex Media Server {plex_badge} {plex_status}
Image: {plex_image_ver} | Built: {current_image_date}{plex_error_html}
🛡 Fail2Ban {f2b_badge} {f2b_status}
🐳 Docker Engine {docker_badge} {docker_status}
{update_commands} {apt_packages_html}
Update Monitor  |  PLEX32  |  KingDezigns Infrastructure
""" msg = MIMEMultipart("alternative") msg["Subject"] = subject msg["From"] = f"KingDezigns PLEX32 <{from_email}>" msg["To"] = "$EMAIL_TO" msg.attach(MIMEText(html, "html")) context = ssl.create_default_context() try: with smtplib.SMTP_SSL("$SMTP_SERVER", $SMTP_PORT, context=context, timeout=15) as server: server.login("$SMTP_USER", "$SMTP_PASS") server.sendmail("$EMAIL_FROM", "$EMAIL_TO", msg.as_string()) print("[$TIMESTAMP] Email sent successfully.") except Exception as e: print(f"[$TIMESTAMP] Email send failed: {e}") exit(1) PYEOF echo "[$TIMESTAMP] Update check complete."