--- - name: Ensure borg_client_user exists ansible.builtin.getent: database: passwd key: "{{ borg_client_user }}" become: true - name: Compute borg_client_user_home if not set ansible.builtin.set_fact: borg_client_user_home: "{{ ansible_facts.getent_passwd[borg_client_user][4] }}" when: borg_client_user_home is not defined - name: Validate borg_client_user home exists ansible.builtin.stat: path: "{{ borg_client_user_home }}" register: user_home_stat become: true - name: Fail if borg_client_user home missing ansible.builtin.fail: msg: | Home directory {{ borg_client_user_home }} for user {{ borg_client_user }} does not exist. Please ensure the user has a valid home directory before running this role. when: not user_home_stat.stat.exists - name: Check readability of included paths ansible.builtin.stat: path: "{{ item }}" loop: "{{ borg_included_dirs }}" register: included_paths_stat become: true become_user: "{{ borg_client_user }}" when: borg_included_dirs | length > 0 - name: Compute SSH key identifier ansible.builtin.set_fact: borg_ssh_key_identifier: "{{ (borg_server_host_url ~ '_' ~ borg_repo_name) | regex_replace('[^a-zA-Z0-9]', '_') }}" - name: Compute SSH key path ansible.builtin.set_fact: borg_ssh_key_path: >- {{ borg_client_user_home }}/.ssh/id_{{ borg_ssh_key_type }} {%- if borg_ssh_key_per_repo -%} _borgbackup_{{ borg_ssh_key_identifier }} {%- endif -%} - name: Create SSH Directory ansible.builtin.file: path: "{{ borg_client_user_home }}/.ssh" owner: "{{ borg_client_user }}" group: "{{ borg_client_user }}" mode: "0700" state: directory become: true - name: Add borg server to known_hosts ansible.builtin.known_hosts: name: "{{ borg_server_host_url }}" key: "{{ borg_server_host_url }} {{ borg_server_host_ssh_key }}" path: "{{ borg_client_user_home }}/.ssh/known_hosts" state: present become: true become_user: "{{ borg_client_user }}" - name: Generate SSH keys community.crypto.openssh_keypair: path: "{{ borg_ssh_key_path }}" type: "{{ borg_ssh_key_type }}" owner: "{{ borg_client_user }}" group: "{{ borg_client_user }}" mode: "0600" comment: "{{ borg_client_user }}@{{ inventory_hostname }}{% if borg_ssh_key_per_repo %}_{{ borg_ssh_key_identifier }}{% endif %}" become: true register: ssh_key - name: Ensure authorized_keys file exists on borg server ansible.builtin.file: path: "{{ borg_server_user_home }}/.ssh/authorized_keys" state: touch owner: "{{ borg_server_user }}" group: "{{ borg_server_user }}" mode: "0600" access_time: preserve modification_time: preserve become: true delegate_to: "{{ borg_server_host }}" - name: Read existing authorized_keys content ansible.builtin.slurp: src: "{{ borg_server_user_home }}/.ssh/authorized_keys" become: true delegate_to: "{{ borg_server_host }}" register: auth_keys_content - name: Find existing authorized_keys line for this host ansible.builtin.set_fact: existing_line: >- {{ (auth_keys_content.content | b64decode).splitlines() | select("search", ssh_key.public_key | trim | regex_escape) | first | default("") }} - name: Detect --append-only setting from existing entry ansible.builtin.set_fact: existing_append_only: "{{ existing_line is search('--append-only') }}" when: existing_line | length > 0 - name: Detect --storage-quota setting from existing entry ansible.builtin.set_fact: existing_storage_quota: >- {{ (existing_line | regex_findall('--storage-quota[= ](\d+[KMGT]?)') | first | default("")) }} when: existing_line | length > 0 - name: Fail if --append-only setting differs from existing entry ansible.builtin.fail: msg: | Inconsistent --append-only setting for host {{ inventory_hostname }}. Existing entry has --append-only={{ existing_append_only }}, but current invocation uses --append-only={{ borg_mode_append_only }}. All repositories for a host must have the same --append-only setting. when: - existing_line | length > 0 - existing_append_only != borg_mode_append_only - name: Fail if --storage-quota setting differs from existing entry ansible.builtin.fail: msg: | Inconsistent --storage-quota setting for host {{ inventory_hostname }}. Existing entry has --storage-quota={{ existing_storage_quota }}, but current invocation uses --storage-quota={{ borg_storage_quota }}. All repositories for a host must have the same --storage-quota setting. when: - existing_line | length > 0 - existing_storage_quota != (borg_storage_quota | default("")) - name: Compute all repos for this host ansible.builtin.set_fact: all_repos: >- {{ ( (existing_line | regex_findall('--restrict-to-repository ([^\s"]+)')) + [borg_server_user_home + '/' + borg_repo_name] ) | unique | sort }} - name: Update authorized_keys entry for this host ansible.builtin.lineinfile: path: "{{ borg_server_user_home }}/.ssh/authorized_keys" search_string: "{{ ssh_key.public_key | trim }}" line: >- restrict,command="borg serve{{ " --append-only" if borg_mode_append_only }}{{ " --storage-quota " ~ borg_storage_quota if borg_storage_quota }} {{ all_repos | map('regex_replace', '^', '--restrict-to-repository ') | join(' ') }}" {{ ssh_key.public_key | trim }} {{ borg_client_user }}@{{ inventory_hostname }} state: present become: true delegate_to: "{{ borg_server_host }}" - name: Initialise Borg repository ansible.builtin.command: > borg init --encryption=repokey {{ borg_server_user }}@{{ borg_server_host_url }}:{{ borg_server_user_home }}/{{ borg_repo_name }} environment: BORG_PASSPHRASE: "{{ borg_passphrase }}" BORG_RSH: "{{ ('ssh -i ' ~ borg_ssh_key_path) if borg_ssh_key_per_repo else omit }}" become: true become_user: "{{ borg_client_user }}" register: init_borg_output changed_when: init_borg_output.rc == 0 failed_when: - init_borg_output.rc not in [0, 2] or ( init_borg_output.rc == 2 and 'already exists' not in init_borg_output.stdout and 'already exists' not in init_borg_output.stderr ) - name: Export decryption keys when: borg_decryption_keys_yaml_path | default('') | length > 0 block: - name: Make sure key file exists ansible.builtin.file: path: "{{ borg_decryption_keys_yaml_path }}" state: touch mode: "0600" access_time: preserve modification_time: preserve delegate_to: localhost become: false - name: Read Vars file ansible.builtin.include_vars: file: "{{ borg_decryption_keys_yaml_path }}" register: local - name: Add repository encryption keys to ansible repo when: not (inventory_hostname ~ '_' ~ borg_repo_name) in local.ansible_facts throttle: 1 block: - name: If host new read encryption keys ansible.builtin.command: > borg key export --paper {{ borg_server_user }}@{{ borg_server_host_url }}:{{ borg_server_user_home }}/{{ borg_repo_name }} environment: BORG_RSH: "{{ ('ssh -i ' ~ borg_ssh_key_path) if borg_ssh_key_per_repo else omit }}" become: true become_user: "{{ borg_client_user }}" register: borg_keys changed_when: borg_keys.rc != 0 - name: If host new add encryption keys to vars ansible.builtin.set_fact: decryption_keys: "{{ local.ansible_facts | combine({(inventory_hostname ~ '_' ~ borg_repo_name): borg_keys.stdout}) }}" - name: Update encryption vars ansible.builtin.copy: content: "{{ decryption_keys | to_nice_yaml(indent=2, width=2048) }}" dest: "{{ borg_decryption_keys_yaml_path }}" mode: "0600" when: decryption_keys is defined delegate_to: localhost become: false - name: Create backup scripts ansible.builtin.include_tasks: client_create_scripts_each.yml loop: - "{{ borg_backup_script_location }}" - "{{ borg_backup_script_location }}{{ '@' if borg_backup_argument != '' else '' }}{{ borg_backup_argument }}" loop_control: loop_var: script_location - name: Configure systemd borg_backup service ansible.builtin.template: src: borg_backup.service.j2 dest: /etc/systemd/system/{{ borg_backup_timer_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.service mode: "0644" owner: root group: root notify: Reload systemd become: true - name: Configure systemd borg_backup timer ansible.builtin.template: src: borg_backup.timer.j2 dest: /etc/systemd/system/{{ borg_backup_timer_name }}{{ "@" if borg_backup_argument != "" }}{{ borg_backup_argument }}.timer mode: "0644" owner: root group: root notify: Reload systemd become: true - name: Reload systemd now before enabling services ansible.builtin.meta: flush_handlers - name: Enable borg_backup systemd timer ansible.builtin.systemd: name: "{{ borg_backup_timer_name }}{{ '@' if borg_backup_argument != '' else '' }}{{ borg_backup_argument }}.timer" state: started enabled: true become: true