Add ansible-managed borgmatic config with PostgreSQL backup

- Move borgmatic config.yaml from manual to ansible-managed template
- Add postgresql_databases backup for miniflux database
- Consolidate 1Password credential fetching to playbook pre_tasks
  to reduce auth prompts during full playbook runs
- Roles now check if credentials are already defined before fetching,
  so they still work when running with --tags

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Erich Blume 2026-01-16 11:32:03 -08:00
commit cdb8432691
7 changed files with 177 additions and 3 deletions

View file

@ -1,6 +1,66 @@
---
- name: Configure indri
hosts: indri
# Fetch all 1Password credentials upfront to minimize prompts
# Each role also fetches its own credentials (with 'when: <var> is not defined')
# so they still work when running with --tags
pre_tasks:
- name: Fetch PostgreSQL superuser password
ansible.builtin.command:
cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get guxu3j7ajhjyey6xxl2ovsl2ui --fields password --reveal
delegate_to: localhost
register: _pg_superuser_pw
changed_when: false
no_log: true
- name: Set PostgreSQL superuser password fact
ansible.builtin.set_fact:
pg_superuser_password: "{{ _pg_superuser_pw.stdout }}"
no_log: true
- name: Fetch PostgreSQL alloy user password
ansible.builtin.command:
cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get guxu3j7ajhjyey6xxl2ovsl2ui --fields alloy-user-pw --reveal
delegate_to: localhost
register: _pg_alloy_pw
changed_when: false
no_log: true
- name: Set PostgreSQL alloy password fact
ansible.builtin.set_fact:
alloy_postgres_password: "{{ _pg_alloy_pw.stdout }}"
no_log: true
- name: Fetch miniflux database password
ansible.builtin.command:
cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get ns6wylqiuqgczpo7gq2akaxbti --fields password --reveal
delegate_to: localhost
register: _miniflux_db_pw
changed_when: false
no_log: true
- name: Set miniflux passwords fact
ansible.builtin.set_fact:
miniflux_db_password: "{{ _miniflux_db_pw.stdout }}"
no_log: true
- name: Fetch borgmatic database password
ansible.builtin.command:
cmd: op --vault vg6xf6vvfmoh5hqjjhlhbeoaie item get mw2bv5we7woicjza7hc6s44yvy --fields db-password --reveal
delegate_to: localhost
register: _borgmatic_db_pw
changed_when: false
no_log: true
- name: Build PostgreSQL user password lookup
ansible.builtin.set_fact:
pg_user_passwords:
miniflux: "{{ _miniflux_db_pw.stdout }}"
borgmatic: "{{ _borgmatic_db_pw.stdout }}"
alloy: "{{ _pg_alloy_pw.stdout }}"
no_log: true
roles:
- role: loki
tags: loki

View file

@ -26,6 +26,8 @@
mode: '0755'
# === Fetch PostgreSQL password from 1Password ===
# Skipped when running full playbook (pre_tasks sets it)
# but runs when using --tags alloy
- name: Fetch PostgreSQL metrics password from 1Password
ansible.builtin.command:
@ -34,13 +36,17 @@
register: alloy_postgres_password_result
changed_when: false
no_log: true
when: alloy_collect_postgres | default(false)
when:
- alloy_collect_postgres | default(false)
- alloy_postgres_password is not defined
- name: Set PostgreSQL password fact
ansible.builtin.set_fact:
alloy_postgres_password: "{{ alloy_postgres_password_result.stdout }}"
no_log: true
when: alloy_collect_postgres | default(false)
when:
- alloy_collect_postgres | default(false)
- alloy_postgres_password is not defined
# === Deploy configuration ===

View file

@ -1,7 +1,48 @@
---
borgmatic_config: /Users/erichblume/.config/borgmatic/config.yaml
borgmatic_config_dir: /Users/erichblume/.config/borgmatic
borgmatic_log_dir: /Users/erichblume/Library/Logs
# Schedule: runs daily at 2:00 AM
borgmatic_schedule_hour: 2
borgmatic_schedule_minute: 0
# Source directories to back up
borgmatic_source_directories:
- /Users/erichblume/code/personal/zk
- /opt/homebrew/var/forgejo
- /Users/erichblume/code/3rd/kiwix-tools
- /Users/erichblume/.config/borgmatic
- /Users/erichblume/Documents
- /Users/erichblume/Pictures
- /Users/erichblume/devpi
- /opt/homebrew/var/loki
# Backup repository
borgmatic_repositories:
- path: /Volumes/backups/borg/
label: sifaka-borg-backups
encryption: repokey
append_only: true
# Exclude patterns
borgmatic_exclude_patterns:
# Exclude mirrored PyPI cache (only backup private packages)
- /Users/erichblume/devpi/+files/root/pypi
- /opt/homebrew/var/loki
# Encryption passcommand (reads borg passphrase)
borgmatic_encryption_passcommand: cat /Users/erichblume/.borg/config.yaml
# Retention policy
borgmatic_keep_daily: 7
borgmatic_keep_monthly: 12
borgmatic_keep_yearly: 1000
# PostgreSQL databases to backup (streamed via pg_dump)
# Password is read from ~/.pgpass (managed by postgresql role)
borgmatic_postgresql_databases:
- name: miniflux
hostname: localhost
port: 5432
username: borgmatic

