From e31b9b260d999834ee748f6375bd8819574fdeae Mon Sep 17 00:00:00 2001 From: Viyurz Date: Thu, 22 Feb 2024 19:09:43 +0100 Subject: [PATCH] Add borg backup playbook/roles/tasks + Implement for Vaultwarden. --- README.md | 12 +++++++ env.yml | 19 +++++++++- playbooks/backup-services.yml | 31 ++++++++++++++++ playbooks/update-services.yml | 3 ++ roles/borg-compact/tasks/main.yml | 4 +++ roles/borg-init/tasks/main.yml | 35 +++++++++++++++++++ roles/borg-init/templates/borg-passphrase.txt | 1 + roles/include-vars/tasks/main.yml | 1 + roles/vaultwarden/tasks/backup.yml | 29 +++++++++++++++ roles/vaultwarden/tasks/main.yml | 33 +++++------------ roles/vaultwarden/tasks/update.yml | 26 ++++++++++++++ secrets.yml.example | 2 ++ 12 files changed, 170 insertions(+), 26 deletions(-) create mode 100644 playbooks/backup-services.yml create mode 100644 roles/borg-compact/tasks/main.yml create mode 100644 roles/borg-init/tasks/main.yml create mode 100644 roles/borg-init/templates/borg-passphrase.txt create mode 100644 roles/vaultwarden/tasks/backup.yml create mode 100644 roles/vaultwarden/tasks/update.yml diff --git a/README.md b/README.md index 8d8fd44..f0f6bbb 100644 --- a/README.md +++ b/README.md @@ -18,3 +18,15 @@ sudo apt install -y certbot python3-certbot-dns-ovh Copy the existing `secrets.yml.example` to `secrets.yml`, run `ansible-vault encrypt secrets.yml` to encrypt the file with a password, and finally edit the newly encrypted file with `ansible-vault edit secrets.yml`. If you want to change the vault password run `ansible-vault rekey secrets.yml`. + + +## Backups +Run the `backup-services.yml` playbook once to setup the passphrase file. + +After that, you can create a root cronjob to run this playbook without requiring interactivity: + +``` +ANSIBLE_ROLES_PATH=/home/viyurz/vps/roles/ ansible-playbook /home/viyurz/vps/playbooks/backup-services.yml -e include_secrets=false -e selected_projects='' +``` + +Here we leave `selected_projects` empty to backup all projects. diff --git a/env.yml b/env.yml index 58b16b1..3a49bb7 100644 --- a/env.yml +++ b/env.yml @@ -7,7 +7,10 @@ docker_projects_dir: "{{ ansible_env['HOME'] }}/docker-projects" uid_shift: 99999 -cifs_host: "{{ cifs_credentials['username'] }}.your-storagebox.de" +# cifs_credentials is undefined when we run the backup playbook +# as a cronjob, so set empty default value to prevent errors, +# which is fine because we don't use it. +cifs_host: "{{ cifs_credentials['username'] | default('') }}.your-storagebox.de" cifs_mounts: backups: @@ -46,6 +49,20 @@ projects: - vaultwarden +projects_to_backup: + - vaultwarden + + +borg_repodir: "{{ cifs_mounts['backups']['path'] }}/borg" +borg_passphrase_file: /etc/borg-passphrase.txt +borg_prune_options: | + --keep-within=1d + --keep-daily=7 + --keep-weekly=4 + --keep-monthly=12 + --keep-yearly=10 + + # Ports exposed to host ports: coturn_listening: 3478 diff --git a/playbooks/backup-services.yml b/playbooks/backup-services.yml new file mode 100644 index 0000000..591ce44 --- /dev/null +++ b/playbooks/backup-services.yml @@ -0,0 +1,31 @@ +- name: Include variables files & run borg-init role + hosts: localhost + roles: + - include-vars + - borg-init + +- name: Backup project(s) + hosts: localhost + vars: + run_backup: true + run_update: false + vars_prompt: + - name: selected_projects + prompt: "Choose projects to backup (leave empty to backup all. Projects list: {{ hostvars['localhost']['projects_to_backup'] }})" + private: false + unsafe: true + + tasks: + - name: Backup project(s) + include_role: + name: "{{ project }}" + loop: "{{ (selected_projects | split) | default(projects_to_backup, true) }}" + loop_control: + # Do not use default variable name 'item' to prevent collisions with loops in roles. + loop_var: project + when: project in projects_to_backup + +- name: Compact borg repository + hosts: localhost + roles: + - borg-compact diff --git a/playbooks/update-services.yml b/playbooks/update-services.yml index 5defac3..7556b77 100644 --- a/playbooks/update-services.yml +++ b/playbooks/update-services.yml @@ -6,6 +6,9 @@ - name: Update project(s) hosts: localhost + vars: + run_backup: false + run_update: true vars_prompt: - name: selected_projects prompt: "Choose projects to update (Keep empty to update all. Projects list: {{ hostvars['localhost']['projects'] }})" diff --git a/roles/borg-compact/tasks/main.yml b/roles/borg-compact/tasks/main.yml new file mode 100644 index 0000000..3dce159 --- /dev/null +++ b/roles/borg-compact/tasks/main.yml @@ -0,0 +1,4 @@ +- name: Compact borg repository + command: + cmd: "borg compact {{ borg_repodir }}" + become: true diff --git a/roles/borg-init/tasks/main.yml b/roles/borg-init/tasks/main.yml new file mode 100644 index 0000000..6dee7f6 --- /dev/null +++ b/roles/borg-init/tasks/main.yml @@ -0,0 +1,35 @@ +- name: + become: true + block: + - name: Install packages borgbackup & sqlite3 + apt: + name: + - borgbackup + # SQLite required for Vaultwarden + - sqlite3 + + - name: Get borg passphrase file stat + stat: + path: "{{ borg_passphrase_file }}" + register: borg_stat_passphrase_file_result + + - name: "Template borg-passphrase.txt to {{ borg_passphrase_file }}" + template: + src: borg-passphrase.txt + dest: "{{ borg_passphrase_file }}" + owner: root + group: root + mode: '600' + when: not borg_stat_passphrase_file_result.stat.exists or borg_update_passphrase | default(false) | bool + + - name: Get borg repository stat + stat: + path: "{{ borg_repodir }}" + register: borg_stat_repodir_result + + - name: Create borg repository + command: + cmd: "borg init --encryption repokey {{ borg_repodir }}" + environment: + BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}" + when: not borg_stat_repodir_result.stat.exists diff --git a/roles/borg-init/templates/borg-passphrase.txt b/roles/borg-init/templates/borg-passphrase.txt new file mode 100644 index 0000000..6d774f7 --- /dev/null +++ b/roles/borg-init/templates/borg-passphrase.txt @@ -0,0 +1 @@ +{{ borg_passphrase }} diff --git a/roles/include-vars/tasks/main.yml b/roles/include-vars/tasks/main.yml index 26cb685..d8860ef 100644 --- a/roles/include-vars/tasks/main.yml +++ b/roles/include-vars/tasks/main.yml @@ -5,3 +5,4 @@ - name: Include secrets from secrets.yml file include_vars: file: "{{ playbook_dir }}/../secrets.yml" + when: include_secrets | default(true) | bool diff --git a/roles/vaultwarden/tasks/backup.yml b/roles/vaultwarden/tasks/backup.yml new file mode 100644 index 0000000..20e5971 --- /dev/null +++ b/roles/vaultwarden/tasks/backup.yml @@ -0,0 +1,29 @@ +- name: + become: true + block: + - name: Backup SQLite database + command: + cmd: | + sqlite3 + "{{ volumes['vaultwarden_datadir'] }}/db.sqlite3" + ".backup {{ volumes['vaultwarden_datadir'] }}/db-backup.sqlite3" + + - name: Create borg backup + command: + cmd: | + borg create + --compression=lzma + "{{ borg_repodir }}::{{ role_name }}-{now:%Y-%m-%d_%H-%M-%S}" + {{ volumes['vaultwarden_datadir'] }}/db-backup.sqlite3 + environment: + BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}" + + - name: Prune borg repository + command: + cmd: | + borg prune + --glob-archives='{{ role_name }}-*' + {{ borg_prune_options }} + {{ borg_repodir }} + environment: + BORG_PASSCOMMAND: "cat {{ borg_passphrase_file }}" diff --git a/roles/vaultwarden/tasks/main.yml b/roles/vaultwarden/tasks/main.yml index c28683b..ed95f38 100644 --- a/roles/vaultwarden/tasks/main.yml +++ b/roles/vaultwarden/tasks/main.yml @@ -1,26 +1,9 @@ -- name: "Create {{ vaultwarden_project_dir }} project directory" - file: - path: "{{ vaultwarden_project_dir }}" - state: directory +- name: Include backup tasks + include_tasks: + file: backup.yml + when: run_backup | default(false) | bool -- name: Template docker-compose.yaml to project directory - template: - src: docker-compose.yaml - dest: "{{ vaultwarden_project_dir }}/docker-compose.yaml" - owner: "{{ ansible_env['USER'] }}" - group: "{{ ansible_env['USER'] }}" - mode: '640' - -- name: "Create directory {{ volumes['vaultwarden_datadir'] }} with correct permissions" - file: - path: "{{ volumes['vaultwarden_datadir'] }}" - state: directory - owner: "{{ users['vaultwarden'] + uid_shift }}" - group: "{{ users['vaultwarden'] + uid_shift }}" - mode: '770' - become: true - -- name: Pull/Create/Restart project services - community.docker.docker_compose: - project_src: "{{ vaultwarden_project_dir }}" - pull: "{{ docker_pull_images | bool }}" +- name: Include update tasks + include_tasks: + file: update.yml + when: run_update | default(false) | bool diff --git a/roles/vaultwarden/tasks/update.yml b/roles/vaultwarden/tasks/update.yml new file mode 100644 index 0000000..c28683b --- /dev/null +++ b/roles/vaultwarden/tasks/update.yml @@ -0,0 +1,26 @@ +- name: "Create {{ vaultwarden_project_dir }} project directory" + file: + path: "{{ vaultwarden_project_dir }}" + state: directory + +- name: Template docker-compose.yaml to project directory + template: + src: docker-compose.yaml + dest: "{{ vaultwarden_project_dir }}/docker-compose.yaml" + owner: "{{ ansible_env['USER'] }}" + group: "{{ ansible_env['USER'] }}" + mode: '640' + +- name: "Create directory {{ volumes['vaultwarden_datadir'] }} with correct permissions" + file: + path: "{{ volumes['vaultwarden_datadir'] }}" + state: directory + owner: "{{ users['vaultwarden'] + uid_shift }}" + group: "{{ users['vaultwarden'] + uid_shift }}" + mode: '770' + become: true + +- name: Pull/Create/Restart project services + community.docker.docker_compose: + project_src: "{{ vaultwarden_project_dir }}" + pull: "{{ docker_pull_images | bool }}" diff --git a/secrets.yml.example b/secrets.yml.example index 60161e5..31aec9a 100644 --- a/secrets.yml.example +++ b/secrets.yml.example @@ -1,4 +1,6 @@ ansible_become_password: + +borg_passphrase: cifs_credentials: username: