From 50a2795c3a6c72203262400db5029f5afdf1d49c Mon Sep 17 00:00:00 2001 From: Colin Wilk Date: Fri, 3 Nov 2023 19:45:20 +0100 Subject: 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 --- README.md | 6 ++-- defaults/main.yml | 55 ++++++++++++++++++++++++---------- handlers/main.yml | 5 ++++ meta/argument_specs.yml | 53 ++++++++++++++++++--------------- molecule/default/Dockerfile.j2 | 3 +- molecule/default/converge.yml | 10 +++++-- molecule/default/molecule.yml | 2 ++ tasks/client_create_scripts_each.yml | 31 ++++++++++++++++++++ tasks/client_setup.yml | 57 +++++++++++++++++++++--------------- tasks/installation.yml | 1 - templates/borg_backup.service.j2 | 12 ++++++++ templates/borg_backup.timer.j2 | 11 +++++++ 12 files changed, 175 insertions(+), 71 deletions(-) create mode 100644 handlers/main.yml create mode 100644 tasks/client_create_scripts_each.yml create mode 100644 templates/borg_backup.service.j2 create mode 100644 templates/borg_backup.timer.j2 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 -- cgit v1.2.3