View file

@ -1,6 +1,18 @@
---
# Note: borgmatic is installed via mise (pipx), not managed here.
# This role manages only the scheduled LaunchAgent.
# This role manages the config file and scheduled LaunchAgent.
- name: Ensure borgmatic config directory exists
ansible.builtin.file:
path: "{{ borgmatic_config_dir }}"
state: directory
mode: '0700'
- name: Deploy borgmatic configuration
ansible.builtin.template:
src: config.yaml.j2
dest: "{{ borgmatic_config }}"
mode: '0600'
- name: Deploy borgmatic LaunchAgent plist
ansible.builtin.template:

View file

@ -0,0 +1,45 @@
# {{ ansible_managed }}
source_directories:
{% for dir in borgmatic_source_directories %}
- {{ dir }}
{% endfor %}
source_directories_must_exist: true
repositories:
{% for repo in borgmatic_repositories %}
- path: {{ repo.path }}
label: {{ repo.label }}
{% if repo.encryption is defined %}
encryption: {{ repo.encryption }}
{% endif %}
{% if repo.append_only is defined %}
append_only: {{ repo.append_only | lower }}
{% endif %}
{% endfor %}
{% if borgmatic_exclude_patterns %}
exclude_patterns:
{% for pattern in borgmatic_exclude_patterns %}
- {{ pattern }}
{% endfor %}
{% endif %}
encryption_passcommand: {{ borgmatic_encryption_passcommand }}
# Retention policy
keep_daily: {{ borgmatic_keep_daily }}
keep_monthly: {{ borgmatic_keep_monthly }}
keep_yearly: {{ borgmatic_keep_yearly }}
{% if borgmatic_postgresql_databases %}
# PostgreSQL database backups (streamed via pg_dump)
postgresql_databases:
{% for db in borgmatic_postgresql_databases %}
- name: {{ db.name }}
hostname: {{ db.hostname | default('localhost') }}
port: {{ db.port | default(5432) }}
username: {{ db.username }}
{% endfor %}
{% endif %}

View file

@ -14,6 +14,8 @@
state: present
# === Fetch passwords from 1Password ===
# These are skipped when running full playbook (pre_tasks sets them)
# but run when using --tags miniflux
- name: Fetch miniflux database password from 1Password
ansible.builtin.command:
@ -22,11 +24,13 @@
register: miniflux_db_password_result
changed_when: false
no_log: true
when: miniflux_db_password is not defined
- name: Set database password fact
ansible.builtin.set_fact:
miniflux_db_password: "{{ miniflux_db_password_result.stdout }}"
no_log: true
when: miniflux_db_password is not defined
- name: Fetch miniflux admin password from 1Password (for first run)
ansible.builtin.command:

View file

@ -10,6 +10,8 @@
state: present
# === Fetch passwords from 1Password (on control machine) ===
# These are skipped when running full playbook (pre_tasks sets them)
# but run when using --tags postgresql
- name: Fetch superuser password from 1Password
ansible.builtin.command:
@ -18,11 +20,13 @@
register: pg_superuser_password_result
changed_when: false
no_log: true
when: pg_superuser_password is not defined
- name: Set superuser password fact
ansible.builtin.set_fact:
pg_superuser_password: "{{ pg_superuser_password_result.stdout }}"
no_log: true
when: pg_superuser_password is not defined
- name: Fetch user passwords from 1Password
ansible.builtin.command:
@ -32,12 +36,14 @@
register: pg_user_passwords_result
changed_when: false
no_log: true
when: pg_user_passwords is not defined
- name: Build user password lookup
ansible.builtin.set_fact:
pg_user_passwords: "{{ pg_user_passwords | default({}) | combine({item.item.name: item.stdout}) }}"
loop: "{{ pg_user_passwords_result.results }}"
no_log: true
when: pg_user_passwords is not defined
# === Initialize PostgreSQL cluster ===