nas16-scripts/nas/nas08_to_nas16_sync.sh

477 lines
23 KiB
Bash
Raw Normal View History

2026-07-26 21:21:03 -04:00
#!/bin/bash
# =============================================================================
# NAS08 → NAS16 Sync Script
# Source : NAS08 (192.168.150.35) /mnt/kingdezigns08/Public
# Dest : NAS16 (192.168.150.40) /export/kingdezignsnas-16/Public
#
# Behavior:
# - Copies only changed/new files from NAS08 to NAS16
# - Does NOT delete files on NAS16 that are missing from NAS08
# - Preserves file timestamps and symlinks
# - Skips permission syncing (NFS all_squash incompatible with chmod)
# - Skips directory timestamp syncing (NFS all_squash incompatible with utimes)
# - Auto-corrects destination ownership/permissions before sync if needed
# - Sends an HTML status email after every run
# - Logs every run with timestamps
#
# Schedule via OMV Task Scheduler: 30 2 */2 * *
# =============================================================================
# --- Configuration -----------------------------------------------------------
SOURCE="/mnt/kingdezigns08/Public/"
DEST="/export/kingdezignsnas-16/Public/"
LOG_DIR="/var/log/nas-sync"
LOG_FILE="${LOG_DIR}/nas08_to_nas16_$(date +%Y-%m-%d).log"
LOCK_FILE="/tmp/nas08_to_nas16.lock"
REPORT_TO="rufus.king@kingdezigns.com"
REPORT_FROM="nas-sync@kingdezigns16.local"
NOW=$(date '+%Y-%m-%d %H:%M:%S')
NFS_MOUNT="/mnt/kingdezigns08"
DEST_EXPECTED_OWNER="www-data"
DEST_EXPECTED_GROUP="ncshare"
# --- HTML escape helper ------------------------------------------------------
html_escape() { sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g'; }
# --- Safety Checks -----------------------------------------------------------
# Ensure log directory exists and is owned by the running user.
mkdir -p "$LOG_DIR"
if [ "$(stat -c '%U' "$LOG_DIR")" != "$(whoami)" ]; then
sudo chown -R "$(whoami)":users "$LOG_DIR" 2>/dev/null || true
fi
# Prevent overlapping runs — but self-heal stale lock files.
if [ -f "$LOCK_FILE" ]; then
LOCK_PID=$(cat "$LOCK_FILE" 2>/dev/null)
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Sync already running as PID $LOCK_PID. Exiting." | tee -a "$LOG_FILE"
exit 1
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: Stale lock file found (PID $LOCK_PID no longer running). Removing and continuing." | tee -a "$LOG_FILE"
rm -f "$LOCK_FILE"
fi
fi
echo $$ > "$LOCK_FILE"
# Wait for NFS mount to be fully ready (up to 30 seconds)
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Checking NFS mount readiness..." | tee -a "$LOG_FILE"
WAIT=0
until mountpoint -q "$NFS_MOUNT"; do
sleep 5
WAIT=$((WAIT + 5))
if [ $WAIT -ge 30 ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: NFS mount $NFS_MOUNT not ready after 30 seconds. Aborting." | tee -a "$LOG_FILE"
rm -f "$LOCK_FILE"
exit 1
fi
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Waiting for NFS mount... (${WAIT}s)" | tee -a "$LOG_FILE"
done
echo "[$(date '+%Y-%m-%d %H:%M:%S')] NFS mount ready." | tee -a "$LOG_FILE"
# Verify source is mounted and not empty
if [ ! -d "$SOURCE" ] || [ -z "$(ls -A "$SOURCE" 2>/dev/null)" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Source '$SOURCE' is not mounted or is empty. Aborting to prevent data loss." | tee -a "$LOG_FILE"
rm -f "$LOCK_FILE"
exit 1
fi
# Verify destination exists
if [ ! -d "$DEST" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: Destination '$DEST' does not exist. Aborting." | tee -a "$LOG_FILE"
rm -f "$LOCK_FILE"
exit 1
fi
# --- Destination Permission Precheck -----------------------------------------
# NFS all_squash maps all writes to www-data:ncshare (anonuid=33, anongid=1001).
# If the destination tree is not owned by www-data:ncshare, rsync will fail to
# set permissions and timestamps. Self-correct before the sync runs so this
# never requires manual intervention.
PERM_CORRECTED=false
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Verifying destination ownership and permissions..." | tee -a "$LOG_FILE"
DEST_OWNER=$(stat -c '%U' "$DEST" 2>/dev/null)
DEST_GROUP=$(stat -c '%G' "$DEST" 2>/dev/null)
if [ "$DEST_OWNER" != "$DEST_EXPECTED_OWNER" ] || [ "$DEST_GROUP" != "$DEST_EXPECTED_GROUP" ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: Destination ownership is ${DEST_OWNER}:${DEST_GROUP} — expected ${DEST_EXPECTED_OWNER}:${DEST_EXPECTED_GROUP}. Correcting now..." | tee -a "$LOG_FILE"
sudo chown -R "${DEST_EXPECTED_OWNER}:${DEST_EXPECTED_GROUP}" "$DEST" 2>&1 | tee -a "$LOG_FILE"
sudo chmod -R 2770 "$DEST" 2>&1 | tee -a "$LOG_FILE"
sudo setfacl -R -m "g:${DEST_EXPECTED_GROUP}:rwx" "$DEST" 2>&1 | tee -a "$LOG_FILE"
sudo setfacl -R -d -m "g:${DEST_EXPECTED_GROUP}:rwx" "$DEST" 2>&1 | tee -a "$LOG_FILE"
PERM_CORRECTED=true
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Destination permissions corrected successfully." | tee -a "$LOG_FILE"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Destination ownership OK (${DEST_OWNER}:${DEST_GROUP})." | tee -a "$LOG_FILE"
fi
# --- Run Sync ----------------------------------------------------------------
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ===== NAS08 → NAS16 Sync Started =====" | tee -a "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Source : $SOURCE" | tee -a "$LOG_FILE"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Dest : $DEST" | tee -a "$LOG_FILE"
START_TIME=$(date +%s)
# --archive : recursive, preserve symlinks, owner, group, devices
# --no-perms : skip chmod — NFS all_squash prevents non-owner chmod
# --omit-dir-times : skip directory utimes — NFS all_squash prevents non-owner utimes on dirs
# File timestamps are still preserved exactly.
# --update : skip files that are newer on destination
RSYNC_OUTPUT=$(rsync \
--archive \
--no-perms \
--omit-dir-times \
--update \
--human-readable \
--itemize-changes \
--stats \
--exclude='.Trash-1000/' \
"$SOURCE" \
"$DEST" 2>&1)
RSYNC_EXIT=$?
END_TIME=$(date +%s)
ELAPSED=$(( END_TIME - START_TIME ))
ELAPSED_FMT=$(printf '%dm %ds' $((ELAPSED/60)) $((ELAPSED%60)))
# Log full rsync output
echo "$RSYNC_OUTPUT" >> "$LOG_FILE"
# --- Parse rsync stats -------------------------------------------------------
FILES_TOTAL=$(echo "$RSYNC_OUTPUT" | grep "Number of files:" | grep -oP '[\d,]+' | head -1 | tr -d ',')
FILES_TRANSFERRED=$(echo "$RSYNC_OUTPUT" | grep "Number of regular files transferred:" | grep -oP '[\d,]+' | head -1 | tr -d ',')
FILES_CREATED=$(echo "$RSYNC_OUTPUT" | grep "Number of created files:" | grep -oP '[\d,]+' | head -1 | tr -d ',')
TOTAL_SIZE=$(echo "$RSYNC_OUTPUT" | grep "Total file size:" | grep -oP '[0-9.,]+\s*[KMGT]?' | head -1)
TRANSFERRED_SIZE=$(echo "$RSYNC_OUTPUT" | grep "Total transferred file size:" | grep -oP '[0-9.,]+\s*[KMGT]?' | head -1)
TRANSFER_RATE=$(echo "$RSYNC_OUTPUT" | grep "sent.*bytes.*received" | grep -oP '[\d.,]+\s*[KMGT]?bytes/sec' | head -1)
FILES_TOTAL=${FILES_TOTAL:-0}
FILES_TRANSFERRED=${FILES_TRANSFERRED:-0}
FILES_CREATED=${FILES_CREATED:-0}
TOTAL_SIZE=${TOTAL_SIZE:-"unknown"}
TRANSFERRED_SIZE=${TRANSFERRED_SIZE:-"0"}
TRANSFER_RATE=${TRANSFER_RATE:-"unknown"}
# --- Parse per-folder activity -----------------------------------------------
declare -A FOLDER_COUNTS
declare -A FOLDER_ERRORS
while IFS= read -r LINE; do
if echo "$LINE" | grep -qE '^>f'; then
FILEPATH=$(echo "$LINE" | sed 's/^[^ ]* //')
TOP=$(echo "$FILEPATH" | cut -d'/' -f1)
if [ -n "$TOP" ]; then
FOLDER_COUNTS["$TOP"]=$(( ${FOLDER_COUNTS["$TOP"]:-0} + 1 ))
fi
fi
if echo "$LINE" | grep -qE "failed:|Permission denied|No such file|error:"; then
ERR_FOLDER=$(echo "$LINE" | grep -oP '"[^"]*"' | head -1 | tr -d '"' | xargs basename 2>/dev/null || echo "unknown")
FOLDER_ERRORS["$ERR_FOLDER"]=$(echo "$LINE" | html_escape)
fi
done <<< "$RSYNC_OUTPUT"
# Build folder activity rows
FOLDER_ROWS=""
FOLDERS_WITH_CHANGES=0
FOLDERS_WITH_ERRORS=0
for FOLDER in "${!FOLDER_COUNTS[@]}"; do
COUNT=${FOLDER_COUNTS[$FOLDER]}
FOLDERS_WITH_CHANGES=$((FOLDERS_WITH_CHANGES + 1))
FOLDER_ESC=$(echo "$FOLDER" | html_escape)
FOLDER_ROWS+="
<tr style='background:#f0fdf4;'>
<td style='padding:7px 12px;font-size:12px;font-family:monospace;color:#111827;border-bottom:1px solid #e5e7eb;'>&#128193; ${FOLDER_ESC}</td>
<td style='padding:7px 12px;font-size:12px;text-align:center;border-bottom:1px solid #e5e7eb;'>
<span style='background:#1a7f4b;color:white;padding:2px 10px;border-radius:12px;font-size:11px;font-weight:700;'>${COUNT} file(s) updated</span>
</td>
</tr>"
done
for FOLDER in "${!FOLDER_ERRORS[@]}"; do
FOLDERS_WITH_ERRORS=$((FOLDERS_WITH_ERRORS + 1))
FOLDER_ESC=$(echo "$FOLDER" | html_escape)
ERR_ESC=${FOLDER_ERRORS[$FOLDER]}
FOLDER_ROWS+="
<tr style='background:#fff1f2;'>
<td style='padding:7px 12px;font-size:12px;font-family:monospace;color:#7f1d1d;border-bottom:1px solid #fecdd3;'>&#9888; ${FOLDER_ESC}</td>
<td style='padding:7px 12px;font-size:11px;color:#b91c1c;border-bottom:1px solid #fecdd3;'>${ERR_ESC}</td>
</tr>"
done
if [ -z "$FOLDER_ROWS" ]; then
FOLDER_ROWS="
<tr>
<td colspan='2' style='padding:12px;font-size:12px;color:#6b7280;text-align:center;border-bottom:1px solid #e5e7eb;'>No folder activity — all files are already up to date.</td>
</tr>"
fi
# --- Determine overall status ------------------------------------------------
if [ $RSYNC_EXIT -eq 0 ] && [ "$FOLDERS_WITH_ERRORS" -eq 0 ]; then
OVERALL_STATUS="SUCCESS"
BANNER_BG="#1a7f4b"
BANNER_ICON="&#9989;"
BANNER_MSG="Sync completed successfully — no errors detected."
STATUS_BADGE_BG="#1a7f4b"
CARD_HEADER_BG="#f0fdf4"
elif [ "$FOLDERS_WITH_ERRORS" -gt 0 ]; then
OVERALL_STATUS="WARNING"
BANNER_BG="#b45309"
BANNER_ICON="&#9888;&#65039;"
BANNER_MSG="Sync completed with warnings — some folders had issues."
STATUS_BADGE_BG="#b45309"
CARD_HEADER_BG="#fffbeb"
else
OVERALL_STATUS="FAILED"
BANNER_BG="#b91c1c"
BANNER_ICON="&#128680;"
BANNER_MSG="Sync failed — check the log for details."
STATUS_BADGE_BG="#b91c1c"
CARD_HEADER_BG="#fff1f2"
fi
# Include permission correction notice in subject if it ran
PERM_NOTE=""
if [ "$PERM_CORRECTED" = true ]; then
PERM_NOTE=" [permissions auto-corrected]"
fi
SUBJECT="[NAS Sync] NAS08 → NAS16 — ${OVERALL_STATUS}${PERM_NOTE}${NOW}"
# --- Log result --------------------------------------------------------------
if [ $RSYNC_EXIT -eq 0 ]; then
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ===== Sync Completed Successfully =====" | tee -a "$LOG_FILE"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: rsync exited with code $RSYNC_EXIT. Check log: $LOG_FILE" | tee -a "$LOG_FILE"
fi
# --- Build HTML email --------------------------------------------------------
RAW_LOG_ESC=$(echo "$RSYNC_OUTPUT" | html_escape)
# Permission correction notice block (only shown if correction ran)
if [ "$PERM_CORRECTED" = true ]; then
PERM_NOTICE_BLOCK="
<div style='margin-bottom:20px;padding:12px 16px;background:#fffbeb;border:1px solid #fac775;border-radius:6px;'>
<div style='font-size:11px;font-weight:700;color:#633806;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;'>&#9888; Destination Permissions Auto-Corrected</div>
<div style='font-size:12px;color:#633806;'>Destination ownership was not <code>www-data:ncshare</code>. chown, chmod, and setfacl were applied automatically before the sync ran.</div>
</div>"
else
PERM_NOTICE_BLOCK=""
fi
HTML=$(cat <<HTMLEOF
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></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:30px 0;">
<tr><td align="center">
<table width="720" cellspacing="0" cellpadding="0" style="max-width:720px;width:100%;">
<!-- 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;">NAS File Sync Monitor</div>
<div style="font-size:26px;font-weight:800;color:white;margin-top:4px;">&#128260; NAS08 &rarr; NAS16</div>
<div style="font-size:13px;color:#94a3b8;margin-top:4px;">${NOW} &nbsp;|&nbsp; Schedule: every other day at 2:30 AM</div>
</td>
<td align="right">
<div style="background:${BANNER_BG};color:white;padding:10px 20px;border-radius:8px;font-size:14px;font-weight:700;">${BANNER_ICON} ${OVERALL_STATUS}</div>
</td>
</tr></table>
</td></tr>
<!-- Status Banner -->
<tr><td style="background:${BANNER_BG};padding:12px 32px;">
<span style="color:white;font-size:14px;font-weight:600;">${BANNER_ICON} &nbsp;${BANNER_MSG}</span>
</td></tr>
<!-- Sync Result Card -->
<tr><td style="padding:28px 24px 8px;">
<div style="background:white;border-radius:10px;box-shadow:0 1px 6px rgba(0,0,0,.10);margin-bottom:28px;overflow:hidden;border:1px solid #e5e7eb;">
<div style="background:${CARD_HEADER_BG};padding:16px 22px;border-bottom:1px solid #e5e7eb;">
<table width="100%" cellspacing="0" cellpadding="0"><tr>
<td>
<span style="font-size:18px;font-weight:700;color:#111827;">&#128196; Sync Results</span>
<span style="margin-left:8px;font-size:12px;color:#6b7280;">NAS08 Public &rarr; NAS16 Public</span>
</td>
<td align="right">
<span style="background:${STATUS_BADGE_BG};color:white;padding:5px 14px;border-radius:20px;font-size:12px;font-weight:700;letter-spacing:.5px;">${OVERALL_STATUS}</span>
</td>
</tr></table>
</div>
<div style="padding:20px 22px;">
${PERM_NOTICE_BLOCK}
<!-- Stats tiles -->
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td style="width:18%;padding:8px 10px;background:#f9fafb;border-radius:6px;text-align:center;">
<div style="font-size:11px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;">Scanned</div>
<div style="font-size:20px;font-weight:700;color:#111827;margin-top:2px;">${FILES_TOTAL}</div>
</td><td style="width:2%;"></td>
<td style="width:18%;padding:8px 10px;background:#f9fafb;border-radius:6px;text-align:center;">
<div style="font-size:11px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;">Transferred</div>
<div style="font-size:20px;font-weight:700;color:#111827;margin-top:2px;">${FILES_TRANSFERRED}</div>
</td><td style="width:2%;"></td>
<td style="width:18%;padding:8px 10px;background:#f9fafb;border-radius:6px;text-align:center;">
<div style="font-size:11px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;">New Files</div>
<div style="font-size:20px;font-weight:700;color:#111827;margin-top:2px;">${FILES_CREATED}</div>
</td><td style="width:2%;"></td>
<td style="width:18%;padding:8px 10px;background:#f9fafb;border-radius:6px;text-align:center;">
<div style="font-size:11px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;">Data Moved</div>
<div style="font-size:20px;font-weight:700;color:#111827;margin-top:2px;">${TRANSFERRED_SIZE}</div>
</td><td style="width:2%;"></td>
<td style="width:18%;padding:8px 10px;background:#f9fafb;border-radius:6px;text-align:center;">
<div style="font-size:11px;color:#9ca3af;text-transform:uppercase;letter-spacing:.5px;">Duration</div>
<div style="font-size:20px;font-weight:700;color:#111827;margin-top:2px;">${ELAPSED_FMT}</div>
</td>
</tr>
</table>
<!-- Source / Dest details -->
<div style="margin-top:16px;padding:12px 14px;background:#f8fafc;border-radius:6px;border:1px solid #e5e7eb;">
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td style="font-size:12px;color:#6b7280;width:90px;padding-bottom:5px;">Source</td>
<td style="font-size:12px;font-family:monospace;color:#111827;padding-bottom:5px;">NAS08 (192.168.150.35) &rarr; ${SOURCE}</td>
</tr>
<tr>
<td style="font-size:12px;color:#6b7280;padding-bottom:5px;">Destination</td>
<td style="font-size:12px;font-family:monospace;color:#111827;padding-bottom:5px;">NAS16 (192.168.150.40) &rarr; ${DEST}</td>
</tr>
<tr>
<td style="font-size:12px;color:#6b7280;padding-bottom:5px;">Transfer Rate</td>
<td style="font-size:12px;font-family:monospace;color:#111827;padding-bottom:5px;">${TRANSFER_RATE}</td>
</tr>
<tr>
<td style="font-size:12px;color:#6b7280;">Total Source</td>
<td style="font-size:12px;font-family:monospace;color:#111827;">${TOTAL_SIZE}</td>
</tr>
</table>
</div>
<!-- Folder Activity — collapsible -->
<details style="margin-top:14px;">
<summary style="cursor:pointer;font-size:12px;color:#6b7280;font-weight:600;letter-spacing:.3px;">&#9654; Folder Activity &nbsp;&mdash;&nbsp; ${FOLDERS_WITH_CHANGES} updated &nbsp;|&nbsp; ${FOLDERS_WITH_ERRORS} error(s)</summary>
<div style="margin-top:8px;overflow-x:auto;border-radius:6px;border:1px solid #e5e7eb;">
<table width="100%" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
<thead>
<tr style="background:#0f172a;">
<th style="padding:8px 12px;font-size:11px;color:#94a3b8;text-align:left;text-transform:uppercase;letter-spacing:.5px;">Folder</th>
<th style="padding:8px 12px;font-size:11px;color:#94a3b8;text-align:center;text-transform:uppercase;letter-spacing:.5px;width:200px;">Status</th>
</tr>
</thead>
<tbody>${FOLDER_ROWS}</tbody>
</table>
</div>
</details>
<!-- Full rsync log — collapsible -->
<details style="margin-top:10px;">
<summary style="cursor:pointer;font-size:12px;color:#6b7280;font-weight:600;letter-spacing:.3px;">&#9654; Full rsync Log &nbsp;&mdash;&nbsp; click to expand</summary>
<pre style="margin-top:8px;background:#1e293b;color:#e2e8f0;padding:14px;border-radius:6px;font-size:11px;line-height:1.6;overflow-x:auto;white-space:pre-wrap;">${RAW_LOG_ESC}</pre>
</details>
</div>
</div>
</td></tr>
<!-- Summary -->
<tr><td style="padding:0 24px 28px;">
<div style="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;">&#128203; Report Summary</div>
<table width="100%" cellspacing="0" cellpadding="0">
<tr>
<td style="font-size:13px;color:#6b7280;padding-bottom:6px;">Host</td>
<td style="font-size:13px;color:#111827;font-weight:600;padding-bottom:6px;">NAS16 (kingdezigns16)</td>
</tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding-bottom:6px;">Run Time</td>
<td style="font-size:13px;color:#111827;font-weight:600;padding-bottom:6px;">${NOW}</td>
</tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding-bottom:6px;">rsync Exit Code</td>
<td style="font-size:13px;font-weight:700;padding-bottom:6px;">${RSYNC_EXIT}</td>
</tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding-bottom:6px;">Permissions Auto-Corrected</td>
<td style="font-size:13px;font-weight:700;padding-bottom:6px;">${PERM_CORRECTED}</td>
</tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding-bottom:6px;">Folders Updated</td>
<td style="font-size:13px;color:#111827;font-weight:600;padding-bottom:6px;">${FOLDERS_WITH_CHANGES}</td>
</tr>
<tr>
<td style="font-size:13px;color:#6b7280;padding-bottom:6px;">Folder Errors</td>
<td style="font-size:13px;font-weight:700;padding-bottom:6px;">${FOLDERS_WITH_ERRORS}</td>
</tr>
<tr>
<td style="font-size:13px;color:#6b7280;">Log File</td>
<td style="font-size:13px;font-family:monospace;color:#111827;">${LOG_FILE}</td>
</tr>
</table>
</div>
</td></tr>
<!-- Footer -->
<tr><td style="background:#0f172a;border-radius:0 0 10px 10px;padding:16px 32px;text-align:center;">
<span style="font-size:11px;color:#64748b;">Automated NAS Sync Monitor &nbsp;|&nbsp; NAS16 &nbsp;|&nbsp; KingDezigns Infrastructure</span>
</td></tr>
</table>
</td></tr>
</table>
</body>
</html>
HTMLEOF
)
# --- Send email --------------------------------------------------------------
send_email() {
if command -v sendmail &>/dev/null; then
{ echo "To: ${REPORT_TO}"; echo "From: ${REPORT_FROM}"; echo "Subject: ${SUBJECT}"
echo "MIME-Version: 1.0"; echo "Content-Type: text/html; charset=UTF-8"; echo ""
echo "$HTML"; } | sendmail -t
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Report sent via sendmail to ${REPORT_TO}" | tee -a "$LOG_FILE"
elif command -v mailx &>/dev/null; then
echo "$HTML" | mailx -a "Content-Type: text/html" -s "$SUBJECT" "$REPORT_TO"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Report sent via mailx to ${REPORT_TO}" | tee -a "$LOG_FILE"
elif command -v mail &>/dev/null; then
echo "$HTML" | mail -a "Content-Type: text/html; charset=UTF-8" -s "$SUBJECT" "$REPORT_TO"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Report sent via mail to ${REPORT_TO}" | tee -a "$LOG_FILE"
else
echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: No mail transport found." | tee -a "$LOG_FILE"
FALLBACK="/tmp/nas_sync_report_$(date +%Y%m%d_%H%M%S).html"
echo "$HTML" > "$FALLBACK"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] HTML report saved to: $FALLBACK" | tee -a "$LOG_FILE"
fi
}
send_email
# --- Cleanup -----------------------------------------------------------------
rm -f "$LOCK_FILE"
# Rotate logs older than 30 days
find "$LOG_DIR" -name "nas08_to_nas16_*.log" -mtime +30 -delete
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Done — Status: ${OVERALL_STATUS} | Transferred: ${FILES_TRANSFERRED} file(s) | Duration: ${ELAPSED_FMT}" | tee -a "$LOG_FILE"
exit $RSYNC_EXIT