add backup restic role
This commit is contained in:
parent
ee97b7acc1
commit
126e3ffd0d
12 changed files with 349 additions and 5 deletions
|
@ -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')}}"
|
|
@ -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}
|
125
roles/stable/restic/README.md
Normal file
125
roles/stable/restic/README.md
Normal 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).
|
24
roles/stable/restic/defaults/main.yml
Normal file
24
roles/stable/restic/defaults/main.yml
Normal 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
|
6
roles/stable/restic/handlers/main.yml
Normal file
6
roles/stable/restic/handlers/main.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
|
||||
- name: systemd reload
|
||||
become: yes
|
||||
systemd:
|
||||
daemon_reload: yes
|
42
roles/stable/restic/tasks/install.yml
Normal file
42
roles/stable/restic/tasks/install.yml
Normal 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
|
67
roles/stable/restic/tasks/main.yml
Normal file
67
roles/stable/restic/tasks/main.yml
Normal 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}}"
|
40
roles/stable/restic/templates/restic-backup.service.j2
Normal file
40
roles/stable/restic/templates/restic-backup.service.j2
Normal 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 -%}
|
9
roles/stable/restic/templates/restic-backup.timer.j2
Normal file
9
roles/stable/restic/templates/restic-backup.timer.j2
Normal 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
|
11
roles/stable/restic/templates/restic_env.j2
Normal file
11
roles/stable/restic/templates/restic_env.j2
Normal 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 %}
|
5
roles/stable/restic/templates/ssh_config.j2
Normal file
5
roles/stable/restic/templates/ssh_config.j2
Normal 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 }}
|
1
roles/stable/restic/templates/ssh_private_key.j2
Normal file
1
roles/stable/restic/templates/ssh_private_key.j2
Normal file
|
@ -0,0 +1 @@
|
|||
{{ restic_ssh_private_key }}
|
Loading…
Reference in a new issue