diff --git a/README.md b/README.md index 9bdbe7b..09a7aa6 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ This role installs Borg backup on backupservers and clients. The role contains a wrapper-script 'borg-backup' to ease the usage on the client. Supported options include borg-backup info | init | list | backup | mount. Automysqlbackup will run as pre-backup command if it's installed. The role supports both self hosted and rsync.net as Borg server. +It's possible to configure append-only repositories to secure the backups against deletion from the client. ## Required variables Define a group backupservers in your inventory with one or multiple hosts. @@ -9,6 +10,9 @@ Define a group backupservers in your inventory with one or multiple hosts. infra: [backupservers] backup1.fiaas.co + +[borgbackup_management] +supersecurehost ``` group\_vars/all.yml: @@ -37,6 +41,8 @@ host\_vars\client1: borgbackup_passphrase: Ahl9EiNohr5koosh1Wohs3Shoo3ooZ6p ``` +Set borgbackup\_appendonly: True in host or group vars if you want append-only repositories. In that case it's possible to define a hostname in borgbackup\_management\_station where a borg prune script will be configured. + *Make sure to check the configured defaults for this role, which contains the list of default locations being backed up in backup_include.* Override this in your inventory where required. ## Usage diff --git a/defaults/main.yml b/defaults/main.yml index 44ae601..a2589ac 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -22,4 +22,7 @@ borgbackup_retention: monthly: 6 yearly: 1 +borgbackup_appendonly: False borgbackup_management_station: '' +borgbackup_management_user: '' +borgbackup_management_sshkey: '' diff --git a/tasks/borg-client.yml b/tasks/borg-client.yml index 654a246..09a89a4 100644 --- a/tasks/borg-client.yml +++ b/tasks/borg-client.yml @@ -25,7 +25,7 @@ authorized_key: user: "{{ item.user }}" key: "{{ sshkey.stdout }}" - key_options: 'command="cd {{ item.home }}{{ item.pool }}/{{ inventory_hostname }};borg serve --restrict-to-path {{ item.home }}/{{ item.pool }}/{{ inventory_hostname }}",no-port-forwarding,no-X11-forwarding,no-pty,no-agent-forwarding,no-user-rc' + key_options: 'command="cd {{ item.home }}{{ item.pool }}/{{ inventory_hostname }};borg serve {% if borgbackup_appendonly %}--append-only {% endif %}--restrict-to-path {{ item.home }}/{{ item.pool }}/{{ inventory_hostname }}",no-port-forwarding,no-X11-forwarding,no-pty,no-agent-forwarding,no-user-rc' delegate_to: "{{ item.fqdn }}" when: item.type == 'normal' with_items: "{{ backupservers }}" @@ -43,7 +43,7 @@ authorized_key: user: "{{ ansible_user_id }}" key: "{{ sshkey.stdout }}" - key_options: 'command="cd {{ item.home }}{{ item.pool }}/{{ inventory_hostname }};borg serve --restrict-to-path {{ item.home }}/{{ item.pool }}/{{ inventory_hostname }}",no-port-forwarding,no-X11-forwarding,no-pty,no-agent-forwarding,no-user-rc' + key_options: 'command="cd {{ item.home }}{{ item.pool }}/{{ inventory_hostname }};borg serve {% if borgbackup_appendonly %}--append-only {% endif %}--restrict-to-path {{ item.home }}/{{ item.pool }}/{{ inventory_hostname }}",no-port-forwarding,no-X11-forwarding,no-pty,no-agent-forwarding,no-user-rc' path: "/tmp/rsync.net-{{ item.fqdn }}-authkeys" manage_dir: no delegate_to: localhost diff --git a/tasks/main.yml b/tasks/main.yml index 5f2e9e1..357a756 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,5 +1,4 @@ --- - - include: install.yml when: > borgbackup_required == True or @@ -12,3 +11,8 @@ when: > borgbackup_required == True and inventory_hostname not in groups.backupservers + +- include: management.yml + when: > + inventory_hostname in groups.borgbackup_management and + inventory_hostname not in groups.backupservers diff --git a/tasks/management.yml b/tasks/management.yml new file mode 100644 index 0000000..0bb30fc --- /dev/null +++ b/tasks/management.yml @@ -0,0 +1,55 @@ +--- +- name: management | put management station prune script + template: + src: prune.sh.j2 + dest: "~{{ borgbackup_management_user }}/prune.sh" + owner: "{{ borgbackup_management_user }}" + group: "{{ borgbackup_management_user }}" + mode: 0700 + +- name: management | put management sshpubkey on the normal backupserver + authorized_key: + user: "{{ item.user }}" + key: "{{ borgbackup_management_sshkey }}" + delegate_to: "{{ item.fqdn }}" + when: item.type == 'normal' + with_items: "{{ backupservers }}" + + +# rsync.net has no python, so we can only use raw to manage ssh keys - workaround with local tmp file +- name: management | get rsync.net authorized_keys file + raw: scp {{ item.user }}@{{ item.fqdn }}:.ssh/authorized_keys /tmp/rsync.net-{{ item.fqdn }}-authkeys + delegate_to: localhost + become: no + when: item.type == 'rsync.net' + with_items: "{{ backupservers }}" + changed_when: false + +- name: management | modify local rsync.net authorized_keys + authorized_key: + user: "{{ ansible_user_id }}" + key: "{{ borgbackup_management_sshkey }}" + path: "/tmp/rsync.net-{{ item.fqdn }}-authkeys" + manage_dir: no + delegate_to: localhost + become: no + when: item.type == 'rsync.net' + with_items: "{{ backupservers }}" + register: authkeys + +- name: management | upload local authorized_keys to rsync.net + raw: scp /tmp/rsync.net-{{ item.fqdn }}-authkeys {{ item.user }}@{{ item.fqdn }}:.ssh/authorized_keys + delegate_to: localhost + become: no + when: item.type == 'rsync.net' and authkeys.changed + with_items: "{{ backupservers }}" + +- name: management | remove tmp authorized_keys files + file: + path: /tmp/rsync.net-{{ item.fqdn }}-authkeys + state: absent + delegate_to: localhost + become: no + with_items: "{{ backupservers }}" + when: authkeys.changed + changed_when: false diff --git a/templates/borg-backup.sh.j2 b/templates/borg-backup.sh.j2 index 5290243..d01d21d 100644 --- a/templates/borg-backup.sh.j2 +++ b/templates/borg-backup.sh.j2 @@ -69,10 +69,11 @@ if [ "$1" = "backup" ] /usr/local/bin/borg create --compression zlib,6 --stats $REPOSITORY::$date {{ b.options }} {% for dir in borgbackup_include %}{{ dir }} {% endfor %}{% if automysql.stat.isdir is defined and automysql.stat.isdir == True %}/var/lib/automysqlbackup{% endif %} if [ "$?" -eq "0" ]; then printf "Backup succeeded on $date\n" >> /var/log/borg-backup.log; fi - - # Use the `prune` subcommand to maintain 7 daily, 4 weekly - # and 6 monthly archives. + + {% if not borgbackup_appendonly %} + # prune old backups /usr/local/bin/borg prune -v $REPOSITORY {{ b.options }} -H {{ borgbackup_retention.hourly }} -d {{ borgbackup_retention.daily }} -w {{ borgbackup_retention.weekly }} -m {{ borgbackup_retention.monthly }} -y {{ borgbackup_retention.yearly }} + {% endif %} {% endfor %} fi diff --git a/templates/prune.sh.j2 b/templates/prune.sh.j2 new file mode 100644 index 0000000..7157dda --- /dev/null +++ b/templates/prune.sh.j2 @@ -0,0 +1,19 @@ +#!/bin/bash + +# This script is intended to run on a trusted management station to purge borg repo's in +# append-only mode. +# Don't put it on the backup server, it contains all borg secrets! + +{% for h in groups['all'] %} + {% if hostvars[h].borgbackup_required is defined and hostvars[h].borgbackup_required %} + # Host: {{ h }} + {% for b in hostvars[h].backupservers %} + {% if hostvars[h].borgbackup_managementstation is defined and inventory_hostname == hostvars[h].borgbackup_managementstation %} + export BORG_PASSPHRASE={{ hostvars[h].borgbackup_passphrase }} + REPOSITORY={{ b.user }}@{{ b.fqdn }}:{{ b.home }}{{ b.pool }}/{{ h }} + /usr/local/bin/borg prune -v $REPOSITORY {{ b.options }} -H {{ hostvars[h].borgbackup_retention.hourly }} -d {{ hostvars[h].borgbackup_retention.daily }} -w {{ hostvars[h].borgbackup_retention.weekly }} -m {{ hostvars[h].borgbackup_retention.monthly }} -y {{ hostvars[h].borgbackup_retention.yearly }} + + {% endif %} + {% endfor %} + {% endif %} +{% endfor %}