From 31dc0191f1e19fde8bb8292a6e76728210351c83 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sun, 28 May 2023 16:35:45 +0200 Subject: [PATCH] Optimized auto backup --- roles/independent_backup-to-usb/README.md | 22 +++---- .../files/backup-to-usb.python | 60 +++++++++++++++++++ .../handlers/main.yml | 9 ++- .../independent_backup-to-usb/tasks/main.yml | 18 +----- .../templates/99-usbstick.rules.j2 | 1 - .../templates/backup-to-usb.service.j2 | 11 ++-- .../templates/backup-to-usb.sh.j2 | 35 ----------- roles/independent_backup-to-usb/vars/main.yml | 8 +-- 8 files changed, 89 insertions(+), 75 deletions(-) create mode 100644 roles/independent_backup-to-usb/files/backup-to-usb.python delete mode 100644 roles/independent_backup-to-usb/templates/99-usbstick.rules.j2 delete mode 100644 roles/independent_backup-to-usb/templates/backup-to-usb.sh.j2 diff --git a/roles/independent_backup-to-usb/README.md b/roles/independent_backup-to-usb/README.md index 5a69ee89..0ee1468b 100644 --- a/roles/independent_backup-to-usb/README.md +++ b/roles/independent_backup-to-usb/README.md @@ -4,22 +4,18 @@ This Ansible role automates the process of performing backups to a swappable USB ## Features -- Automatically starts the backup process when a specific USB device is plugged in. -- Provides a systemd service to run the backup script at boot if the USB device is already connected. -- Supports customization of the backup source path and mount point. +- Automatically starts the backup process when mounted to a specific destination. +- Supports customization of the backup source path and destination. +- Provides a systemd service to run the backup script. -## Configuration +## Author -The following variables can be customized in the `vars/main.yml` file: +This role was created and is maintained by Kevin Veen-Birkenbach. -- `mount_point`: The mount point where the USB device will be mounted. -- `backup_to_usb_script_path`: The path to the backup script that will be executed when the USB device is connected. +## License + +This code is released under the AGPL v3 license. Please refer to the [LICENSE](LICENSE) file for more details. ## Credits -This role was created and maintained by Kevin Veen-Birkenbach. -Contact: kevin@veen.world - -## More Information - -For more details on how the `independent_backup-to-usb` role works, please refer to the Ansible documentation and the role's source code. \ No newline at end of file +This software was created with the assistance of [OpenAI ChatGPT](https://chat.openai.com/share/a75ca771-d8a4-4b75-9912-c515ba371ae4). \ No newline at end of file diff --git a/roles/independent_backup-to-usb/files/backup-to-usb.python b/roles/independent_backup-to-usb/files/backup-to-usb.python new file mode 100644 index 00000000..8a5c179a --- /dev/null +++ b/roles/independent_backup-to-usb/files/backup-to-usb.python @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +import sys +import subprocess +import shutil +import os +import glob +import datetime + +def main(): + source_path = sys.argv[1] + print(f"source path: {source_path}") + + backup_to_usb_destination_path = sys.argv[2] + print(f"backup to usb destination path: {backup_to_usb_destination_path}") + + if not os.path.isdir(backup_to_usb_destination_path): + print(f"Directory {backup_to_usb_destination_path} does not exist") + sys.exit(1) + + machine_id = subprocess.run(["sha256sum", "/etc/machine-id"], capture_output=True, text=True).stdout.strip()[:64] + print(f"machine id: {machine_id}") + + versions_path = os.path.join(backup_to_usb_destination_path, f"{machine_id}/backup-to-usb/") + print(f"versions path: {versions_path}") + + if not os.path.isdir(versions_path): + print(f"Creating {versions_path}...") + os.makedirs(versions_path, exist_ok=True) + + previous_version_path = max(glob.glob(f"{versions_path}*"), key=os.path.getmtime, default=None) + print(f"previous versions path: {previous_version_path}") + + current_version_path = os.path.join(versions_path, datetime.datetime.now().strftime("%Y%m%d%H%M%S")) + print(f"current versions path: {current_version_path}") + + print("Creating backup destination folder...") + os.makedirs(current_version_path, exist_ok=True) + + print("Starting synchronization...") + try: + rsync_command = [ + "rsync", "-abP", "--delete", "--delete-excluded", + "--link-dest=" + previous_version_path, + source_path, current_version_path + ] + rsync_output = subprocess.check_output(rsync_command, stderr=subprocess.STDOUT, text=True) + + if "rsync warning: some files vanished before they could be transferred" in rsync_output: + print("Synchronization finished with rsync warning") + sys.exit(0) + else: + print("Synchronization finished") + sys.exit(0) + except subprocess.CalledProcessError as e: + print("Synchronization failed:", e.output) + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/roles/independent_backup-to-usb/handlers/main.yml b/roles/independent_backup-to-usb/handlers/main.yml index 4a05c470..7f986191 100644 --- a/roles/independent_backup-to-usb/handlers/main.yml +++ b/roles/independent_backup-to-usb/handlers/main.yml @@ -1,3 +1,6 @@ ---- -- name: Reload udev rules - command: udevadm control --reload-rules && udevadm trigger +- name: "reload backup-to-usb.service" + systemd: + name: backup-to-usb.service + state: reloaded + enabled: yes + daemon_reload: yes diff --git a/roles/independent_backup-to-usb/tasks/main.yml b/roles/independent_backup-to-usb/tasks/main.yml index 03376b83..6d64d0a1 100644 --- a/roles/independent_backup-to-usb/tasks/main.yml +++ b/roles/independent_backup-to-usb/tasks/main.yml @@ -1,13 +1,6 @@ ---- -- name: Copy udev rule to the rules directory - template: - src: 99-usbstick.rules.j2 - dest: /etc/udev/rules.d/ - notify: Reload udev rules - - name: Copy backup script to the scripts directory - template: - src: backup-to-usb.sh.j2 + copy: + src: backup-to-usb.python dest: "{{ backup_to_usb_script_path }}" owner: root group: root @@ -20,9 +13,4 @@ owner: root group: root mode: '0644' - -- name: Enable and start service - systemd: - name: backup-to-usb - enabled: yes - state: started + notify: reload backup-to-usb.service \ No newline at end of file diff --git a/roles/independent_backup-to-usb/templates/99-usbstick.rules.j2 b/roles/independent_backup-to-usb/templates/99-usbstick.rules.j2 deleted file mode 100644 index 8b0540fc..00000000 --- a/roles/independent_backup-to-usb/templates/99-usbstick.rules.j2 +++ /dev/null @@ -1 +0,0 @@ -ACTION=="add", SUBSYSTEM=="block", ENV{ID_SERIAL_SHORT}=="{{ backup_to_usb_serial_short }}", RUN+="/usr/bin/systemd-mount --no-block $devnode {{ mount_point }}", SYMLINK+="backup_usb" diff --git a/roles/independent_backup-to-usb/templates/backup-to-usb.service.j2 b/roles/independent_backup-to-usb/templates/backup-to-usb.service.j2 index 9418fde6..aaf9f90e 100644 --- a/roles/independent_backup-to-usb/templates/backup-to-usb.service.j2 +++ b/roles/independent_backup-to-usb/templates/backup-to-usb.service.j2 @@ -1,10 +1,13 @@ [Unit] -Description=Backup to USB when it's plugged in -After=local-fs.target +Description=Backup to USB when mounted to {{ backup_to_usb_mount }} +Wants={{systemctl_mount_service_name}} +After={{systemctl_mount_service_name}} OnFailure=systemd-email@%n.service +Requires=backups-cleanup.service +After=backups-cleanup.service [Service] -ExecStart={{ backup_to_usb_script_path }} {{ mount_point }}/{{ backup_to_usb_subdirectory }} {{ backup_to_usb_source_path }} +ExecStart=/bin/python {{ backup_to_usb_script_path }} {{backup_to_usb_source}} {{backup_to_usb_destination}} [Install] -WantedBy=multi-user.target +WantedBy=multi-user.target \ No newline at end of file diff --git a/roles/independent_backup-to-usb/templates/backup-to-usb.sh.j2 b/roles/independent_backup-to-usb/templates/backup-to-usb.sh.j2 deleted file mode 100644 index 7783cb95..00000000 --- a/roles/independent_backup-to-usb/templates/backup-to-usb.sh.j2 +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/sh -backup_to_usb_destination_path="{{ mount_point }}" && -echo "backup to usb destination path: $backup_to_usb_destination_path" && - -source_path="{{ backup_to_usb_source_path }}" && -echo "source path: $source_path" || exit 1 - -if [ ! -d "$backup_to_usb_destination_path" ]; then - echo "Directory $backup_to_usb_destination_path does not exist" && - exit 1 -fi - -machine_id="$(sha256sum /etc/machine-id | head -c 64 )" && -echo "machine id: $machine_id" && - -versions_path="$backup_to_usb_destination_path$machine_id/backup-to-usb/" && -echo "versions path: $versions_path" || exit 1 - -if [ ! -d "$versions_path" ]; then - echo "Creating $versions_path..." && - mkdir -vp $versions_path || exit 1 -fi - -previous_version_path="$(ls -d $versions_path* | tail -1)" && -echo "previous versions path: $previous_version_path" && - -current_version_path="$versions_path$(date '+%Y%m%d%H%M%S')" && -echo "current versions path: $current_version_path" && - -echo "creating backup destination folder..." && -mkdir -vp "$current_version_path" && - -echo "Starting synchronization..." -rsync -abP --delete --delete-excluded --link-dest="$previous_version_path" "$source_path" "$current_version_path" && -echo "Synchronization finished." || exit 1 diff --git a/roles/independent_backup-to-usb/vars/main.yml b/roles/independent_backup-to-usb/vars/main.yml index fc51dd91..4bc6427d 100644 --- a/roles/independent_backup-to-usb/vars/main.yml +++ b/roles/independent_backup-to-usb/vars/main.yml @@ -1,4 +1,4 @@ ---- -mount_point: "/mnt/usbstick_{{ backup_to_usb_serial_short }}" -backup_to_usb_script_path: "/usr/local/sbin/backup-to-usb.sh" -backups_folder_path: "{{mount_point}}{{backup_to_usb_subdirectory}}" +backup_to_usb_script_path: "/usr/local/sbin/backup-to-usb.python" +backup_to_usb_destination: "{{backup_to_usb_mount}}{{backup_to_usb_destination_subdirectory}}" +backups_folder_path: "{{backup_to_usb_destination}}" +systemctl_mount_service_name: "{{ backup_to_usb_mount | trim('/') | replace('/', '-') }}.mount" \ No newline at end of file