diff --git a/apps/authentik/authentik-backup-secret.yaml b/apps/authentik/authentik-backup-secret.yaml new file mode 100644 index 0000000..d2ccc7c --- /dev/null +++ b/apps/authentik/authentik-backup-secret.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Secret +metadata: + name: authentik-backup-credentials + namespace: authentik +stringData: + borg-passphrase: ENC[AES256_GCM,data:5PApz4TqSNN2vVXeFSuomd051nl+cYk+a+STViwddG/Hj7XWQ099vvTlKSE=,iv:MFG04/66YqtOjZWsLpy236MYwR05z91ngOQ0BmLNxzA=,tag:gxD5prW8Ted3Q2ZY3sYSYQ==,type:str] + ssh-private-key: ENC[AES256_GCM,data:87PrkqAIH2xRCu3jlAK3Ts24XKRDoXNCWLomRhizCY43hwLeY5fNzfsGnMTKBt5IxL2bIljx+pbh/Y8IO38ISUUbUAczfi1KeN1gg69wKgqNAlckcmD0dkA2N2F0ZnEFX/rkad68YR8Hw1/A5h3qGUd5FwoVWzIFD/diY86s/LiQBYzwD4bYLUACHqIL7+QyJ6jEsFzA3tLwDySpUpM9OAjFQF0EazObChNYqe/qrc8i1gI8CptWOFkqiZ8Pen1E79gg/zgleOBqSmlcefgZk+JFPAfvORTHkDrHW9X/bLygaTQvskmGNzaKHpe2s/bVafWb7G60ROrpdjYIJfS7c0baRCSrd9kwdM4JQC2gSsMkI4v6oeFSvkTrb684B10zacZdiTKfySDV+Ry4hCZofF2RhAnsTbOpo0XANoOi4InoxUqGPfctVfUCVb6X+HIR5XYxpLrNwWqRpscMP2P3RJsaUZOinmQUKrHX/nMtPZ/ltpwjhQeAXZTtdyPxTCjHAh6fzouuHk66vlCdukXsMQDBdIfdA/3jjGbW,iv:yFNKkdegLLq8jq1Ya6v67urSJTdG3Ge4ZbmKizqQhmk=,tag:6l/qMb1d1oD8sfVEebW6gQ==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBWMllTL2hRcEhCT2MxcWZy + dkxndmlZRW5FZ2NiWWdFckxqYTFKS1B6SXlvCjJySXRsZThvczNnWjlyM0N2Snha + eitvWUhhdmU4U0V3OG55WjdLbU9KcUEKLS0tIEM3MStzNUJhaGg4M0hKMVF1bnBB + RWVqVDRBNmJ0b1Bwd0l3dWxPT3Q0SU0KOEoyejkH4RC0p8ka3FjI7MyzRJg+uu7h + j3wf1q+Hgg73djDBSPYJkrB6Bdl4YMwo8SzbtW8O9elDE0qAioR0bQ== + -----END AGE ENCRYPTED FILE----- + recipient: age14l0hwfqylwpemz5y2ghh2yxk0phszlnj3qlejhue0fw0kz3tmfgqdsjzdh + encrypted_regex: ^(data|stringData)$ + lastmodified: "2026-07-28T18:20:58Z" + mac: ENC[AES256_GCM,data:lwwNbxSxtgDTDmaWMU1uf7TBOEw8gFBFKb982VIsGMeM0fIPHvX12Qts90MNgYIJliOWgAWrwyvAgfXWKuE37RNo+BtyfHCWi4IESKSN/RJrd/yMpRKx+02rifH3nl26ZCAQT1Pa0fjI1SfMhbVzfnD9a/AMARXZMhLRc0OqczY=,iv:SgP2iMtENRtZfw6I9EaOsmvecFNYCIZWqVj+cZ+T7EI=,tag:s4oEyxqyUx6ibqsk4g9xpw==,type:str] + version: 3.13.3 diff --git a/apps/authentik/authentik-backup.yaml b/apps/authentik/authentik-backup.yaml new file mode 100644 index 0000000..7591852 --- /dev/null +++ b/apps/authentik/authentik-backup.yaml @@ -0,0 +1,85 @@ +# Nightly Borg backup of the authentik Postgres database to a Hetzner Storage Box +# (issues #6 + #15). See apps/authentik/authentik-backup-secret.yaml for the SSH key + +# Borg repo passphrase, and apps/production/synapse-backup.yaml for the matrix-side job +# (same Storage Box, separate repo/passphrase, offset schedule). +apiVersion: v1 +kind: ConfigMap +metadata: + name: authentik-backup-known-hosts + namespace: authentik +data: + known_hosts: | + [u641795.your-storagebox.de]:23 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIICf9svRenC/PLKIL9nk6K/pxQgoiFC41wTNvoIncOxs +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: authentik-backup + namespace: authentik +spec: + schedule: "15 3 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 2 + template: + metadata: + labels: + app.kubernetes.io/name: authentik-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/./authentik-backup" + - name: BORG_PASSPHRASE + valueFrom: + secretKeyRef: + name: authentik-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: "authentik:authentik-postgresql" + - name: PGUSER + value: authentik + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: authentik-credentials + key: pg-password + volumeMounts: + - name: ssh-key + mountPath: /secrets/ssh + readOnly: true + - name: known-hosts + mountPath: /secrets/known-hosts + readOnly: true + - name: scratch + mountPath: /scratch + resources: + requests: + cpu: 50m + memory: 128Mi + limits: + memory: 256Mi + volumes: + - name: ssh-key + secret: + secretName: authentik-backup-credentials + items: + - key: ssh-private-key + path: ssh-private-key + mode: 0400 + - name: known-hosts + configMap: + name: authentik-backup-known-hosts + - name: scratch + emptyDir: {} diff --git a/apps/authentik/kustomization.yaml b/apps/authentik/kustomization.yaml index c29e79a..980fad1 100644 --- a/apps/authentik/kustomization.yaml +++ b/apps/authentik/kustomization.yaml @@ -9,3 +9,6 @@ resources: - authentik.yaml - ingress.yaml - networkpolicy.yaml + # Backup zur Hetzner Storage Box (Issues #6 + #15) + - authentik-backup-secret.yaml + - authentik-backup.yaml diff --git a/apps/authentik/networkpolicy.yaml b/apps/authentik/networkpolicy.yaml index 2881745..d8f3ed7 100644 --- a/apps/authentik/networkpolicy.yaml +++ b/apps/authentik/networkpolicy.yaml @@ -68,6 +68,9 @@ spec: matchLabels: app.kubernetes.io/name: authentik app.kubernetes.io/component: worker + - podSelector: + matchLabels: + app.kubernetes.io/name: authentik-backup ports: - protocol: TCP port: 5432 diff --git a/apps/production/kustomization.yaml b/apps/production/kustomization.yaml index c2b4202..7532a19 100644 --- a/apps/production/kustomization.yaml +++ b/apps/production/kustomization.yaml @@ -30,4 +30,7 @@ resources: - element-server-suite.yaml # Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat - apex-ingress.yaml # Custom Apex Ingress für Element Web + Well-Known auf axion1337.chat - - networkpolicy.yaml \ No newline at end of file + - networkpolicy.yaml + # Backup zur Hetzner Storage Box (Issues #6 + #15) + - synapse-backup-secret.yaml + - synapse-backup.yaml \ No newline at end of file diff --git a/apps/production/networkpolicy.yaml b/apps/production/networkpolicy.yaml index e8d1439..4e3bcb4 100644 --- a/apps/production/networkpolicy.yaml +++ b/apps/production/networkpolicy.yaml @@ -258,6 +258,9 @@ spec: - podSelector: matchLabels: app.kubernetes.io/name: matrix-authentication-service + - podSelector: + matchLabels: + app.kubernetes.io/name: synapse-backup ports: - protocol: TCP port: 5432 diff --git a/apps/production/synapse-backup-secret.yaml b/apps/production/synapse-backup-secret.yaml new file mode 100644 index 0000000..7ec929c --- /dev/null +++ b/apps/production/synapse-backup-secret.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Secret +metadata: + name: synapse-backup-credentials + namespace: matrix +stringData: + borg-passphrase: ENC[AES256_GCM,data:RRXPwr4UGX30IdozM53abN7ZYztTO3Y1/63dtTh0JSZmU8i8l/ATb4gc3lc=,iv:rIyUr+lOUjo9J53OKZ5ZDmp3d8Nrb9PP2JDK2oCutYU=,tag:MqUmoYgCA03WJQy+RQi04w==,type:str] + ssh-private-key: ENC[AES256_GCM,data:320B/lSq7DljCrXZ0BluGv8gLIzYF3KL6VQnR6CM/Vzuf/6qbbZ7lMzrS96XfoubU95OxDeJuOsuNZBQUZNyUGtt4QjQmnl8XHUJzl48xLqh83HLFPtQm2uqU072lscf5OTT7I+JzD2BGks3OIowhrg1q0MdVQdfd4Rhdz8Jphjb+WxvstzNmEX5gxQ9mnBVPmj4DS6ikdXnpe+VDvaCJaVkzD5KgwRmqPy0qbFs9WXziQSo3am5fPeHbwhV3UlRhlok9WrDI40a1T1S5DBhgNhwShq1jAxjr9onuq92REymxAV50oLzsw9ivnH0uimw+3PcplRG1v2xxJ/pimWTCjE17bO7OZ8TyzzwyZ7QA02vSpOMNUdUwVU3N6pSdYQpdTETuVBTVqVc+GKC18Z9fWh2rdwX5eUDTwp0bbDDvPEHdANNxMg3VYD3gwpCgy6/wVnjrO+pVMs8K1CqFn4/H7azhzzeEPkrz27ZBxjlnqvDODsm9tlklr6X4jZDZmTaEmiH+WXuc+1qbNxvsrqtSFfa3CfFGxM1nBOh,iv:sKGsTLsxdQYVUvw7CEARL3YNInSd9LPbFp5Ci5CTgIw=,tag:O1mjNZUsGEqV4uu+LlgD/g==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3ZUdtN3hyNHlyMkIrbTVS + aHQ4OGNUWnA4eTFUVkx4UVp5VWlnMDgxTEU0CkU3M2dMYWgyKytlRVFOVWptZEd2 + NXlIY0JCd24xcGFzaGpIeks0R2U0U3MKLS0tIHlxYVZ1ZTJsRXNaZ25sVzZtSnp0 + SEhzQ0tUYzZTRXcwMVNwbG85SHpyb0UKOn3nxy6Y7yQkGargXQ9z6O36vUWW4qJZ + D/GbFGmoRi94EtVFdmTGALhjy2D4J9QXy6gHsTapvKyMxF8NEtk+FQ== + -----END AGE ENCRYPTED FILE----- + recipient: age14l0hwfqylwpemz5y2ghh2yxk0phszlnj3qlejhue0fw0kz3tmfgqdsjzdh + encrypted_regex: ^(data|stringData)$ + lastmodified: "2026-07-28T18:20:58Z" + mac: ENC[AES256_GCM,data:Rur32fQdCyM3nr/X+KeSgmPYEi4nKyh8lqTuSW3TPBVDjwTWMDp1I1ZPPyy5syeW6RHbKputFUzBWVnuQmVrfbZaQ6DBBI5kP9InspUAVUjXDRk9XqiWtdg/wYaTMMJ4Nxv/zdwkh6uJQSG2JHQBWce4NZc2hoPokLR0CjcWiZk=,iv:eWTUj48EFjjtuIIuErMltEdDfabLZeolkpInMYtVP5Q=,tag:6DPxkPqLt0ihJ80WnTzHeA==,type:str] + version: 3.13.3 diff --git a/apps/production/synapse-backup.yaml b/apps/production/synapse-backup.yaml new file mode 100644 index 0000000..1bdbb2e --- /dev/null +++ b/apps/production/synapse-backup.yaml @@ -0,0 +1,96 @@ +# 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: {}