#!/bin/sh # Network-triggered backup daemon # Monitors for NAS availability and triggers backups with safeguards set -e # Config CONFIG_FILE="/etc/backup.conf" LOG_DIR="${HOME}/.local/var/log" LOG_FILE="${LOG_DIR}/backup-monitor.log" STATE_DIR="${HOME}/.local/var/backup" LAST_BACKUP_FILE="${STATE_DIR}/last-backup" LAST_CHECK_FILE="${STATE_DIR}/last-check" BACKUP_IN_PROGRESS_FILE="${STATE_DIR}/backup-in-progress" # Ensure directories exist mkdir -p "$LOG_DIR" "$STATE_DIR" # Logging log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" >> "$LOG_FILE" } # Load config if [ ! -f "$CONFIG_FILE" ]; then log "ERROR: Configuration file not found: $CONFIG_FILE" exit 1 fi # shellcheck source=/dev/null . "$CONFIG_FILE" # Check if backup is already running is_backup_running() { # Check if any backup lock files exist if ls /tmp/backup-*.lock >/dev/null 2>&1; then return 0 # Backup is running fi # Check our own in-progress marker if [ -f "$BACKUP_IN_PROGRESS_FILE" ]; then # Check if marker is stale (older than 6 hours) if [ -n "$(find "$BACKUP_IN_PROGRESS_FILE" -mmin +360 2>/dev/null)" ]; then log "WARNING: Stale backup-in-progress marker found, removing" rm -f "$BACKUP_IN_PROGRESS_FILE" return 1 # Not running fi return 0 # Backup is running fi return 1 # Not running } # Check if cooldown period has passed check_cooldown() { if [ ! -f "$LAST_BACKUP_FILE" ]; then return 0 # No previous backup, proceed fi LAST_BACKUP=$(cat "$LAST_BACKUP_FILE") NOW=$(date +%s) ELAPSED=$((NOW - LAST_BACKUP)) if [ "$ELAPSED" -lt "$BACKUP_COOLDOWN" ]; then REMAINING=$((BACKUP_COOLDOWN - ELAPSED)) log "Cooldown active: ${REMAINING}s remaining until next backup allowed" return 1 # Still in cooldown fi return 0 # Cooldown passed } # Check if we recently checked (prevent spam checks) check_rate_limit() { CHECK_INTERVAL=300 # 5 minutes between checks if [ ! -f "$LAST_CHECK_FILE" ]; then date +%s > "$LAST_CHECK_FILE" return 0 # First check, proceed fi LAST_CHECK=$(cat "$LAST_CHECK_FILE") NOW=$(date +%s) ELAPSED=$((NOW - LAST_CHECK)) if [ "$ELAPSED" -lt "$CHECK_INTERVAL" ]; then return 1 # Too soon since last check fi date +%s > "$LAST_CHECK_FILE" return 0 # Can check now } # Check if NAS is available check_nas_available() { # Quick ping check if ! ping -c 1 -W 2 "$NAS_HOST" >/dev/null 2>&1; then return 1 fi # SSH check if ! ssh -p "$NAS_PORT" -o ConnectTimeout=5 -o BatchMode=yes \ "${NAS_USER}@${NAS_HOST}" "echo test" >/dev/null 2>&1; then return 1 fi return 0 } # Trigger backup trigger_backup() { BACKUP_TYPE="${1:-incremental}" log "NAS detected, triggering $BACKUP_TYPE backup..." # Mark backup in progress touch "$BACKUP_IN_PROGRESS_FILE" # Run backup in background ( if /usr/local/bin/backup-"$BACKUP_TYPE" >> "$LOG_FILE" 2>&1; then log "Backup completed successfully" else log "ERROR: Backup failed" fi # Remove in-progress marker rm -f "$BACKUP_IN_PROGRESS_FILE" ) & # Don't wait for backup to complete log "Backup started in background (PID: $!)" } # Main monitoring loop main() { log "==========================================" log "Backup monitor started" log "Monitoring for NAS: ${NAS_HOST}" log "Check interval: 5 minutes" log "Backup cooldown: ${BACKUP_COOLDOWN}s ($(( BACKUP_COOLDOWN / 3600 ))h)" log "==========================================" while true; do # Rate limit checks (every 5 minutes) if ! check_rate_limit; then sleep 60 continue fi # Skip if backup already running if is_backup_running; then log "Backup already in progress, skipping check" sleep 300 # Sleep 5 minutes continue fi # Check if NAS is available if check_nas_available; then log "NAS is available at ${NAS_HOST}" # Check cooldown before triggering if check_cooldown; then log "Cooldown passed, backup allowed" trigger_backup "incremental" else log "Cooldown active, skipping backup" fi else log "NAS not available (offline or not connected)" fi # Sleep before next check (5 minutes) sleep 300 done } # Handle signals trap 'log "Received shutdown signal, exiting..."; exit 0' TERM INT # Start monitoring main