Files
axion1337.chat-gitops/apps/production/synapse-backup.yaml
T
Thore CimbalandClaude Sonnet 5 f5e9fc5d2b
Auto-Deploy on Push / verify-and-notify (push) Canceled after 0s
feat(backup): add nightly Borg backups to Hetzner Storage Box
Closes issues #6 and #15 (treated jointly, per combined analysis in their
comments). Two CronJobs, one per namespace, each pushing to its own Borg
repo on the newly booked Storage Box (u641795.your-storagebox.de:23, BX11
1TB) with its own repo passphrase - a leaked passphrase for one doesn't
expose the other:

- matrix: synapse-backup dumps the synapse + matrixauthenticationservice
  DBs (shared postgres, existing chart-generated POSTGRES_ADMIN_PASSWORD)
  and the Synapse media_store PVC (read-only mount)
- authentik: authentik-backup dumps the authentik DB (existing
  authentik-credentials pg-password)

Custom image (rohana.axion1337.de/sorb/axion-backup:v1, postgres:17-alpine
+ borgbackup + openssh-client - matches the live Postgres major version
exactly, unlike Alpine's stock postgresql16-client) pushed as a public
package, same pattern as the existing threadnet-web image (no
imagePullSecret needed).

SSH host key pinned via known_hosts ConfigMap (captured via ssh-keyscan
ahead of time) rather than trusting on first connect in an unattended job.
Retention: 7 daily / 4 weekly / 6 monthly via borg prune. Schedule: 03:00
and 03:15, offset to avoid resource contention.

NetworkPolicy: added each backup job's pod as an allowed source to its
namespace's existing postgres ingress rule (matrix's allow-ingress-postgres,
authentik's allow-ingress-authentik-postgresql from #37). Egress already
unrestricted in both namespaces, so no change needed for the outbound SSH
connection to the Storage Box.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 20:21:40 +02:00

97 lines
3.4 KiB
YAML

# Nightly Borg backup of the shared Postgres instance (synapse + MAS databases) and the
# Synapse media_store PVC to a Hetzner Storage Box (issues #6 + #15). See
# apps/production/synapse-backup-secret.yaml for the SSH key + Borg repo passphrase, and
# apps/authentik/authentik-backup.yaml for the equivalent authentik-side job.
apiVersion: v1
kind: ConfigMap
metadata:
name: synapse-backup-known-hosts
namespace: matrix
data:
# Pinned via `ssh-keyscan -p 23 u641795.your-storagebox.de` (2026-07-28) rather than
# trusting the host key on first connect in an unattended job.
known_hosts: |
[u641795.your-storagebox.de]:23 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIICf9svRenC/PLKIL9nk6K/pxQgoiFC41wTNvoIncOxs
---
apiVersion: batch/v1
kind: CronJob
metadata:
name: synapse-backup
namespace: matrix
spec:
schedule: "0 3 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
template:
metadata:
labels:
app.kubernetes.io/name: synapse-backup
app.kubernetes.io/component: backup
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: rohana.axion1337.de/sorb/axion-backup:v1
env:
- name: BORG_REPO
value: "ssh://u641795@u641795.your-storagebox.de:23/./synapse-backup"
- name: BORG_PASSPHRASE
valueFrom:
secretKeyRef:
name: synapse-backup-credentials
key: borg-passphrase
- name: SSH_PRIVATE_KEY_FILE
value: /secrets/ssh/ssh-private-key
- name: SSH_KNOWN_HOSTS_FILE
value: /secrets/known-hosts/known_hosts
- name: DB_HOSTS
value: "synapse:matrix-stack-postgres matrixauthenticationservice:matrix-stack-postgres"
- name: PGUSER
value: postgres
- name: PGPASSWORD
valueFrom:
secretKeyRef:
name: matrix-stack-generated
key: POSTGRES_ADMIN_PASSWORD
- name: MEDIA_PATH
value: /media/media_store
volumeMounts:
- name: ssh-key
mountPath: /secrets/ssh
readOnly: true
- name: known-hosts
mountPath: /secrets/known-hosts
readOnly: true
- name: media
mountPath: /media
readOnly: true
- name: scratch
mountPath: /scratch
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
memory: 256Mi
volumes:
- name: ssh-key
secret:
secretName: synapse-backup-credentials
items:
- key: ssh-private-key
path: ssh-private-key
mode: 0400
- name: known-hosts
configMap:
name: synapse-backup-known-hosts
- name: media
persistentVolumeClaim:
claimName: matrix-stack-synapse-media
readOnly: true
- name: scratch
emptyDir: {}