aboutsummaryrefslogtreecommitdiffstats
path: root/tasks/client_setup.yml
blob: a1a2267f73be63f1c5b25e06e06d1d3a1b861490 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
---
- 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: "{{ 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: 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_client_user_home }}/.ssh/id_{{ borg_ssh_key_type }}"
    type: "{{ borg_ssh_key_type }}"
    owner: "{{ borg_client_user }}"
    group: "{{ borg_client_user }}"
    mode: "0600"
    comment: "{{ borg_client_user }}@{{ inventory_hostname }}"
  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
    group: borg
    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: 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: 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 }}
      {{ 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@{{ borg_server_host_url }}:{{ borg_server_user_home }}/{{ borg_repo_name }}
  environment:
    BORG_PASSPHRASE: "{{ borg_passphrase }}"
  become: true
  become_user: "{{ borg_client_user }}"
  register: init_borg_output
  changed_when: init_borg_output.rc != 2
  failed_when:
    - init_borg_output.rc != 2
    - init_borg_output.rc != 0

- 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@{{ borg_server_host_url }}:{{ borg_server_user_home }}/{{ borg_repo_name }}
      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