Add offsite backup for immich photo library to BorgBase (#315)
## Summary - Adds a second borgmatic config (`photos.yaml`) that backs up `/Volumes/photos` (sifaka SMB mount, ~128 GB) to a dedicated BorgBase repo (`immich-photos`), running daily at 4 AM - Separate launchd agent (`mcquack.eblume.borgmatic-photos`) so photo backups run independently from the main backup - Refactors `borgmatic_metrics` script to support multiple repos with a `repo` Prometheus label - Updates Grafana "Borg Backups" dashboard with a `repo` template variable so you can filter/compare repos - Docs updated: `backups.md`, `borgmatic.md` ## Prerequisites (manual) - [x] Create `immich-photos` repo on BorgBase with same SSH key - [ ] Upgrade BorgBase plan to Small ($24/yr) if currently on free tier (128 GB exceeds 10 GB limit) - [ ] After deploy: `borg init` the new repo (borgmatic does this automatically on first run) ## Test plan - [ ] Dry run: `mise run provision-indri -- --check --diff --tags borgmatic,borgmatic_metrics` - [ ] Deploy borgmatic role and verify both configs deployed - [ ] Run `borgmatic --config ~/.config/borgmatic/photos.yaml create --verbosity 1` manually for first backup (will take hours) - [ ] Verify metrics script collects from both repos: `~/.local/bin/borgmatic-metrics && cat /opt/homebrew/var/node_exporter/textfile/borgmatic.prom` - [ ] Sync grafana-config in ArgoCD and verify dashboard repo selector works 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #315
This commit is contained in:
parent
ca0c9354ee
commit
c78b86c72c
11 changed files with 305 additions and 136 deletions
|
|
@ -59,6 +59,18 @@ borgmatic_keep_yearly: 1000
|
|||
# PostgreSQL databases to backup (streamed via pg_dump)
|
||||
# Password is read from ~/.pgpass (managed by this role)
|
||||
# pg_dump_command must be full path since LaunchAgent doesn't have homebrew in PATH
|
||||
# --- Immich photo library backup (BorgBase offsite only) ---
|
||||
borgmatic_photos_config: /Users/erichblume/.config/borgmatic/photos.yaml
|
||||
borgmatic_photos_source_dir: /Volumes/photos
|
||||
borgmatic_photos_borgbase_repo: ssh://xcrtl5tg@xcrtl5tg.repo.borgbase.com/./repo
|
||||
# Schedule: runs daily at 4:00 AM (offset from main backup at 2:00 AM)
|
||||
borgmatic_photos_schedule_hour: 4
|
||||
borgmatic_photos_schedule_minute: 0
|
||||
# Retention: photos are precious, keep more history
|
||||
borgmatic_photos_keep_daily: 7
|
||||
borgmatic_photos_keep_monthly: 12
|
||||
borgmatic_photos_keep_yearly: 1000
|
||||
|
||||
borgmatic_pg_dump_command: /opt/homebrew/opt/postgresql@18/bin/pg_dump
|
||||
borgmatic_postgresql_databases:
|
||||
# k8s PostgreSQL (CloudNativePG) via Caddy L4 proxy
|
||||
|
|
|
|||
|
|
@ -4,3 +4,9 @@
|
|||
launchctl unload ~/Library/LaunchAgents/mcquack.eblume.borgmatic.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/mcquack.eblume.borgmatic.plist
|
||||
changed_when: true
|
||||
|
||||
- name: Reload borgmatic-photos
|
||||
ansible.builtin.shell: |
|
||||
launchctl unload ~/Library/LaunchAgents/mcquack.eblume.borgmatic-photos.plist 2>/dev/null || true
|
||||
launchctl load ~/Library/LaunchAgents/mcquack.eblume.borgmatic-photos.plist
|
||||
changed_when: true
|
||||
|
|
|
|||
|
|
@ -28,11 +28,14 @@
|
|||
mode: '0600'
|
||||
no_log: true
|
||||
|
||||
- name: Add BorgBase host key to known_hosts
|
||||
- name: Add BorgBase host keys to known_hosts
|
||||
ansible.builtin.known_hosts:
|
||||
name: u3ugi1x1.repo.borgbase.com
|
||||
key: "u3ugi1x1.repo.borgbase.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGU0mISTyHBw9tBs6SuhSq8tvNM8m9eifQxM+88TowPO"
|
||||
name: "{{ item }}"
|
||||
key: "{{ item }} ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGU0mISTyHBw9tBs6SuhSq8tvNM8m9eifQxM+88TowPO"
|
||||
state: present
|
||||
loop:
|
||||
- u3ugi1x1.repo.borgbase.com
|
||||
- xcrtl5tg.repo.borgbase.com
|
||||
|
||||
- name: Ensure k8s dump directory exists
|
||||
ansible.builtin.file:
|
||||
|
|
@ -65,3 +68,30 @@
|
|||
when: borgmatic_launchctl_check.rc != 0
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
||||
# --- Immich photo library backup (BorgBase offsite only) ---
|
||||
|
||||
- name: Deploy borgmatic photos configuration
|
||||
ansible.builtin.template:
|
||||
src: photos.yaml.j2
|
||||
dest: "{{ borgmatic_photos_config }}"
|
||||
mode: '0600'
|
||||
|
||||
- name: Deploy borgmatic-photos LaunchAgent plist
|
||||
ansible.builtin.template:
|
||||
src: borgmatic-photos.plist.j2
|
||||
dest: ~/Library/LaunchAgents/mcquack.eblume.borgmatic-photos.plist
|
||||
mode: '0644'
|
||||
notify: Reload borgmatic-photos
|
||||
|
||||
- name: Check if borgmatic-photos LaunchAgent is loaded
|
||||
ansible.builtin.command: launchctl list mcquack.eblume.borgmatic-photos
|
||||
register: borgmatic_photos_launchctl_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Load borgmatic-photos LaunchAgent if not loaded
|
||||
ansible.builtin.command: launchctl load ~/Library/LaunchAgents/mcquack.eblume.borgmatic-photos.plist
|
||||
when: borgmatic_photos_launchctl_check.rc != 0
|
||||
changed_when: true
|
||||
failed_when: false
|
||||
|
|
|
|||
39
ansible/roles/borgmatic/templates/borgmatic-photos.plist.j2
Normal file
39
ansible/roles/borgmatic/templates/borgmatic-photos.plist.j2
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- {{ ansible_managed }} -->
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>KeepAlive</key>
|
||||
<false/>
|
||||
<key>Label</key>
|
||||
<string>mcquack.eblume.borgmatic-photos</string>
|
||||
<key>EnvironmentVariables</key>
|
||||
<dict>
|
||||
<key>PATH</key>
|
||||
<string>/opt/homebrew/bin:/usr/bin:/bin</string>
|
||||
</dict>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/opt/homebrew/opt/mise/bin/mise</string>
|
||||
<string>x</string>
|
||||
<string>--</string>
|
||||
<string>borgmatic</string>
|
||||
<string>--config</string>
|
||||
<string>{{ borgmatic_photos_config }}</string>
|
||||
<string>create</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>{{ borgmatic_log_dir }}/mcquack.borgmatic-photos.err.log</string>
|
||||
<key>StandardOutPath</key>
|
||||
<string>{{ borgmatic_log_dir }}/mcquack.borgmatic-photos.out.log</string>
|
||||
<key>StartCalendarInterval</key>
|
||||
<dict>
|
||||
<key>Hour</key>
|
||||
<integer>{{ borgmatic_photos_schedule_hour }}</integer>
|
||||
<key>Minute</key>
|
||||
<integer>{{ borgmatic_photos_schedule_minute }}</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
29
ansible/roles/borgmatic/templates/photos.yaml.j2
Normal file
29
ansible/roles/borgmatic/templates/photos.yaml.j2
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# {{ ansible_managed }}
|
||||
#
|
||||
# Borgmatic config for immich photo library backup.
|
||||
# Backs up /Volumes/photos (sifaka SMB mount) to BorgBase offsite ONLY.
|
||||
# Separate from the main borgmatic config to keep concerns isolated:
|
||||
# - main config: indri data → sifaka + borgbase
|
||||
# - this config: sifaka photos → borgbase (different repo)
|
||||
|
||||
local_path: {{ borgmatic_local_path }}
|
||||
|
||||
source_directories:
|
||||
- {{ borgmatic_photos_source_dir }}
|
||||
|
||||
source_directories_must_exist: true
|
||||
|
||||
repositories:
|
||||
- path: {{ borgmatic_photos_borgbase_repo }}
|
||||
label: borgbase-immich-photos
|
||||
encryption: repokey
|
||||
append_only: true
|
||||
|
||||
encryption_passcommand: {{ borgmatic_encryption_passcommand }}
|
||||
|
||||
ssh_command: ssh -o IdentitiesOnly=yes -i {{ borgmatic_borgbase_ssh_key_path }}
|
||||
|
||||
# Retention policy — photos are precious, keep more history
|
||||
keep_daily: {{ borgmatic_photos_keep_daily }}
|
||||
keep_monthly: {{ borgmatic_photos_keep_monthly }}
|
||||
keep_yearly: {{ borgmatic_photos_keep_yearly }}
|
||||
Loading…
Add table
Add a link
Reference in a new issue