Created
April 25, 2025 18:31
-
-
Save seqis/fb43b874d37c69d29b6c4b0472b4e9d8 to your computer and use it in GitHub Desktop.
This bash script performs both mirrored and versioned backups every 30 minutes—ideal for running via cron or Task Scheduler—using rsync with hard-link deduplication, time-slot logic, and detailed logging, all while ensuring clean snapshots and efficient storage use.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # --------------------------------------------------------------------------- | |
| # CRITICAL CAPSULE BACKUP SCRIPT (PUBLISHABLE VERSION) | |
| # --------------------------------------------------------------------------- | |
| # This script performs two types of backups every 30 minutes: | |
| # | |
| # 1. **MIRRORED BACKUPS**: One-way sync that mirrors directories to a target. | |
| # - No versioning; it's a direct snapshot of the source as it exists. | |
| # | |
| # 2. **VERSIONED BACKUPS**: Uses rsync with `--link-dest` to create efficient | |
| # snapshots that hard-link unchanged files to the previous backup. | |
| # - This enables full snapshots with minimal storage overhead. | |
| # - Backups are named by hour and time slot: e.g., `1am/`, `1am_b/`, etc. | |
| # | |
| # --------------------------------------------------------------------------- | |
| # ❖ HOW TO USE: | |
| # - Recommended to run as a scheduled task: | |
| # - **Linux (via crontab)**: | |
| # `15,45 * * * * /home/username/backup_scripts/critical_capsule` | |
| # - **Windows (via Task Scheduler)**: | |
| # - Set to run at :15 and :45 of every hour. | |
| # | |
| # ❖ IF RUN MANUALLY: | |
| # - The script determines the backup folder using the current minute: | |
| # - :00–:30 → saved as `<hour>am/pm/` | |
| # - :31–:59 → saved as `<hour>am/pm_b/` | |
| # - This means: | |
| # - A run at 9:22pm will back up to `9pm/` | |
| # - A run at 9:35pm will back up to `9pm_b/` | |
| # - A warning is logged if not run at :15 or :45, but the script will proceed. | |
| # | |
| # ❖ LOGGING: | |
| # - All actions are logged to: | |
| # `/home/username/logs/critical-capsule.log` | |
| # --------------------------------------------------------------------------- | |
| # Define source directories to mirror | |
| SOURCES=( | |
| "/mnt/data/projects" | |
| "/home/username/Documents" | |
| "/home/username/Downloads" | |
| "/home/username/Pictures" | |
| "/home/username/Videos" | |
| "/home/username/Webcam" | |
| "/home/username/code" | |
| "/home/username/backup_scripts" | |
| ) | |
| # Directories to version separately (with deduplication) | |
| VERSIONED=( | |
| "/mnt/data/projects/notes/Obsidian" | |
| "/home/username/Documents/Personal/Obsidian-Vault" | |
| "/home/username/code" | |
| "/home/username/backup_scripts" | |
| ) | |
| # Rsync exclude patterns | |
| EXCLUDES=( | |
| "--exclude=/mnt/data/projects/archived" | |
| "--exclude=/home/username/Downloads/Temp" | |
| "--exclude=/home/username/code/env" | |
| "--exclude=*.tmp" | |
| ) | |
| # Paths | |
| TARGET_BASE="/mnt/backup-drive/critical-capsule" | |
| MIRROR_DIR="$TARGET_BASE/mirror" | |
| VERSION_BASE="$TARGET_BASE/versioned" | |
| LOG_FILE="/home/username/logs/critical-capsule.log" | |
| # Determine time slot | |
| HOUR=$(date +"%I" | sed 's/^0*//') | |
| MERIDIEM=$(date +"%P") | |
| MINUTE=$(date +"%M") | |
| # Determine suffix based on minute range | |
| SUFFIX="" | |
| if (( 10#$MINUTE >= 31 )); then | |
| SUFFIX="_b" | |
| fi | |
| CURRENT_HOUR="${HOUR}${MERIDIEM}${SUFFIX}" | |
| VERSION_HOUR_DIR="$VERSION_BASE/$CURRENT_HOUR" | |
| # Warn if manual run not at 15 or 45 | |
| if [ "$MINUTE" != "15" ] && [ "$MINUTE" != "45" ]; then | |
| echo "WARNING: Running at minute $MINUTE – outside expected :15 or :45 slots. Will back up to '$CURRENT_HOUR'." | tee -a "$LOG_FILE" | |
| fi | |
| # Find most recent prior version for link-dest reference | |
| PREV_VERSION=$(find "$VERSION_BASE" -maxdepth 1 -mindepth 1 -type d -printf "%f\n" | | |
| grep -E "^[0-9]{1,2}[ap]m(_b)?$" | sort | tail -n 1) | |
| PREV_VERSION="$VERSION_BASE/$PREV_VERSION" | |
| # Ensure mirror destination exists | |
| mkdir -p "$MIRROR_DIR" | |
| # ------------------------------ | |
| # 1. MIRROR SYNC | |
| # ------------------------------ | |
| for SRC in "${SOURCES[@]}"; do | |
| NAME=$(basename "$SRC") | |
| DEST="$MIRROR_DIR/$NAME" | |
| echo "Backing up $SRC to $DEST (mirror sync)..." | tee -a "$LOG_FILE" | |
| if [ ! -d "$SRC" ]; then | |
| echo "WARNING: Source directory $SRC does not exist. Skipping." | tee -a "$LOG_FILE" | |
| continue | |
| fi | |
| if rsync -a --delete "${EXCLUDES[@]}" "$SRC/" "$DEST/" >>"$LOG_FILE" 2>&1; then | |
| echo "SUCCESS: $SRC mirrored to $DEST" | tee -a "$LOG_FILE" | |
| else | |
| echo "ERROR: Failed to mirror $SRC to $DEST" | tee -a "$LOG_FILE" | |
| fi | |
| done | |
| # ------------------------------ | |
| # 2. VERSIONED BACKUPS | |
| # ------------------------------ | |
| rm -rf "$VERSION_HOUR_DIR" | |
| mkdir -p "$VERSION_HOUR_DIR" | |
| for VDIR in "${VERSIONED[@]}"; do | |
| NAME=$(basename "$VDIR") | |
| DEST="$VERSION_HOUR_DIR/$NAME" | |
| LINK_DEST="" | |
| if [[ -n "$PREV_VERSION" && -d "$PREV_VERSION/$NAME" ]]; then | |
| LINK_DEST="--link-dest=$PREV_VERSION/$NAME" | |
| fi | |
| echo "Versioning $VDIR to $DEST using link-dest from $PREV_VERSION/$NAME..." | tee -a "$LOG_FILE" | |
| if [ ! -d "$VDIR" ]; then | |
| echo "WARNING: Versioned directory $VDIR does not exist. Skipping." | tee -a "$LOG_FILE" | |
| continue | |
| fi | |
| if rsync -a --delete $LINK_DEST "$VDIR/" "$DEST/" >>"$LOG_FILE" 2>&1; then | |
| echo "SUCCESS: $VDIR versioned to $DEST" | tee -a "$LOG_FILE" | |
| else | |
| echo "ERROR: Failed to version $VDIR to $DEST" | tee -a "$LOG_FILE" | |
| fi | |
| done | |
| # ------------------------------ | |
| # Completion Timestamp | |
| # ------------------------------ | |
| DATESTAMP=$(date +"%Y-%m-%d %H:%M:%S") | |
| echo "[$DATESTAMP] Mirror sync to $MIRROR_DIR and versioning to $VERSION_HOUR_DIR completed." | tee -a "$LOG_FILE" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment