diff --git a/defaults/main.yml b/defaults/main.yml index a19056c..76d7319 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,3 +1,45 @@ --- -backup_ssh_key_path: /root/.ssh/borgbase_backup +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_file_path: "/root/.ssh/{{ borg_ssh_key_name }}" +borg_ssh_key_name: borgbackup +borg_ssh_key_type: "ed25519" +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) }}" + borgbase_ssh_key_name: "backup@{{ inventory_hostname }}" + +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: false diff --git a/meta/main.yml b/meta/main.yml index 141028c..ebd39a2 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,6 +1,6 @@ galaxy_info: author: Sense.Lab e.V. administrators - description: Role to configure borgbase repositories and ssh keys + description: Role to configure (borgbase) borgbackup company: Sense.Lab e.V. license: GPLv3 min_ansible_version: "2.9" diff --git a/tasks/borgbase.yml b/tasks/borgbase.yml new file mode 100644 index 0000000..32857fa --- /dev/null +++ b/tasks/borgbase.yml @@ -0,0 +1,24 @@ +--- +- name: Read key from file + ansible.builtin.slurp: + src: "{{ borg_ssh_key_file_path }}/{{ borg_ssh_key_name }}.pub" + register: ssh_key + check_mode: true + +- name: Add key to borgbase + adhawkins.borgbase.borgbase_ssh: + state: present + apikey: "{{ borgbase_api_key }}" + name: "{{ borgbase_ssh_key_name }}" + key: "{{ (ssh_key.content | b64decode).split()[:2] | join(' ') }}" + register: borgbase_key + +- name: Create repo + adhawkins.borgbase.borgbase_repo: + state: present + apikey: "{{ borgbase_api_key }}" + name: "{{ ansible_hostname }}" + full_access_keys: ["{{ borgbase_key.key_id }}"] + quota_enabled: false + alert_days: 1 + register: borgbase_repo diff --git a/tasks/main.yml b/tasks/main.yml index 33c5e28..8f7f888 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,29 +1,29 @@ --- +- name: Install Packages + ansible.builtin.import_tasks: packages.yml + tags: packages + - name: Generate an OpenSSH keypair community.crypto.openssh_keypair: - path: "{{ backup_ssh_key_path }}" + path: "{{ borg_ssh_key_file_path }}" type: "ed25519" -- name: Read key from file - ansible.builtin.slurp: - src: "{{ backup_ssh_key_path }}.pub" - register: ssh_key - check_mode: true +- name: Setup borgbase backup + ansible.builtin.import_tasks: borgbase.yml + when: borgbase is defined and borgbase + tags: borgbase -- name: Add key to borgbase - adhawkins.borgbase.borgbase_ssh: - state: present - apikey: "{{ borgbase_api_key }}" - name: "{{ borgbase_ssh_key_name }}" - key: "{{ (ssh_key.content | b64decode).split()[:2] | join(' ') }}" - register: borgbase_key +- name: Copy borgmatic configuration + ansible.builtin.template: + src: config.yaml.j2 + dest: "/etc/borgmatic/{{ borgmatic_config_name }}" + mode: "0600" -- name: Create repo - adhawkins.borgbase.borgbase_repo: - state: present - apikey: "{{ borgbase_api_key }}" - name: "{{ ansible_hostname }}" - full_access_keys: ["{{ borgbase_key.key_id }}"] - quota_enabled: false - alert_days: 1 - register: borgbase_repo +- name: Add single Cron job for borgmatic + ansible.builtin.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 }}" diff --git a/tasks/packages.yml b/tasks/packages.yml new file mode 100644 index 0000000..9ad1eff --- /dev/null +++ b/tasks/packages.yml @@ -0,0 +1,6 @@ +--- +- name: "Packages | Install borgbackup and borgmatic" + ansible.builtin.apt: + pkg: + - borgbackup + - borgmatic diff --git a/templates/config.yaml.j2 b/templates/config.yaml.j2 new file mode 100644 index 0000000..dc7b485 --- /dev/null +++ b/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 %}