#!/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; 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; 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
| Package |
Available Version |
'
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+="| $pkg | $ver |
"
done <<< "$APT_DETAILS"
APT_PACKAGES_HTML+='
'
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'}
|
|
📋 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."