From 0db60e2d602d2b839963c40c5adaa9897266bd65 Mon Sep 17 00:00:00 2001 From: uumas Date: Mon, 31 Mar 2025 03:15:14 +0300 Subject: [PATCH] Add borgmatic --- roles/borgmatic/meta/argument_specs.yaml | 22 +++++++ roles/borgmatic/tasks/main.yaml | 55 ++++++++++++++++ roles/borgmatic/tasks/target.yaml | 26 ++++++++ .../borgmatic/templates/borgmatic@.service.j2 | 63 ++++++++++++++++++ roles/borgmatic_config/defaults/main.yaml | 8 +++ roles/borgmatic_config/handlers/main.yaml | 6 ++ .../borgmatic_config/meta/argument_specs.yaml | 65 +++++++++++++++++++ roles/borgmatic_config/meta/main.yaml | 3 + roles/borgmatic_config/tasks/main.yaml | 35 ++++++++++ .../templates/borgmatic.yaml.j2 | 35 ++++++++++ .../templates/borgmatic@.timer.j2 | 21 ++++++ roles/borgmatic_config/vars/main.yaml | 5 ++ 12 files changed, 344 insertions(+) create mode 100644 roles/borgmatic/meta/argument_specs.yaml create mode 100644 roles/borgmatic/tasks/main.yaml create mode 100644 roles/borgmatic/tasks/target.yaml create mode 100644 roles/borgmatic/templates/borgmatic@.service.j2 create mode 100644 roles/borgmatic_config/defaults/main.yaml create mode 100644 roles/borgmatic_config/handlers/main.yaml create mode 100644 roles/borgmatic_config/meta/argument_specs.yaml create mode 100644 roles/borgmatic_config/meta/main.yaml create mode 100644 roles/borgmatic_config/tasks/main.yaml create mode 100644 roles/borgmatic_config/templates/borgmatic.yaml.j2 create mode 100644 roles/borgmatic_config/templates/borgmatic@.timer.j2 create mode 100644 roles/borgmatic_config/vars/main.yaml diff --git a/roles/borgmatic/meta/argument_specs.yaml b/roles/borgmatic/meta/argument_specs.yaml new file mode 100644 index 0000000..0470760 --- /dev/null +++ b/roles/borgmatic/meta/argument_specs.yaml @@ -0,0 +1,22 @@ +--- +argument_specs: + main: + short_description: Borgmatic + description: + - Installs borgmatic + options: + borgmatic_targets: + description: List of backup targets + type: list + required: true + elements: dict + options: + host: + description: Target hostname + type: str + required: true + directories: + description: Directories on the host where backup repos will be created under + type: list + required: true + elements: str diff --git a/roles/borgmatic/tasks/main.yaml b/roles/borgmatic/tasks/main.yaml new file mode 100644 index 0000000..1839fc4 --- /dev/null +++ b/roles/borgmatic/tasks/main.yaml @@ -0,0 +1,55 @@ +--- +- name: Ensure host distribution is supported + ansible.builtin.import_role: + name: compatcheck + vars: + compatcheck_supported_distributions: + - name: debian + version_min: 11 + - name: ubuntu + version_min: 22 + +- name: Install borgmatic + ansible.builtin.apt: + name: borgmatic + +- name: Disable borgmatic global timer + ansible.builtin.systemd_service: + name: borgmatic.timer + state: stopped + enabled: false + +- name: Add systemd drop-in service for borgmatic + ansible.builtin.template: + src: borgmatic@.service.j2 + dest: /etc/systemd/system/borgmatic@.service + mode: "0644" + +- name: Create borgmatic configurations directory + ansible.builtin.file: + path: /etc/borgmatic.d + state: directory + mode: "0755" + +- name: Generate ssh key for borg + community.crypto.openssh_keypair: + type: ed25519 + path: "{{ ansible_user_dir }}/.ssh/id_ed25519_borg" + comment: "{{ ansible_user_id }}@{{ ansible_fqdn }} borg" + register: _borgmatic_key + +- name: Setup backup targets + ansible.builtin.include_tasks: + file: target.yaml + apply: + delegate_to: "{{ target.host }}" + become: false + loop: "{{ borgmatic_targets }}" + loop_control: + loop_var: target + +- name: Add borg target ssh host keys to known hosts + ansible.builtin.known_hosts: + name: "{{ item }}" + key: "{{ item }} ssh-ed25519 {{ hostvars[item].ansible_ssh_host_key_ed25519_public }}" + loop: "{{ borgmatic_targets | map(attribute='host') }}" diff --git a/roles/borgmatic/tasks/target.yaml b/roles/borgmatic/tasks/target.yaml new file mode 100644 index 0000000..39f6f7a --- /dev/null +++ b/roles/borgmatic/tasks/target.yaml @@ -0,0 +1,26 @@ +--- +- name: Gather facts + ansible.builtin.setup: + delegate_facts: true + +- name: Add ssh key to authorized_keys + ansible.posix.authorized_key: + user: "{{ hostvars[target.host].ansible_user_id }}" + key: >- + {{ + _borgmatic_key.public_key + ' ' + _borgmatic_key.comment + if not (ansible_check_mode and _borgmatic_key.changed) + else 'ssh-ed25519 AAAA' + }} + key_options: >- + command="borg + serve{% for directory in target.directories %} + --restrict-to-path + {{ hostvars[target.host].ansible_user_dir }}/{{ directory }}/{{ ansible_fqdn }}{%- endfor -%}",restrict + +- name: Create backup directories + ansible.builtin.file: + path: "{{ hostvars[target.host].ansible_user_dir }}/{{ item }}/{{ ansible_fqdn }}" + state: directory + mode: "0700" + loop: "{{ target.directories }}" diff --git a/roles/borgmatic/templates/borgmatic@.service.j2 b/roles/borgmatic/templates/borgmatic@.service.j2 new file mode 100644 index 0000000..a4144bb --- /dev/null +++ b/roles/borgmatic/templates/borgmatic@.service.j2 @@ -0,0 +1,63 @@ +# {{ ansible_managed }} + +[Unit] +Description=borgmatic backup %i +Wants=network-online.target +After=network-online.target +Documentation=https://torsion.org/borgmatic/ + +[Service] +Type=oneshot +RuntimeDirectory=borgmatic +StateDirectory=borgmatic + +# Security settings for systemd running as root, optional but recommended to improve security. You +# can disable individual settings if they cause problems for your use case. For more details, see +# the systemd manual: https://www.freedesktop.org/software/systemd/man/systemd.exec.html +LockPersonality=true +# Certain borgmatic features like Healthchecks integration need MemoryDenyWriteExecute to be off. +# But you can try setting it to "yes" for improved security if you don't use those features. +MemoryDenyWriteExecute=no +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +ProtectClock=yes +ProtectControlGroups=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallErrorNumber=EPERM +# To restrict write access further, change "ProtectSystem" to "strict" and +# uncomment "ReadWritePaths", "TemporaryFileSystem", "BindPaths" and +# "BindReadOnlyPaths". Then add any local repository paths to the list of +# "ReadWritePaths". This leaves most of the filesystem read-only to borgmatic. +ProtectSystem=full +# ReadWritePaths=-/mnt/my_backup_drive +# This will mount a tmpfs on top of /root and pass through needed paths +# TemporaryFileSystem=/root:ro +# BindPaths=-/root/.cache/borg -/root/.config/borg -/root/.borgmatic +# BindReadOnlyPaths=-/root/.ssh + +# May interfere with running external programs within borgmatic hooks. +CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_NET_RAW + +# Lower CPU and I/O priority. +Nice=19 +CPUSchedulingPolicy=batch +IOSchedulingClass=best-effort +IOSchedulingPriority=7 +IOWeight=100 + +Restart=no +# Prevent rate limiting of borgmatic log events. If you are using an older version of systemd that +# doesn't support this (pre-240 or so), you may have to remove this option. +LogRateLimitIntervalSec=0 + +ExecStart=systemd-inhibit --who="borgmatic" --what="sleep:shutdown" --why="Prevent interrupting scheduled backup" /usr/bin/borgmatic -c "/etc/borgmatic.d/%I.yaml" --verbosity -2 --syslog-verbosity 1 diff --git a/roles/borgmatic_config/defaults/main.yaml b/roles/borgmatic_config/defaults/main.yaml new file mode 100644 index 0000000..ddcedac --- /dev/null +++ b/roles/borgmatic_config/defaults/main.yaml @@ -0,0 +1,8 @@ +--- +borgmatic_config_backup_frequency: + unit: h + amount: 1 + +borgmatic_config_keep_backups_months: 6 + +borgmatic_config_targets: "{{ borgmatic_targets }}" diff --git a/roles/borgmatic_config/handlers/main.yaml b/roles/borgmatic_config/handlers/main.yaml new file mode 100644 index 0000000..3255006 --- /dev/null +++ b/roles/borgmatic_config/handlers/main.yaml @@ -0,0 +1,6 @@ +--- +- name: Restart borgmatic timer {{ borgmatic_config_name }} + ansible.builtin.systemd_service: + name: "borgmatic@{{ borgmatic_config_name }}.timer" + state: restarted + daemon_reload: true diff --git a/roles/borgmatic_config/meta/argument_specs.yaml b/roles/borgmatic_config/meta/argument_specs.yaml new file mode 100644 index 0000000..edaf849 --- /dev/null +++ b/roles/borgmatic_config/meta/argument_specs.yaml @@ -0,0 +1,65 @@ +--- +argument_specs: + main: + short_description: Borgmatic config + description: + - Creates a bormatic configuration in /etc/borgmatic.d/ and creates the repos + options: + borgmatic_config_name: + description: + - Name of the borgmatic config. + - Must be unique within the (source) host. + type: str + required: true + borgmatic_config_directories: + description: Directories to backup + type: list + required: true + elements: str + borgmatic_config_encryption_passphrase: + description: Passphrase for borg repo encryption + type: str + required: true + borgmatic_config_targets: + description: + - List of backup targets for this config. + - All backup targets and directories must be listed in borgmatic_targets. + - Defaults to all defined in borgmatic_targets. + type: list + required: false + elements: dict + options: + host: + description: Target hostname + type: str + required: true + directories: + description: Directories on the host where backup repos will be created under + type: list + required: true + elements: str + borgmatic_config_backup_frequency: + description: How often to take backups. Defaults to once per hour. + type: dict + required: false + default: + unit: h + amount: 1 + options: + unit: + description: Time unit + type: str + required: true + choices: + - min + - h + - d + amount: + description: Every how many time units to take backpus + type: int + required: true + borgmatic_config_keep_backups_months: + description: How many months to keep backups for + type: int + required: false + default: 6 diff --git a/roles/borgmatic_config/meta/main.yaml b/roles/borgmatic_config/meta/main.yaml new file mode 100644 index 0000000..05cfa43 --- /dev/null +++ b/roles/borgmatic_config/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - borgmatic diff --git a/roles/borgmatic_config/tasks/main.yaml b/roles/borgmatic_config/tasks/main.yaml new file mode 100644 index 0000000..23daab9 --- /dev/null +++ b/roles/borgmatic_config/tasks/main.yaml @@ -0,0 +1,35 @@ +--- +- name: Fail if hosts or directories listed in borgmatic_config_targets not in borgmatic_targets. + ansible.builtin.fail: + msg: All backup targets and directories must be listed in borgmatic_targets. + when: >- + borgmatic_config_targets + | items2dict(key_name='host', value_name='directories') + | ansible.builtin.combine( + borgmatic_targets | items2dict(key_name='host', value_name='directories'), list_merge='prepend_rp' + ) + | dict2items(key_name='host', value_name='directories') + | difference(borgmatic_targets) + | length != 0 + +- name: Add borgmatic configuration for {{ borgmatic_config_name }} + ansible.builtin.template: + src: borgmatic.yaml.j2 + dest: /etc/borgmatic.d/{{ borgmatic_config_name }}.yaml + mode: "0600" + no_log: true + +- name: Add systemd timer for borgmatic {{ borgmatic_config_name }} + ansible.builtin.template: + src: borgmatic@.timer.j2 + dest: /etc/systemd/system/borgmatic@{{ borgmatic_config_name }}.timer + mode: "0644" + register: _borgmatic_config_systemd_timer + notify: Restart borgmatic timer {{ borgmatic_config_name }} + +- name: Enable systemd timer for borgmatic {{ borgmatic_config_name }} + ansible.builtin.systemd_service: + name: borgmatic@{{ borgmatic_config_name }}.timer + state: started + enabled: true + when: "not (ansible_check_mode and _borgmatic_config_systemd_timer.changed and _borgmatic_config_systemd_timer.diff.before == '')" diff --git a/roles/borgmatic_config/templates/borgmatic.yaml.j2 b/roles/borgmatic_config/templates/borgmatic.yaml.j2 new file mode 100644 index 0000000..1fdd414 --- /dev/null +++ b/roles/borgmatic_config/templates/borgmatic.yaml.j2 @@ -0,0 +1,35 @@ +# {{ ansible_managed }} +# vim:ft=yaml + +source_directories: + {{ borgmatic_config_directories | to_nice_yaml }} +repositories: +{% for target in borgmatic_config_targets %} +{% for directory in target.directories %} + - path: "ssh://{{ hostvars[target.host].ansible_user_id }}@{{ target.host }}/{{ directory }}/{{ ansible_fqdn }}/{{ borgmatic_config_name }}" +{% endfor %} +{% endfor %} +working_directory: "~" + +one_file_system: true +exclude_patterns: + - /var/cache +exclude_caches: true +exclude_if_present: + - .nobackup +keep_exclude_tags: true + +source_directories_must_exist: true + +compression: zstd + +encryption_passphrase: "{{ borgmatic_config_encryption_passphrase }}" +ssh_command: ssh -i ~/.ssh/id_ed25519_borg + + +keep_within: 48H +keep_hourly: 168 +keep_daily: 30 +keep_weekly: 26 +keep_monthly: {{ (borgmatic_config_keep_backups_months / 2) | round(0, 'ceil') | int }} +keep_yearly: {{ (borgmatic_config_keep_backups_months / 12) | round(0, 'ceil') | int }} diff --git a/roles/borgmatic_config/templates/borgmatic@.timer.j2 b/roles/borgmatic_config/templates/borgmatic@.timer.j2 new file mode 100644 index 0000000..68d4854 --- /dev/null +++ b/roles/borgmatic_config/templates/borgmatic@.timer.j2 @@ -0,0 +1,21 @@ +# {{ ansible_managed }} + +[Unit] +Description=Run borgmatic backup + +[Timer] +{% if borgmatic_config_backup_frequency.unit == "min" %} +OnCalendar=*:0/{{ borgmatic_config_backup_frequency.amount }} +{% elif borgmatic_config_backup_frequency.unit == "h" %} +OnCalendar=0/{{ borgmatic_config_backup_frequency.amount }}:30 +{% elif borgmatic_config_backup_frequency.unit == "d" %} +OnCalendar=*-1/{{ borgmatic_config_backup_frequency.amount }} 22:00 +{% else %} +{{ dafuq }} +{% endif %} +Persistent=true +RandomizedDelaySec={{ 10 * borgmatic_config_backup_frequency.amount }}{{ _borgmatic_config_previous_time_unit[borgmatic_config_backup_frequency.unit] }} +FixedRandomDelay=true + +[Install] +WantedBy=timers.target diff --git a/roles/borgmatic_config/vars/main.yaml b/roles/borgmatic_config/vars/main.yaml new file mode 100644 index 0000000..f15abaf --- /dev/null +++ b/roles/borgmatic_config/vars/main.yaml @@ -0,0 +1,5 @@ +--- +_borgmatic_config_previous_time_unit: + min: s + h: min + d: h