#!/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="

The Nextcloud Docker container was not found running on ${HOSTNAME_LABEL}. No update check could be performed.

" 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+=" ${APP_NAME} ${NEW_VER} " done fi # --- Build warning rows ----------------------------------- WARN_HTML="" for w in "${WARNINGS[@]}"; do WARN_HTML+="
โš  ${w}
" 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="
๐Ÿ“‹ Commands to run on NAS08
${CMD_LINES}
" # --- Maintenance/upgrade block ---------------------------- MAINT_BLOCK="" if [[ -n "$WARN_HTML" ]]; then MAINT_BLOCK="
โš  Issues Detected
${WARN_HTML}
" fi # --- Core update block -------------------------------------- CORE_BLOCK="" if [[ "$CORE_UPDATE_AVAILABLE" == "true" ]]; then CORE_BLOCK="
๐Ÿ†™ Core Update Available
Current Version ${NC_VERSION}
New Version ${CORE_NEW_VERSION}
" fi # --- App update table ------------------------------------- APP_TABLE="" if [[ $APP_COUNT -gt 0 ]]; then APP_TABLE="
๐Ÿ”„ App Updates Available (${APP_COUNT})
${APP_ROWS}
App New Version
" else APP_TABLE="
โœ“ All apps are up to date
" fi BODY_HTML="${MAINT_BLOCK}
Nextcloud Version ${NC_VERSION} Maintenance Mode ${MAINTENANCE:-false}
${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 <
Nextcloud ยท Daily Update Report
โ˜ ${HOSTNAME_LABEL}
${TIMESTAMP} | cloud.kingdezigns.com
${STATUS_LABEL}
${STATUS_MSG}
${BODY_HTML}
Nextcloud Update Check  |  ${HOSTNAME_LABEL}  |  KingDezigns Infrastructure
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 - <&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."