add backup restic role

This commit is contained in:
les 2021-01-04 18:56:12 +01:00
parent ee97b7acc1
commit 126e3ffd0d
12 changed files with 349 additions and 5 deletions

View file

@ -18,3 +18,10 @@ test:
all:
vars:
passwordstore_path: cisti.org/ansible
restic_default_folders: []
restic_password: "{{lookup('community.general.passwordstore', '{{passwordstore_path}}/restic/{{{{ansible_hostname}}_pwd create=True nosymbols=true')}}"
restic_repository_name: "{{ansible_hostname}}"
restic_ssh_private_key: "{{lookup('community.general.passwordstore', '{{passwordstore_path}}/restic/ssh_private returnall=true')}}"
restic_ssh_hostname: "{{lookup('community.general.passwordstore', '{{passwordstore_path}}/restic/ssh_hostname')}}"
restic_ssh_user: "{{lookup('community.general.passwordstore', '{{passwordstore_path}}/restic/ssh_user')}}"
restic_ssh_port: "{{lookup('community.general.passwordstore', '{{passwordstore_path}}/restic/ssh_port')}}"

View file

@ -13,8 +13,15 @@ dependencies:
database: etherpad
# install certbot nginx and configure it as reverse proxy
# - role: stable/nginx
# when: with_nginx | bool
# vars:
# with_certbot: true
# proxy_pass: http://
- role: stable/nginx
when: with_nginx | bool
vars:
with_certbot: true
proxy_pass: http://localhost:8001
# backup etherpad database
- role: stable/restic
when: with_backup | bool
vars:
restic_databases:
- {name: 'etherpad', dump_command: sudo -Hiu postgres pg_dump -Fc etherpad}

View file

@ -0,0 +1,125 @@
# Ansible role for Restic
This role will setup [Restic](https://restic.net/) backups on a Debian/Ubuntu machine using a systemd service and timer.
It supports S3 backend or SFTP backend and will thus setup the SSH config and SSH private keys (see variables below).
## Role Variables
### Restic installation
The role will download and install the restic binary (version `restic_version`) into `restic_path` if the file does not exist.
If you want to force the installation, overwrite the binary or update restic, you can run ansible with `--extra-vars restic_install=true`.
### Restic configuration
- `restic_user`: user to run restic as (`root`)
- `restic_user_home`: home directory of the restic_user (`/root`)
- `restic_password`: password used for repository encryption
- `restic_repository_name`: the name of the repository (`restic`)
- `restic_check`: run `restic check` as `ExecStartPre` if true (`false`)
- `restic_default_folders`: a default list of folders that restic will backup (`/etc/`, `/root` and `/var/log`)
- `restic_folders`: the list of folder you want to backup
- `restic_dump_compression_enabled`: enable piping to pigz for database dumps
Each folder has a `path` and an `exclude` property (which defaults to nothing). The `exclude` property is the literal argument passed to restic (exemple: `--exclude .cache --exclude .local`).
`restic_default_folders` and `restic_folders` are combined to form the final list of backuped folders.
- `restic_databases`: a list of databases to dump
Each database has a `name` property which will be the name of the restic snapshot (`{{ database.name }}.sql`). They also have a `dump_command` property which is the command to dump the database to stdout (like `mysqldump dbname`).
- `restic_forget`: run `restic forget` as `ExecStartPost` with `--keep-within {{ restic_forget_keep_within }}` (`true`)
- `restic_forget_keep_within`: period of time to use with `--keep-within` (`30d`)
- `restic_prune`: run `restic prune` as `ExecStartPost` (`true`)
### SSH/SFTP backend configuration
The SSH configuration will be written in `{{ restic_user_home }}/.ssh/config`.
- `restic_ssh_host`: backend name and SSH alias for the backup host
- `restic_ssh_user`: user for SSH connection
- `restic_ssh_hostname`: actual SSH hostname of the backup machine
- `restic_ssh_private_key`: private SSH key used to connect to the backup host
- `restic_ssh_private_key_path`: path of the private key to use (`~/.ssh/backup`)
- `restic_ssh_port`: SSH port to use with the backup machine (`23`)
### S3 backend configuration
- `restic_ssh_enabled`: set to false
- `restic_repository_name`: set to s3 endpoint + bucket, restic syntax (e.g. `s3:https://s3.fr-par.scw.cloud/restic-bucket`)
- `restic_aws_access_key_id`: `AWS_ACCESS_KEY_ID`
- `restic_aws_secret_access_key`: `AWS_SECRET_ACCESS_KEY`
### Sytemd service and timer
A `restic-backup.service` service will be created with all the parameters defined above. The service is of type `oneshot` and will be triggered periodically with `restic-backup.timer`.
The timer is configurable as follows:
- `restic_systemd_timer_on_calender`: defines the `OnCalendar` directive (`*-*-* 03:00:00`)
- `restic_systemd_timer_randomized_delay_sec`: Delay the timer by a random amount of time between 0 and the specified time value. (`0`)
See the [systemd.timer](https://www.freedesktop.org/software/systemd/man/systemd.timer.html) documentation for more information.
You can see the logs of the backup with `journalctl`. (`journalctl -xefu restic-backup`).
## Example playbook
```yaml
---
- hosts: myhost
roles: restic
vars:
restic_ssh_user: backupuser
restic_ssh_hostname: storage-server.infra.tld
restic_folders:
- {path: "/srv"}
- {path: "/var/www"}
restic_databases:
- {name: website, dump_command: sudo -Hiu postgres pg_dump -Fc website}
- {name: website2, dump_command: mysqldump website2}
restic_password: mysuperduperpassword
restic_ssh_private_key: |-
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACAocs5g1I4kFQ1HH/YZiVU+zLhRDu4tfzZ9CmFAfKhL2AAAAJi02XEwtNlx
MAAAAAtzc2gtZWQyNTUxOQAAACAocs5g1I4kFQ1HH/YZiVU+zLhRDu4tfzZ9CmFAfKhL2A
AAAEADZf2Pv4G74x+iNtuwSV/ItnR3YQJ/KUaNTH19umA/tChyzmDUjiQVDUcf9hmJVT7M
uFEO7i1/Nn0KYUB8qEvYAAAAE3N0YW5pc2xhc0BtYnAubG9jYWwBAg==
-----END OPENSSH PRIVATE KEY-----
```
S3 example:
```yaml
---
- hosts: myhost
roles: restic
vars:
restic_ssh_enabled: false
restic_repository: "s3:https://s3.fr-par.scw.cloud/restic-bucket"
restic_aws_access_key_id: xxxxx
restic_aws_secret_access_key: xxxxx
restic_folders:
- {path: "/srv"}
- {path: "/var/www"}
restic_databases:
- {name: website, dump_command: sudo -Hiu postgres pg_dump -Fc website}
- {name: website2, dump_command: mysqldump website2}
restic_password: mysuperduperpassword
```
Of course, `restic_password` and `restic_ssh_private_key` should be stored using ansible-vault.
## License
MIT
## Author Information
See my other Ansible roles at [angristan/ansible-roles](https://github.com/angristan/ansible-roles).

View file

@ -0,0 +1,24 @@
---
restic_install: false
restic_version: 0.11.0
restic_path: /usr/local/bin/restic
restic_user: root
restic_user_home: /root
restic_repository_name: restic
restic_default_folders: []
restic_folders: []
restic_databases: []
restic_dump_compression_enabled: true
restic_forget: true
restic_forget_keep_within: 30d
restic_prune: true
restic_check: true
restic_ssh_enabled: true
restic_ssh_host: backup
restic_ssh_port: 22
restic_ssh_private_key_path: '/root/.ssh/backup'
restic_systemd_timer_on_calender: '*-*-* 03:00:00'
restic_systemd_timer_randomized_delay_sec: 1000

View file

@ -0,0 +1,6 @@
---
- name: systemd reload
become: yes
systemd:
daemon_reload: yes

View file

@ -0,0 +1,42 @@
---
- name: Install fuse (to mount repositories)
become: yes
apt:
name: fuse
- name: Install bzip2 (to install restic)
become: yes
apt:
name: bzip2
- name: Install pigz (to compress db dumps)
become: yes
apt:
name: pigz
- name: Download restic
become: yes
get_url:
url: 'https://github.com/restic/restic/releases/download/v{{ restic_version }}/restic_{{ restic_version }}_linux_amd64.bz2'
dest: '/tmp/restic_{{ restic_version }}_linux_amd64.bz2'
- name: Extract restic
become: yes
command: 'bzip2 -d /tmp/restic_{{ restic_version }}_linux_amd64.bz2'
args:
creates: '/tmp/restic_{{ restic_version }}_linux_amd64'
- name: Install restic
become: yes
copy:
remote_src: true
src: '/tmp/restic_{{ restic_version }}_linux_amd64'
dest: "{{ restic_path }}"
mode: 0755
- name: Remove downloaded file
become: yes
file:
path: '/tmp/restic_{{ restic_version }}_linux_amd64'
state: absent

View file

@ -0,0 +1,67 @@
---
- name: Check if restic is installed
stat:
path: '{{ restic_path }}'
register: restic_binary
- include_tasks: install.yml
when: not restic_binary.stat.exists or restic_install
- name: Overwrite SSH config for backup server
become: yes
template:
src: ssh_config.j2
dest: '{{ restic_user_home }}/.ssh/config'
owner: root
group: root
mode: '0600'
when: restic_ssh_enabled
- name: Add SSH private key
become: yes
template:
src: ssh_private_key.j2
dest: '{{ restic_ssh_private_key_path }}'
mode: '0600'
when: restic_ssh_private_key is defined and restic_ssh_enabled
- name: Add restic_env in home folder
become: yes
template:
src: restic_env.j2
dest: '{{ restic_user_home }}/.restic_env'
owner: root
group: root
mode: '0600'
- name: Add systemd service for restic
become: yes
template:
src: restic-backup.service.j2
dest: /etc/systemd/system/restic-backup.service
mode: '0644'
vars:
restic_folders_combined: '{{ restic_default_folders + restic_folders }}'
notify: systemd reload
- name: Add systemd timer for restic
become: yes
template:
src: restic-backup.timer.j2
dest: /etc/systemd/system/restic-backup.timer
mode: '0644'
notify: systemd reload
- name: Enable and start restic timer
become: yes
systemd:
name: restic-backup.timer
enabled: true
state: started
- name: Initialize restic repo if needed
become: yes
command: "{{restic_path}} init"
environment:
RESTIC_REPOSITORY: "sftp:{{ restic_ssh_host }}:{{ restic_repository_name }}"
RESTIC_PASSWORD: "{{restic_password}}"

View file

@ -0,0 +1,40 @@
[Unit]
Description=Restic backup
[Service]
Type=oneshot
User={{ restic_user }}
CPUQuota={{ 25 * ansible_processor_vcpus }}%
{% if restic_ssh_enabled %}
Environment="RESTIC_REPOSITORY=sftp:{{ restic_ssh_host }}:{{ restic_repository_name }}"
{% else %}
Environment="RESTIC_REPOSITORY={{ restic_repository }}"
{% endif -%}
Environment="RESTIC_PASSWORD={{ restic_password}}"
{% if restic_aws_access_key_id is defined and restic_aws_secret_access_key is defined %}
Environment="AWS_ACCESS_KEY_ID={{ restic_aws_access_key_id}}"
Environment="AWS_SECRET_ACCESS_KEY={{ restic_aws_secret_access_key}}"
{% endif %}
{% if restic_check %}
ExecStartPre={{ restic_path }} check
{% endif -%}
{% for folder in restic_folders_combined %}
ExecStart={{ restic_path }} backup --verbose {{ folder.path }} {{ folder.exclude if folder.exclude is defined else '' }}
{% endfor -%}
{% for database in restic_databases %}
ExecStart=/bin/sh -c "{{ database.dump_command }} {{ '| pigz |' if restic_dump_compression_enabled else '|' }} {{ restic_path }} backup --verbose --stdin --stdin-filename {{ database.name }}{{ '.sql.gz' if restic_dump_compression_enabled else '.sql' }}"
{% endfor -%}
{% if restic_forget %}
ExecStartPost={{ restic_path }} forget --keep-within {{ restic_forget_keep_within }}
{% endif -%}
{% if restic_prune %}
ExecStartPost={{ restic_path }} prune
{% endif -%}

View file

@ -0,0 +1,9 @@
[Unit]
Description=Restic backup
[Timer]
OnCalendar={{ restic_systemd_timer_on_calender }}
RandomizedDelaySec={{ restic_systemd_timer_randomized_delay_sec }}
[Install]
WantedBy=timers.target

View file

@ -0,0 +1,11 @@
{% if restic_ssh_enabled %}
export RESTIC_REPOSITORY=sftp:{{ restic_ssh_host }}:{{ restic_repository_name }}
{% else %}
export RESTIC_REPOSITORY="{{ restic_repository }}"
{% endif -%}
export RESTIC_PASSWORD={{ restic_password}}
{% if restic_aws_access_key_id is defined and restic_aws_secret_access_key is defined %}
export AWS_ACCESS_KEY_ID={{ restic_aws_access_key_id}}
export AWS_SECRET_ACCESS_KEY={{ restic_aws_secret_access_key}}
{% endif %}

View file

@ -0,0 +1,5 @@
Host {{ restic_ssh_host }}
User {{ restic_ssh_user }}
HostName {{ restic_ssh_hostname }}
IdentityFile {{ restic_ssh_private_key_path }}
Port {{ restic_ssh_port }}

View file

@ -0,0 +1 @@
{{ restic_ssh_private_key }}