aboutsummaryrefslogtreecommitdiffstats
path: root/tasks
diff options
context:
space:
mode:
Diffstat (limited to 'tasks')
-rw-r--r--tasks/absent.yml298
-rw-r--r--tasks/client_setup.yml21
-rw-r--r--tasks/main.yml7
3 files changed, 319 insertions, 7 deletions
diff --git a/tasks/absent.yml b/tasks/absent.yml
new file mode 100644
index 0000000..b1a5592
--- /dev/null
+++ b/tasks/absent.yml
@@ -0,0 +1,298 @@
+---
+- name: Ensure borg_client_user exists
+ ansible.builtin.getent:
+ database: passwd
+ key: "{{ borg_client_user }}"
+ become: true
+ failed_when: false
+
+- 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
+ - ansible_facts.getent_passwd[borg_client_user] is defined
+
+- 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 -%}
+ when: borg_client_user_home is defined
+
+- name: Read SSH public key
+ ansible.builtin.slurp:
+ src: "{{ borg_ssh_key_path }}.pub"
+ register: ssh_pubkey_slurp
+ become: true
+ when: borg_ssh_key_path is defined
+ failed_when: false
+
+- name: Check if systemd timer exists
+ ansible.builtin.stat:
+ path: /etc/systemd/system/{{ borg_backup_timer_name }}{{ '@' if borg_backup_argument != '' }}{{ borg_backup_argument }}.timer
+ register: timer_stat
+ become: true
+
+- name: Stop systemd timer
+ ansible.builtin.systemd:
+ name: "{{ borg_backup_timer_name }}{{ '@' if borg_backup_argument != '' }}{{ borg_backup_argument }}.timer"
+ state: stopped
+ enabled: false
+ become: true
+ when: timer_stat.stat.exists
+
+- name: Check if systemd service exists
+ ansible.builtin.stat:
+ path: /etc/systemd/system/{{ borg_backup_service_name }}{{ '@' if borg_backup_argument != '' }}{{ borg_backup_argument }}.service
+ register: service_stat
+ become: true
+
+- name: Stop systemd service
+ ansible.builtin.systemd:
+ name: "{{ borg_backup_service_name }}{{ '@' if borg_backup_argument != '' }}{{ borg_backup_argument }}.service"
+ state: stopped
+ enabled: false
+ become: true
+ when: service_stat.stat.exists
+
+- name: Remove systemd timer file
+ ansible.builtin.file:
+ path: /etc/systemd/system/{{ borg_backup_timer_name }}{{ '@' if borg_backup_argument != '' }}{{ borg_backup_argument }}.timer
+ state: absent
+ become: true
+
+- name: Remove systemd service file
+ ansible.builtin.file:
+ path: /etc/systemd/system/{{ borg_backup_service_name }}{{ '@' if borg_backup_argument != '' }}{{ borg_backup_argument }}.service
+ state: absent
+ become: true
+
+- name: Reload systemd daemon
+ ansible.builtin.systemd:
+ daemon_reload: true
+ become: true
+
+- name: Check if base backup script exists
+ ansible.builtin.stat:
+ path: "{{ borg_backup_script_location }}"
+ register: base_script_stat
+ become: true
+
+- name: Remove repo-specific backup script
+ ansible.builtin.file:
+ path: "{{ borg_backup_script_location }}{{ '@' if borg_backup_argument != '' }}{{ borg_backup_argument }}"
+ state: absent
+ become: true
+ when: borg_backup_argument | length > 0
+
+- name: Remove block from base backup script
+ ansible.builtin.blockinfile:
+ path: "{{ borg_backup_script_location }}"
+ marker: "## {mark} ANSIBLE MANAGED BLOCK for {{ borg_server_host_url }}/{{ borg_repo_name }}"
+ state: absent
+ become: true
+ when: base_script_stat.stat.exists
+
+- name: Read base script content
+ ansible.builtin.slurp:
+ src: "{{ borg_backup_script_location }}"
+ register: base_script_content
+ become: true
+ when: base_script_stat.stat.exists
+
+- name: Remove empty base script
+ ansible.builtin.file:
+ path: "{{ borg_backup_script_location }}"
+ state: absent
+ become: true
+ when:
+ - base_script_stat.stat.exists
+ - ('ANSIBLE MANAGED BLOCK' not in (base_script_content.content | b64decode))
+
+- name: Remove per-repo SSH private key
+ ansible.builtin.file:
+ path: "{{ borg_ssh_key_path }}"
+ state: absent
+ become: true
+ when:
+ - borg_ssh_key_per_repo
+ - borg_ssh_key_path is defined
+
+- name: Remove per-repo SSH public key
+ ansible.builtin.file:
+ path: "{{ borg_ssh_key_path }}.pub"
+ state: absent
+ become: true
+ when:
+ - borg_ssh_key_per_repo
+ - borg_ssh_key_path is defined
+
+- name: Read existing authorized_keys on server
+ ansible.builtin.slurp:
+ src: "{{ borg_server_user_home }}/.ssh/authorized_keys"
+ become: true
+ delegate_to: "{{ borg_server_host }}"
+ register: auth_keys_slurp
+ ignore_errors: true
+
+- name: Update authorized_keys on server
+ when:
+ - auth_keys_slurp.content is defined
+ - ssh_pubkey_slurp.content is defined
+ block:
+ - name: Get public key content
+ ansible.builtin.set_fact:
+ ssh_public_key: "{{ ssh_pubkey_slurp.content | b64decode | trim }}"
+
+ - name: Normalize SSH public key for matching
+ ansible.builtin.set_fact:
+ ssh_public_key_material: >-
+ {{
+ ssh_public_key
+ | regex_search('^[^ ]+ [^ ]+')
+ | default(ssh_public_key)
+ }}
+
+ - name: Parse authorized_keys lines
+ ansible.builtin.set_fact:
+ auth_keys_lines: "{{ (auth_keys_slurp.content | b64decode).splitlines() }}"
+
+ - name: Find line matching this host's public key
+ ansible.builtin.set_fact:
+ matching_line: >-
+ {{
+ auth_keys_lines
+ | select('search', ssh_public_key_material | regex_escape)
+ | first
+ | default('')
+ }}
+
+ - name: Extract authorized_keys comment for shared key
+ ansible.builtin.set_fact:
+ authorized_keys_comment: "{{ matching_line | regex_search('([^\" ]+@[^\" ]+)$') | default(borg_client_user ~ '@' ~ inventory_hostname) }}"
+ when:
+ - not borg_ssh_key_per_repo
+ - matching_line | length > 0
+
+ - name: Remove entire authorized_keys line (per-repo key)
+ ansible.builtin.lineinfile:
+ path: "{{ borg_server_user_home }}/.ssh/authorized_keys"
+ regexp: "{{ ssh_public_key_material | regex_escape }}"
+ state: absent
+ become: true
+ delegate_to: "{{ borg_server_host }}"
+ when:
+ - borg_ssh_key_per_repo
+ - matching_line | length > 0
+
+ - name: Extract repos from authorized_keys line (shared key)
+ ansible.builtin.set_fact:
+ existing_repos: >-
+ {{
+ (matching_line | regex_findall('--restrict-to-repository ([^\s"]+)'))
+ }}
+ repo_to_delete: "{{ borg_server_user_home }}/{{ borg_repo_name }}"
+ remaining_repos: >-
+ {{
+ (matching_line | regex_findall('--restrict-to-repository ([^\s"]+)'))
+ | reject('eq', borg_server_user_home ~ '/' ~ borg_repo_name)
+ | list
+ }}
+ when:
+ - not borg_ssh_key_per_repo
+ - matching_line | length > 0
+
+ - name: Remove entire authorized_keys line (shared key, last repo)
+ ansible.builtin.lineinfile:
+ path: "{{ borg_server_user_home }}/.ssh/authorized_keys"
+ regexp: "{{ ssh_public_key_material | regex_escape }}"
+ state: absent
+ become: true
+ delegate_to: "{{ borg_server_host }}"
+ when:
+ - not borg_ssh_key_per_repo
+ - matching_line | length > 0
+ - existing_repos is defined
+ - repo_to_delete in existing_repos
+ - remaining_repos is defined
+ - remaining_repos | length == 0
+
+ - name: Update authorized_keys keeping other repos (shared key, multiple repos)
+ ansible.builtin.lineinfile:
+ path: "{{ borg_server_user_home }}/.ssh/authorized_keys"
+ search_string: "{{ ssh_public_key_material }}"
+ line: >-
+ restrict,command="borg serve
+ {{ ' --append-only' if ('--append-only' in matching_line) }}
+ {{ ' --storage-quota ' ~ (matching_line | regex_findall('--storage-quota[= ](\S+)') | first) if (matching_line | regex_search('--storage-quota')) }}
+ {{ (remaining_repos | map('regex_replace', '^', '--restrict-to-repository ')) | join(' ') }}"
+ {{ ssh_public_key }} {{ authorized_keys_comment }}
+ state: present
+ become: true
+ delegate_to: "{{ borg_server_host }}"
+ when:
+ - not borg_ssh_key_per_repo
+ - matching_line | length > 0
+ - authorized_keys_comment is defined
+ - existing_repos is defined
+ - repo_to_delete in existing_repos
+ - remaining_repos is defined
+ - remaining_repos | length > 0
+
+- name: Delete repository data on server
+ ansible.builtin.file:
+ path: "{{ borg_server_user_home }}/{{ borg_repo_name }}"
+ state: absent
+ become: true
+ delegate_to: "{{ borg_server_host }}"
+ when:
+ - borg_dangerously_delete_backups | default(false) | bool
+ - borg_server_user_home | default('') | length > 0
+ - borg_repo_name | default('') | length > 0
+
+- name: Remove decryption key entry
+ when: borg_decryption_keys_yaml_path | default('') | length > 0
+ block:
+ - name: Check if decryption keys file exists
+ ansible.builtin.stat:
+ path: "{{ borg_decryption_keys_yaml_path }}"
+ delegate_to: localhost
+ become: false
+ register: decryption_keys_stat
+
+ - name: Read existing decryption keys file
+ ansible.builtin.slurp:
+ src: "{{ borg_decryption_keys_yaml_path }}"
+ delegate_to: localhost
+ become: false
+ register: decryption_keys_slurp
+ when: decryption_keys_stat.stat.exists
+
+ - name: Remove entry for this host+repo
+ vars:
+ existing_keys: >-
+ {{
+ ((decryption_keys_slurp.content | b64decode | from_yaml) | default({}, true))
+ if decryption_keys_slurp.content is defined
+ else {}
+ }}
+ key_name: "{{ inventory_hostname ~ '_' ~ borg_repo_name }}"
+ ansible.builtin.copy:
+ content: >-
+ {{
+ existing_keys
+ | dict2items
+ | rejectattr('key', '==', key_name)
+ | items2dict
+ | to_nice_yaml(indent=2, width=2048)
+ }}
+ dest: "{{ borg_decryption_keys_yaml_path }}"
+ mode: "0600"
+ delegate_to: localhost
+ become: false
+ when: decryption_keys_slurp.content is defined
diff --git a/tasks/client_setup.yml b/tasks/client_setup.yml
index 5d162ed..ab09926 100644
--- a/tasks/client_setup.yml
+++ b/tasks/client_setup.yml
@@ -39,10 +39,7 @@
- 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 -%}
+ {{ 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:
@@ -73,6 +70,16 @@
become: true
register: ssh_key
+- name: Normalize SSH public key for matching
+ ansible.builtin.set_fact:
+ ssh_public_key: "{{ ssh_key.public_key | trim }}"
+ ssh_public_key_material: >-
+ {{
+ (ssh_key.public_key | trim)
+ | regex_search('^[^ ]+ [^ ]+')
+ | default(ssh_key.public_key | trim)
+ }}
+
- name: Ensure authorized_keys file exists on borg server
ansible.builtin.file:
path: "{{ borg_server_user_home }}/.ssh/authorized_keys"
@@ -97,7 +104,7 @@
existing_line: >-
{{
(auth_keys_content.content | b64decode).splitlines()
- | select("search", ssh_key.public_key | trim | regex_escape)
+ | select("search", ssh_public_key_material | regex_escape)
| first
| default("")
}}
@@ -149,11 +156,11 @@
- 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 }}"
+ search_string: "{{ ssh_public_key_material }}"
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 }}
+ {{ ssh_public_key }} {{ borg_client_user }}@{{ inventory_hostname }}
state: present
become: true
delegate_to: "{{ borg_server_host }}"
diff --git a/tasks/main.yml b/tasks/main.yml
index 7ffef06..6feca79 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -1,9 +1,16 @@
---
- name: Install dependencies
ansible.builtin.include_tasks: installation.yml
+ when: state == "present"
- name: Run setup on server
ansible.builtin.include_tasks: server_setup.yml
+ when: state == "present"
- name: Run setup on client
ansible.builtin.include_tasks: client_setup.yml
+ when: state == "present"
+
+- name: Remove borg backup configuration
+ ansible.builtin.include_tasks: absent.yml
+ when: state == "absent"