From c8f92b5614da01fe40ac68f3c93a17ee6c76bf6a Mon Sep 17 00:00:00 2001 From: Judah Paul Date: Sat, 27 Apr 2024 01:18:01 -0400 Subject: [PATCH] Added urbackup support - See https://github.com/uroni/urbackup-server-python-web-api-wrapper --- .bumpversion.cfg | 2 +- README.md | 59 ++++++++++++++++++++++++++++++-- config.yaml | 14 +++++++- dirconfig/main.py | 85 ++++++++++++++++++++++++++++++++++++++++++++-- requirements.txt | Bin 66 -> 140 bytes setup.py | 2 +- 6 files changed, 154 insertions(+), 8 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6ef1061..b8a01be 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.2.2 +current_version = 0.2.3 commit = True tag = True diff --git a/README.md b/README.md index 112455b..eaf21a1 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ ## Features -- **File Organization**: Automatically move files based on their extension from one directory to another. -- **Notification System** (Future Feature): Get notified regarding specific events specified in the configuration file. -- **Automated Backups** (Future Feature): Set up scheduled backups for important directories. +- [x] **File Organization**: Automatically move files based on their extension from one directory to another. +- [x] **Automated Backups**: Set up scheduled backups for important directories using [urbackup](https://github.com/uroni/urbackup-server-python-web-api-wrapper). +- [ ] **Notification System**: Get notified regarding specific events specified in the configuration file. ## Installation @@ -35,6 +35,18 @@ tasks: destination: /path/to/your/destination/for/images - extension: .pdf destination: /path/to/your/destination/for/documents +backup: + - name: Backup Important Files + type: incremental-file # incremental-image, full-file, full-image + schedule: daily # weekly, monthly + retention: 7 # number of days to keep backups + connection: + server: http://your-backup-server:55414 + username: foo + password: bar + directories: + - /path/to/your/important/directory + - /path/to/another/important/directory ``` ## Usage @@ -98,6 +110,47 @@ For long-term operation or deployment, integrating **dirconfig** with system ser **dirconfig** welcomes enhancements and customization. If you're interested in adding new features or improving the tool, consider contributing to the source code. Your input and contributions are highly appreciated. +## Urbackup Documentation +For more information on the Urbackup API, please refer to these resources: +* *[Urbackup Python API Wrapper](https://github.com/uroni/urbackup-server-python-web-api-wrapper)* +* *[Urbackup Backend ClientCTL](https://github.com/uroni/urbackup_backend/tree/dev/clientctl)* + +Command Line Options for `urbackupclientctl` are as follows: +```sh +USAGE: + + urbackupclientctl [--help] [--version] [] + +Get specific command help with urbackupclientctl --help + + urbackupclientctl start + Start an incremental/full image/file backup + + urbackupclientctl status + Get current backup status + + urbackupclientctl browse + Browse backups and files/folders in backups + + urbackupclientctl restore-start + Restore files/folders from backup + + urbackupclientctl set-settings + Set backup settings + + urbackupclientctl reset-keep + Reset keeping files during incremental backups + + urbackupclientctl add-backupdir + Add new directory to backup set + + urbackupclientctl list-backupdirs + List directories that are being backed up + + urbackupclientctl remove-backupdir + Remove directory from backup set +``` + ## License **dirconfig** is licensed under the MIT License. See the `LICENSE` file for more details. diff --git a/config.yaml b/config.yaml index 007472b..cd96040 100644 --- a/config.yaml +++ b/config.yaml @@ -11,4 +11,16 @@ tasks: - extension: .jpg destination: /images - extension: .pdf - destination: /documents \ No newline at end of file + destination: /documents +# backup: +# - name: Backup Important Files +# type: incremental-file # incremental-image, full-file, full-image +# schedule: daily # weekly, monthly +# retention: 7 # number of days to keep backups +# connection: +# server: http://your-backup-server:55414 +# username: foo +# password: bar +# directories: +# - /path/to/your/important/directory +# - /path/to/another/important/directory \ No newline at end of file diff --git a/dirconfig/main.py b/dirconfig/main.py index 09f63e8..693ae96 100644 --- a/dirconfig/main.py +++ b/dirconfig/main.py @@ -1,16 +1,22 @@ +from urbackup_api import urbackup_server, installer_os from watchdog.events import FileSystemEventHandler from watchdog.observers import Observer -from pathlib import Path +from threading import Thread, Event +import subprocess import argparse import logging import signal import shutil +import time import yaml import sys import os +# Global variables observer = None -PID_FILE = 'dirconfig.pid' +backup_thread = None # Thread for backup scheduling +shutdown_event = Event() # Event to signal shutdown to backup thread +PID_FILE = 'dirconfig.pid' # Default PID file path class ChangeHandler(FileSystemEventHandler): def __init__(self, tasks): @@ -68,19 +74,94 @@ def signal_handler(signum, frame): os.remove(PID_FILE) sys.exit(0) +def check_and_install_urbackup_client(backup_config): + stdout, stderr, returncode = subprocess.run(["urbackupclientctl", "status"], capture_output=True, text=True) + if returncode != 0: + print("UrBackup client not running. Attempting installation...") + logging.info("UrBackup client not running. Attempting installation...") + # Determine OS type for choosing the correct installer + os_type = installer_os.Linux if os.name != 'nt' else installer_os.Windows + installer_filename = "urbackup_client_installer" + (".exe" if os_type == installer_os.Windows else "") + backup_server = urbackup_server( + server_url=backup_config['connection']['server'], + server_username=backup_config['connection']['username'], + server_password=backup_config['connection']['password'] + ) + if backup_server.login(): + client_name = f"{backup_config['name']}-dirconfig" + if backup_server.download_installer(installer_filename, client_name, os_type): + if os.name != 'nt': + subprocess.run(["chmod", "+x", installer_filename]) # Make executable on Unix/Linux + subprocess.run([f"./{installer_filename}"]) # Execute installer + print("Installation successful.") + logging.info("Installation successful.") + else: + print("Failed to download installer.") + logging.error("Failed to download installer.") + else: + print("Failed to log in to the backup server.") + logging.error("Failed to log in to the backup server.") + else: + print("UrBackup client is running.") + logging.info("UrBackup client is running.") + +def setup_backup_dirs(backup_config): + """Add directories specified in config to UrBackup.""" + for directory in backup_config['directories']: + cmd = ["urbackupclientctl", "add-backupdir", "--path", directory] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + print(f"Successfully added backup directory: {directory}") + logging.info(f"Successfully added backup directory: {directory}") + else: + print(f"Failed to add backup directory: {directory}. Error: {result.stderr}") + logging.error(f"Failed to add backup directory: {directory}. Error: {result.stderr}") + +def initiate_backup(backup_type, client_name): + """Initiate the backup process using the urbackupclientctl command with detailed options.""" + if 'incremental' in backup_type: + backup_option = '-i' + else: + backup_option = '-f' + + cmd = [ + "urbackupclientctl", "start", backup_option, + "--non-blocking", "--client", client_name + ] + result = subprocess.run(cmd, capture_output=True, text=True) + if result.returncode == 0: + print(f"Successfully started {backup_type} backup for {client_name}.") + logging.info(f"Successfully started {backup_type} backup for {client_name}.") + else: + print(f"Failed to start {backup_type} backup for {client_name}. Error: {result.stderr}") + logging.error(f"Failed to start {backup_type} backup for {client_name}. Error: {result.stderr}") + +def backup_task(backup_config): + """Performs the entire backup task from checking/installing client to starting backups.""" + check_and_install_urbackup_client(backup_config) + setup_backup_dirs(backup_config) + initiate_backup(backup_config['type']) + def start_daemon(config_path): global observer config = load_config(config_path) tasks = config['tasks'] observer = Observer() + for task in tasks: if task['type'] == 'file-organization': observer.schedule(ChangeHandler(tasks), os.path.abspath(task['source']), recursive=True) + + # Start backup scheduling in a separate thread if 'backup' is defined in the config + if 'backup' in config: + backup_thread = Thread(target=backup_task, args=(config,)) + backup_thread.start() # Register the signal handler for SIGINT signal.signal(signal.SIGINT, signal_handler) observer.start() + with open(PID_FILE, 'w') as f: f.write(str(os.getpid())) diff --git a/requirements.txt b/requirements.txt index 43ead60819bd3469dcf4daafa8e81d6bd8f4d3d2..a7c8e418d128311fabfb9c1431a4c6d3730c9544 100644 GIT binary patch delta 80 zcmZ?jVVvOPRmxDrki?M4kj#+HP|8ripvzFqkO~wl1CzQ8