From 0f4dea90071edafd0a0b580c156997124e1de557 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Wed, 4 Sep 2024 08:31:10 -0500 Subject: [PATCH] restic: Add role+playbook for Restic backups The `restic.yml` playbook applies the _restic_ role to hosts in the _restic_ group. The _restic_ role installs `restic` and creates a systemd timer and service unit to run `restic backup` every day. Restic doesn't really have a configuration file; all its settings are controlled either by environment variables or command-line options. Some options, such as the list of files to include in or exclude from backups, take paths to files containing the values. We can make use of these to provide some configurability via Ansible variables. The `restic_env` variable is a map of environment variables and values to set for `restic`. The `restic_include` and `restic_exclude` variables are lists of paths/patterns to include and exclude, respectively. Finally, the `restic_password` variable contains the password to decrypt the repository contents. The password is written to a file and exposed to the _restic-backup.service_ unit using [systemd credentials][0]. When using S3 or a compatible service for respository storage, Restic of course needs authentication credentials. These can be set using the `restic_aws_credentials` variable. If this variable is defined, it should be a map containing the`aws_access_key_id` and `aws_secret_access_key` keys, which will be written to an AWS shared credentials file. This file is then exposed to the _restic-backup.service_ unit using [systemd credentials][0]. [0]: https://systemd.io/CREDENTIALS/ --- group_vars/restic.yml | 22 +++++ hosts | 2 + restic.yml | 5 ++ roles/restic/defaults/main.yml | 4 + roles/restic/files/restic-backup.service | 12 +++ roles/restic/files/restic-backup.timer | 11 +++ roles/restic/handlers/main.yml | 8 ++ roles/restic/tasks/main.yml | 106 +++++++++++++++++++++++ roles/restic/templates/credentials.j2 | 6 ++ roles/restic/templates/exclude.j2 | 3 + roles/restic/templates/include.j2 | 3 + roles/restic/templates/restic.env.j2 | 3 + roles/restic/vars/main.yml | 2 + 13 files changed, 187 insertions(+) create mode 100644 group_vars/restic.yml create mode 100644 restic.yml create mode 100644 roles/restic/defaults/main.yml create mode 100644 roles/restic/files/restic-backup.service create mode 100644 roles/restic/files/restic-backup.timer create mode 100644 roles/restic/handlers/main.yml create mode 100644 roles/restic/tasks/main.yml create mode 100644 roles/restic/templates/credentials.j2 create mode 100644 roles/restic/templates/exclude.j2 create mode 100644 roles/restic/templates/include.j2 create mode 100644 roles/restic/templates/restic.env.j2 create mode 100644 roles/restic/vars/main.yml diff --git a/group_vars/restic.yml b/group_vars/restic.yml new file mode 100644 index 0000000..fa95283 --- /dev/null +++ b/group_vars/restic.yml @@ -0,0 +1,22 @@ +restic_env: + RESTIC_REPOSITORY: s3:s3.backups.pyrocufflink.blue/restic +restic_password: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 62663336623030623165636439326337623638303633356564376136323338363532363837326138 + 3037613363383238346661336437663464363735323664340a393764343230353938383730633934 + 32616434303834636236663835663233356431306665613331613433353531373266323430663463 + 3935343335663837390a663961623764316236653234393333663533383638666230356362303432 + 65626335306165326262393763306533393430356134623666646339353663326137353236383464 + 32376366363239666636323734343864313231363765303537313464646464623666373939306131 + 33343265383636313938306630386164656163663463653731666364633261333230303565326231 + 39306636373331633764 +restic_aws_credentials: + aws_access_key_id: qVjUcNjwrISvDVXbkFMZ + aws_secret_access_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 62303466353962396236626337323839303836386465636231643463363662343033353564616336 + 3138316166363333663531616561346134376666373830610a633535333134323733653834616332 + 33623230343933323532633831326633343931633438313037633634646563653730386131373831 + 6639313765373262650a323138346161376138396536623632636663356532616136336166303861 + 37643566346231333531623131633763323433313834643566373061366164663430623562623331 + 3462396662383536386535373263666631393835343534393561 diff --git a/hosts b/hosts index a8b5bf4..57ea5f5 100644 --- a/hosts +++ b/hosts @@ -163,6 +163,8 @@ samba-dc [repohost] file0.pyrocufflink.blue +[restic] + [rw-root] [samba-dc] diff --git a/restic.yml b/restic.yml new file mode 100644 index 0000000..c014776 --- /dev/null +++ b/restic.yml @@ -0,0 +1,5 @@ +- hosts: restic + roles: + - role: restic + tags: + - restic diff --git a/roles/restic/defaults/main.yml b/roles/restic/defaults/main.yml new file mode 100644 index 0000000..4460c13 --- /dev/null +++ b/roles/restic/defaults/main.yml @@ -0,0 +1,4 @@ +restic_env: {} +restic_include: +- /home +restic_exclude: [] diff --git a/roles/restic/files/restic-backup.service b/roles/restic/files/restic-backup.service new file mode 100644 index 0000000..65ab554 --- /dev/null +++ b/roles/restic/files/restic-backup.service @@ -0,0 +1,12 @@ +[Unit] +Description=Back up filesystem with restic + +[Service] +Type=oneshot +LoadCredential=restic.aws.credentials +LoadCredential=restic.password +Environment=AWS_SHARED_CREDENTIALS_FILE=%d/restic.aws.credentials +Environment=RESTIC_PASSWORD_FILE=%d/restic.password +Environment=XDG_CACHE_HOME=%C +EnvironmentFile=-%E/restic/environment +ExecStart=/usr/bin/restic backup --files-from %E/restic/include --exclude-file %E/restic/exclude --exclude-if-present .nobackup diff --git a/roles/restic/files/restic-backup.timer b/roles/restic/files/restic-backup.timer new file mode 100644 index 0000000..07911be --- /dev/null +++ b/roles/restic/files/restic-backup.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Daily directory back up with restic + +[Timer] +OnCalendar=daily +RandomizedDelaySec=12h +FixedRandomDelay=true +Persistent=true + +[Install] +WantedBy=default.target diff --git a/roles/restic/handlers/main.yml b/roles/restic/handlers/main.yml new file mode 100644 index 0000000..6d9ce53 --- /dev/null +++ b/roles/restic/handlers/main.yml @@ -0,0 +1,8 @@ +- name: reload systemd + systemd: + daemon_reload: true + +- name: restart restic backup timer + systemd: + name: restic-backup.timer + state: restarted diff --git a/roles/restic/tasks/main.yml b/roles/restic/tasks/main.yml new file mode 100644 index 0000000..f17e636 --- /dev/null +++ b/roles/restic/tasks/main.yml @@ -0,0 +1,106 @@ +- name: ensure restic is installed + package: + name: restic + state: present + tags: + - install + +- name: ensure restic configuration directory exists + file: + path: /etc/restic + owner: root + group: root + mode: u=rwx,go=rx + state: directory + tags: + - config + +- name: ensure restic environment is configured + template: + src: restic.env.j2 + dest: /etc/restic/environment + owner: root + group: root + mode: u=rw,go=r + tags: + - config + - restic-environment +- name: ensure restic file list is populated + template: + src: include.j2 + dest: /etc/restic/include + owner: root + group: root + mode: u=rw,go=r + tags: + - config + - restic-include +- name: ensure restic exclude list is populated + template: + src: exclude.j2 + dest: /etc/restic/exclude + owner: root + group: root + mode: u=rw,go=r + tags: + - config + - restic-exclude + +- name: ensure restic password is set + copy: + content: >- + {{ restic_password }} + dest: /etc/credstore/restic.password + owner: root + group: root + mode: a= + diff: false + tags: + - config + - credentials +- name: ensure restic aws credentials are set + template: + src: credentials.j2 + dest: /etc/credstore/restic.aws.credentials + owner: root + group: root + mode: a= + diff: false + tags: + - config + - credentials + +- name: ensure restic-backup systemd service unit is installed + copy: + src: restic-backup.service + dest: /etc/systemd/system/restic-backup.service + owner: root + group: root + mode: u=rw,go=r + tags: + - systemd + notify: + - reload systemd + - restart restic backup timer +- name: ensure restic-backup systemd timer unit is installed + copy: + src: restic-backup.timer + dest: /etc/systemd/system/restic-backup.timer + owner: root + group: root + mode: u=rw,go=r + tags: + - systemd + +- name: ensure restic-backup timer is enabled + systemd: + name: restic-backup.timer + enabled: true + tags: + - service +- name: ensure restic-backup timer is running + systemd: + name: restic-backup.timer + state: started + tags: + - service diff --git a/roles/restic/templates/credentials.j2 b/roles/restic/templates/credentials.j2 new file mode 100644 index 0000000..219144c --- /dev/null +++ b/roles/restic/templates/credentials.j2 @@ -0,0 +1,6 @@ +[default] +{% if restic_aws_credentials is defined %} +{% for key, value in restic_aws_credentials.items() %} +{{ key }} = {{ value }} +{% endfor %} +{% endif %} diff --git a/roles/restic/templates/exclude.j2 b/roles/restic/templates/exclude.j2 new file mode 100644 index 0000000..cbf633d --- /dev/null +++ b/roles/restic/templates/exclude.j2 @@ -0,0 +1,3 @@ +{% for path in restic_exclude %} +{{ path }} +{% endfor %} diff --git a/roles/restic/templates/include.j2 b/roles/restic/templates/include.j2 new file mode 100644 index 0000000..88c763f --- /dev/null +++ b/roles/restic/templates/include.j2 @@ -0,0 +1,3 @@ +{% for path in restic_include %} +{{ path }} +{% endfor %} diff --git a/roles/restic/templates/restic.env.j2 b/roles/restic/templates/restic.env.j2 new file mode 100644 index 0000000..e5064e5 --- /dev/null +++ b/roles/restic/templates/restic.env.j2 @@ -0,0 +1,3 @@ +{% for key, value in _restic_env.items() %} +{{ key }}={{ value }} +{% endfor %} diff --git a/roles/restic/vars/main.yml b/roles/restic/vars/main.yml new file mode 100644 index 0000000..40ae982 --- /dev/null +++ b/roles/restic/vars/main.yml @@ -0,0 +1,2 @@ +restic_default_env: {} +_restic_env: '{{ restic_default_env|combine(restic_env) }}'