Skip to content

Enhancements — Backup Script

Extend the core backup script with production-grade features.

Enhancement 1 — Exclusion Patterns

Skip node_modules, .git, build artefacts, and cache directories:

# Add to defaults or config
EXCLUDES=(".git" "node_modules" "__pycache__" "*.pyc" ".DS_Store" "*.log" "target/" "dist/")

build_rsync_excludes() {
    local flags=()
    for pattern in "${EXCLUDES[@]}"; do
        flags+=("--exclude=${pattern}")
    done
    echo "${flags[@]}"
}

# In do_backup():
local exclude_flags
exclude_flags=$(build_rsync_excludes)
rsync -a --delete $exclude_flags -- "$src/" "$dest/"

Load custom exclusions from a file (~/.backup_exclude):

[-f ~/.backup_exclude](<../../-f ~/.backup_exclude>) && RSYNC_EXCLUDES+=("--exclude-from=$HOME/.backup_exclude")

Enhancement 2 — Email Notification

Send a summary email on backup completion or failure:

# Requires: mailutils (sudo apt install mailutils) or postfix configured

NOTIFY_EMAIL=""   # Set in config: NOTIFY_EMAIL="you@example.com"

send_notification() {
    local subject="$1" body="$2"
    [[ -z "${NOTIFY_EMAIL:-}" ]] && return 0

    if command -v mail &>/dev/null; then
        echo "$body" | mail -s "$subject" "$NOTIFY_EMAIL"
        log_info "Notification sent to $NOTIFY_EMAIL"
    else
        log_warn "mail command not found — skipping notification"
    fi
}

# Call at end of do_backup():
if [[ "$failed" -eq 0 ]]; then
    send_notification "[OK] Backup complete — $(hostname)" \
        "Backup to $DEST_DIR/$TIMESTAMP completed successfully."
else
    send_notification "[FAILED] Backup errors — $(hostname)" \
        "$failed source(s) failed. Check $LOGFILE for details."
fi

Enhancement 3 — S3 / Remote Upload

After a local backup, upload the archive to S3 (requires aws CLI):

S3_BUCKET=""   # Set in config: S3_BUCKET="my-backups"

upload_to_s3() {
    local archive="$1"
    [[ -z "${S3_BUCKET:-}" ]] && return 0
    [[ -f "$archive"       ]] || return 0

    local key="$(hostname)/$(basename "$archive")"
    log_info "uploading to s3://${S3_BUCKET}/${key}"
    if aws s3 cp "$archive" "s3://${S3_BUCKET}/${key}" --storage-class STANDARD_IA; then
        log_info "S3 upload OK"
    else
        log_warn "S3 upload failed (backup still local)"
    fi
}

Enhancement 4 — Encryption

Encrypt archives before storing or uploading (requires gpg):

GPG_RECIPIENT=""   # Set in config: GPG_RECIPIENT="you@example.com"

encrypt_archive() {
    local archive="$1"
    [[ -z "${GPG_RECIPIENT:-}" ]] && return 0

    if gpg --recipient "$GPG_RECIPIENT" --encrypt "$archive"; then
        rm -f "$archive"   # Remove unencrypted copy
        log_info "encrypted: ${archive}.gpg"
    else
        log_warn "GPG encryption failed — keeping unencrypted archive"
    fi
}

Encryption key management

If you lose your GPG private key, you cannot decrypt your backups. Store a copy of your key in a secure, separate location before encrypting production data.

Enhancement 5 — Pre/Post Hooks

Run custom commands before and after each backup (database dumps, service stops):

# In config file:
# PRE_HOOK="mysqldump -u root mydb > /tmp/db_dump.sql"
# POST_HOOK="rm -f /tmp/db_dump.sql"

run_hook() {
    local name="$1" cmd="${2:-}"
    [[ -z "$cmd" ]] && return 0
    log_info "Running $name hook: $cmd"
    if eval "$cmd"; then
        log_info "$name hook OK"
    else
        log_warn "$name hook failed (exit $?) — continuing"
    fi
}

# In do_backup():
run_hook "pre" "${PRE_HOOK:-}"
# ... rsync ...
run_hook "post" "${POST_HOOK:-}"

Enhancement 6 — Backup Health Dashboard

Generate a one-line status summary for cron mail or monitoring:

print_status() {
    local newest_dir oldest_dir count total_size
    newest_dir=$(find "$DEST_DIR" -maxdepth 1 -mindepth 1 -type d \
                  -name '[0-9][0-9][0-9][0-9]-*' | sort -r | head -1)
    oldest_dir=$(find "$DEST_DIR" -maxdepth 1 -mindepth 1 -type d \
                  -name '[0-9][0-9][0-9][0-9]-*' | sort | head -1)
    count=$(find "$DEST_DIR" -maxdepth 1 -mindepth 1 -type d \
             -name '[0-9][0-9][0-9][0-9]-*' | wc -l)
    total_size=$(du -sh "$DEST_DIR" | cut -f1)

    echo "Backup status for $(hostname):"
    echo "  Backups:  $count"
    echo "  Newest:   $(basename "$newest_dir")"
    echo "  Oldest:   $(basename "$oldest_dir")"
    echo "  Total:    $total_size"
}

testing | solution