352 lines
15 KiB
Bash
352 lines
15 KiB
Bash
#!/bin/bash
|
|
# =============================================================
|
|
# nextcloud_update_check.sh
|
|
# Checks for Nextcloud CORE and app updates daily.
|
|
# Sends an HTML email report with update commands ready to
|
|
# copy-paste into terminal.
|
|
#
|
|
# Schedule: Daily (recommended 7:00 AM)
|
|
# Install: /usr/scripts/omv/nextcloud_update_check.sh
|
|
# Log: /var/log/nextcloud-updates/
|
|
#
|
|
# 2026-07-24 fix: previously only checked `occ app:update --all
|
|
# --showonly`, which reports APP updates only. It never checked
|
|
# for a Nextcloud CORE/server version update, so a core release
|
|
# (e.g. Nextcloud 34.0.2) was silently missed and the report
|
|
# said "up to date" even when it wasn't. Added a separate core
|
|
# check via `occ update:check`.
|
|
# =============================================================
|
|
|
|
# --- Config --------------------------------------------------
|
|
CONTAINER="nextcloud"
|
|
OCC="sudo docker exec -u www-data ${CONTAINER} php occ"
|
|
SMTP_SERVER="smtppro.zoho.com"
|
|
SMTP_PORT="465"
|
|
SMTP_USER="rufus.king@kingdezigns.com"
|
|
SMTP_PASS_FILE="/etc/nextcloud-smtp-pass"
|
|
FROM_NAME="NAS08"
|
|
FROM_EMAIL="rufus.king@kingdezigns.com"
|
|
TO_EMAIL="rufus.king@kingdezigns.com"
|
|
HOSTNAME_LABEL="NAS08"
|
|
LOG_DIR="/var/log/nextcloud-updates"
|
|
LOG_RETENTION_DAYS=90
|
|
# -------------------------------------------------------------
|
|
|
|
mkdir -p "$LOG_DIR"
|
|
DATESTAMP=$(date +"%Y%m%d")
|
|
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
|
LOGFILE="$LOG_DIR/update-check-${DATESTAMP}.log"
|
|
|
|
log() { echo "[$(date +"%H:%M:%S")] $*" | tee -a "$LOGFILE"; }
|
|
|
|
# --- Load SMTP password --------------------------------------
|
|
if [[ ! -f "$SMTP_PASS_FILE" ]]; then
|
|
log "ERROR: SMTP password file not found at $SMTP_PASS_FILE"
|
|
exit 1
|
|
fi
|
|
SMTP_PASS=$(cat "$SMTP_PASS_FILE")
|
|
|
|
# --- Check container is running ------------------------------
|
|
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
|
|
log "ERROR: Container '${CONTAINER}' is not running."
|
|
# Send critical alert
|
|
STATUS_COLOR="#b91c1c"
|
|
STATUS_LABEL="CRITICAL"
|
|
STATUS_MSG="⛔ Nextcloud container is not running"
|
|
BODY_HTML="<p style='color:#b91c1c;font-weight:600;'>The Nextcloud Docker container was not found running on ${HOSTNAME_LABEL}. No update check could be performed.</p>"
|
|
NEEDS_ACTION=true
|
|
APP_ROWS=""
|
|
CORE_ROW=""
|
|
CMD_BLOCK=""
|
|
else
|
|
# --- Get Nextcloud status ----------------------------------
|
|
log "Checking Nextcloud status..."
|
|
NC_STATUS=$($OCC status 2>/dev/null | grep -v '^{')
|
|
MAINTENANCE=$(echo "$NC_STATUS" | grep "maintenance:" | awk '{print $3}')
|
|
NEEDS_UPGRADE=$(echo "$NC_STATUS" | grep "needsDbUpgrade:" | awk '{print $3}')
|
|
NC_VERSION=$(echo "$NC_STATUS" | grep "versionstring:" | awk '{print $3}')
|
|
|
|
log "Nextcloud version: $NC_VERSION | maintenance: $MAINTENANCE | needsDbUpgrade: $NEEDS_UPGRADE"
|
|
|
|
# --- Check for CORE (server) update -------------------------
|
|
# occ app:update only reports app updates, never core/server
|
|
# releases. occ update:check is the command that actually
|
|
# checks the updater channel for a new Nextcloud version.
|
|
log "Checking core (server) update..."
|
|
CORE_CHECK_RAW=$($OCC update:check 2>/dev/null | grep -v '^{')
|
|
CORE_UPDATE_AVAILABLE=false
|
|
CORE_NEW_VERSION=""
|
|
if echo "$CORE_CHECK_RAW" | grep -qi "is available"; then
|
|
CORE_UPDATE_AVAILABLE=true
|
|
CORE_NEW_VERSION=$(echo "$CORE_CHECK_RAW" | grep -oE 'Nextcloud [0-9]+\.[0-9]+\.[0-9]+' | head -1 | awk '{print $2}')
|
|
log "Core update available: Nextcloud ${CORE_NEW_VERSION}"
|
|
else
|
|
log "Core is up to date."
|
|
fi
|
|
|
|
# --- Check for app updates --------------------------------
|
|
log "Checking app updates..."
|
|
# Filter out admin_audit JSON log lines that appear when admin_audit app is enabled
|
|
APP_UPDATE_RAW=$($OCC app:update --all --showonly 2>/dev/null | grep -v '^{' )
|
|
|
|
# Parse apps with available updates
|
|
# Format from occ app:update --showonly: "appname new version available: X.Y.Z"
|
|
UPDATABLE_APPS=()
|
|
while IFS= read -r line; do
|
|
if echo "$line" | grep -q "new version available"; then
|
|
APP_NAME=$(echo "$line" | awk '{print $1}')
|
|
NEW_VER=$(echo "$line" | awk '{print $NF}')
|
|
UPDATABLE_APPS+=("${APP_NAME}|||${NEW_VER}")
|
|
log "Update available: $APP_NAME → $NEW_VER"
|
|
fi
|
|
done <<< "$APP_UPDATE_RAW"
|
|
|
|
APP_COUNT=${#UPDATABLE_APPS[@]}
|
|
|
|
# --- Build status -----------------------------------------
|
|
NEEDS_ACTION=false
|
|
WARNINGS=()
|
|
|
|
if [[ "$MAINTENANCE" == "true" ]]; then
|
|
WARNINGS+=("Nextcloud is currently in maintenance mode")
|
|
NEEDS_ACTION=true
|
|
fi
|
|
if [[ "$NEEDS_UPGRADE" == "true" ]]; then
|
|
WARNINGS+=("Database upgrade required (needsDbUpgrade: true)")
|
|
NEEDS_ACTION=true
|
|
fi
|
|
if [[ $APP_COUNT -gt 0 ]]; then
|
|
NEEDS_ACTION=true
|
|
fi
|
|
if [[ "$CORE_UPDATE_AVAILABLE" == "true" ]]; then
|
|
NEEDS_ACTION=true
|
|
fi
|
|
|
|
# --- Determine overall status color -----------------------
|
|
if [[ "$MAINTENANCE" == "true" ]] || [[ "$NEEDS_UPGRADE" == "true" ]]; then
|
|
STATUS_COLOR="#b91c1c"
|
|
STATUS_LABEL="CRITICAL"
|
|
STATUS_MSG="⛔ Nextcloud requires immediate attention"
|
|
elif [[ "$CORE_UPDATE_AVAILABLE" == "true" ]] && [[ $APP_COUNT -gt 0 ]]; then
|
|
STATUS_COLOR="#b45309"
|
|
STATUS_LABEL="UPDATES AVAILABLE"
|
|
STATUS_MSG="🔄 Core update to ${CORE_NEW_VERSION} + ${APP_COUNT} app update(s) available"
|
|
elif [[ "$CORE_UPDATE_AVAILABLE" == "true" ]]; then
|
|
STATUS_COLOR="#b45309"
|
|
STATUS_LABEL="UPDATES AVAILABLE"
|
|
STATUS_MSG="🔄 Nextcloud core update available (${NC_VERSION} → ${CORE_NEW_VERSION})"
|
|
elif [[ $APP_COUNT -gt 0 ]]; then
|
|
STATUS_COLOR="#b45309"
|
|
STATUS_LABEL="UPDATES AVAILABLE"
|
|
STATUS_MSG="🔄 ${APP_COUNT} app update(s) available"
|
|
else
|
|
STATUS_COLOR="#1a7f4b"
|
|
STATUS_LABEL="UP TO DATE"
|
|
STATUS_MSG="✅ Nextcloud is fully up to date"
|
|
fi
|
|
|
|
# --- Maintenance mode color (was previously unset/unused) --
|
|
if [[ "$MAINTENANCE" == "true" ]]; then
|
|
MAINTENANCE_COLOR="#b91c1c"
|
|
else
|
|
MAINTENANCE_COLOR="#1a7f4b"
|
|
fi
|
|
|
|
# --- Build app update rows --------------------------------
|
|
APP_ROWS=""
|
|
if [[ $APP_COUNT -gt 0 ]]; then
|
|
for entry in "${UPDATABLE_APPS[@]}"; do
|
|
APP_NAME=$(echo "$entry" | cut -d'|' -f1)
|
|
NEW_VER=$(echo "$entry" | cut -d'|' -f4)
|
|
APP_ROWS+="<tr>
|
|
<td style='padding:9px 14px;border-bottom:1px solid #e5e7eb;font-size:13px;color:#111827;font-family:monospace;'>${APP_NAME}</td>
|
|
<td style='padding:9px 14px;border-bottom:1px solid #e5e7eb;font-size:13px;color:#1a7f4b;font-weight:600;'>${NEW_VER}</td>
|
|
</tr>"
|
|
done
|
|
fi
|
|
|
|
# --- Build warning rows -----------------------------------
|
|
WARN_HTML=""
|
|
for w in "${WARNINGS[@]}"; do
|
|
WARN_HTML+="<div style='margin-top:10px;padding:10px 14px;background:#fff1f2;border-left:4px solid #b91c1c;border-radius:4px;'>
|
|
<span style='font-size:13px;color:#7f1d1d;font-weight:600;'>⚠ ${w}</span>
|
|
</div>"
|
|
done
|
|
|
|
# --- Build copy-paste command block -----------------------
|
|
# NOTE: must use $'...' (ANSI-C quoting) below, not "...". Plain
|
|
# double quotes do NOT interpret \n as a newline in bash — they
|
|
# insert the two literal characters \ and n, which is why the
|
|
# previous version of this script emailed out commands with
|
|
# literal "\n" text instead of real line breaks.
|
|
CMD_LINES=""
|
|
if [[ "$MAINTENANCE" == "true" ]]; then
|
|
CMD_LINES+=$'# Disable maintenance mode\nsudo docker exec -u www-data nextcloud php occ maintenance:mode --off\n\n'
|
|
fi
|
|
if [[ "$CORE_UPDATE_AVAILABLE" == "true" ]]; then
|
|
CMD_LINES+="# Update Nextcloud CORE to ${CORE_NEW_VERSION} (do this before app updates)"$'\n'"cd ~/docker/nextcloud"$'\n'"docker compose pull"$'\n'"docker compose up -d"$'\n'"sudo docker exec -u www-data nextcloud php occ upgrade --no-interaction"$'\n\n'
|
|
fi
|
|
if [[ "$NEEDS_UPGRADE" == "true" ]]; then
|
|
CMD_LINES+=$'# Run database upgrade\nsudo docker exec -u www-data nextcloud php occ upgrade --no-interaction\n\n'
|
|
fi
|
|
if [[ $APP_COUNT -gt 0 ]]; then
|
|
CMD_LINES+=$'# Update all apps\nsudo docker exec -u www-data nextcloud php occ app:update --all\n\n'
|
|
fi
|
|
if [[ -z "$CMD_LINES" ]]; then
|
|
CMD_LINES="# No action required — everything is up to date"$'\n'
|
|
fi
|
|
CMD_LINES+=$'# Verify status after updates\nsudo docker exec -u www-data nextcloud php occ status'
|
|
|
|
CMD_BLOCK="<div style='margin-top:16px;'>
|
|
<div style='font-size:11px;font-weight:700;color:#374151;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;'>📋 Commands to run on NAS08</div>
|
|
<pre style='margin:0;background:#1e293b;color:#e2e8f0;padding:14px;border-radius:6px;font-size:12px;line-height:1.6;overflow-x:auto;white-space:pre;'>${CMD_LINES}</pre>
|
|
</div>"
|
|
|
|
# --- Maintenance/upgrade block ----------------------------
|
|
MAINT_BLOCK=""
|
|
if [[ -n "$WARN_HTML" ]]; then
|
|
MAINT_BLOCK="<div style='margin-top:20px;'>
|
|
<div style='font-size:11px;font-weight:700;color:#b91c1c;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px;'>⚠ Issues Detected</div>
|
|
${WARN_HTML}
|
|
</div>"
|
|
fi
|
|
|
|
# --- Core update block --------------------------------------
|
|
CORE_BLOCK=""
|
|
if [[ "$CORE_UPDATE_AVAILABLE" == "true" ]]; then
|
|
CORE_BLOCK="<div style='margin-top:20px;background:white;border-radius:10px;border:1px solid #e5e7eb;padding:18px 22px;'>
|
|
<div style='font-size:13px;font-weight:700;color:#374151;margin-bottom:12px;text-transform:uppercase;letter-spacing:.5px;'>🆙 Core Update Available</div>
|
|
<table width='100%' cellspacing='0' cellpadding='0'>
|
|
<tr>
|
|
<td style='font-size:13px;color:#6b7280;padding-bottom:6px;'>Current Version</td>
|
|
<td style='font-size:13px;color:#111827;font-weight:600;padding-bottom:6px;font-family:monospace;'>${NC_VERSION}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style='font-size:13px;color:#6b7280;'>New Version</td>
|
|
<td style='font-size:13px;color:#b45309;font-weight:700;font-family:monospace;'>${CORE_NEW_VERSION}</td>
|
|
</tr>
|
|
</table>
|
|
</div>"
|
|
fi
|
|
|
|
# --- App update table -------------------------------------
|
|
APP_TABLE=""
|
|
if [[ $APP_COUNT -gt 0 ]]; then
|
|
APP_TABLE="<div style='margin-top:20px;background:white;border-radius:10px;border:1px solid #e5e7eb;padding:18px 22px;'>
|
|
<div style='font-size:13px;font-weight:700;color:#374151;margin-bottom:12px;text-transform:uppercase;letter-spacing:.5px;'>🔄 App Updates Available (${APP_COUNT})</div>
|
|
<table width='100%' cellspacing='0' cellpadding='0' style='border-collapse:collapse;font-size:13px;'>
|
|
<thead>
|
|
<tr>
|
|
<th style='text-align:left;padding:8px 14px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:#9ca3af;background:#f9fafb;border-bottom:1px solid #e5e7eb;font-weight:500;'>App</th>
|
|
<th style='text-align:left;padding:8px 14px;font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:#9ca3af;background:#f9fafb;border-bottom:1px solid #e5e7eb;font-weight:500;'>New Version</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>${APP_ROWS}</tbody>
|
|
</table>
|
|
</div>"
|
|
else
|
|
APP_TABLE="<div style='margin-top:20px;padding:12px 14px;background:#f0fdf4;border-radius:6px;border:1px solid #bbf7d0;'>
|
|
<span style='font-size:13px;color:#166534;font-weight:600;'>✓ All apps are up to date</span>
|
|
</div>"
|
|
fi
|
|
|
|
BODY_HTML="${MAINT_BLOCK}
|
|
|
|
<!-- NC Version info -->
|
|
<div style='margin-top:20px;padding:12px 14px;background:#f8fafc;border-radius:6px;border:1px solid #e5e7eb;'>
|
|
<span style='font-size:12px;font-weight:600;color:#374151;text-transform:uppercase;letter-spacing:.5px;'>Nextcloud Version</span>
|
|
<span style='margin-left:10px;font-size:13px;color:#374151;font-family:monospace;'>${NC_VERSION}</span>
|
|
<span style='margin-left:20px;font-size:12px;font-weight:600;color:#374151;text-transform:uppercase;letter-spacing:.5px;'>Maintenance Mode</span>
|
|
<span style='margin-left:10px;font-size:13px;color:${MAINTENANCE_COLOR};font-family:monospace;'>${MAINTENANCE:-false}</span>
|
|
</div>
|
|
|
|
${CORE_BLOCK}
|
|
${APP_TABLE}
|
|
${CMD_BLOCK}"
|
|
fi
|
|
|
|
# --- Determine email subject ---------------------------------
|
|
if [[ "$NEEDS_ACTION" == "true" ]]; then
|
|
SUBJECT="[NAS08] Nextcloud — ${STATUS_LABEL} — Action Required"
|
|
else
|
|
SUBJECT="[NAS08] Nextcloud — All Up To Date"
|
|
fi
|
|
|
|
log "Composing email: $SUBJECT"
|
|
|
|
# --- Build full HTML email -----------------------------------
|
|
HTML_EMAIL=$(cat <<HTML
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"></head>
|
|
<body style="margin:0;padding:0;background:#f1f5f9;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
|
<table width="100%" cellspacing="0" cellpadding="0" style="background:#f1f5f9;padding:24px 16px;">
|
|
<tr><td align="center">
|
|
<table width="100%" cellspacing="0" cellpadding="0" style="max-width:720px;">
|
|
|
|
<!-- HEADER -->
|
|
<tr><td style="background:#0f172a;border-radius:10px 10px 0 0;padding:28px 32px;">
|
|
<table width="100%" cellspacing="0" cellpadding="0"><tr>
|
|
<td>
|
|
<div style="font-size:11px;color:#94a3b8;letter-spacing:1px;text-transform:uppercase;">Nextcloud · Daily Update Report</div>
|
|
<div style="font-size:26px;font-weight:800;color:white;margin-top:4px;">☁ ${HOSTNAME_LABEL}</div>
|
|
<div style="font-size:13px;color:#94a3b8;margin-top:4px;">${TIMESTAMP} | cloud.kingdezigns.com</div>
|
|
</td>
|
|
<td align="right">
|
|
<div style="background:${STATUS_COLOR};color:white;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:700;">${STATUS_LABEL}</div>
|
|
</td>
|
|
</tr></table>
|
|
</td></tr>
|
|
|
|
<!-- STATUS BANNER -->
|
|
<tr><td style="background:${STATUS_COLOR};padding:12px 32px;">
|
|
<span style="color:white;font-size:14px;font-weight:600;">${STATUS_MSG}</span>
|
|
</td></tr>
|
|
|
|
<!-- BODY -->
|
|
<tr><td style="background:white;border-radius:0 0 10px 10px;padding:28px 32px;">
|
|
${BODY_HTML}
|
|
</td></tr>
|
|
|
|
<!-- FOOTER -->
|
|
<tr><td style="background:#0f172a;border-radius:0 0 10px 10px;padding:16px 32px;text-align:center;margin-top:8px;">
|
|
<span style="font-size:11px;color:#64748b;">Nextcloud Update Check | ${HOSTNAME_LABEL} | KingDezigns Infrastructure</span>
|
|
</td></tr>
|
|
|
|
</table>
|
|
</td></tr>
|
|
</table>
|
|
</body>
|
|
</html>
|
|
HTML
|
|
)
|
|
|
|
# --- Send email via curl/SMTP --------------------------------
|
|
log "Sending email to ${TO_EMAIL}..."
|
|
|
|
SEND_RESULT=$(curl --silent --show-error \
|
|
--url "smtps://${SMTP_SERVER}:${SMTP_PORT}" \
|
|
--ssl-reqd \
|
|
--mail-from "${FROM_EMAIL}" \
|
|
--mail-rcpt "${TO_EMAIL}" \
|
|
--user "${SMTP_USER}:${SMTP_PASS}" \
|
|
--upload-file - <<EOF 2>&1
|
|
From: ${FROM_NAME} <${FROM_EMAIL}>
|
|
To: ${TO_EMAIL}
|
|
Subject: ${SUBJECT}
|
|
MIME-Version: 1.0
|
|
Content-Type: text/html; charset=UTF-8
|
|
|
|
${HTML_EMAIL}
|
|
EOF
|
|
)
|
|
|
|
if [[ $? -eq 0 ]]; then
|
|
log "Email sent successfully."
|
|
else
|
|
log "ERROR sending email: ${SEND_RESULT}"
|
|
fi
|
|
|
|
# --- Rotate old logs -----------------------------------------
|
|
find "$LOG_DIR" -name "update-check-*.log" -mtime +${LOG_RETENTION_DAYS} -delete
|
|
log "Done."
|