aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Wilk <colin.wilk@tum.de>2023-11-03 19:45:20 +0100
committerColin Wilk <colin.wilk@tum.de>2023-11-04 14:02:38 +0100
commit50a2795c3a6c72203262400db5029f5afdf1d49c (patch)
treedb1476895f9e0a4f1ed7c393c5b58e5d4a85d862
parentabc40a024ea6ee1d2e3db642b56c6b27a603bb2f (diff)
downloadansible-role-borgbackup-50a2795c3a6c72203262400db5029f5afdf1d49c.tar.gz
ansible-role-borgbackup-50a2795c3a6c72203262400db5029f5afdf1d49c.zip
Migrate role from cron to systemd
Systemd gives us the ability to monitor backup job status using existing monitoring solutions (node exporter) and allows us greater control over the scheduling of the backup jobs. This introduces a breaking change that requires users to manually remove the old repositories from the clients and redeploying them with the role. You will have to remove the Cron job that was created by the Ansible script, everything else will be overwritten with a run from the newer version. - name: Remove backup cron jobs ansible.builtin.cron: name: BORG (Application level backups) state: absent become: true - name: Remove env for backup cron job ansible.builtin.cron: name: BORG_PASSPHRASE env: true state: absent become: true Performing manual migrations on the Borg server is not required. We now additionally support multiple Borg repositories per client host using the `borg_backup_argument` variable. Signed-off-by: Colin Wilk <colin.wilk@tum.de>
-rw-r--r--README.md6
-rw-r--r--defaults/main.yml55
-rw-r--r--handlers/main.yml5
-rw-r--r--meta/argument_specs.yml53
-rw-r--r--molecule/default/Dockerfile.j23
-rw-r--r--molecule/default/converge.yml10
-rw-r--r--molecule/default/molecule.yml2
-rw-r--r--tasks/client_create_scripts_each.yml31
-rw-r--r--tasks/client_setup.yml57
-rw-r--r--tasks/installation.yml1
-rw-r--r--templates/borg_backup.service.j212
-rw-r--r--templates/borg_backup.timer.j211
12 files changed, 175 insertions, 71 deletions
diff --git a/README.md b/README.md
index 1e9a634..4c2fd32 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
Ansible Role: BorgBackup
========================
-An ansible role that installs and configures a cron task for BorgBackup on a
-client and (delegated) server.
-This allows you to very simply add backups to your hosts.
+An Ansible role that installs and configures a systemd service for BorgBackup on
+a client and (delegated) server.
+This allows you to very easily add backups to your hosts.
The documentation assumes basic knowledge about BorgBackup. You can get up to
speed with Borg by reading their excellent documentation on
diff --git a/defaults/main.yml b/defaults/main.yml
index fa8190d..c915ef9 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -77,7 +77,7 @@ borg_backup_name_format: '{hostname}-{now:%Y-%m-%dT%H:%M:%S}'
# https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-compression
borg_compression: zstd
-# This is a list of files and directories to be backed up in the cron job.
+# This is a list of files and directories to be backed up in the systemd job.
# In case you leave this empty, the role will not create an automatic backup job
borg_included_dirs: []
@@ -92,7 +92,7 @@ borg_excluded_dirs: []
# By default the role is configured to only use an encryption key with no
# passphrase. This allows it to use the borgs command on the machine without any
# haste. If you wish to enable the borg passphrase you can do so here. Note that
-# The passphrase will be stored in plaintext inside the cron job.
+# The passphrase will be stored in plaintext inside the backup script.
# For more information about the borg passphrase see
# https://borgbackup.readthedocs.io/en/stable/quickstart.html#passphrase-notes
borg_passphrase: ''
@@ -109,17 +109,40 @@ borg_passphrase: ''
# management system.
borg_decryption_keys_yaml_path: '{{ inventory_dir }}/decryption_keys.yml'
-# Define the cron values for the automatic backup job as specified in the cron
-# module.
-# https://docs.ansible.com/ansible/latest/collections/ansible/builtin/cron_module.html
-# Values that are not specified are omitted
-# borg_cron_time:
-# minute:
-# hour:
-# weekday:
-# day:
-# month:
-# special_time:
-borg_cron_time:
- minute: 0
- hour: 3
+# The role creates a script for backing up with the configured parameters that
+# the regular systemd service then executes. This specifies the default location
+# and name where the script is stored. By default, we store it as
+# `/usr/local/bin/run_borg_backup` so that you can run `run_borg_backup` from
+# your shell to create manual backups.
+# When you use multiple backups, this script will trigger all of them. You can
+# trigger them individually by calling
+# {{ borg_backup_script_location }}@{{ borg_backup_argument }}.
+# See: `borg_backup_argument` variable.
+borg_backup_script_location: /usr/local/bin/run_borg_backup
+
+# Name of the systemd timer that is created for the borg service.
+# The borg backup argument is appended to the timer name, meaning the timer will
+# be called {{ borg_backup_timer_name }}@{{ borg_backup_argument }}
+borg_backup_timer_name: borg_backup
+
+# Name of the systemd service that is created for the borg service.
+# The borg backup argument is appended to the service name, meaning the service
+# will be called {{ borg_backup_service_name }}@{{ borg_backup_argument }}
+borg_backup_service_name: borg_backup
+
+# The backup argument is appended to systemd timer / systemd service and the
+# backup script. It is used to distinguish backup targets from one another
+# meaning it should be unique per target.
+# By default, we use borg_server_host_url, which is fine as long as you don't
+# need multiple backup repositories from the same client on the same server.
+borg_backup_argument: '{{ borg_server_host_url }}'
+
+# Configures the systemd timer for how regularly to run the backup. By default,
+# the backup will run every night attacker 2AM. For more information on how to
+# configure this, see: systemd.timer(5)
+borg_systemd_oncalendar: '*-*-* 02:00:00'
+
+# Specify the accuracy the timer shall elapse with. By default, we use 60min
+# to distribute the load on the backup server. For more information on how to
+# configure this see: systemd.timer(5)
+borg_systemd_accuracysec: 60min
diff --git a/handlers/main.yml b/handlers/main.yml
new file mode 100644
index 0000000..9e55a3a
--- /dev/null
+++ b/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Reload systemd
+ ansible.builtin.systemd:
+ daemon_reload: true
+ become: true
diff --git a/meta/argument_specs.yml b/meta/argument_specs.yml
index b243fe0..5b55ac9 100644
--- a/meta/argument_specs.yml
+++ b/meta/argument_specs.yml
@@ -67,27 +67,32 @@ argument_specs:
type: str
required: false
- borg_cron_time:
- type: dict
- required: false
- options:
- minute:
- type: str
- required: false
- default: 0
- hour:
- type: str
- required: false
- default: 3
- weekday:
- type: str
- required: false
- day:
- type: str
- required: false
- month:
- type: str
- required: false
- special_time:
- type: str
- required: false
+ borg_backup_script_location:
+ type: str
+ required: false
+ default: /usr/local/bin/run_borg_backup
+
+ borg_backup_timer_name:
+ type: str
+ required: false
+ default: borg_backup
+
+ borg_backup_service_name:
+ type: str
+ required: false
+ default: borg_backup
+
+ borg_backup_argument:
+ type: str
+ required: false
+ default: '{ borg_server_host_url }'
+
+ borg_systemd_oncalendar:
+ type: str
+ required: false
+ default: '*-*-* 02:00:00'
+
+ borg_systemd_accuracysec:
+ type: str
+ required: false
+ default: 60min
diff --git a/molecule/default/Dockerfile.j2 b/molecule/default/Dockerfile.j2
index 091ef00..cc459e3 100644
--- a/molecule/default/Dockerfile.j2
+++ b/molecule/default/Dockerfile.j2
@@ -6,10 +6,11 @@ RUN apt-get update \
sudo wget \
python3-pip python3-dev python3-setuptools python3-wheel python3-apt \
sed ssh openssh-server \
+ systemd systemd-sysv dbus dbus-user-session \
&& rm -rf /var/lib/apt/lists/* \
&& rm -Rf /usr/share/doc && rm -Rf /usr/share/man \
&& apt-get clean
RUN mkdir /run/sshd
-ENTRYPOINT ["bash", "-c", "/usr/sbin/sshd && sleep infinity"]
+ENTRYPOINT ["/sbin/init"]
diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml
index 17b5f0e..7212eb2 100644
--- a/molecule/default/converge.yml
+++ b/molecule/default/converge.yml
@@ -9,6 +9,13 @@
- name: Set borg server openssh key variable
become: true
block:
+ - name: Start ssh
+ ansible.builtin.systemd:
+ name: sshd
+ state: started
+ become: true
+ delegate_to: '{{ borg_server_host }}'
+
- name: Fetch ssh_key
ansible.builtin.command: >
ssh-keyscan -t rsa
@@ -35,9 +42,6 @@
- /opt
- /var
- /reee reeee
- borg_cron_time:
- minute: '*'
- hour: '*'
roles:
- role: kliwniloc.borgbackup
diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml
index 3c0dfef..fc7a266 100644
--- a/molecule/default/molecule.yml
+++ b/molecule/default/molecule.yml
@@ -13,6 +13,7 @@ platforms:
image: ${MOLECULE_DISTRO_CLIENT:-debian:10}
dockerfile: Dockerfile.j2
pre_build_image: false
+ privileged: true
docker_networks:
- name: molecule-container-net
driver_options:
@@ -25,6 +26,7 @@ platforms:
image: ${MOLECULE_DISTRO_SERVER:-debian:10}
dockerfile: Dockerfile.j2
pre_build_image: false
+ privileged: true
networks:
- name: molecule-container-net
diff --git a/tasks/client_create_scripts_each.yml b/tasks/client_create_scripts_each.yml
new file mode 100644
index 0000000..dcf379f
--- /dev/null
+++ b/tasks/client_create_scripts_each.yml
@@ -0,0 +1,31 @@
+---
+- name: Create script for automatic borg backup
+ ansible.builtin.file:
+ dest: '{{ script_location }}'
+ state: touch
+ owner: root
+ group: root
+ modification_time: preserve
+ access_time: preserve
+ mode: '0711'
+ become: true
+
+- name: Insert shebang into backup script
+ ansible.builtin.lineinfile:
+ path: '{{ script_location }}'
+ line: '#!/bin/bash'
+ insertbefore: BOF
+ state: present
+ become: true
+
+- name: Insert Backup job block into scripts
+ ansible.builtin.blockinfile:
+ path: '{{ script_location }}'
+ marker: '## {mark} ANSIBLE MANAGED BLOCK for server: {{ borg_server_host_url }}'
+ block: |
+ export BORG_PASSPHRASE={{ borg_passphrase }}
+ borg create -C {{ borg_compression }} \
+ borg@{{ borg_server_host_url }}:{{ borg_server_user_home }}/{{ borg_repo_name }}::{{ borg_backup_name_format }} \
+ {{ borg_included_dirs | map('quote') | join(' ') }} \
+ {% for e in (borg_excluded_dirs | map('quote')) %} --exclude {{ e }} {% endfor %}
+ become: true
diff --git a/tasks/client_setup.yml b/tasks/client_setup.yml
index d3e9f75..eb6c9a1 100644
--- a/tasks/client_setup.yml
+++ b/tasks/client_setup.yml
@@ -92,29 +92,40 @@
delegate_to: localhost
become: false
-- name: Set up env for cron job
- ansible.builtin.cron:
- name: BORG_PASSPHRASE
- job: '{{ borg_passphrase }}'
- state: '{{ "present" if (borg_included_dirs | length > 0) else "absent" }}'
- env: true
- user: root
+- name: Create backup scripts
+ ansible.builtin.include_tasks: client_create_scripts_each.yml
+ loop:
+ - '{{ borg_backup_script_location }}'
+ - '{{ borg_backup_script_location }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}'
+ loop_control:
+ loop_var: script_location
+
+- name: Configure systemd borg_backup service
+ ansible.builtin.template:
+ src: borg_backup.service.j2
+ dest: /etc/systemd/system/{{ borg_backup_timer_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.service
+ mode: '0644'
+ owner: root
+ group: root
+ notify: Reload systemd
+ become: true
+
+- name: Configure systemd borg_backup timer
+ ansible.builtin.template:
+ src: borg_backup.timer.j2
+ dest: /etc/systemd/system/{{ borg_backup_timer_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.timer
+ mode: '0644'
+ owner: root
+ group: root
+ notify: Reload systemd
become: true
-- name: Set up backup cron jobs
- ansible.builtin.cron:
- name: BORG (Application level backups)
- job: >
- borg create -C {{ borg_compression }}
- borg@{{ borg_server_host_url }}:{{ borg_server_user_home }}/{{ borg_repo_name }}::{{ borg_backup_name_format }}
- {{ borg_included_dirs | map('quote') | join(' ') }}
- {% for e in (borg_excluded_dirs | map('quote')) %} --exclude {{ e }} {% endfor %}
- user: root
- state: '{{ "present" if (borg_included_dirs | length > 0) else "absent" }}'
- minute: '{{ borg_cron_time.minute | default(omit) }}'
- hour: '{{ borg_cron_time.hour | default(omit) }}'
- weekday: '{{ borg_cron_time.weekday | default(omit) }}'
- day: '{{ borg_cron_time.day | default(omit) }}'
- month: '{{ borg_cron_time.month | default(omit) }}'
- special_time: '{{ borg_cron_time.special_time | default(omit) }}'
+- name: Reload systemd now before enabling services
+ ansible.builtin.meta: flush_handlers
+
+- name: Enable borg_backup systemd timer
+ ansible.builtin.systemd:
+ name: '{{ borg_backup_timer_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.timer'
+ state: started
+ enabled: true
become: true
diff --git a/tasks/installation.yml b/tasks/installation.yml
index 8ee0835..7e64ff3 100644
--- a/tasks/installation.yml
+++ b/tasks/installation.yml
@@ -13,7 +13,6 @@
ansible.builtin.apt:
name:
- borgbackup
- - cron
- ssh
state: present
update_cache: true
diff --git a/templates/borg_backup.service.j2 b/templates/borg_backup.service.j2
new file mode 100644
index 0000000..e3f6910
--- /dev/null
+++ b/templates/borg_backup.service.j2
@@ -0,0 +1,12 @@
+[Unit]
+Description=Runs borgbackup to create application level backup (ANSIBLE MANAGED)
+Wants={{ borg_backup_timer_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.timer
+
+[Service]
+Type=oneshot
+ExecStart={{ borg_backup_script_location }}
+User=root
+Group=root
+
+[Install]
+WantedBy=multi-user.target
diff --git a/templates/borg_backup.timer.j2 b/templates/borg_backup.timer.j2
new file mode 100644
index 0000000..40eb03c
--- /dev/null
+++ b/templates/borg_backup.timer.j2
@@ -0,0 +1,11 @@
+[Unit]
+Description=Runs borgbackup to create application level backup (ANSIBLE MANAGED)
+Requires={{ borg_backup_service_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.service
+
+[Timer]
+Unit={{ borg_backup_service_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.service
+OnCalendar={{ borg_systemd_oncalendar }}
+AccuracySec={{ borg_systemd_accuracysec }}
+
+[Install]
+WantedBy=timers.target