diff --git a/.ansible-lint b/.ansible-lint index 0801758..d8a99cc 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -6,3 +6,4 @@ exclude_paths: - "**/*docker-compose.yml" - .github/ - .pre-commit-config.yaml + - roles/borgbase.ansible_role_borgbackup/** diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b7ce25c..3688a9e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,6 +7,7 @@ ci: - ggshield - caddy-fmt +exclude: ^roles/borgbase.ansible_role_borgbackup/.*$ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 diff --git a/playbooks/apps/files/bin/borgmatic_on_after_backup.sh b/playbooks/apps/files/bin/borgmatic_on_after_backup.sh new file mode 100755 index 0000000..56a23be --- /dev/null +++ b/playbooks/apps/files/bin/borgmatic_on_after_backup.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +SOURCE_DIRECTORY="/mnt/data/nextcloud-aio/backups/borg" + +rm "$SOURCE_DIRECTORY/aio-lockfile" + +if docker ps --format "{{.Names}}" | grep "^nextcloud-aio-nextcloud$"; then + docker exec -en nextcloud-aio-nextcloud bash /notify.sh "Remote Borg backup successful!" "Synchronised the backup repository successfully!" +else + echo "Synchronised the backup repository successfully." +fi diff --git a/playbooks/apps/files/bin/borgmatic_on_before_backup.sh b/playbooks/apps/files/bin/borgmatic_on_before_backup.sh new file mode 100755 index 0000000..3a767b1 --- /dev/null +++ b/playbooks/apps/files/bin/borgmatic_on_before_backup.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +SOURCE_DIRECTORY="/mnt/data/nextcloud-aio/backups/borg" + +if [ "$EUID" -ne 0 ]; then + echo "run as root" + output 1 +fi + +if ! [ -d "$SOURCE_DIRECTORY" ]; then + echo "The root directory does not exist." + exit 1 +fi + +if [ -z "$(ls -A "$SOURCE_DIRECTORY/")" ]; then + echo "The source directory is empty, which is not allowed." + exit 1 +fi + +if [ -f "$SOURCE_DIRECTORY/lock.roster" ]; then + echo "Unable to execute the script as the backup archive is currently modified. Please try again later." + exit 1 +fi + +if [ -f "$SOURCE_DIRECTORY/aio-lockfile" ]; then + echo "Cannot continue because aio-lockfile already exists." + exit 1 +fi + +touch "$SOURCE_DIRECTORY/aio-lockfile" + +echo "$(date) - Starting backup." diff --git a/playbooks/apps/files/bin/borgmatic_on_error_backup.sh b/playbooks/apps/files/bin/borgmatic_on_error_backup.sh new file mode 100755 index 0000000..01f2b08 --- /dev/null +++ b/playbooks/apps/files/bin/borgmatic_on_error_backup.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +if docker ps --format "{{.Names}}" | grep "^nextcloud-aio-nextcloud$"; then + docker exec -en nextcloud-aio-nextcloud bash /notify.sh "Remote Borg backup FAILED!" "Repository: $1 failed to synchronise with error: $2 when executing command: $3>." +else + echo "Repository: $1 failed to synchronise with error: $2 when executing command: $3" +fi diff --git a/playbooks/system/all.yaml b/playbooks/system/all.yaml index 7071666..153c583 100644 --- a/playbooks/system/all.yaml +++ b/playbooks/system/all.yaml @@ -1,3 +1,5 @@ --- - import_playbook: update.yaml # noqa: name[play] +- import_playbook: borg.yaml # noqa: name[play] - import_playbook: cron.yaml # noqa: name[play] +- import_playbook: cron-root.yaml # noqa: name[play] diff --git a/playbooks/system/borg.yaml b/playbooks/system/borg.yaml new file mode 100644 index 0000000..c875fee --- /dev/null +++ b/playbooks/system/borg.yaml @@ -0,0 +1,43 @@ +--- +- name: Borgmatic + hosts: servers + pre_tasks: + - name: Install system deps + become: true + ansible.builtin.apt: + pkg: + - liblz4-dev + - libzstd-dev + - libxxhash-dev + roles: + - role: borgbase.ansible_role_borgbackup + become: true + borg_encryption_passphrase: "{{ borg_encryption_key }}" + borg_repository: + - ssh://ch78x32l@ch78x32l.repo.borgbase.com/./repo + borg_source_directories: + - /mnt/data/nextcloud-aio/backups/borg + borgmatic_timer_hour: 5 + borgmatic_timer_minute: 30 + borgmatic_hooks: + before_backup: + - /mnt/data/bin/borgmatic_on_before_backup.sh + after_backup: + - /mnt/data/bin/borgmatic_on_after_backup.sh + on_error: + - /mnt/data/bin/borgmatic_on_error_backup.sh {repository} {error} {output} + borg_retention_policy: + keep_daily: 7 + keep_weekly: 4 + keep_monthly: 6 + + vars: + borg_encryption_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 65386462653063386665316566303864353630386161393931343962613438626163326439626364 + 3262303034306539396163396461386366303262653265630a663335323737646465643538613666 + 34396131373834616265353336346261333362333735643963323762356234623234656166356239 + 6362333266326666300a393435376135623136346537393532336337363264386361643330323038 + 65373362393063333464316235343538316661636138356462336164613262616265646263396136 + 39376534656239643539613663626261616637393737636337653936663837656636613963346164 + 353537353131636139303061613137353065 diff --git a/roles/borgbase.ansible_role_borgbackup/.ansible-lint b/roles/borgbase.ansible_role_borgbackup/.ansible-lint new file mode 100644 index 0000000..2aea330 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/.ansible-lint @@ -0,0 +1,3 @@ +--- +skip_list: + - fqcn-builtins diff --git a/roles/borgbase.ansible_role_borgbackup/.github/workflows/main.yml b/roles/borgbase.ansible_role_borgbackup/.github/workflows/main.yml new file mode 100644 index 0000000..ef664fc --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/.github/workflows/main.yml @@ -0,0 +1,21 @@ +name: Test + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + - name: Install Molecule + run: | + pip install -U pip setuptools wheel + pip install -r requirements-dev.txt + # - name: Debugging with tmate + # uses: mxschmitt/action-tmate@v3.5 + - name: Test using Molecule + run: molecule test diff --git a/roles/borgbase.ansible_role_borgbackup/.gitignore b/roles/borgbase.ansible_role_borgbackup/.gitignore new file mode 100644 index 0000000..16d3c4d --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/.gitignore @@ -0,0 +1 @@ +.cache diff --git a/roles/borgbase.ansible_role_borgbackup/.yamllint b/roles/borgbase.ansible_role_borgbackup/.yamllint new file mode 100644 index 0000000..09db584 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/.yamllint @@ -0,0 +1,36 @@ +--- +# Based on ansible-lint config +ignore: | + .direnv + +extends: default + +rules: + braces: + max-spaces-inside: 1 + level: error + brackets: + max-spaces-inside: 1 + level: error + colons: + max-spaces-after: -1 + level: error + commas: + max-spaces-after: -1 + level: error + comments: disable + comments-indentation: disable + document-start: disable + empty-lines: + max: 3 + level: error + hyphens: + level: error + indentation: disable + key-duplicates: enable + line-length: disable + new-line-at-end-of-file: disable + new-lines: + type: unix + trailing-spaces: disable + truthy: disable diff --git a/roles/borgbase.ansible_role_borgbackup/EXAMPLES.md b/roles/borgbase.ansible_role_borgbackup/EXAMPLES.md new file mode 100644 index 0000000..9e63152 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/EXAMPLES.md @@ -0,0 +1,129 @@ +# Additional Examples + +## Custom SSH key for backups only + +``` +- hosts: webservers + roles: + - role: borgbase.ansible_role_borgbackup + borg_encryption_passphrase: CHANGEME + borg_repository: ssh://m5vz9gp4@m5vz9gp4.repo.borgbase.com/./repo + borgmatic_timer: systemd + borg_ssh_key_name: id_backup + borg_ssh_command: "ssh -i {{ borg_ssh_key_file_path }} -o StrictHostKeyChecking=accept-new" + borg_user: backupuser + borg_group: backupuser +``` + +## Use service user and copy SSH key to target server + +Installs and configures the Borgmatic client and also initializes the repo on the +remote backup server. (not tested) + +``` +- name: Configure backup + hosts: test.lab + pre_tasks: + - name: Get home of {{ borg_user }} + ansible.builtin.user: + name: "{{ borg_user }}" + state: present + register: user_info + changed_when: false + check_mode: true # Important, otherwise user will be created + + - name: Save the user_info, we need them for the home_dir + ansible.builtin.set_fact: + backup_user_info: "{{ user_info }}" + vars_files: [] + vars: + borg_encryption_passphrase: "CHANGEME" + borg_repository: "USER@TARGET_SERVER:/PATH/TO/BACKUP" + borg_user: "srv_backup" + borg_group: "srv_backup" + borg_ssh_key_name: id_backup + borg_ssh_command: "ssh -i {{ borg_ssh_key_file_path }} -o StrictHostKeyChecking=accept-new" + borgmatic_timer: systemd + borg_source_directories: + - /srv/www + - /var/lib/automysqlbackup + borg_exclude_patterns: + - /srv/www/old-sites + borg_retention_policy: + keep_hourly: 3 + keep_daily: 7 + keep_weekly: 4 + keep_monthly: 6 + borgmatic_hooks: + before_backup: + - echo "`date` - Starting backup." + tasks: + - name: Configure Borg Backup and Backupmatic + tags: + - always + - install_backup + ansible.builtin.include_role: + name: ansible_role_borgbackup + apply: + tags: + - always + + + - name: Copy SSH-Key to Target {{ borg_repository }} and Init Repo + tags: + - never + - backup_init_repo + block: + - name: Read ssh key + ansible.builtin.slurp: + src: "{{ borg_ssh_key_file_path }}.pub" + register: backup_local_ssh_key + + - name: Set authorized key taken from file + ansible.posix.authorized_key: + # example: + # borg_repository: m5vz9gp4@m5vz9gp4.repo.borgbase.com:repo + # have three parts: "username"@"FQDN":"path/to/store/backup", specific: + # a) user: m5vz9gp4 + # b) fqdn: m5vz9gp4.repo.borgbase.co + # c) dir: repo + user: "{{ borg_repository | regex_search('(.*)@', '\\1') | first }}" # part a) + state: present + key: "{{ backup_local_ssh_key['content'] | b64decode }}" + delegate_to: "{{ borg_repository | regex_search('@(.*):', '\\1') | first }}" # part b) + + - name: Init repository + ansible.builtin.command: + cmd: "su - {{ borg_user }} -c '/usr/local/bin/borgmatic rcreate --encryption keyfile --append-only'" + + - name: Activate systemd service and timer + when: + - borgmatic_timer is defined and borgmatic_timer == "systemd" + tags: + - never + - backup_init_repo + block: + - name: Populate service facts + ansible.builtin.service_facts: + + - name: Start borgmatic services + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: true + masked: false + daemon_reload: true + when: "item in services" + with_items: + - borgmatic.service + + # bug: Need own section without masked else the timer are skipped + - name: Start borgmatic timers + ansible.builtin.systemd: + name: "{{ item }}" + state: started + enabled: true + daemon_reload: true + with_items: + - "borgmatic.timer" +``` diff --git a/roles/borgbase.ansible_role_borgbackup/LICENSE b/roles/borgbase.ansible_role_borgbackup/LICENSE new file mode 100644 index 0000000..523eac0 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Manuel Riel + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/roles/borgbase.ansible_role_borgbackup/README.md b/roles/borgbase.ansible_role_borgbackup/README.md new file mode 100644 index 0000000..5707a04 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/README.md @@ -0,0 +1,136 @@ +# Ansible Role: BorgBackup Client + +[![Test](https://github.com/borgbase/ansible-role-borgbackup/actions/workflows/main.yml/badge.svg)](https://github.com/borgbase/ansible-role-borgbackup/actions/workflows/main.yml) [![Ansible Galaxy](https://img.shields.io/ansible/role/48519)](https://galaxy.ansible.com/borgbase/ansible_role_borgbackup) + +Set up encrypted, compressed and deduplicated backups using [BorgBackup](https://borgbackup.readthedocs.io/en/stable/) and [Borgmatic](https://github.com/witten/borgmatic). Currently supports Debian/Ubuntu, CentOS/Red Hat/Fedora, Archlinux and Manjaro. + +Works great with [BorgBase.com](https://www.borgbase.com) - Simple and Secure Hosting for your Borg Repositories. To manage BorgBase repos via Ansible, also see Andy Hawkins' [BorgBase Collection](https://galaxy.ansible.com/adhawkins/borgbase). + +**Main features** +- Install Borg and Borgmatic from PyPi or distro packages +- Set up Borgmatic config +- Schedule regular backups using Cron or Systemd timer + +## Breaking changes +- Older versions of this role set up a separate Cron job for creating and checking + backups. With recent Borgmatic version, this feature is now managed in Borgmatic. + As a result the extra Cron job will be removed by this role. +- Older versions of this role only supported Cron for scheduling. If you use + Systemd timers, be sure to remove the Cron job in `/etc/cron.d/borgmatic` first. + The role will also alert you when trying to use both timers. + +## Example playbook with root as backup user and Cron timer + +``` +- hosts: all + roles: + - role: borgbase.ansible_role_borgbackup + borg_encryption_passphrase: CHANGEME + borg_repository: ssh://xxxxxx@xxxxxx.repo.borgbase.com/./repo + borg_source_directories: + - /var/www + borgmatic_hooks: + before_backup: + - echo "`date` - Starting backup." + postgresql_databases: + - name: users + hostname: database1.example.org + port: 5433 +``` + +## Example playbook with service user and Systemd timer + +``` +- hosts: all + roles: + - role: borgbase.ansible_role_borgbackup + borg_encryption_passphrase: CHANGEME + borg_repository: ssh://xxxxxx@xxxxxx.repo.borgbase.com/./repo + borgmatic_timer: systemd + borg_user: "backupuser" + borg_group: "backupuser" + borg_source_directories: + - /var/www + borg_retention_policy: + keep_hourly: 3 + keep_daily: 7 + keep_weekly: 4 + keep_monthly: 6 +``` + + + +## Installation + +Download from Ansible Galaxy +``` +$ ansible-galaxy install borgbase.ansible_role_borgbackup +``` + +Clone latest version from Github +``` +$ git clone https://github.com/borgbase/ansible-role-borgbackup.git roles/ansible_role_borgbackup +``` + + +## Role Variables + +### Required Variables +- `borg_repository`: Full path to repository. Your own server or [BorgBase.com](https://www.borgbase.com) repo. + Can be a list if you want to backup to multiple repositories. + +### Optional Variables +- `borg_dep_packages`: Dependency Packages to install `borg(backup)` and `borgmatic`. +- `borg_distro_packages`: contains the names of distributions packages for `borg(backup)` and `borgmatic`, only used if `borg_install_method` is set to `package`. +- `borg_encryption_passcommand`: The standard output of this command is used to unlock the encryption key. +- `borg_encryption_passphrase`: Password to use for repokey or keyfile. Empty if repo is unencrypted. +- `borg_exclude_from`: Read exclude patterns from one or more separate named files, one pattern per line. +- `borg_exclude_patterns`: Paths or patterns to exclude from backup. See [official documentation](https://borgbackup.readthedocs.io/en/stable/usage/help.html#borg-help-patterns) for more. +- `borg_install_method`: By default `pip` is used to install borgmatic. To install via your distributions package manager set this to `package` and (if needed) overwrite the `borg_distro_packages` variable to contain your distributions package names required to install borgmatic. Note that many distributions ship outdated versions of borgbackup and borgmatic; use at your own risk. +- `borg_require_epel`: When using `borg_install_method: package` on RHEL-based distributions, the EPEL repo is required. To disable the check (e.g. when using a custom mirror instead of the `epel-release` package), set this to `false`. Defaults to `{{ ansible_os_family == 'RedHat' and ansible_distribution != 'Fedora' }}` (i.e. `true` on Enterprise Linux-based distros). +- `borg_lock_wait_time`: Config maximum seconds to wait for acquiring a repository/cache lock. Defaults to 5 seconds. +- `borg_one_file_system`: Don't cross file-system boundaries. Defaults to `true` +- `borg_pip_packages`: Dependancy Packages (pip) to install `borg(backup)` and `borgmatic`. +- `borg_remote_path`: Path to the borg executable on the remote. It will default to `borg`. +- `borg_remote_rate_limit`: Remote network upload rate limit in kiBytes/second. +- `borg_retention_policy`: Retention policy for how many backups to keep in each category (daily, weekly, monthly, etc). +- `borg_source_directories`: List of local folders to back up. Default is `/etc/hostname` to prevent an empty backup. +- `borg_ssh_key_name`: Name of the SSH public and pivate key. Default `id_ed25519` +- `borg_ssh_key_file_path`: SSH-key to be used. Default `~/.ssh/{{ borg_ssh_key_name }}` +- `borg_ssh_key_type`: The algorithm used to generate the SSH private key. Choose: `rsa`, `dsa`, `rsa1`, `ecdsa`, `ed25519`. Default: `ed25519` +- `borg_ssh_command`: Command to use instead of just "ssh". This can be used to specify SSH options. +- `borg_version`: Force a specific borg version to be installed +- `borg_venv_path`: Path to store the venv for `borg(backup)` and `borgmatic` + +- `borgmatic_check_last`: Number of archives to check. Defaults to `3` +- `borgmatic_checks`: List of consistency checks. Defaults to monthly checks. See [docs](https://torsion.org/borgmatic/docs/how-to/deal-with-very-large-backups/#check-frequency) for all options. +- `borgmatic_config_name`: Name to use for the Borgmatic config file. Defaults to `config.yaml` +- `borgmatic_timer_hour`: Hour when regular create and prune cron/systemd-timer job will run. Defaults to `{{ 6 | random }}` +- `borgmatic_timer_minute`: Minute when regular create and prune cron/systemd-timer job will run. Defaults to `{{ 59 | random }}` +- `borgmatic_hooks`: Hooks to monitor your backups e.g. with [Healthchecks](https://healthchecks.io/). See [official documentation](https://torsion.org/borgmatic/docs/how-to/monitor-your-backups/) for more. +- `borgmatic_timer`: If the variable is set, a timer is installed. A choice must be made between `cron` and `systemd`. +- `borgmatic_relocated_repo_access_is_ok`: Bypass Borg error about a repository that has been moved. Defaults to `false` +- `borgmatic_store_atime`: Store atime into archive. Defaults to `true` +- `borgmatic_store_ctime`: Store ctime into archive. Defaults to `true` +- `borgmatic_version`: Force a specific borgmatic version to be installed + +- `borg_user`: Name of the User to create Backups (service account) +- `borg_group`: Name of the Group to create Backups (service account) + + +## Contributing + +Pull requests (PR) are welcome, as long as they add features that are relevant for a meaningful number of users. All PRs are tested for style and functionality. To run tests locally (needs Docker): + +``` +$ pip install -r requirements-dev.txt +$ molecule test +``` + +## License + +MIT/BSD + +## Author + +© 2018-2023 Manuel Riel and contributors. diff --git a/roles/borgbase.ansible_role_borgbackup/defaults/main.yml b/roles/borgbase.ansible_role_borgbackup/defaults/main.yml new file mode 100755 index 0000000..02e0a65 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/defaults/main.yml @@ -0,0 +1,49 @@ +--- +borg_encryption_passphrase: '' +borg_exclude_patterns: [] +borg_one_file_system: true +borg_exclude_from: [] +borg_encryption_passcommand: false +borg_lock_wait_time: 5 +borg_ssh_key_type: "ed25519" +borg_ssh_key_name: "id_{{ borg_ssh_key_type }}" +borg_ssh_key_file_path: "{{ backup_user_info.home }}/.ssh/{{ borg_ssh_key_name }}" +borg_ssh_command: false +borg_remote_path: false +borg_remote_rate_limit: 0 +borg_retention_policy: + keep_hourly: 3 + keep_daily: 7 + keep_weekly: 4 + keep_monthly: 6 +borg_version: false +borgmatic_timer_cron_name: "borgmatic" +borgmatic_timer: cron +borgmatic_timer_hour: "{{ range(0, 5) | random(seed=inventory_hostname) }}" +borgmatic_timer_minute: "{{ range(0, 59) | random(seed=inventory_hostname) }}" +borg_install_method: "pip" +borg_require_epel: "{{ ansible_os_family == 'RedHat' and ansible_distribution != 'Fedora' }}" + +borgmatic_config_name: config.yaml +borgmatic_hooks: + on_error: + - echo "`date` - Error while creating a backup." + before_backup: + - echo "`date` - Starting backup." + after_backup: + - echo "`date` - Finished backup." +borgmatic_checks: + - name: repository + frequency: "4 weeks" + - name: archives + frequency: "6 weeks" +borgmatic_check_last: 3 +borgmatic_store_atime: true +borgmatic_store_ctime: true +borgmatic_relocated_repo_access_is_ok: false +borgmatic_version: ">=1.7.11" + +borg_venv_path: "/opt/borgmatic" +borg_user: "root" +borg_group: "root" +... diff --git a/roles/borgbase.ansible_role_borgbackup/meta/.galaxy_install_info b/roles/borgbase.ansible_role_borgbackup/meta/.galaxy_install_info new file mode 100644 index 0000000..063077d --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/meta/.galaxy_install_info @@ -0,0 +1,2 @@ +install_date: Sun Oct 13 17:15:12 2024 +version: v1.0.1 diff --git a/roles/borgbase.ansible_role_borgbackup/meta/arguments_specs.yml b/roles/borgbase.ansible_role_borgbackup/meta/arguments_specs.yml new file mode 100644 index 0000000..2908b4f --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/meta/arguments_specs.yml @@ -0,0 +1,182 @@ +argument_specs: + main: + # https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html#role-argument-validation + # https://docs.ansible.com/ansible/latest/dev_guide/developing_program_flow_modules.html#argument-spec + short_description: Role to install borgbackup and borgmatic. + description: Role to install borgbackup and borgmatic + author: + - 2018-2020 Manuel Riel and contributors. + - Frank Dornheim + options: + borg_dep_packages: + type: str + required: false + description: Dependancy Packages to install borg(backup) and borgmatic. + borg_distro_packages: + type: str + required: false + description: | + Contains the names of distributions packages for borg(backup) and borgmatic, + only used if `borg_install_method` is set to package + borg_pip_packages: + type: str + required: false + description: Dependancy Packages (pip) to install borg(backup) and borgmatic. + borg_venv_path: + type: str + required: false + description: Path to store the venv for borg(backup) and borgmatic. + borg_install_method: + type: str + required: false + default: pip + description: | + By default pip is used to install borgmatic. + To install via your distributions package manager set this to package and (if needed) + overwrite the borg_distro_packages variable to contain your distributions package names + required to install borgmatic. + Note that many distributions ship outdated versions of borgbackup and borgmatic; use at your own risk. + borgmatic_config_name: + type: str + required: false + default: config.yaml + description: Name to use for the borgmatic config file. + borg_user: + type: str + default: root + description: Name of the User to create Backups (Service Account) + borg_group: + type: str + default: root + description: Name of the Group to create Backups (Service Account) + borg_source_directories: + type: List + default: "/etc/hostname" + required: false + description: List of local folders to back up. + borg_one_file_system: + type: str + required: false + description: Don't cross file-system boundaries. + borg_repository: + type: List + required: true + description: | + Full path to repository. + Your own server or [BorgBase.com](https://www.borgbase.com) repo. + Not required when using auto creation of repositories. + Can be a list if you want to backup to multiple repositories. + borgmatic_store_atime: + type: bool + required: false + description: Store atime into archive. + borgmatic_store_ctime: + type: bool + required: false + description: Store ctime into archive. + borg_exclude_patterns: + type: List + required: false + description: Any paths matching these patterns are excluded from backups. Globs and tildes are expanded. + borg_exclude_from: + type: List + required: false + description: Read exclude patterns from one or more separate named files, one pattern per line. + borg_remote_path: + type: str + required: false + description: Alternate Borg remote executable. Defaults to "borg" + borg_encryption_passphrase: + type: str + description: | + The standard output of this command is used to unlock the encryption key. + Only use on repositories that were initialized with passcommand/repokey encryption. + Note that if both encryption_passcommand and encryption_passphrase are set, + then encryption_passphrase takes precedence. + borg_encryption_passcommand: + type: str + required: false + description: secret-tool lookup borg-repository repo-name + borg_remote_rate_limit: + type: int + required: false + description: Remote network upload rate limit in kiBytes/second. + borg_ssh_key_file_path: + type: str + required: false + description: Path to ssh-key + borg_ssh_command: + type: str + description: Command to use instead of just ssh. This can be used to specify ssh options. + borg_lock_wait_time: + type: int + description: Maximum seconds to wait for acquiring a repository/cache lock. + borgmatic_relocated_repo_access_is_ok: + type: bool + description: Bypass Borg error about a repository that has been moved. + borg_retention_policy: + keep_secondly: + type: int + required: false + description: Number of secondly archives to keep. + keep_minutely: + type: int + required: false + description: Number of minutely archives to keep. + keep_hourly: + type: int + required: false + description: Number of hourly archives to keep. + keep_daily: + type: int + required: false + description: Number of daily archives to keep. + keep_weekly: + type: int + required: false + description: Number of weekly archives to keep. + keep_monthly: + type: int + required: false + description: Number of monthly archives to keep. + keep_yearly: + type: int + required: false + description: Number of yearly archives to keep. + borgmatic_checks: + type: List + required: false + description: | + List of one or more consistency checks to run + "repository", "archives", "data", and/or "extract". + Defaults to "repository" and "archives". + Set to "disabled" to disable all consistency checks. + "repository" checks the consistency of the repository, + "archives" checks all of the archives, "data" verifies + the integrity of the data within the archives, and + "extract" does an extraction dry-run of the most recent archive. + Note that "data" implies "archives". + borgmatic_check_last: + type: int + required: false + description: Restrict the number of checked archives to the last n. Applies only to the "archives" check. + borgmatic_hooks: + type: dict + required: false + description: Shell commands or scripts to execute before and after a backup or if an error has occurred. + borgmatic_timer_cron_name: + type: str + required: false + description: Name of the cron Job + borgmatic_timer: + type: str + required: false + description: If the variable is set, a timer is installed. A choice must be made between "cron" and "systemd". + borg_ssh_key_type: + type: str + required: false + description: The algorithm used to generate the SSH private key + borg_ssh_key_name: + type: str + required: false + description: Name of the SSH public and private key diff --git a/roles/borgbase.ansible_role_borgbackup/meta/main.yml b/roles/borgbase.ansible_role_borgbackup/meta/main.yml new file mode 100644 index 0000000..0438447 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/meta/main.yml @@ -0,0 +1,30 @@ +--- +dependencies: [] + +galaxy_info: + author: Manuel Riel + role_name: ansible_role_borgbackup + namespace: borgbase + description: Set up backup to remote machine using Borg and Borgmatic. + company: "BorgBase.com" + license: license (BSD, MIT) + min_ansible_version: "2.0" + platforms: + - name: Debian + versions: + - all + - name: Ubuntu + versions: + - trusty + - xenial + - bionic + - name: ArchLinux + versions: + - all + galaxy_tags: + - backup + - cloud + - system + - security + +allow_duplicates: true diff --git a/roles/borgbase.ansible_role_borgbackup/molecule/default/Dockerfile.j2 b/roles/borgbase.ansible_role_borgbackup/molecule/default/Dockerfile.j2 new file mode 100644 index 0000000..0b331c5 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/molecule/default/Dockerfile.j2 @@ -0,0 +1,23 @@ +# Molecule managed + +{% if item.registry is defined %} +FROM {{ item.registry.url }}/{{ item.image }} +{% else %} +FROM {{ item.image }} +{% endif %} + +{% if item.env is defined %} +{% for var, value in item.env.items() %} +{% if value %} +ENV {{ var }} {{ value }} +{% endif %} +{% endfor %} +{% endif %} + +RUN if [ $(command -v apt-get) ]; then apt-get update && apt-get install -y python3 python3-pip sudo bash ca-certificates iproute2 python3-apt aptitude && apt-get clean; \ + elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 sudo bash iproute && dnf clean all; \ + elif [ $(command -v yum) ]; then yum makecache fast && yum install -y /usr/bin/python /usr/bin/python2-config sudo yum-plugin-ovl bash iproute && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \ + elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python sudo bash python-xml iproute2 && zypper clean -a; \ + elif [ $(command -v apk) ]; then apk update && apk add --no-cache python sudo bash ca-certificates; \ + elif [ $(command -v pacman) ]; then pacman --noconfirm -Suy python python-pip sudo openssh; \ + elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python sudo bash ca-certificates iproute2 && xbps-remove -O; fi diff --git a/roles/borgbase.ansible_role_borgbackup/molecule/default/INSTALL.rst b/roles/borgbase.ansible_role_borgbackup/molecule/default/INSTALL.rst new file mode 100644 index 0000000..6a44bde --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/molecule/default/INSTALL.rst @@ -0,0 +1,22 @@ +******* +Docker driver installation guide +******* + +Requirements +============ + +* Docker Engine + +Install +======= + +Please refer to the `Virtual environment`_ documentation for installation best +practices. If not using a virtual environment, please consider passing the +widely recommended `'--user' flag`_ when invoking ``pip``. + +.. _Virtual environment: https://virtualenv.pypa.io/en/latest/ +.. _'--user' flag: https://packaging.python.org/tutorials/installing-packages/#installing-to-the-user-site + +.. code-block:: bash + + $ pip install 'molecule[docker]' diff --git a/roles/borgbase.ansible_role_borgbackup/molecule/default/converge.yml b/roles/borgbase.ansible_role_borgbackup/molecule/default/converge.yml new file mode 100644 index 0000000..394383f --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/molecule/default/converge.yml @@ -0,0 +1,48 @@ +--- +- name: Converge + hosts: all + pre_tasks: + - name: Set ssh server package name for non-Archlinux ansible_os_family + set_fact: + openssh_package: "openssh-server" + when: ansible_os_family != "Archlinux" + + - name: Set ssh server package name for Archlinux ansible_os_family + set_fact: + openssh_package: "openssh" + when: ansible_os_family == "Archlinux" + + - name: Install openssh + package: + name: "{{ openssh_package }}" + state: present + + roles: + - role: borgbase.ansible_role_borgbackup + borg_install_method: pip + borgmatic_timer: cron + borg_repository: m5vz9gp4@m5vz9gp4.repo.borgbase.com:repo + borg_encryption_passphrase: CHANGEME + borg_source_directories: + - /srv/www + - /var/lib/automysqlbackup + borg_exclude_patterns: + - /srv/www/old-sites + borg_retention_policy: + keep_hourly: 3 + keep_daily: 7 + keep_weekly: 4 + keep_monthly: 6 + borgmatic_hooks: + before_backup: + - echo "`date` - Starting backup." + postgresql_databases: + - name: users + hostname: database1.example.org + port: 5433 + + post_tasks: + - name: Install yamllint for checking config file + pip: + name: yamllint + executable: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/molecule/default/molecule.yml b/roles/borgbase.ansible_role_borgbackup/molecule/default/molecule.yml new file mode 100644 index 0000000..9c8fa0e --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/molecule/default/molecule.yml @@ -0,0 +1,24 @@ +--- +dependency: + name: galaxy +driver: + name: docker +platforms: + - name: archlinux-latest + image: archlinux:latest + - name: almalinux-9 + image: almalinux:9 + - name: fedora-latest + image: fedora:latest + - name: debian-bullseye + image: debian:bullseye + - name: ubuntu-latest + image: ubuntu:latest +provisioner: + name: ansible +verifier: + name: ansible +lint: | + set -e + yamllint . + ansible-lint . diff --git a/roles/borgbase.ansible_role_borgbackup/molecule/default/verify.yml b/roles/borgbase.ansible_role_borgbackup/molecule/default/verify.yml new file mode 100644 index 0000000..f816e3f --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/molecule/default/verify.yml @@ -0,0 +1,13 @@ +--- +- name: Verify + hosts: all + tasks: + - name: Ensure Borgmatic is installed correctly + command: borgmatic --version + + - name: Ensure Borg is installed correctly + command: borgmatic borg --version + + - name: Ensure produced YAML is valid + command: | + yamllint -d "{extends: relaxed, rules: {line-length: {max: 120}}}" /etc/borgmatic/config.yaml diff --git a/roles/borgbase.ansible_role_borgbackup/requirements-dev.txt b/roles/borgbase.ansible_role_borgbackup/requirements-dev.txt new file mode 100644 index 0000000..a10d42b --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/requirements-dev.txt @@ -0,0 +1,4 @@ +ansible +ansible-lint +molecule +molecule-plugins[docker] diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/01_install.yml b/roles/borgbase.ansible_role_borgbackup/tasks/01_install.yml new file mode 100644 index 0000000..e4478d9 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/01_install.yml @@ -0,0 +1,29 @@ +--- +- name: Install borgbackup + block: + - name: Ensure legacy hooks aren't used + ansible.builtin.assert: + that: + - borgmatic_failure_command is undefined + - borgmatic_before_backup_command is undefined + - borgmatic_after_backup_command is undefined + msg: Please use the new borgmatic_hooks variable instead of individual before/after/failure hooks. + + - name: Include OS-specific variables + include_vars: "{{ item }}" + with_first_found: + - "{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml" + - "{{ ansible_os_family }}-{{ ansible_distribution_major_version }}.yml" + - "{{ ansible_distribution }}.yml" + - "{{ ansible_os_family }}.yml" + - "{{ ansible_lsb.id }}.yml" + + - name: Install general dependencies (openssh) + ansible.builtin.package: + name: "{{ borg_dep_packages }}" + state: present + + - name: Install Borg and Borgmatic + ansible.builtin.include_tasks: + file: noauto_install_{{ borg_install_method }}.yml +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/02_user_management.yml b/roles/borgbase.ansible_role_borgbackup/tasks/02_user_management.yml new file mode 100644 index 0000000..fb396d1 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/02_user_management.yml @@ -0,0 +1,25 @@ +--- +# So in different positions in that role we need the user home +# Since we cannot be sure that this FSH is compatible we will determine it. +- name: Get home dir + when: + - borg_user == "root" + block: + - name: Get home if borg_user == "root" + ansible.builtin.user: + name: "{{ borg_user }}" + state: present + register: user_info + changed_when: false + check_mode: true # Important, otherwise user will be created + + - name: Save the user_info, we need them for the home_dir + ansible.builtin.set_fact: + backup_user_info: "{{ user_info }}" + +- name: Create user if borg_user != "root" + when: + - borg_user != "root" + ansible.builtin.include_tasks: + file: noauto_create_backup_user_and_group.yml +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/03_create_key.yml b/roles/borgbase.ansible_role_borgbackup/tasks/03_create_key.yml new file mode 100644 index 0000000..3827d77 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/03_create_key.yml @@ -0,0 +1,28 @@ +--- +- name: Create SSH key (if neeeded) for {{ borg_user }} + block: + - name: Ensure directory exist + ansible.builtin.file: + path: "{{ backup_user_info.home }}/.ssh/" + state: directory + mode: "0700" + owner: "{{ borg_user }}" + group: "{{ borg_group }}" + + - name: Generate an OpenSSH keypair + community.crypto.openssh_keypair: + path: "{{ borg_ssh_key_file_path }}" + mode: "0600" + type: "{{ borg_ssh_key_type }}" + owner: "{{ borg_user }}" + group: "{{ borg_group }}" + + - name: Read SSH key + ansible.builtin.slurp: + src: "{{ borg_ssh_key_file_path }}.pub" + register: backup_local_ssh_key + + - name: Print key + ansible.builtin.debug: + msg: "The generated key is: {{ backup_local_ssh_key['content'] | b64decode }}" +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/05_configure.yml b/roles/borgbase.ansible_role_borgbackup/tasks/05_configure.yml new file mode 100755 index 0000000..e055c20 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/05_configure.yml @@ -0,0 +1,19 @@ +--- +- name: Add Borgmatic config file + block: + - name: Ensure /etc/borgmatic exists + ansible.builtin.file: + path: /etc/borgmatic + state: directory + mode: "0700" + owner: "{{ borg_user }}" + group: "{{ borg_group }}" + + - name: Add Borgmatic configuration + ansible.builtin.template: + src: config.yaml.j2 + dest: "/etc/borgmatic/{{ borgmatic_config_name }}" + mode: "0600" + owner: "{{ borg_user }}" + group: "{{ borg_group }}" +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/07_install_timer.yml b/roles/borgbase.ansible_role_borgbackup/tasks/07_install_timer.yml new file mode 100644 index 0000000..4ca6afa --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/07_install_timer.yml @@ -0,0 +1,8 @@ +--- +- name: Install timer to run Borgmatic + when: + - borgmatic_timer is defined and borgmatic_timer | length > 0 + block: + - name: Start timer install script + ansible.builtin.include_tasks: + file: noauto_create_timer_{{ borgmatic_timer }}.yml diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/main.yml b/roles/borgbase.ansible_role_borgbackup/tasks/main.yml new file mode 100644 index 0000000..d6b56c1 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/main.yml @@ -0,0 +1,7 @@ +--- +- name: Add and run all plays + include_tasks: "{{ bak_element }}" + with_items: "{{ lookup('ansible.builtin.fileglob', '*.yml').split(',') | reject('search', 'main.yml') | reject('search', 'noauto_*') | sort }}" + loop_control: + loop_var: bak_element +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_backup_user_and_group.yml b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_backup_user_and_group.yml new file mode 100644 index 0000000..d11526e --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_backup_user_and_group.yml @@ -0,0 +1,34 @@ +--- +- name: Setup backup environment + when: + - backup_create_local_user is not defined or backup_create_local_user + - borg_user != "root" + block: + - name: Add local backup group + ansible.builtin.group: + name: "{{ borg_group }}" + state: present + + - name: Add local backup user + ansible.builtin.user: + name: "{{ borg_user }}" + shell: "/bin/bash" + groups: "{{ borg_group }}" + comment: "Backup User Account" + append: true + register: user_info + + - name: Save the user_info, we need them for the home_dir + ansible.builtin.set_fact: + backup_user_info: "{{ user_info }}" + + - name: Add sudo users + community.general.sudoers: + name: "backup" + state: present + user: "{{ borg_user }}" + nopassword: true + commands: + - "/opt/borgmatic/bin/borg" + - "/usr/local/bin/borgmatic -c /etc/borgmatic/{{ borgmatic_config_name }}" +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_timer_cron.yml b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_timer_cron.yml new file mode 100644 index 0000000..6a557a4 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_timer_cron.yml @@ -0,0 +1,31 @@ +--- +- name: Ensure Cron is installed + ansible.builtin.package: + name: "{{ borg_cron_package }}" + state: present + +- name: Add Cron job for borgmatic + block: + - name: Add single Cron job for borgmatic + cron: + name: "{{ borgmatic_timer_cron_name }}" + hour: "{{ borgmatic_timer_hour }}" + minute: "{{ borgmatic_timer_minute }}" + user: "{{ borg_user }}" + cron_file: "{{ borgmatic_timer_cron_name }}" + job: "borgmatic -c /etc/borgmatic/{{ borgmatic_config_name }}" + + - name: Ensure separate check cron job is absent + cron: + name: "{{ borgmatic_timer_cron_name }}-check" + cron_file: "{{ borgmatic_timer_cron_name }}" + state: absent + +- name: Set PATH for borgmatic cron job. + cron: + user: "{{ borg_user }}" + cron_file: "{{ borgmatic_timer_cron_name }}" + name: PATH + env: yes + value: /usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_timer_systemd.yml b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_timer_systemd.yml new file mode 100644 index 0000000..53e0b6b --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_create_timer_systemd.yml @@ -0,0 +1,63 @@ +--- +- name: Register existence of Borgmatic cron file. + cron: + name: "{{ borgmatic_timer_cron_name }}" + cron_file: "{{ borgmatic_timer_cron_name }}" + state: absent + check_mode: true + register: cron_file_exists + +- name: Ensure no Borgmatic Cron file exists. + ansible.builtin.assert: + that: + - not cron_file_exists.changed + fail_msg: Found an existing Borgmatic Cron job. Please remove before using Systemd timer. + +- name: Create borgbackup timer + block: + - name: Copy systemd files + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest }}" + owner: root + group: root + backup: true + mode: "{{ item.mode }}" + with_items: + - { src: "borgmatic.timer.j2", dest: "/usr/lib/systemd/system/borgmatic.timer", mode: "0644" } + - { src: "borgmatic.service.j2", dest: "/usr/lib/systemd/system/borgmatic.service", mode: "0644" } + + - name: Populate service facts + ansible.builtin.service_facts: + + # If the role is running and the repo is not yet initialized, an error will occur. + # Therefore the service is stopped by default and must be started manually. + - name: Stop fresh installed borgmatic.timer and borgmatic.service + when: "'borgmatic.service' not in ansible_facts.services" + block: + - name: Set borgmatic services to stopped - newly installed + ansible.builtin.systemd: + name: "{{ item }}" + state: stopped + enabled: false + masked: false + daemon_reload: true + when: item in ansible_facts.services + with_items: + - borgmatic.service + + # bug: Need own section without masked else the timer are skipped + - name: Set borgmatic timers to stopped - newly installed + ansible.builtin.systemd: + name: "{{ item }}" + state: stopped + enabled: false + daemon_reload: true + with_items: + - "borgmatic.timer" + + - name: Show hints + when: "'backup_init_repo' not in ansible_run_tags" + ansible.builtin.debug: + msg: "Attention: Since the repo was not initialized automatically, the systemd service (borgmatic.service) and the timer (borgmatic.timer) are not activated." +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/noauto_install_package.yml b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_install_package.yml new file mode 100644 index 0000000..5f28a58 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_install_package.yml @@ -0,0 +1,21 @@ +--- +- name: Install borgbackup by distro + block: + - name: Check if EPEL repo is enabled, if installation from distro is requested + when: borg_require_epel + block: + - name: Get list of installed packages + ansible.builtin.package_facts: + manager: auto + - name: Ensure EPEL is enabled + ansible.builtin.assert: + that: + - "'epel-release' in ansible_facts.packages" + fail_msg: Need EPEL repo to install via distro package. + + - name: Install borgmatic and borg via distribution package manager + ansible.builtin.package: + name: "{{ item }}" + state: present + loop: "{{ borg_distro_packages }}" +... diff --git a/roles/borgbase.ansible_role_borgbackup/tasks/noauto_install_pip.yml b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_install_pip.yml new file mode 100644 index 0000000..0cf26c1 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/tasks/noauto_install_pip.yml @@ -0,0 +1,51 @@ +--- +- name: Install Borg and Borgmatic via pip + block: + - name: Install build dependencies + ansible.builtin.package: + name: "{{ borg_pip_packages }}" + state: present + + - name: Create virtualenv for borg # noqa package-latest + ansible.builtin.pip: + name: + - pip + - setuptools + state: latest + virtualenv: "{{ borg_venv_path }}" + virtualenv_command: "{{ python_bin }} -m venv" + + - name: Install dependent Python packages + ansible.builtin.pip: + name: "{{ borg_dependent_python_packages }}" + virtualenv: "{{ borg_venv_path }}" + when: borg_dependent_python_packages is defined + + - name: Install main Python packages + ansible.builtin.pip: + name: "{{ item.name }}" + version: "{{ item.version | default(omit, true) }}" + virtualenv: "{{ borg_venv_path }}" + when: borg_python_packages is defined + loop: "{{ borg_python_packages }}" + +- name: Create links to Borgmatic and Borg binaries + block: + - name: Create borgmatic command in /usr/local/bin + ansible.builtin.copy: + content: | + #!/bin/bash + . "{{ borg_venv_path }}"/bin/activate + borgmatic "$@" + dest: /usr/local/bin/borgmatic + mode: "0755" + + - name: Create borg command in /usr/local/bin + ansible.builtin.copy: + content: | + #!/bin/bash + . "{{ borg_venv_path }}"/bin/activate + borg "$@" + dest: /usr/local/bin/borg + mode: "0755" +... diff --git a/roles/borgbase.ansible_role_borgbackup/templates/borgmatic.service.j2 b/roles/borgbase.ansible_role_borgbackup/templates/borgmatic.service.j2 new file mode 100644 index 0000000..4e4406c --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/templates/borgmatic.service.j2 @@ -0,0 +1,60 @@ +# Managed by Ansible, please don't edit manually + +[Unit] +Description=borgmatic backup +Wants=backup_normal_repo.timer +Wants=network-online.target +After=network-online.target +# Prevent borgmatic from running unless the machine is plugged into power. Remove this line if you +# want to allow borgmatic to run anytime. +ConditionACPower=true + +[Service] +Type=oneshot +User={{ borg_user }} +ExecStart=borgmatic -c /etc/borgmatic/{{ borgmatic_config_name }} + +# Source: https://projects.torsion.org/borgmatic-collective/borgmatic/raw/branch/master/sample/systemd/borgmatic.service +# Security settings for systemd running as root, optional but recommended to improve security. You +# can disable individual settings if they cause problems for your use case. For more details, see +# the systemd manual: https://www.freedesktop.org/software/systemd/man/systemd.exec.html +LockPersonality=true +# Certain borgmatic features like Healthchecks integration need MemoryDenyWriteExecute to be off. +# But you can try setting it to "yes" for improved security if you don't use those features. +MemoryDenyWriteExecute=no +NoNewPrivileges=yes +PrivateDevices=yes +PrivateTmp=yes +ProtectClock=yes +ProtectControlGroups=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallArchitectures=native +SystemCallFilter=@system-service +SystemCallErrorNumber=EPERM +# To restrict write access further, change "ProtectSystem" to "strict" and uncomment +# "ReadWritePaths", "ReadOnlyPaths", "ProtectHome", and "BindPaths". Then add any local repository +# paths to the list of "ReadWritePaths" and local backup source paths to "ReadOnlyPaths". This +# leaves most of the filesystem read-only to borgmatic. +ProtectSystem=full +# ReadWritePaths=-/mnt/my_backup_drive +# ReadOnlyPaths=-/var/lib/my_backup_source +# This will mount a tmpfs on top of /root and pass through needed paths +# ProtectHome=tmpfs +# BindPaths=-/root/.cache/borg -/root/.config/borg -/root/.borgmatic + +# May interfere with running external programs within borgmatic hooks. +# CapabilityBoundingSet=CAP_DAC_READ_SEARCH CAP_NET_RAW + +# Lower CPU and I/O priority. +Nice=19 +CPUSchedulingPolicy=batch +IOSchedulingClass=best-effort +IOSchedulingPriority=7 +IOWeight=100 diff --git a/roles/borgbase.ansible_role_borgbackup/templates/borgmatic.timer.j2 b/roles/borgbase.ansible_role_borgbackup/templates/borgmatic.timer.j2 new file mode 100644 index 0000000..2ff402c --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/templates/borgmatic.timer.j2 @@ -0,0 +1,13 @@ +# Managed by Ansible, please don't edit manually + +[Unit] +Description=Start creating of Backups - see: https://www.freedesktop.org/software/systemd/man/systemd.time.html# + +[Timer] +# Day-of-the-Week Year-Month-Day Hour:Minutes:Seconds +# Persistent -> resume backup after shutdown +OnCalendar= *-*-* {{ borgmatic_timer_hour }}:{{ borgmatic_timer_minute }}:00 +Persistent=true + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/roles/borgbase.ansible_role_borgbackup/templates/config.yaml.j2 b/roles/borgbase.ansible_role_borgbackup/templates/config.yaml.j2 new file mode 100644 index 0000000..dc7b485 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/templates/config.yaml.j2 @@ -0,0 +1,180 @@ +#jinja2: lstrip_blocks: "True", trim_blocks: "True" +--- +# Managed by Ansible, please don't edit manually + +# Full config: https://torsion.org/borgmatic/docs/reference/config.yaml +location: +{% if borg_source_directories is not defined or borg_source_directories | length == 0 %} + source_directories: + - /etc/hostname # prevent empty backupconfig +{% else %} + source_directories: + {% for dir in borg_source_directories %} + - {{ dir }} + {% endfor %} +{% endif %} + + # Stay in same file system (do not cross mount points). + one_file_system: {{ borg_one_file_system }} + repositories: +{% if borg_repository is iterable and (borg_repository is not string and borg_repository is not mapping) %} + {% for repo in borg_repository %} + - {{ repo }} + {% endfor %} +{% elif borg_repository is defined and borg_repository is string %} + - {{ borg_repository }} +{% endif %} + + # Store atime into archive. + atime: {{ borgmatic_store_atime }} + + # Store ctime into archive. + ctime: {{ borgmatic_store_ctime }} + +{% if borg_exclude_patterns %} + # Any paths matching these patterns are excluded from backups. Globs and tildes + # are expanded. See the output of "borg help patterns" for more details. + exclude_patterns: +{% for dir in borg_exclude_patterns %} + - '{{ dir }}' +{% endfor %} +{% endif %} +{% if borg_exclude_from %} + # Read exclude patterns from one or more separate named files, one pattern per + # line. See the output of "borg help patterns" for more details. + exclude_from: +{% for dir in borg_exclude_from %} + - {{ dir }} +{% endfor %} +{% endif %} + + # Exclude directories that contain a CACHEDIR.TAG file. See + # http://www.brynosaurus.com/cachedir/spec.html for details. + exclude_caches: true + + # Exclude directories that contain a file with the given filename. + exclude_if_present: .nobackup + + # Alternate Borg remote executable. Defaults to "borg". + # remote_path: borg1 +{% if borg_remote_path %} + remote_path: {{ borg_remote_path }} +{% endif %} + +# Repository storage options. See +# https://borgbackup.readthedocs.io/en/stable/usage.html#borg-create and +# https://borgbackup.readthedocs.io/en/stable/usage/general.html#environment-variables for +# details. +storage: + encryption_passphrase: {{ borg_encryption_passphrase }} + + # The standard output of this command is used to unlock the encryption key. Only + # use on repositories that were initialized with passcommand/repokey encryption. + # Note that if both encryption_passcommand and encryption_passphrase are set, + # then encryption_passphrase takes precedence. + # encryption_passcommand: secret-tool lookup borg-repository repo-name +{% if borg_encryption_passcommand %} + encryption_passcommand: {{ borg_encryption_passcommand }} +{% endif %} + + # Type of compression to use when creating archives. See + # https://borgbackup.readthedocs.org/en/stable/usage.html#borg-create for details. + # Defaults to no compression. + compression: auto,zstd + + # Remote network upload rate limit in kiBytes/second. +{% if borg_remote_rate_limit %} + remote_rate_limit: {{ borg_remote_rate_limit }} +{% endif %} + + # Command to use instead of just "ssh". This can be used to specify ssh options. + # ssh_command: ssh -i ~/.ssh/id_ed25519 +{% if borg_ssh_command %} + ssh_command: {{ borg_ssh_command }} +{% endif %} + + # Umask to be used for borg create. + umask: 0077 + + # Maximum seconds to wait for acquiring a repository/cache lock. + lock_wait: {{ borg_lock_wait_time }} + + # Name of the archive. Borg placeholders can be used. See the output of + # "borg help placeholders" for details. Default is + # "{hostname}-{now:%Y-%m-%dT%H:%M:%S.%f}". If you specify this option, you must + # also specify a prefix in the retention section to avoid accidental pruning of + # archives with a different archive name format. And you should also specify a + # prefix in the consistency section as well. + archive_name_format: '{hostname}-{now:%Y-%m-%d-%H%M%S}' + + # Bypass Borg error about a repository that has been moved. + relocated_repo_access_is_ok: {{ borgmatic_relocated_repo_access_is_ok }} + +# Retention policy for how many backups to keep in each category. See +# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-prune for details. +# At least one of the "keep" options is required for pruning to work. +retention: +{% if borg_retention_policy.keep_secondly is defined %} + # Number of secondly archives to keep. + keep_secondly: {{ borg_retention_policy.keep_secondly }} +{% endif %} + +{% if borg_retention_policy.keep_minutely is defined %} + # Number of minutely archives to keep. + keep_minutely: {{ borg_retention_policy.keep_minutely }} +{% endif %} + +{% if borg_retention_policy.keep_hourly is defined %} + # Number of hourly archives to keep. + keep_hourly: {{ borg_retention_policy.keep_hourly }} +{% endif %} + +{% if borg_retention_policy.keep_daily is defined %} + # Number of daily archives to keep. + keep_daily: {{ borg_retention_policy.keep_daily }} +{% endif %} + +{% if borg_retention_policy.keep_weekly is defined %} + # Number of weekly archives to keep. + keep_weekly: {{ borg_retention_policy.keep_weekly }} +{% endif %} + +{% if borg_retention_policy.keep_monthly is defined %} + # Number of monthly archives to keep. + keep_monthly: {{ borg_retention_policy.keep_monthly }} +{% endif %} + +{% if borg_retention_policy.keep_yearly is defined %} + # Number of yearly archives to keep. + keep_yearly: {{ borg_retention_policy.keep_yearly }} +{% endif %} + +# Consistency checks to run after backups. See +# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-check and +# https://borgbackup.readthedocs.org/en/stable/usage.html#borg-extract for details. +consistency: + # List of one or more consistency checks to run: "repository", + # "archives", "data", and/or "extract". Defaults to + # "repository" and "archives". Set to "disabled" to disable + # all consistency checks. "repository" checks the consistency + # of the repository, "archives" checks all of the archives, + # "data" verifies the integrity of the data within the + # archives, and "extract" does an extraction dry-run of the + # most recent archive. Note that "data" implies "archives". + checks: + {% for checks in borgmatic_checks %} + - {{ checks }} + {% endfor %} + + # Restrict the number of checked archives to the last n. Applies only to the "archives" check. + check_last: {{ borgmatic_check_last }} + +# Shell commands or scripts to execute before and after a backup or if an error has occurred. +# IMPORTANT: All provided commands and scripts are executed with user permissions of borgmatic. +# Do not forget to set secure permissions on this file as well as on any script listed (chmod 0700) to +# prevent potential shell injection or privilege escalation. +hooks: +{% for hook in borgmatic_hooks %} + {{ hook }}: + {{ borgmatic_hooks[hook] | to_nice_yaml(indent=2) | trim | indent(8) }} +{% endfor %} diff --git a/roles/borgbase.ansible_role_borgbackup/vars/Archlinux.yml b/roles/borgbase.ansible_role_borgbackup/vars/Archlinux.yml new file mode 100644 index 0000000..48c3688 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/Archlinux.yml @@ -0,0 +1,18 @@ +--- +borg_dep_packages: + - openssh + +borg_cron_package: cronie + +borg_pip_packages: + - gcc + - pkgconfig + - python-pip + - python-setuptools + +borg_distro_packages: + - borg + - borgmatic + +python_bin: python3 +pip_bin: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/vars/Debian.yml b/roles/borgbase.ansible_role_borgbackup/vars/Debian.yml new file mode 100644 index 0000000..c5fb44b --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/Debian.yml @@ -0,0 +1,24 @@ +--- +borg_dep_packages: + - openssh-client + +borg_cron_package: cron + +borg_pip_packages: + - libssl-dev + - libacl1-dev + - libacl1 + - build-essential + - python3-setuptools + - python3-dev + - python3-pip + - python3-pkgconfig + - python3-msgpack + - python3-venv + +borg_distro_packages: + - borgbackup + - borgmatic + +python_bin: python3 +pip_bin: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/vars/Fedora.yml b/roles/borgbase.ansible_role_borgbackup/vars/Fedora.yml new file mode 100644 index 0000000..a5583ec --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/Fedora.yml @@ -0,0 +1,24 @@ +--- +borg_dep_packages: + - openssh-clients + +borg_cron_package: cronie + +borg_pip_packages: + - libacl-devel + - libacl + - gcc + - gcc-c++ + - openssl-devel + - python3-pip + - python3-wheel + - python3-devel + - python3-setuptools + - python3-Cython + +borg_distro_packages: + - borgbackup + - borgmatic + +python_bin: python3 +pip_bin: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/vars/ManjaroLinux.yml b/roles/borgbase.ansible_role_borgbackup/vars/ManjaroLinux.yml new file mode 100644 index 0000000..fd4a65b --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/ManjaroLinux.yml @@ -0,0 +1,24 @@ +--- +borg_dep_packages: + - openssh + +borg_cron_package: cronie + +borg_pip_packages: # untested + - libssl-dev + - libacl1-dev + - libacl1 + - build-essential + - python3-setuptools + - python3-dev + - python3-pip + - python3-pkgconfig + - python3-msgpack + - python3-venv + +borg_distro_packages: + - borg + - borgmatic + +python_bin: python3 +pip_bin: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/vars/RedHat-8.yml b/roles/borgbase.ansible_role_borgbackup/vars/RedHat-8.yml new file mode 100644 index 0000000..4497b8d --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/RedHat-8.yml @@ -0,0 +1,24 @@ +--- +borg_dep_packages: + - openssh-clients + +borg_cron_package: cronie + +borg_pip_packages: + - libacl-devel + - libacl + - gcc + - gcc-c++ + - openssl-devel + - python3-pip + - python3-wheel + - python3-devel + - python3-setuptools + - python3-virtualenv + +borg_distro_packages: + - borgbackup + - borgmatic + +python_bin: python3 +pip_bin: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/vars/RedHat-9.yml b/roles/borgbase.ansible_role_borgbackup/vars/RedHat-9.yml new file mode 100644 index 0000000..2b900f4 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/RedHat-9.yml @@ -0,0 +1,24 @@ +--- +borg_dep_packages: + - openssh-clients + +borg_cron_package: cronie + +borg_pip_packages: + - libacl-devel + - libacl + - gcc + - gcc-c++ + - openssl-devel + - python3-pip + # - python3-wheel + - python3-devel + - python3-setuptools + # - python3-virtualenv + +borg_distro_packages: + - borgbackup + - borgmatic + +python_bin: python3 +pip_bin: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/vars/RedHat.yml b/roles/borgbase.ansible_role_borgbackup/vars/RedHat.yml new file mode 100644 index 0000000..3115a75 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/RedHat.yml @@ -0,0 +1,23 @@ +--- +borg_dep_packages: + - openssh-clients + +borg_cron_package: cronie + +borg_pip_packages: + - libacl-devel + - libacl + - gcc + - gcc-c++ + - openssl-devel + - python36-pip + - python36-wheel + - python36-devel + - python-setuptools + +borg_distro_packages: + - borgbackup + - borgmatic + +python_bin: python3 +pip_bin: pip3 diff --git a/roles/borgbase.ansible_role_borgbackup/vars/main.yml b/roles/borgbase.ansible_role_borgbackup/vars/main.yml new file mode 100644 index 0000000..9f34550 --- /dev/null +++ b/roles/borgbase.ansible_role_borgbackup/vars/main.yml @@ -0,0 +1,10 @@ +--- +borg_dependent_python_packages: + - cython + - pkgconfig + +borg_python_packages: + - name: borgbackup + version: "{{ borg_version }}" + - name: borgmatic + version: "{{ borgmatic_version }}"