From b993d55f55dd4772a4c76eea19c550cf8158398b Mon Sep 17 00:00:00 2001 From: "pulsar89.5" Date: Tue, 7 Mar 2023 00:13:54 +0100 Subject: [PATCH] feat: Create role --- README.md | 4 +- defaults/main.yml | 57 ++++++++++++++ handlers/main.yml | 28 +++++++ meta/main.yml | 2 +- tasks/configuration.yml | 109 ++++++++++++++++++++++++++ tasks/configuration_borgserver.yml | 20 +++++ tasks/configuration_borgwarehouse.yml | 38 +++++++++ tasks/installation.yml | 39 +++++++++ tasks/main.yml | 10 +++ templates/borgmatic.service.j2 | 66 ++++++++++++++++ templates/borgmatic.timer.j2 | 12 +++ templates/config.yaml.j2 | 71 +++++++++++++++++ vars/main.yml | 13 +++ 13 files changed, 466 insertions(+), 3 deletions(-) create mode 100644 defaults/main.yml create mode 100644 handlers/main.yml create mode 100644 tasks/configuration.yml create mode 100644 tasks/configuration_borgserver.yml create mode 100644 tasks/configuration_borgwarehouse.yml create mode 100644 tasks/installation.yml create mode 100644 tasks/main.yml create mode 100644 templates/borgmatic.service.j2 create mode 100644 templates/borgmatic.timer.j2 create mode 100644 templates/config.yaml.j2 create mode 100644 vars/main.yml diff --git a/README.md b/README.md index 3edfbf7..02a9f58 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ -# role_modele +# role_borgmatic -Modèle \ No newline at end of file +Deploy and configure borgmatic. diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..0168e65 --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,57 @@ +--- +# defaults file for borgmatic + +# Define path to store borgmatic configuration and ssh keys +borgmatic_conf_path: /etc/borgmatic.d + +# Configure bormatic +borgmatic_checks: + - name: repository + frequency: 4 weeks + - name: extract + frequency: 2 weeks + +borgmatic_healthchecks: "" +borgmatic_exclude_patterns: [] + +borgmatic_repositories: [] +# Exemple: +# - label: default +# path: ssh://user@backupserver/./sourcehostname.borg + +borgmatic_source_directories: [] + +borgmatic_retention: + daily: 7 + monthly: 0 + weekly: 4 + +borgmatic_name: "{{ inventory_hostname }}_{now}" +borgmatic_compression: lz4 +borgmatic_passphrase: "" + +borgmatic_actions: {} + +borgmatic_mariadb_enabled: false +borgmatic_postgresql_enabled: false +borgmatic_restore_first: true + +# targets +## BorgServer configuration +borgmatic_server: {} +# Example: +# host: borg.service.gaia.ykn.fr +# repo_path: /srv/borg +# user: borg +# group: borg + +## BorgWarehouse configuration +borgmatic_borgwarehouse_url: "" +borgmatic_borgwarehouse_key: "" +borgmatic_borgwarehouse_alias: "{{ inventory_hostname }}" +borgmatic_borgwarehouse_sshPublicKey: "" +borgmatic_borgwarehouse_storageSize: 100 +borgmatic_borgwarehouse_comment: "" +borgmatic_borgwarehouse_alert: 90000 # one day +borgmatic_borgwarehouse_lanCommand: true +borgmatic_borgwarehouse_appendOnlyMode: false diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..182bff8 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,28 @@ +--- +# handlers file for borgmatic + +- name: Initialize repository + ansible.builtin.command: + argv: + - "{{ which_borgmatic.stdout }}" + - init + - --encryption + - repokey + when: repo_infos | length == 0 + become: true + loop: "{{ borgmatic_repositories }}" + +- name: Restore repository + ansible.builtin.command: + argv: + - "{{ which_borgmatic.stdout }}" + - extract + - --archive + - latest + - --destination + - / + when: + - repo_infos | length > 0 + - borgmatic_restore_first + become: true + loop: "{{ borgmatic_repositories }}" diff --git a/meta/main.yml b/meta/main.yml index c58bebf..e6acbc8 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,7 +1,7 @@ galaxy_info: namespace: ykn author: pulsar89.5 - description: Rôle modèle + description: Deploy and configure borgmatic license: GPL-3.0-or-later diff --git a/tasks/configuration.yml b/tasks/configuration.yml new file mode 100644 index 0000000..d1490b3 --- /dev/null +++ b/tasks/configuration.yml @@ -0,0 +1,109 @@ +--- +# tasks file for borgmatic +# +- name: Get path to borgmatic + ansible.builtin.command: + cmd: which borgmatic + become: true + register: which_borgmatic + +- name: Create configuration example + ansible.builtin.command: + argv: + - "{{ which_borgmatic.stdout }}" + - config + - generate + - --destination + - "{{ borgmatic_conf_path }}/config.yaml" + creates: "{{ borgmatic_conf_path }}/config.yaml" + become: true + +- name: Create keys pair + community.crypto.openssh_keypair: + path: "{{ borgmatic_conf_path }}/id_ed25519" + comment: borgmatic@{{ inventory_hostname }} + type: ed25519 + become: true + +- name: Get public key content + ansible.builtin.slurp: + src: "{{ borgmatic_conf_path }}/id_ed25519.pub" + become: true + register: id_ed25519 + +- name: Import task to manage borgwarehouse repository + ansible.builtin.import_tasks: + file: configuration_borgwarehouse.yml + when: borgmatic_borgwarehouse_url | length > 0 + +- name: Import task to manage borgserver repository + ansible.builtin.import_tasks: + file: configuration_borgserver.yml + when: borgmatic_server | length > 0 + +- name: Deploy ssh configuration + ansible.builtin.blockinfile: + path: /root/.ssh/config + owner: root + group: root + mode: u=rw,g=r,o= + create: true + marker: "# {mark} ANSIBLE MANAGED BLOCK for role_borgmatic" + block: | + {% for repository in borgmatic_repositories %} + Host {{ repository.path | ansible.builtin.urlsplit('hostname') }} + Compression yes + Protocol 2 + PreferredAuthentications=publickey + StrictHostKeyChecking no + UserKnownHostsFile /dev/null + IdentityFile {{ borgmatic_conf_path }}/id_ed25519 + IdentitiesOnly yes + {% endfor %} + become: true + +- name: Deploy borgmatic configuration + ansible.builtin.template: + src: config.yaml.j2 + dest: "{{ borgmatic_conf_path }}/config.yaml" + owner: root + group: root + mode: u=rw,g=,o= + validate: borgmatic config validate --config %s + become: true + notify: + - Initialize repository + - Restore repository + +- name: Deploy borgmatic.service + ansible.builtin.template: + src: borgmatic.service.j2 + dest: /etc/systemd/system/borgmatic.service + owner: root + group: root + mode: u=rw,g=r,o=r + become: true + +- name: Ensure borgmatic.service is disabled + ansible.builtin.systemd: + enabled: false + state: stopped + name: borgmatic.service + become: true + +- name: Deploy borgmatic.timer + ansible.builtin.template: + src: borgmatic.timer.j2 + dest: /etc/systemd/system/borgmatic.timer + owner: root + group: root + mode: u=rw,g=r,o=r + become: true + +- name: Enable and start borgmatic.service + ansible.builtin.systemd: + daemon_reload: true + enabled: true + state: started + name: borgmatic.timer + become: true diff --git a/tasks/configuration_borgserver.yml b/tasks/configuration_borgserver.yml new file mode 100644 index 0000000..29bff66 --- /dev/null +++ b/tasks/configuration_borgserver.yml @@ -0,0 +1,20 @@ +--- + +- name: Create repository on borg server + ansible.builtin.file: + path: "{{ borgmatic_server.repo_path }}/{{ inventory_hostname }}" + state: directory + owner: "{{ borgmatic_server.user }}" + group: "{{ borgmatic_server.group }}" + mode: u=rwX,g=rX,o= + become: true + delegate_to: "{{ borgmatic_server.host }}" + +- name: Deploy public key on borg server + ansible.posix.authorized_key: + user: "{{ borgmatic_server.user }}" + state: present + key: "{{ key['content'] | b64decode }}" + key_options: 'command="cd {{ borgmatic_server.repo_path }}/{{ inventory_hostname }};borg serve --restrict-to-path {{ borgmatic_server.repo_path }}/{{ inventory_hostname }}",restrict' + become: true + delegate_to: "{{ borgmatic_server.host }}" diff --git a/tasks/configuration_borgwarehouse.yml b/tasks/configuration_borgwarehouse.yml new file mode 100644 index 0000000..4ad5e77 --- /dev/null +++ b/tasks/configuration_borgwarehouse.yml @@ -0,0 +1,38 @@ +--- + +- name: Get list of repositories + ansible.builtin.uri: + url: "{{ borgmatic_borgwarehouse_url }}/api/repo" + method: GET + headers: + Authorization: "Bearer {{ borgmatic_borgwarehouse_key }}" + Content-Type: "application/json" + body_format: json + return_content: true + register: repo_list + +- name: Get repository informations + ansible.builtin.set_fact: + repo_infos: "{{ repo_list.json.repoList | selectattr('alias', 'equalto', inventory_hostname) }}" + +- name: Create repository + ansible.builtin.uri: + url: "{{ borgmatic_borgwarehouse_url }}/api/repo/add" + method: POST + headers: + Authorization: "Bearer {{ borgmatic_borgwarehouse_key }}" + Content-Type: "application/json" + body_format: json + body: "{{ borgwarehouse_body }}" + when: repo_infos | length == 0 + +- name: Update repository + ansible.builtin.uri: + url: "{{ borgmatic_borgwarehouse_url }}/api/repo/id/{{ repo_infos[0].id }}/edit" + method: PATCH + headers: + Authorization: "Bearer {{ borgmatic_borgwarehouse_key }}" + Content-Type: "application/json" + body_format: json + body: "{{ borgwarehouse_body }}" + when: repo_infos | length > 0 diff --git a/tasks/installation.yml b/tasks/installation.yml new file mode 100644 index 0000000..479b005 --- /dev/null +++ b/tasks/installation.yml @@ -0,0 +1,39 @@ +--- +# tasks file for borgmatic + +- name: Install prerequisites + ansible.builtin.apt: + name: + - pipx + - build-essential + - libacl1-dev + - libacl1 + - libb2-dev + - liblz4-dev + - libssl-dev + - libxxhash-dev + - libzstd-dev + - pkg-config + - python3 + - python3-dev + - python3-pkgconfig + become: true + +- name: Install packages + community.general.pipx: + state: latest + name: "{{ item }}" + install_deps: true + include_injected: true + become: true + environment: + PIPX_HOME: /opt/pipx + PIPX_BIN_DIR: /usr/local/bin + loop: + - borgbackup + - borgmatic + +- name: Ensure binary installed by pipx are in the path # noqa: no-changed-when + ansible.builtin.command: + cmd: pipx ensurepath + become: true diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..639a8c3 --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,10 @@ +--- +# tasks file for borgmatic + +- name: Importer les tâches d'installation + ansible.builtin.include_tasks: + file: installation.yml + +- name: Importer les tâches de configuration + ansible.builtin.import_tasks: + file: configuration.yml diff --git a/templates/borgmatic.service.j2 b/templates/borgmatic.service.j2 new file mode 100644 index 0000000..79e77f8 --- /dev/null +++ b/templates/borgmatic.service.j2 @@ -0,0 +1,66 @@ +# {{ ansible_managed }} + +[Unit] +Description=borgmatic backup +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 + +# 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 + +Restart=no +# Prevent rate limiting of borgmatic log events. If you are using an older version of systemd that +# doesn't support this (pre-240 or so), you may have to remove this option. +LogRateLimitIntervalSec=0 + +# Delay start to prevent backups running during boot. Note that systemd-inhibit requires dbus and +# dbus-user-session to be installed. +ExecStartPre=sleep 1m +ExecStart=systemd-inhibit --who="borgmatic" --what="sleep:shutdown" --why="Prevent interrupting scheduled backup" {{ which_borgmatic.stdout }} --verbosity -1 --syslog-verbosity 1 diff --git a/templates/borgmatic.timer.j2 b/templates/borgmatic.timer.j2 new file mode 100644 index 0000000..4a98b80 --- /dev/null +++ b/templates/borgmatic.timer.j2 @@ -0,0 +1,12 @@ +# {{ ansible_managed }} + +[Unit] +Description=Run borgmatic backup + +[Timer] +OnCalendar=daily +Persistent=true +RandomizedDelaySec=3h + +[Install] +WantedBy=timers.target diff --git a/templates/config.yaml.j2 b/templates/config.yaml.j2 new file mode 100644 index 0000000..34853f7 --- /dev/null +++ b/templates/config.yaml.j2 @@ -0,0 +1,71 @@ +--- +# {{ ansible_managed }} + +repositories: +{% for repository in borgmatic_repositories %} + - label: {{ repository.label }} + path: {{ repository.path }} +{% endfor %} + +{% if borgmatic_exclude_patterns | length > 0 %} +exclude_patterns: +{% for pattern in borgmatic_exclude_patterns %} + - {{ pattern }} +{% endfor %} +{% endif %} + +{% if borgmatic_source_directories | length > 0%} +source_directories: +{% for directory in borgmatic_source_directories %} + - {{ directory }} +{% endfor %} +{% endif %} + +archive_name_format: {{ borgmatic_name }} +compression: {{ borgmatic_compression }} +encryption_passphrase: {{ borgmatic_passphrase }} + +checks: +{% for check in borgmatic_checks %} + - name: {{ check.name }} + frequency: {{ check.frequency }} +{% endfor %} + +healthchecks: + ping_url: {{ borgmatic_healthchecks }} + send_logs: false +# states: +# - finish +# - fail + +{% if borgmatic_retention | length > 0 %} +{% for param, value in borgmatic_retention.items() %} +keep_{{ param }}: {{ value }} +{% endfor %} +{% endif %} + +{% if borgmatic_actions | length > 0 %} +{% for param, values in borgmatic_actions.items() %} +{{ param }}: +{% for value in values %} + - {{ value }} +{% endfor %} +{% endfor %} +{% endif %} + +{% if borgmatic_mariadb_enabled %} +mariadb_databases: + - name: all + format: sql + add_drop_database: true +{% endif %} + +{% if borgmatic_postgresql_enabled %} +postgresql_databases: + - name: all + username: postgres + format: plain + pg_dump_command: sudo -u postgres pg_dump + pg_restore_command: sudo -u postgres pg_restore + psql_command: sudo -u postgres psql +{% endif %} diff --git a/vars/main.yml b/vars/main.yml new file mode 100644 index 0000000..72ca385 --- /dev/null +++ b/vars/main.yml @@ -0,0 +1,13 @@ +--- +# vars file for borgmatic + +borgwarehouse_body: > + { + "alert": {{ borgmatic_borgwarehouse_alert }}, + "alias": "{{ borgmatic_borgwarehouse_alias }}", + "appendOnlyMode": {{ borgmatic_borgwarehouse_appendOnlyMode }}, + "comment": "{{ borgmatic_borgwarehouse_comment }}", + "lanCommand": {{ borgmatic_borgwarehouse_lanCommand }}, + "sshPublicKey": "{{ id_ed25519['content'] | b64decode | trim }}", + "storageSize": {{ borgmatic_borgwarehouse_storageSize | int }}, + }