477 lines
23 KiB
Bash
477 lines
23 KiB
Bash
|
|
#!/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/&/\&/g; s/</\</g; s/>/\>/g; s/"/\"/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;'>📁 ${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;'>⚠ ${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="✅"
|
||
|
|
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="⚠️"
|
||
|
|
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="🚨"
|
||
|
|
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;'>⚠ 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;">🔄 NAS08 → NAS16</div>
|
||
|
|
<div style="font-size:13px;color:#94a3b8;margin-top:4px;">${NOW} | 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} ${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;">📄 Sync Results</span>
|
||
|
|
<span style="margin-left:8px;font-size:12px;color:#6b7280;">NAS08 Public → 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) → ${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) → ${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;">▶ Folder Activity — ${FOLDERS_WITH_CHANGES} updated | ${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;">▶ Full rsync Log — 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;">📋 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 | NAS16 | 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
|