Skip to content

Instantly share code, notes, and snippets.

@momja
Created December 30, 2024 22:29
Show Gist options
  • Save momja/83531e6af416d2afd207eaa5fca2572d to your computer and use it in GitHub Desktop.
Save momja/83531e6af416d2afd207eaa5fca2572d to your computer and use it in GitHub Desktop.
Borg and Rsync Backup Script
import os
import subprocess
import sys
import logging
from dotenv import load_dotenv
from datetime import datetime
import argparse # Import argparse for handling command line arguments
# Set up logging (change this if you want your logs to go elsewhere)
log_dir = "/var/log/backup_script"
os.makedirs(log_dir, exist_ok=True)
os.chmod(log_dir, 0o755) # Set appropriate permissions
log_file = os.path.join(log_dir, f"backup_{datetime.now().strftime('%Y-%m-%d')}.log")
logging.basicConfig(
filename=log_file, level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
# Backup directories (Make sure these environment variables are defined)
# I.E. in .env file:
# export REMOTE_BACKUP_DIR="you@computer:/path/to/data/"
# export LOCAL_BACKUP_DIR="/mnt/backup/data"
# export BORG_REPO_DIR="/mnt/backup/data_borg"
# export BACKUP_DRIVE="/dev/sda1"
load_dotenv()
remote_backup_dir = os.getenv("REMOTE_BACKUP_DIR")
local_backup_dir = os.getenv("LOCAL_BACKUP_DIR")
borg_repo_dir = os.getenv("BORG_REPO_DIR")
backup_drive = os.getenv("BACKUP_DRIVE")
def run_command(command, should_exit_on_fail=True):
logging.info(f"Executing: {command}")
process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
output, error = process.communicate()
# Allow rsync warning codes (e.g., 23, 24), but exit on hard errors
if process.returncode != 0:
logging.error(f"Error executing command: {command}")
logging.error(f"Error message: {error.decode('utf-8')}")
if (should_exit_on_fail):
logging.error("Marked exit on failure. Exiting...")
sys.exit(1)
else:
logging.error("Marked continue on failure. Continuing...")
return output.decode('utf-8')
def mount_drive():
logging.info("Mounting drive...")
# Check if the drive is already mounted
with open('/proc/mounts', 'r') as f:
mounts = f.read()
if '/mnt/backup' in mounts:
logging.info("Drive is already mounted, skipping mount step.")
else:
run_command("mount /dev/sda1 /mnt/backup")
def sync_files():
logging.info("Syncing files...")
# exclude-file.txt must be in /etc/backup-config
# and provide a list of files to not include in synchronization.
# E.G. log files
rsync_command = (
f"rsync -az --delete --exclude-from='/etc/backup-config/exclude-file.txt' -e 'ssh -c aes128-ctr' {remote_backup_dir} {local_backup_dir}"
)
output = run_command(rsync_command, False)
logging.info(f"Rsync output:\n{output}")
def backup_files():
logging.info("Backing up files...")
borg_command = (
f"borg create --stats --progress "
f"{borg_repo_dir}::dockerData-{{now}} "
f"{local_backup_dir}"
)
output = run_command(borg_command)
logging.info(f"Borg output:\n{output}")
def unmount_drive():
logging.info("Unmounting drive...")
run_command("umount /mnt/backup")
def main():
parser = argparse.ArgumentParser(
description='Backup script using rsync and Borg.'
)
parser.add_argument(
'--sync',
action='store_true',
help='Run rsync for file synchronization'
)
parser.add_argument(
'--backup',
action='store_true',
help='Run borg for backup creation'
)
args = parser.parse_args()
if not args.sync and not args.backup:
logging.error('Either --sync, --backup, or both must be specified.')
sys.exit(1)
logging.info("Starting backup process")
try:
mount_drive()
if args.sync:
sync_files()
if args.backup:
backup_files()
except Exception as e:
logging.error(f"An error occurred: {str(e)}")
finally:
unmount_drive()
logging.info("Backup process completed")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment