# Backup diario do Postgres do tenant escolinha. # # Cifrado com `age` desde 2026-09-21: ate aqui ele saia em texto comprimido, # e o escolinha e o UNICO tenant com dado de cliente real — quem pegasse o # arquivo lia tudo. Os outros tres ja cifravam; este era o que faltava. apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pg-backups namespace: escolinha-prod spec: accessModes: [ReadWriteOnce] storageClassName: local-path resources: requests: storage: 5Gi --- apiVersion: v1 kind: ConfigMap metadata: name: pg-backup-chave namespace: escolinha-prod data: # A MESMA publica do `plataforma` (2026-09-21): uma chave privada # restaura tudo, e ela vive fora do servidor. Chave por tenant seria # decisao do dono, e multiplicaria o que ele precisa guardar. destinatario: "age1g37gc3gvq0zn7802pqh5dd0m3pkvc4z3g2hqk5w3dgvnaz3hwsrq7ps04y" --- apiVersion: batch/v1 kind: CronJob metadata: name: pg-backup namespace: escolinha-prod spec: schedule: "0 2 * * *" successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 concurrencyPolicy: Forbid jobTemplate: spec: template: # O label e o que a 19-netpol-backup-offsite seleciona: so o pod do backup # sai para a internet (443), para mandar o dump cifrado ao bucket. metadata: labels: { app: pg-backup } spec: restartPolicy: OnFailure containers: # Imagem propria: postgres:16 + age + rclone. O dump e cifrado ANTES # de sair do processo, entao as ferramentas tem de estar no mesmo # container. Construida de scripts/backup/Containerfile. # # Substituiu um shell embutido que fazia `pg_dump | gzip` e mais nada. - name: pg-backup image: docker.io/library/atm-pg-backup:1.3 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" } # Os MESMOS dois bancos que o shell antigo copiava. - { name: DATABASES, value: "athleticmap keycloak" } - { name: DESTINO, value: "/backups" } - { name: RETENCAO_DIAS, value: "7" } # O envio para fora se cala sem o Secret, e falha alto quando ele # existe e nao funciona — sao coisas diferentes. - { name: COMANDO_ENVIO, value: "enviar_offsite.sh {}" } # Um prefixo POR ORIGEM no bucket: sem ele todos gravavam em `athleticmap/` com o # mesmo nome de arquivo — nao se sabia de quem era o dump, e dois no mesmo segundo # se sobrescreveriam (visto em 01/10/2026). - { name: OFFSITE_PREFIXO, value: "athleticmap/escolinha" } # `optional: true`: sem o Secret o CronJob roda igual e guarda local. envFrom: - secretRef: { name: backup-offsite, optional: true } command: ["backup_tenant.sh"] volumeMounts: - name: backups mountPath: /backups volumes: - name: backups persistentVolumeClaim: claimName: pg-backups