mirror of
https://github.com/fscotto/infra.git
synced 2026-09-27 19:03:47 +00:00
529 lines
17 KiB
YAML
529 lines
17 KiB
YAML
---
|
|
- name: Validate Atlas Borg backup configuration
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.assert:
|
|
that:
|
|
- atlas_manage_storage | bool
|
|
- atlas_zfs_pool != 'CHANGEME_ZFS_POOL'
|
|
- atlas_mount_root.startswith('/')
|
|
- atlas_borg_username is match('^[a-z_][a-z0-9_-]*$')
|
|
- atlas_borg_group is match('^[a-z_][a-z0-9_-]*$')
|
|
- atlas_borg_username not in ['root', atlas_admin_username]
|
|
- atlas_borg_group != 'wheel'
|
|
- atlas_borg_home.startswith('/var/lib/')
|
|
- atlas_borg_repository_host is match('^[A-Za-z0-9.-]+$')
|
|
- atlas_borg_repository_user is match('^[A-Za-z0-9_-]+$')
|
|
- atlas_borg_repository_port | int > 0
|
|
- atlas_borg_repository_port | int < 65536
|
|
- atlas_borg_repository_path is match('^\./[A-Za-z0-9][A-Za-z0-9._/-]*$')
|
|
- "'/../' not in ('/' ~ atlas_borg_repository_path ~ '/')"
|
|
- atlas_borg_remote_path is match('^borg-[0-9]+\.[0-9]+$')
|
|
- atlas_borg_host_key.startswith(
|
|
'[' ~ atlas_borg_repository_host ~ ']:' ~ (atlas_borg_repository_port | string) ~ ' ssh-ed25519 '
|
|
)
|
|
- atlas_borg_ssh_private_key_path.startswith('/etc/atlas-borg/')
|
|
- atlas_borg_known_hosts_path.startswith('/etc/atlas-borg/')
|
|
- atlas_borg_passphrase_path.startswith('/etc/atlas-borg/')
|
|
- atlas_borg_ssh_wrapper_path.startswith('/usr/local/libexec/')
|
|
- atlas_borg_encryption_mode == 'repokey'
|
|
- atlas_borg_archive_prefix is match('^[a-z0-9][a-z0-9_-]*$')
|
|
- atlas_borg_snapshot_prefix is match('^[a-z0-9][a-z0-9_-]*$')
|
|
- atlas_borg_keep_daily | int > 0
|
|
- atlas_borg_keep_weekly | int > 0
|
|
- atlas_borg_keep_monthly | int > 0
|
|
fail_msg: >-
|
|
Atlas Borg needs a safe relative repository path, a pinned ED25519 host
|
|
key, positive retention counts, and valid dedicated SSH settings.
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Create the Atlas Borg system group
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.group:
|
|
name: "{{ atlas_borg_group }}"
|
|
system: true
|
|
state: present
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Create the least-privilege Atlas Borg account
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.user:
|
|
name: "{{ atlas_borg_username }}"
|
|
group: "{{ atlas_borg_group }}"
|
|
groups: []
|
|
append: false
|
|
comment: Atlas Borg backup service
|
|
home: "{{ atlas_borg_home }}"
|
|
create_home: false
|
|
shell: /sbin/nologin
|
|
password_lock: true
|
|
system: true
|
|
state: present
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Read Atlas Borg account group membership
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- id
|
|
- -nG
|
|
- "{{ atlas_borg_username }}"
|
|
register: atlas_borg_account_groups
|
|
changed_when: false
|
|
check_mode: false
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Require the Atlas Borg account to have no supplementary groups
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.assert:
|
|
that:
|
|
- atlas_borg_account_groups.stdout.split() == [atlas_borg_group]
|
|
fail_msg: >-
|
|
The Atlas Borg service account must belong only to its private primary
|
|
group and must never receive wheel or other supplementary membership.
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Validate Atlas Borg systemd calendars
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- systemd-analyze
|
|
- calendar
|
|
- "{{ item }}"
|
|
loop:
|
|
- "{{ atlas_borg_backup_calendar }}"
|
|
- "{{ atlas_borg_check_calendar }}"
|
|
changed_when: false
|
|
check_mode: false
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Create Atlas Borg configuration directory
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.file:
|
|
path: /etc/atlas-borg
|
|
state: directory
|
|
owner: root
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0750"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Generate the dedicated Atlas Borg SSH identity
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- ssh-keygen
|
|
- -q
|
|
- -t
|
|
- ed25519
|
|
- -N
|
|
- ""
|
|
- -C
|
|
- atlas-borg@atlas
|
|
- -f
|
|
- "{{ atlas_borg_ssh_private_key_path }}"
|
|
creates: "{{ atlas_borg_ssh_private_key_path }}"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Protect the Atlas Borg private SSH identity
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.file:
|
|
path: "{{ atlas_borg_ssh_private_key_path }}"
|
|
owner: "{{ atlas_borg_username }}"
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0600"
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Set permissions on the Atlas Borg public SSH identity
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.file:
|
|
path: "{{ atlas_borg_ssh_private_key_path }}.pub"
|
|
owner: "{{ atlas_borg_username }}"
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0644"
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Read the dedicated Atlas Borg public SSH identity
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.slurp:
|
|
src: "{{ atlas_borg_ssh_private_key_path }}.pub"
|
|
register: atlas_borg_public_key
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Report the public SSH identity to install in the Hetzner sub-account
|
|
tags: [atlas, storage, backup, borg, borg_key]
|
|
ansible.builtin.debug:
|
|
msg: "{{ atlas_borg_public_key.content | b64decode | trim }}"
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Pin the Hetzner Storage Box SSH host key
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.copy:
|
|
content: "{{ atlas_borg_host_key }}\n"
|
|
dest: "{{ atlas_borg_known_hosts_path }}"
|
|
owner: "{{ atlas_borg_username }}"
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0600"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Create Atlas Borg state directories
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.file:
|
|
path: "{{ item }}"
|
|
state: directory
|
|
owner: "{{ atlas_borg_username }}"
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0700"
|
|
loop:
|
|
- "{{ atlas_borg_config_dir }}"
|
|
- "{{ atlas_borg_cache_dir }}"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Create the shared Atlas Borg operation lock
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.copy:
|
|
content: ""
|
|
dest: "{{ atlas_borg_lock_path }}"
|
|
owner: "{{ atlas_borg_username }}"
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0600"
|
|
force: false
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Require the Atlas Borg encryption passphrase from Vault
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.assert:
|
|
that:
|
|
- atlas_borg_passphrase | length >= 20
|
|
fail_msg: >-
|
|
Define vault_atlas_borg_passphrase with a strong unique value in the
|
|
encrypted Vault before activating the Borg repository.
|
|
no_log: true
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Install the Atlas Borg passphrase
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.copy:
|
|
content: "{{ atlas_borg_passphrase }}\n"
|
|
dest: "{{ atlas_borg_passphrase_path }}"
|
|
owner: "{{ atlas_borg_username }}"
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0600"
|
|
diff: false
|
|
no_log: true
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Install the Atlas Borg backup helper
|
|
tags: [atlas, storage, backup, borg, borg_logging]
|
|
ansible.builtin.template:
|
|
src: atlas-borg-backup.sh.j2
|
|
dest: /usr/local/sbin/atlas-borg-backup
|
|
owner: root
|
|
group: root
|
|
mode: "0750"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Install the Atlas Borg check helper
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.template:
|
|
src: atlas-borg-check.sh.j2
|
|
dest: /usr/local/sbin/atlas-borg-check
|
|
owner: root
|
|
group: "{{ atlas_borg_group }}"
|
|
mode: "0750"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Create the local libexec directory for the Atlas Borg SSH wrapper
|
|
tags: [atlas, storage, backup, borg, borg_logging]
|
|
ansible.builtin.file:
|
|
path: "{{ atlas_borg_ssh_wrapper_path | dirname }}"
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Install the Atlas Borg progress formatter
|
|
tags: [atlas, storage, backup, borg, borg_logging]
|
|
ansible.builtin.copy:
|
|
src: atlas-borg-progress.py
|
|
dest: /usr/local/libexec/atlas-borg-progress
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Install the capability-dropping Atlas Borg SSH wrapper
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.template:
|
|
src: atlas-borg-ssh.sh.j2
|
|
dest: "{{ atlas_borg_ssh_wrapper_path }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Install Atlas Borg systemd units
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.template:
|
|
src: "{{ item }}.j2"
|
|
dest: "/etc/systemd/system/{{ item }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
loop:
|
|
- atlas-borg-backup.service
|
|
- atlas-borg-backup.timer
|
|
- atlas-borg-check.service
|
|
- atlas-borg-check.timer
|
|
notify: Restart Atlas Borg timers
|
|
when: atlas_manage_borg_backup | bool
|
|
|
|
- name: Verify dedicated SSH access to the Hetzner Storage Box
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- ssh
|
|
- -T
|
|
- -i
|
|
- "{{ atlas_borg_ssh_private_key_path }}"
|
|
- -p
|
|
- "{{ atlas_borg_repository_port | string }}"
|
|
- -o
|
|
- BatchMode=yes
|
|
- -o
|
|
- IdentitiesOnly=yes
|
|
- -o
|
|
- StrictHostKeyChecking=yes
|
|
- -o
|
|
- "UserKnownHostsFile={{ atlas_borg_known_hosts_path }}"
|
|
- "{{ atlas_borg_repository_user }}@{{ atlas_borg_repository_host }}"
|
|
- pwd
|
|
register: atlas_borg_ssh_probe
|
|
become: true
|
|
become_user: "{{ atlas_borg_username }}"
|
|
changed_when: false
|
|
failed_when: false
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Require the dedicated public key on the Hetzner sub-account
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.assert:
|
|
that:
|
|
- atlas_borg_ssh_probe.rc == 0
|
|
fail_msg: >-
|
|
Install the reported Atlas Borg public key in the Hetzner sub-account
|
|
before rerunning the Borg tasks. Password authentication is never used.
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Probe the remote Atlas Borg repository path
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- ssh
|
|
- -T
|
|
- -i
|
|
- "{{ atlas_borg_ssh_private_key_path }}"
|
|
- -p
|
|
- "{{ atlas_borg_repository_port | string }}"
|
|
- -o
|
|
- BatchMode=yes
|
|
- -o
|
|
- IdentitiesOnly=yes
|
|
- -o
|
|
- StrictHostKeyChecking=yes
|
|
- -o
|
|
- "UserKnownHostsFile={{ atlas_borg_known_hosts_path }}"
|
|
- "{{ atlas_borg_repository_user }}@{{ atlas_borg_repository_host }}"
|
|
- stat
|
|
- "{{ atlas_borg_repository_path }}"
|
|
register: atlas_borg_repository_path_probe
|
|
become: true
|
|
become_user: "{{ atlas_borg_username }}"
|
|
changed_when: false
|
|
failed_when: false
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Probe the Atlas Borg repository
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- /usr/bin/borg
|
|
- --remote-path
|
|
- "{{ atlas_borg_remote_path }}"
|
|
- info
|
|
- >-
|
|
ssh://{{ atlas_borg_repository_user }}@{{ atlas_borg_repository_host }}:
|
|
{{- atlas_borg_repository_port }}/{{ atlas_borg_repository_path }}
|
|
environment:
|
|
BORG_CACHE_DIR: "{{ atlas_borg_cache_dir }}"
|
|
BORG_CONFIG_DIR: "{{ atlas_borg_config_dir }}"
|
|
BORG_PASSCOMMAND: "cat {{ atlas_borg_passphrase_path }}"
|
|
BORG_RSH: "{{ atlas_borg_ssh_wrapper_path }}"
|
|
register: atlas_borg_repository_probe
|
|
become: true
|
|
become_user: "{{ atlas_borg_username }}"
|
|
changed_when: false
|
|
failed_when: false
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
- atlas_borg_repository_path_probe.rc == 0
|
|
|
|
- name: Reject an existing path that is not the configured Borg repository
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.assert:
|
|
that:
|
|
- atlas_borg_repository_probe.rc == 0
|
|
fail_msg: >-
|
|
The remote repository path already exists but Borg could not open it.
|
|
Refusing to initialize over existing data; verify the path, passphrase,
|
|
and repository state manually.
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
- atlas_borg_repository_path_probe.rc == 0
|
|
|
|
- name: Initialize the encrypted Atlas Borg repository
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- /usr/bin/borg
|
|
- --remote-path
|
|
- "{{ atlas_borg_remote_path }}"
|
|
- init
|
|
- --encryption
|
|
- "{{ atlas_borg_encryption_mode }}"
|
|
- >-
|
|
ssh://{{ atlas_borg_repository_user }}@{{ atlas_borg_repository_host }}:
|
|
{{- atlas_borg_repository_port }}/{{ atlas_borg_repository_path }}
|
|
environment:
|
|
BORG_CACHE_DIR: "{{ atlas_borg_cache_dir }}"
|
|
BORG_CONFIG_DIR: "{{ atlas_borg_config_dir }}"
|
|
BORG_PASSCOMMAND: "cat {{ atlas_borg_passphrase_path }}"
|
|
BORG_RSH: "{{ atlas_borg_ssh_wrapper_path }}"
|
|
become: true
|
|
become_user: "{{ atlas_borg_username }}"
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
- atlas_borg_repository_path_probe.rc != 0
|
|
|
|
- name: Verify the encrypted Atlas Borg repository
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.command:
|
|
argv:
|
|
- /usr/bin/borg
|
|
- --remote-path
|
|
- "{{ atlas_borg_remote_path }}"
|
|
- info
|
|
- >-
|
|
ssh://{{ atlas_borg_repository_user }}@{{ atlas_borg_repository_host }}:
|
|
{{- atlas_borg_repository_port }}/{{ atlas_borg_repository_path }}
|
|
environment:
|
|
BORG_CACHE_DIR: "{{ atlas_borg_cache_dir }}"
|
|
BORG_CONFIG_DIR: "{{ atlas_borg_config_dir }}"
|
|
BORG_PASSCOMMAND: "cat {{ atlas_borg_passphrase_path }}"
|
|
BORG_RSH: "{{ atlas_borg_ssh_wrapper_path }}"
|
|
become: true
|
|
become_user: "{{ atlas_borg_username }}"
|
|
changed_when: false
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Check for the local Atlas Borg recovery-key export
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.stat:
|
|
path: "{{ atlas_borg_recovery_export_path }}"
|
|
register: atlas_borg_recovery_export
|
|
delegate_to: localhost
|
|
become: false
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
|
|
- name: Export the Atlas Borg recovery key for offline preservation
|
|
tags: [atlas, storage, backup, borg]
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|
|
- not atlas_borg_recovery_export.stat.exists
|
|
no_log: true
|
|
block:
|
|
- name: Create the local recovery-material directory
|
|
ansible.builtin.file:
|
|
path: "{{ atlas_borg_recovery_export_path | dirname }}"
|
|
state: directory
|
|
mode: "0700"
|
|
delegate_to: localhost
|
|
become: false
|
|
|
|
- name: Export the encrypted Borg repository key on Atlas
|
|
ansible.builtin.command:
|
|
argv:
|
|
- /usr/bin/borg
|
|
- --remote-path
|
|
- "{{ atlas_borg_remote_path }}"
|
|
- key
|
|
- export
|
|
- >-
|
|
ssh://{{ atlas_borg_repository_user }}@{{ atlas_borg_repository_host }}:
|
|
{{- atlas_borg_repository_port }}/{{ atlas_borg_repository_path }}
|
|
- "{{ atlas_borg_config_dir }}/atlas-borg-repokey.export"
|
|
environment:
|
|
BORG_CACHE_DIR: "{{ atlas_borg_cache_dir }}"
|
|
BORG_CONFIG_DIR: "{{ atlas_borg_config_dir }}"
|
|
BORG_PASSCOMMAND: "cat {{ atlas_borg_passphrase_path }}"
|
|
BORG_RSH: "{{ atlas_borg_ssh_wrapper_path }}"
|
|
become: true
|
|
become_user: "{{ atlas_borg_username }}"
|
|
|
|
- name: Fetch the encrypted Borg recovery key from Atlas
|
|
ansible.builtin.fetch:
|
|
src: "{{ atlas_borg_config_dir }}/atlas-borg-repokey.export"
|
|
dest: "{{ atlas_borg_recovery_export_path }}"
|
|
flat: true
|
|
|
|
- name: Protect the local Borg recovery-key export
|
|
ansible.builtin.file:
|
|
path: "{{ atlas_borg_recovery_export_path }}"
|
|
mode: "0600"
|
|
delegate_to: localhost
|
|
become: false
|
|
always:
|
|
- name: Remove the temporary recovery-key export from Atlas
|
|
ansible.builtin.file:
|
|
path: "{{ atlas_borg_config_dir }}/atlas-borg-repokey.export"
|
|
state: absent
|
|
|
|
- name: Enable Atlas Borg backup and check timers
|
|
tags: [atlas, storage, backup, borg]
|
|
ansible.builtin.systemd:
|
|
name: "{{ item }}"
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
loop:
|
|
- atlas-borg-backup.timer
|
|
- atlas-borg-check.timer
|
|
when:
|
|
- atlas_manage_borg_backup | bool
|
|
- not ansible_check_mode
|