# Backup diario do Postgres do tenant `plataforma` (pg_dump cifrado com age, ver P2.4) apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pg-backups namespace: plataforma-prod spec: accessModes: [ReadWriteOnce] storageClassName: local-path resources: requests: storage: 5Gi --- # ConfigMap com o DESTINATARIO age (chave PUBLICA). # # Publica pode ser versionada: ela so cifra. Quem tem a publica NAO abre o # backup. A privada e gerada por scripts/backup/gerar_chave_backup.sh na maquina # de quem restaura, e NUNCA chega neste repositorio nem no VPS. # # O valor abaixo e um marcador: substitua pelo destinatario real antes de # aplicar. Com o marcador, o backup_tenant.sh recusa rodar em vez de gravar em # claro — e o comportamento certo. apiVersion: v1 kind: ConfigMap metadata: name: pg-backup-chave namespace: plataforma-prod data: # Chave PUBLICA do age. Versionar isto e seguro e proposital: ela so serve # para CIFRAR. Abrir um backup exige a privada, que por desenho nao esta # neste repositorio nem no servidor — se estivesse no VPS, quem invadisse a # maquina levaria a chave junto com o arquivo cifrado, e o backup cifrado # nao teria servido para nada. # # Gerada em 2026-09-17 por `scripts/backup/gerar_chave_backup.sh`, na maquina # do operador. ANOTE A DATA: rotacionar exige manter a chave antiga enquanto # houver backup cifrado com ela dentro da retencao de 7 dias. destinatario: "age1g37gc3gvq0zn7802pqh5dd0m3pkvc4z3g2hqk5w3dgvnaz3hwsrq7ps04y" --- apiVersion: batch/v1 kind: CronJob metadata: name: pg-backup namespace: plataforma-prod spec: schedule: "0 2 * * *" # diariamente 02:00 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 concurrencyPolicy: Forbid jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: # Imagem propria: postgres:16 + age. O dump precisa ser cifrado # ANTES de sair do processo, entao as duas ferramentas tem de estar # no mesmo container. Construida de scripts/backup/Containerfile. - name: pg-backup image: docker.io/library/atm-pg-backup:1.0 env: - name: PGPASSWORD valueFrom: secretKeyRef: { name: db-credentials, key: password } - name: CHAVE_PUBLICA valueFrom: configMapKeyRef: { name: pg-backup-chave, key: destinatario } - { name: PGHOST, value: "postgres" } - { name: PGUSER, value: "atm" } - { name: DATABASES, value: "athleticmap keycloak" } - { name: DESTINO, value: "/backups" } - { name: RETENCAO_DIAS, value: "7" } # COMANDO_ENVIO fica vazio ate haver credencial de armazenamento # externo. Sem ele o backup fica cifrado, mas NO MESMO SERVIDOR — # e o proprio script avisa isso no fim da execucao. - { name: COMANDO_ENVIO, value: "" } command: ["backup_tenant.sh"] volumeMounts: - { name: backups, mountPath: /backups } volumes: - name: backups persistentVolumeClaim: claimName: pg-backups