Auto-register borg repo on the controller per host
backup role now logs into borgcontroller and creates (or looks up) a
repository with alias=inventory_hostname, registering root's pubkey and
the requested storageSize. The resulting SSH URI is injected into the
borgmatic config so each host gets a remote-managed repo without manual
config.
backup_hosts entries gain a `storage_size_gb` field (stripped before
templating) and lose the manual `repositories` entry — the role fills it.
borgcontroller_{username,password} are expected from Semaphore secrets.
Also gitignores .claude/ local state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,3 +5,6 @@ venv/
|
||||
# MikroTik backup output
|
||||
mikrotik/backups/
|
||||
mikrotik/output/
|
||||
|
||||
# Claude Code local state
|
||||
.claude/
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
---
|
||||
# Per-host borgmatic config. Keys must match inventory_hostname.
|
||||
# Hosts not listed here are skipped by the `backup` role.
|
||||
# The value under each host is rendered verbatim as the borgmatic
|
||||
# config file (see https://torsion.org/borgmatic/docs/reference/configuration/).
|
||||
# Borg Controller — auto-creates a repo per host on a BorgWarehouse-backed server.
|
||||
# borgcontroller_username / borgcontroller_password come from Semaphore secrets.
|
||||
borgcontroller_url: https://borgcontroller.internet-master.cz
|
||||
|
||||
# Per-host borgmatic config. Hosts not listed here are skipped by the `backup` role.
|
||||
# `storage_size_gb` is stripped before rendering and used to size the controller-side
|
||||
# repo. `repositories` is auto-filled from the controller — don't set it manually.
|
||||
# Other keys are passed through verbatim to borgmatic, see
|
||||
# https://torsion.org/borgmatic/docs/reference/configuration/
|
||||
backup_hosts:
|
||||
testipaclient:
|
||||
storage_size_gb: 10
|
||||
source_directories:
|
||||
- /home/jakub
|
||||
repositories:
|
||||
- path: /var/backups/borg/jakub-home
|
||||
label: jakub-home
|
||||
keep_daily: 7
|
||||
keep_weekly: 4
|
||||
keep_monthly: 6
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
- name: Login to borg controller
|
||||
ansible.builtin.uri:
|
||||
url: "{{ borgcontroller_url }}/api/auth/login"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
username: "{{ borgcontroller_username }}"
|
||||
password: "{{ borgcontroller_password }}"
|
||||
status_code: 200
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: _bc_login
|
||||
no_log: true
|
||||
|
||||
- name: Get borg server SSH endpoint
|
||||
ansible.builtin.uri:
|
||||
url: "{{ borgcontroller_url }}/api/config"
|
||||
method: GET
|
||||
headers:
|
||||
Cookie: "{{ _bc_login.cookies_string }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: _bc_config
|
||||
|
||||
- name: List repositories
|
||||
ansible.builtin.uri:
|
||||
url: "{{ borgcontroller_url }}/api/repositories"
|
||||
method: GET
|
||||
headers:
|
||||
Cookie: "{{ _bc_login.cookies_string }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: _bc_repos
|
||||
|
||||
- name: Find existing repository for this host
|
||||
ansible.builtin.set_fact:
|
||||
_bc_existing: >-
|
||||
{{ _bc_repos.json | selectattr('alias', 'eq', inventory_hostname) | list }}
|
||||
|
||||
- name: Create repository if missing
|
||||
ansible.builtin.uri:
|
||||
url: "{{ borgcontroller_url }}/api/repositories"
|
||||
method: POST
|
||||
body_format: json
|
||||
body:
|
||||
alias: "{{ inventory_hostname }}"
|
||||
sshPublicKey: "{{ root_ssh.ssh_public_key }}"
|
||||
storageSize: "{{ backup_hosts[inventory_hostname].storage_size_gb | int }}"
|
||||
headers:
|
||||
Cookie: "{{ _bc_login.cookies_string }}"
|
||||
status_code: [200, 201]
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
when: _bc_existing | length == 0
|
||||
|
||||
- name: Update repository SSH key if root's key changed
|
||||
ansible.builtin.uri:
|
||||
url: "{{ borgcontroller_url }}/api/repositories/{{ _bc_existing[0].id }}"
|
||||
method: PATCH
|
||||
body_format: json
|
||||
body:
|
||||
sshPublicKey: "{{ root_ssh.ssh_public_key }}"
|
||||
headers:
|
||||
Cookie: "{{ _bc_login.cookies_string }}"
|
||||
status_code: 200
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
when:
|
||||
- _bc_existing | length > 0
|
||||
- _bc_existing[0].sshPublicKey != root_ssh.ssh_public_key
|
||||
|
||||
- name: Re-list repositories after possible create/update
|
||||
ansible.builtin.uri:
|
||||
url: "{{ borgcontroller_url }}/api/repositories"
|
||||
method: GET
|
||||
headers:
|
||||
Cookie: "{{ _bc_login.cookies_string }}"
|
||||
delegate_to: localhost
|
||||
become: false
|
||||
register: _bc_repos_after
|
||||
|
||||
- name: Resolve repository for this host
|
||||
ansible.builtin.set_fact:
|
||||
_bc_repo: >-
|
||||
{{ (_bc_repos_after.json | selectattr('alias', 'eq', inventory_hostname) | list)[0] }}
|
||||
|
||||
- name: Build borg SSH URI
|
||||
ansible.builtin.set_fact:
|
||||
borgcontroller_repo_uri: "ssh://{{ _bc_repo.repository }}@{{ _bc_config.json.borgSshHost.split('@')[1] }}/./repos"
|
||||
+27
-11
@@ -21,14 +21,6 @@
|
||||
group: root
|
||||
mode: '0750'
|
||||
|
||||
- name: Deploy borgmatic config
|
||||
ansible.builtin.template:
|
||||
src: borgmatic.yaml.j2
|
||||
dest: /etc/borgmatic/config.yaml
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0640'
|
||||
|
||||
- name: Ensure root has an SSH key for the borg server
|
||||
ansible.builtin.user:
|
||||
name: root
|
||||
@@ -38,9 +30,33 @@
|
||||
ssh_key_comment: "borgmatic@{{ inventory_hostname }}"
|
||||
register: root_ssh
|
||||
|
||||
- name: Show root's SSH public key (add this to the borg server's authorized_keys)
|
||||
ansible.builtin.debug:
|
||||
msg: "{{ root_ssh.ssh_public_key }}"
|
||||
- name: Register / look up repository on borg controller
|
||||
ansible.builtin.include_tasks: borgcontroller.yml
|
||||
when:
|
||||
- borgcontroller_username is defined
|
||||
- borgcontroller_password is defined
|
||||
|
||||
- name: Build borgmatic config (strip controller-only keys, inject repository)
|
||||
ansible.builtin.set_fact:
|
||||
_borgmatic_config: >-
|
||||
{{
|
||||
(backup_hosts[inventory_hostname]
|
||||
| dict2items
|
||||
| rejectattr('key', 'in', ['storage_size_gb'])
|
||||
| items2dict)
|
||||
| combine(
|
||||
{'repositories': [{'path': borgcontroller_repo_uri, 'label': inventory_hostname}]}
|
||||
if borgcontroller_repo_uri is defined else {}
|
||||
)
|
||||
}}
|
||||
|
||||
- name: Deploy borgmatic config
|
||||
ansible.builtin.template:
|
||||
src: borgmatic.yaml.j2
|
||||
dest: /etc/borgmatic/config.yaml
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0640'
|
||||
|
||||
- name: Enable and start borgmatic timer
|
||||
ansible.builtin.systemd:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
#jinja2: trim_blocks: True, lstrip_blocks: True
|
||||
# Managed by Ansible — do not edit by hand.
|
||||
{{ backup_hosts[inventory_hostname] | to_nice_yaml(indent=2, width=1000) }}
|
||||
{{ _borgmatic_config | to_nice_yaml(indent=2, width=1000) }}
|
||||
|
||||
Reference in New Issue
Block a user