add borgmatic role
This commit is contained in:
16
roles/borgmatic/.ansible-lint
Normal file
16
roles/borgmatic/.ansible-lint
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
profile: production
|
||||||
|
strict: true
|
||||||
|
|
||||||
|
# Enable checking of loop variable prefixes in roles
|
||||||
|
loop_var_prefix: "^(__|{role}_)"
|
||||||
|
|
||||||
|
skip_list:
|
||||||
|
- var-naming[no-role-prefix]
|
||||||
|
|
||||||
|
warn_list:
|
||||||
|
- role-name[path]
|
||||||
|
- var-naming[no-role-prefix]
|
||||||
|
|
||||||
|
exclude_paths:
|
||||||
|
- .github/
|
||||||
3
roles/borgmatic/.gitignore
vendored
Normal file
3
roles/borgmatic/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
.lock
|
||||||
8
roles/borgmatic/.yamllint
Normal file
8
roles/borgmatic/.yamllint
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
rules:
|
||||||
|
brackets:
|
||||||
|
forbid: false
|
||||||
|
min-spaces-inside: 0
|
||||||
|
max-spaces-inside: 2
|
||||||
|
min-spaces-inside-empty: -1
|
||||||
|
max-spaces-inside-empty: 2
|
||||||
40
roles/borgmatic/README.md
Normal file
40
roles/borgmatic/README.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
ansible-borgmatic
|
||||||
|
=========
|
||||||
|
|
||||||
|
This is a wrapper around the `borgmatic` role from the `maxhoesel.borgbackup` collection.
|
||||||
|
|
||||||
|
The wrapper solve the most outstading issue with the current implementation of the original role: inability to install latest (or arbitrary) versions of `borgmatic` and `borg`.
|
||||||
|
|
||||||
|
In the case of Borg we are fetching a release from Github.
|
||||||
|
|
||||||
|
In the case of Borgmatic we are installing it via `pipx`, as recommended by their official documentation found here: https://torsion.org/borgmatic/docs/how-to/set-up-backups/.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
- `maxhoesel.borgbackup` collection installed (see `requirements.yml`);
|
||||||
|
|
||||||
|
Role Variables
|
||||||
|
--------------
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
Dependencies
|
||||||
|
------------
|
||||||
|
|
||||||
|
None
|
||||||
|
|
||||||
|
Example Playbook
|
||||||
|
----------------
|
||||||
|
|
||||||
|
See `molecule/default/converge.yml`
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
|
||||||
|
BSD
|
||||||
|
|
||||||
|
Author Information
|
||||||
|
------------------
|
||||||
|
|
||||||
|
corvus-migratorius@proton.me
|
||||||
11
roles/borgmatic/conda.dev.yml
Normal file
11
roles/borgmatic/conda.dev.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
name: ansible-borgmatic
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
dependencies:
|
||||||
|
- python~=3.12.0
|
||||||
|
- pip>=24.2
|
||||||
|
- actionlint
|
||||||
|
- pip:
|
||||||
|
- -r requirements.txt
|
||||||
|
- -r requirements.ci.txt
|
||||||
9
roles/borgmatic/conda.prod.yml
Normal file
9
roles/borgmatic/conda.prod.yml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
name: ansible-borgmatic
|
||||||
|
channels:
|
||||||
|
- conda-forge
|
||||||
|
dependencies:
|
||||||
|
- python~=3.12.0
|
||||||
|
- pip>=24.2
|
||||||
|
- pip:
|
||||||
|
- -r requirements.txt
|
||||||
17
roles/borgmatic/defaults/main.yml
Normal file
17
roles/borgmatic/defaults/main.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
borg_version: "1.4.0"
|
||||||
|
glibc_version: "2.36"
|
||||||
|
pipx_version: "1.7.1"
|
||||||
|
borg_binary_url: "https://github.com/borgbackup/borg/releases/download/{{ borg_version }}/borg-linux-glibc{{ glibc_version | replace('.', '') }}.tgz"
|
||||||
|
borgmatic_pipx_bin_dir: "/opt/borgmatic/bin"
|
||||||
|
borgmatic_schedule_oncalendar: "daily"
|
||||||
|
push_pubkey: true
|
||||||
|
ssh_key_path: "/root/borgmatic/id_ed25519"
|
||||||
|
borg_compression: "lz4"
|
||||||
|
borg_keep_hourly: 0
|
||||||
|
borg_keep_daily: 3
|
||||||
|
borg_keep_weekly: 3
|
||||||
|
borg_keep_monthly: 1
|
||||||
|
borg_keep_yearly: 0
|
||||||
|
borg_uptime_kuma:
|
||||||
|
borg_loki:
|
||||||
1
roles/borgmatic/handlers/main.yml
Normal file
1
roles/borgmatic/handlers/main.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
---
|
||||||
17
roles/borgmatic/meta/main.yml
Normal file
17
roles/borgmatic/meta/main.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
galaxy_info:
|
||||||
|
role_name: "borgmatic"
|
||||||
|
namespace: genlab
|
||||||
|
author: "Alexander Gorelyshev"
|
||||||
|
company: "Genlab, LLC"
|
||||||
|
description: ""
|
||||||
|
license: "MIT"
|
||||||
|
min_ansible_version: "2.1"
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
- name: "Ubuntu"
|
||||||
|
versions: [ "focal", "jammy", "noble" ]
|
||||||
|
|
||||||
|
galaxy_tags: [ ]
|
||||||
|
|
||||||
|
dependencies: []
|
||||||
48
roles/borgmatic/molecule/default/converge.yml
Normal file
48
roles/borgmatic/molecule/default/converge.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
- name: Converge
|
||||||
|
hosts: all
|
||||||
|
vars:
|
||||||
|
repo_path: "/home/borg/test-repo"
|
||||||
|
|
||||||
|
pre_tasks:
|
||||||
|
- name: "Create a user for borg"
|
||||||
|
ansible.builtin.user:
|
||||||
|
name: borg
|
||||||
|
shell: /bin/bash
|
||||||
|
create_home: true
|
||||||
|
|
||||||
|
- name: "Generate test data file"
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: "/tmp/data"
|
||||||
|
content: "This is a test file!"
|
||||||
|
owner: "{{ ansible_user_id }}"
|
||||||
|
group: "{{ ansible_user_id }}"
|
||||||
|
mode: "0644"
|
||||||
|
|
||||||
|
- name: "Ensure the repo path exists"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ repo_path }}"
|
||||||
|
state: directory
|
||||||
|
owner: "borg"
|
||||||
|
mode: "0700"
|
||||||
|
|
||||||
|
- name: "Install openssh-server"
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name: openssh-server
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
|
||||||
|
- name: "Start an SSH openssh-server"
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: ssh
|
||||||
|
state: started
|
||||||
|
|
||||||
|
roles:
|
||||||
|
- role: genlab.borgmatic
|
||||||
|
borg_source_directories:
|
||||||
|
- "/tmp/data"
|
||||||
|
borg_repo_path: "ssh://borg@localhost/./test-repo"
|
||||||
|
borg_repo_label: "test-repo"
|
||||||
|
borg_encryption_passphrase: "secret"
|
||||||
|
repo_server_inventory_hostname: ubuntu # in production this should be an Ansible inventory hostname
|
||||||
|
repo_server_user: borg
|
||||||
31
roles/borgmatic/molecule/default/molecule.yml
Normal file
31
roles/borgmatic/molecule/default/molecule.yml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
dependency:
|
||||||
|
name: galaxy
|
||||||
|
|
||||||
|
driver:
|
||||||
|
name: docker
|
||||||
|
|
||||||
|
platforms:
|
||||||
|
- name: ubuntu
|
||||||
|
image: geerlingguy/docker-${MOLECULE_DISTRO:-ubuntu2404}-ansible:latest
|
||||||
|
pre_build_image: true
|
||||||
|
command: ${MOLECULE_DOCKER_COMMAND:-""}
|
||||||
|
volumes:
|
||||||
|
- /sys/fs/cgroup:/sys/fs/cgroup:rw
|
||||||
|
cgroupns_mode: host
|
||||||
|
privileged: true
|
||||||
|
|
||||||
|
provisioner:
|
||||||
|
name: ansible
|
||||||
|
|
||||||
|
verifier:
|
||||||
|
name: ansible
|
||||||
|
|
||||||
|
scenario:
|
||||||
|
name: default
|
||||||
|
test_sequence:
|
||||||
|
- destroy
|
||||||
|
- create
|
||||||
|
- converge
|
||||||
|
# - idempotence
|
||||||
|
- verify
|
||||||
48
roles/borgmatic/molecule/default/verify.yml
Normal file
48
roles/borgmatic/molecule/default/verify.yml
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
---
|
||||||
|
- name: Verify
|
||||||
|
hosts: all
|
||||||
|
gather_facts: false
|
||||||
|
any_errors_fatal: true
|
||||||
|
|
||||||
|
vars:
|
||||||
|
repo_path: "/home/borg/test-repo"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: "Include default vars"
|
||||||
|
ansible.builtin.include_vars:
|
||||||
|
dir: "{{ lookup('env', 'MOLECULE_PROJECT_DIRECTORY') }}/defaults/"
|
||||||
|
extensions: [ 'yml' ]
|
||||||
|
|
||||||
|
- name: "Check if Borg is installed"
|
||||||
|
changed_when: false
|
||||||
|
register: borg_installed_version
|
||||||
|
ansible.builtin.command: "/usr/bin/borg --version"
|
||||||
|
|
||||||
|
- name: "Check Borg version"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: borg_installed_version.stdout.find(borg_version)
|
||||||
|
success_msg: "borg version {{ borg_version }} is installed and working"
|
||||||
|
fail_msg: "borg version {{ borg_version }} is not installed or not working correctly"
|
||||||
|
|
||||||
|
- name: "Check if Borgmatic is installed"
|
||||||
|
changed_when: false
|
||||||
|
register: borgmatic_installed_version
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: "/usr/bin/borgmatic --version"
|
||||||
|
|
||||||
|
- name: "Check that the test repo was created"
|
||||||
|
register: test_repo_readme
|
||||||
|
failed_when: test_repo_readme.stat.exists is false
|
||||||
|
ansible.builtin.stat:
|
||||||
|
path: "{{ repo_path }}"
|
||||||
|
|
||||||
|
- name: "Check that the systemd timer for Borgmatic is up and running"
|
||||||
|
register: timer
|
||||||
|
ansible.builtin.systemd:
|
||||||
|
name: borgmatic.timer
|
||||||
|
|
||||||
|
- name: "Assert that the timer is running"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: timer.status.ActiveState == "active"
|
||||||
|
success_msg: "Timer is running"
|
||||||
|
fail_msg: "Unexpected timer state: '{{ timer.status.ActiveState }}'"
|
||||||
6
roles/borgmatic/renovate.json
Normal file
6
roles/borgmatic/renovate.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
6
roles/borgmatic/requirements.ci.txt
Normal file
6
roles/borgmatic/requirements.ci.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ansible-lint
|
||||||
|
molecule==24.12.0
|
||||||
|
molecule-plugins[docker]
|
||||||
|
docker~=7.1.0
|
||||||
|
requests==2.31.0 # pinned to the latest version not breaking Docker SDK
|
||||||
|
yamllint
|
||||||
1
roles/borgmatic/requirements.txt
Normal file
1
roles/borgmatic/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ansible~=11.3.0
|
||||||
6
roles/borgmatic/requirements.yml
Normal file
6
roles/borgmatic/requirements.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# requirements file
|
||||||
|
---
|
||||||
|
collections:
|
||||||
|
- name: https://github.com/maxhoesel/ansible-collection-borgbackup
|
||||||
|
type: git
|
||||||
|
version: "v2.0.1"
|
||||||
34
roles/borgmatic/tasks/config.yml
Normal file
34
roles/borgmatic/tasks/config.yml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
- name: "Compose basic configuration for Borgmatic"
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
borgmatic_composite_config:
|
||||||
|
source_directories: "{{ borg_source_directories }}"
|
||||||
|
repositories:
|
||||||
|
- path: "{{ borg_repo_path }}"
|
||||||
|
label: "{{ borg_repo_label }}"
|
||||||
|
encryption_passphrase: "{{ borg_encryption_passphrase }}"
|
||||||
|
compression: "{{ borg_compression }}"
|
||||||
|
# CLI output configuration
|
||||||
|
list_details: true
|
||||||
|
statistics: true
|
||||||
|
exclude_caches: true
|
||||||
|
# logging verbosity:
|
||||||
|
verbosity: 1
|
||||||
|
syslog_verbosity: 1
|
||||||
|
monitoring_verbosity: 1
|
||||||
|
# backup depth
|
||||||
|
keep_hourly: "{{ borg_keep_hourly }}"
|
||||||
|
keep_daily: "{{ borg_keep_daily }}"
|
||||||
|
keep_weekly: "{{ borg_keep_weekly }}"
|
||||||
|
keep_monthly: "{{ borg_keep_monthly }}"
|
||||||
|
keep_yearly: "{{ borg_keep_yearly }}"
|
||||||
|
|
||||||
|
- name: "Add Uptime Kuma configuration"
|
||||||
|
when: borg_uptime_kuma
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
borgmatic_composite_config: "{{ borgmatic_composite_config | combine({'uptime_kuma': borg_uptime_kuma}) }}"
|
||||||
|
|
||||||
|
- name: "Add Loki configuration"
|
||||||
|
when: borg_loki
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
borgmatic_composite_config: "{{ borgmatic_composite_config | combine({'loki': borg_loki}) }}"
|
||||||
25
roles/borgmatic/tasks/handle-ssh-keys.yml
Normal file
25
roles/borgmatic/tasks/handle-ssh-keys.yml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
- name: "Ensure the path for SSH keys exists"
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ ssh_key_path | dirname }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0700"
|
||||||
|
|
||||||
|
- name: "Generate an ed25519 SSH key pair with 100 KDF rounds"
|
||||||
|
register: ssh_key_pair
|
||||||
|
community.crypto.openssh_keypair:
|
||||||
|
type: ed25519
|
||||||
|
path: "{{ ssh_key_path }}"
|
||||||
|
comment: "Generated by Ansible for Borgmatic"
|
||||||
|
force: false
|
||||||
|
mode: '0600'
|
||||||
|
|
||||||
|
- name: "Push the SSH key pair to the Borg repo host"
|
||||||
|
when: push_pubkey
|
||||||
|
delegate_to: "{{ repo_server_inventory_hostname }}"
|
||||||
|
ansible.posix.authorized_key:
|
||||||
|
user: "{{ repo_server_user }}"
|
||||||
|
key: "{{ ssh_key_pair.public_key }}"
|
||||||
|
state: present
|
||||||
66
roles/borgmatic/tasks/install.yml
Normal file
66
roles/borgmatic/tasks/install.yml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
---
|
||||||
|
- name: "Ensure that system dependencies are installed"
|
||||||
|
ansible.builtin.apt:
|
||||||
|
name:
|
||||||
|
- openssh-client
|
||||||
|
- python3-pip
|
||||||
|
- python3-venv
|
||||||
|
state: present
|
||||||
|
update_cache: true
|
||||||
|
cache_valid_time: 3600
|
||||||
|
|
||||||
|
- name: "Install pipx"
|
||||||
|
retries: 3
|
||||||
|
delay: 1
|
||||||
|
ansible.builtin.pip:
|
||||||
|
name: "pipx=={{ pipx_version }}"
|
||||||
|
executable: pip3
|
||||||
|
break_system_packages: true
|
||||||
|
|
||||||
|
- name: "Ensure pipx binary is available in PATH"
|
||||||
|
changed_when: false
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: pipx ensurepath
|
||||||
|
|
||||||
|
- name: "Install borgmatic via pipx"
|
||||||
|
retries: 3
|
||||||
|
delay: 1
|
||||||
|
environment:
|
||||||
|
PIPX_BIN_DIR: "{{ borgmatic_pipx_bin_dir }}"
|
||||||
|
community.general.pipx:
|
||||||
|
name: borgmatic
|
||||||
|
state: present
|
||||||
|
install_deps: true
|
||||||
|
|
||||||
|
- name: "Install Borg if the correct version is not available"
|
||||||
|
block:
|
||||||
|
# we are looking for Borg installed in a directory that Max Hoesel's role exects to find it
|
||||||
|
- name: "Get the currently installed version of Borg"
|
||||||
|
changed_when: false
|
||||||
|
register: borg_version_installed
|
||||||
|
ansible.builtin.command:
|
||||||
|
cmd: /usr/bin/borg --version
|
||||||
|
|
||||||
|
- name: "Check that the correct version of Borg is installed"
|
||||||
|
ansible.builtin.assert:
|
||||||
|
that: borg_version_installed.stdout.find(borg_version)
|
||||||
|
fail_msg: "The expected Borg version was not found: {{ borg_version_installed }}"
|
||||||
|
success_msg: "Found the expected Borg version ({{ borg_version }})"
|
||||||
|
|
||||||
|
rescue:
|
||||||
|
- name: "Download Borg from a custom URL: '{{ borg_binary_url }}'"
|
||||||
|
retries: 3
|
||||||
|
delay: 1
|
||||||
|
ansible.builtin.unarchive:
|
||||||
|
src: "{{ borg_binary_url }}"
|
||||||
|
dest: "/opt/"
|
||||||
|
remote_src: true
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
|
||||||
|
- name: "Create a symbolic link for Borg"
|
||||||
|
ansible.builtin.file:
|
||||||
|
state: link
|
||||||
|
src: "/opt/borg-dir/borg.exe"
|
||||||
|
dest: "/usr/bin/borg"
|
||||||
1
roles/borgmatic/tasks/integrations.yml
Normal file
1
roles/borgmatic/tasks/integrations.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
---
|
||||||
15
roles/borgmatic/tasks/main.yml
Normal file
15
roles/borgmatic/tasks/main.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
---
|
||||||
|
- name: "Include tool installation tasks"
|
||||||
|
ansible.builtin.include_tasks: "install.yml"
|
||||||
|
|
||||||
|
- name: "Include SSH key handling tasks"
|
||||||
|
ansible.builtin.include_tasks: "handle-ssh-keys.yml"
|
||||||
|
|
||||||
|
- name: "Include configuration tasks"
|
||||||
|
ansible.builtin.include_tasks: "config.yml"
|
||||||
|
|
||||||
|
- name: "Include tasks for third-party integrations"
|
||||||
|
ansible.builtin.include_tasks: "integrations.yml"
|
||||||
|
|
||||||
|
- name: "Include tasks for running borgmatic"
|
||||||
|
ansible.builtin.include_tasks: "run.yml"
|
||||||
22
roles/borgmatic/tasks/run.yml
Normal file
22
roles/borgmatic/tasks/run.yml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
# A workaround for maxhoesel.borgbackup.borgmatic that does not support custom paths
|
||||||
|
- name: "Create symbolic links for Borgmatic executables"
|
||||||
|
loop:
|
||||||
|
- borgmatic
|
||||||
|
- generate-borgmatic-config
|
||||||
|
- validate-borgmatic-config
|
||||||
|
ansible.builtin.file:
|
||||||
|
state: link
|
||||||
|
src: "{{ borgmatic_pipx_bin_dir }}/{{ item }}"
|
||||||
|
dest: /usr/bin/{{ item }}
|
||||||
|
|
||||||
|
- name: "Configure and run Borgmatic"
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: maxhoesel.borgbackup.borgmatic
|
||||||
|
vars:
|
||||||
|
borgmatic_install: false # we handle installation separately to get the recent version
|
||||||
|
# borgmatic_ssh_key_gen_options: "-t ed25519 -a 100"
|
||||||
|
borgmatic_ssh_key_path: "{{ ssh_key_path }}"
|
||||||
|
borgmatic_schedule_on: "{{ borgmatic_schedule_oncalendar }}"
|
||||||
|
borgmatic_schedule_max_random_delay: 600
|
||||||
|
borgmatic_config: "{{ borgmatic_composite_config }}"
|
||||||
1
roles/borgmatic/vars/main.yml
Normal file
1
roles/borgmatic/vars/main.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
---
|
||||||
Reference in New Issue
Block a user