description: 'The ansible role I rewrote to manage my borg backups'
date: '2024-10-07'
tags:
- ansible
- backups
- borg
---
## Introduction
I initially wrote about my borg ansible role in [a blog article three and a half years ago]({{< ref "borg-ansible-role.md" >}}). I released a second version two years ago (time flies!) and it still works well, but I am no longer using it.
I put down ansible when I got infatuated with nixos a little more than a year ago. As I am dialing it back on nixos, I am reviewing and changing some of my design choices.
## Borg repositories changes
One of the main breaking change is that I no longer want to use one borg repository per host as my old role managed: I want one per job/application so that backups are agnostic from the hosts they are running on.
The main advantages are:
- one private ssh key per job
- no more data expiration when a job stops running on a job for a time
- easier monitoring of job run: now checking if a repository has new data is enough, before I had to check the number of jobs that wrote to it in a specific time frame.
The main drawback is that I lose the ability to automatically clean a borg server's `authorized_keys` file when I completely stop using an application or service. Migrating from host to host is properly handled, but complete removal will be manual. I tolerate this because now each job has its own private ssh key, generated on the fly when the job is deployed to a host.
## The new role
### Tasks
The main.yaml contains:
``` yaml
---
- name: 'Install borg'
package:
name:
- 'borgbackup'
# This use attribute is a work around for https://github.com/ansible/ansible/issues/82598
# Invoking the package module without this fails in a delegate_to context
use: '{{ ansible_facts["pkg_mgr"] }}'
```
It will be included in a `delete_to` context when a client configures its server. For the client itself, this tasks file will run normally and be invoked from a `meta` dependency.
The meat of the role is in the client.yaml:
``` yaml
---
# Inputs:
# client:
# name: string
# jobs: list(job)
# server: string
# With:
# job:
# command_to_pipe: optional(string)
# exclude: optional(list(string))
# name: string
# paths: optional(list(string))
# post_command: optional(string)
# pre_command: optional(string)
- name: 'Ensure borg directories exists on server'
file:
state: 'directory'
path: '{{ item }}'
owner: 'root'
mode: '0700'
loop:
- '/etc/borg'
- '/root/.cache/borg'
- '/root/.config/borg'
- name: 'Generate openssh key pair'
openssh_keypair:
path: '/etc/borg/{{ client.name }}.key'
type: 'ed25519'
owner: 'root'
mode: '0400'
- name: 'Read the public key'
ansible.builtin.slurp:
src: '/etc/borg/{{ client.name }}.key.pub'
register: 'borg_public_key'
- include_role:
name: 'borg'
tasks_from: 'server'
args:
apply:
delegate_to: '{{ client.server }}'
vars:
server:
name: '{{ client.name }}'
pubkey: '{{ borg_public_key.content | b64decode | trim }}'