#!/bin/bash # Borg local sync script for creating local copies of cloud backups # This script syncs the Borg repository from JuiceFS/S3 to local ZFS storage # Set environment variables export BORG_REPO_SOURCE="{{ borg_repo_dir }}" export BORG_REPO_LOCAL="/mnt/borg-backups" export ZFS_POOL="datapool" export ZFS_DATASET="datapool/borg-backups" export MOUNT_POINT="/mnt/borg-backups" # Telegram notification variables export TELEGRAM_BOT_TOKEN="{{ lookup('community.general.onepassword', 'Telegram Home Server Bot', vault='Dotfiles', field='password') }}" export TELEGRAM_CHAT_ID="{{ lookup('community.general.onepassword', 'Telegram Home Server Bot', vault='Dotfiles', field='chat_id') }}" # Log function log() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a /var/log/borg-local-sync.log } # Telegram notification function send_telegram() { local message="$1" local silent="${2:-false}" if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then log "Telegram credentials not configured, skipping notification" return fi local payload=$(cat < /dev/null 2>&1 if [ $? -eq 0 ]; then log "Telegram notification sent successfully" else log "Failed to send Telegram notification" fi } # Check if ZFS pool is available check_zfs_pool() { if ! zpool status "$ZFS_POOL" > /dev/null 2>&1; then log "ERROR: ZFS pool $ZFS_POOL is not available" send_telegram "❌ Borg Local Sync Failed ❌ ZFS pool not available: $ZFS_POOL 🕐 Failed: $(date '+%Y-%m-%d %H:%M:%S') The 20TB USB drive may not be connected or the ZFS pool is not imported. Please check the physical connection and run: sudo zpool import $ZFS_POOL" return 1 fi # Check if the specific ZFS dataset exists if ! zfs list "$ZFS_DATASET" > /dev/null 2>&1; then log "ERROR: ZFS dataset $ZFS_DATASET is not available" send_telegram "❌ Borg Local Sync Failed ❌ ZFS dataset not available: $ZFS_DATASET 🕐 Failed: $(date '+%Y-%m-%d %H:%M:%S') The ZFS dataset may not exist or be mounted. Please check: sudo zfs create $ZFS_DATASET" return 1 fi return 0 } # Check if mount point is available check_mount_point() { if ! mountpoint -q "$MOUNT_POINT"; then log "ERROR: Mount point $MOUNT_POINT is not mounted" send_telegram "❌ Borg Local Sync Failed ❌ Mount point not available: $MOUNT_POINT 🕐 Failed: $(date '+%Y-%m-%d %H:%M:%S') The ZFS dataset may not be mounted. Please check: sudo zfs mount $ZFS_DATASET" return 1 fi return 0 } # Check if source repository is available check_source_repo() { if [ ! -d "$BORG_REPO_SOURCE" ]; then log "ERROR: Source Borg repository not found: $BORG_REPO_SOURCE" send_telegram "❌ Borg Local Sync Failed ❌ Source repository not found: $BORG_REPO_SOURCE 🕐 Failed: $(date '+%Y-%m-%d %H:%M:%S') JuiceFS may not be mounted or the source repository path is incorrect." return 1 fi return 0 } # Check available space check_space() { local source_size=$(sudo du -sb "$BORG_REPO_SOURCE" 2>/dev/null | cut -f1) local available_space=$(df -B1 "$MOUNT_POINT" | tail -1 | awk '{print $4}') if [ -z "$source_size" ]; then log "WARNING: Could not determine source repository size" return 0 fi # Add 20% buffer for safety local required_space=$((source_size * 120 / 100)) if [ "$available_space" -lt "$required_space" ]; then local source_gb=$((source_size / 1024 / 1024 / 1024)) local available_gb=$((available_space / 1024 / 1024 / 1024)) local required_gb=$((required_space / 1024 / 1024 / 1024)) log "ERROR: Insufficient space. Source: ${source_gb}GB, Available: ${available_gb}GB, Required: ${required_gb}GB" send_telegram "❌ Borg Local Sync Failed ❌ Insufficient disk space 📊 Source size: ${source_gb}GB 💾 Available: ${available_gb}GB ⚠️ Required: ${required_gb}GB (with 20% buffer) 🕐 Failed: $(date '+%Y-%m-%d %H:%M:%S') Please free up space on the local backup drive." return 1 fi return 0 } # Perform the sync sync_repository() { log "Starting rsync of Borg repository" # Get initial sizes for reporting local source_size_before=$(sudo du -sh "$BORG_REPO_SOURCE" 2>/dev/null | cut -f1) local dest_size_before="0B" if [ -d "$BORG_REPO_LOCAL" ]; then dest_size_before=$(sudo du -sh "$BORG_REPO_LOCAL" 2>/dev/null | cut -f1) fi # Perform the sync with detailed logging sudo rsync -avh --delete --progress \ --exclude="lock.exclusive" \ --exclude="lock.roster" \ "$BORG_REPO_SOURCE/" "$BORG_REPO_LOCAL/" 2>&1 | while read line; do log "rsync: $line" done local rsync_exit=${PIPESTATUS[0]} # Get final sizes for reporting local dest_size_after=$(sudo du -sh "$BORG_REPO_LOCAL" 2>/dev/null | cut -f1) if [ $rsync_exit -eq 0 ]; then log "Rsync completed successfully" send_telegram "🔒 Borg Local Sync Success ✅ Local backup sync completed successfully 📂 Source: $BORG_REPO_SOURCE (${source_size_before}) 💾 Destination: $BORG_REPO_LOCAL (${dest_size_after}) 🕐 Completed: $(date '+%Y-%m-%d %H:%M:%S') Local backup copy is now up to date." "true" return 0 else log "Rsync failed with exit code: $rsync_exit" send_telegram "❌ Borg Local Sync Failed ❌ Rsync failed during repository sync 📂 Source: $BORG_REPO_SOURCE 💾 Destination: $BORG_REPO_LOCAL 🕐 Failed: $(date '+%Y-%m-%d %H:%M:%S') Exit code: $rsync_exit Check logs: /var/log/borg-local-sync.log" return 1 fi } # Main execution log "Starting Borg local sync process" # Run all pre-flight checks if ! check_zfs_pool; then exit 1 fi if ! check_mount_point; then exit 1 fi if ! check_source_repo; then exit 1 fi if ! check_space; then exit 1 fi # All checks passed, proceed with sync log "All pre-flight checks passed, starting sync" if sync_repository; then log "Local sync completed successfully" exit 0 else log "Local sync failed" exit 1 fi