briven-backup.sh133 lines · main
1#!/usr/bin/env bash
2# Daily backup of briven control plane (+ optional off-site mirror).
3# Runs on the France host via systemd (briven-backup.{service,timer}).
4#
5# Topology (2026-07-21): Briven is Doltgres end-to-end.
6# - Control + data plane: DoltGres (briven_control + proj_* databases).
7# - dolt-backup sidecar runs native dolt_backup for ALL databases including
8# briven_control. This host script is a transitional dump helper for
9# stock Postgres rollback windows only — prefer dolt_backup + off-site
10# mirror of the doltgres_backups volume for real DR.
11#
12# Env (optional, /etc/briven/backup.env):
13# BRIVEN_BACKUP_PG_CONTAINER default: briven-brivenfrance-uilsk6-postgres-1
14# BRIVEN_BACKUP_PG_USER default: postgres
15# BRIVEN_BACKUP_DBS space-separated; default: briven_control
16# BRIVEN_BACKUP_S3_ENDPOINT if set with bucket+keys → off-site upload
17# BRIVEN_BACKUP_S3_BUCKET
18# BRIVEN_BACKUP_S3_ACCESS_KEY
19# BRIVEN_BACKUP_S3_SECRET_KEY
20#
21# Local layout:
22# /var/backups/briven/<db-name>/<YYYY-MM-DD>/<hh-mm-ss>.dump.gz
23#
24# Exit 1 if any off-site upload fails (local dump still kept) so
25# OnFailure=briven-backup-alert.service can fire.
26
27set -euo pipefail
28
29BACKUP_ENV_FILE="/etc/briven/backup.env"
30if [ -f "$BACKUP_ENV_FILE" ]; then
31 # shellcheck disable=SC1090
32 source "$BACKUP_ENV_FILE"
33fi
34
35PG_CONTAINER="${BRIVEN_BACKUP_PG_CONTAINER:-briven-brivenfrance-uilsk6-postgres-1}"
36PG_USER="${BRIVEN_BACKUP_PG_USER:-postgres}"
37# shellcheck disable=SC2206
38DBS=(${BRIVEN_BACKUP_DBS:-briven_control})
39LOCAL_BACKUP_ROOT="/var/backups/briven"
40LOCAL_RETENTION_DAYS="${BRIVEN_BACKUP_LOCAL_RETENTION_DAYS:-30}"
41
42STAMP="$(date -u +'%Y-%m-%d/%H-%M-%S')"
43UPLOAD_FAILURES=0
44UPLOAD_FAILURE_DBS=""
45
46log() {
47 printf '[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"
48}
49
50die() {
51 log "ERROR: $*"
52 exit 1
53}
54
55dump_one() {
56 local db_name="$1"
57 local local_dir="${LOCAL_BACKUP_ROOT}/${db_name}/${STAMP%/*}"
58 local local_file="${local_dir}/${STAMP##*/}.dump.gz"
59
60 mkdir -p "$local_dir"
61
62 if ! docker inspect "$PG_CONTAINER" >/dev/null 2>&1; then
63 die "postgres container not found: ${PG_CONTAINER}"
64 fi
65
66 log "dumping ${db_name} from ${PG_CONTAINER}"
67 if ! docker exec "$PG_CONTAINER" pg_dump \
68 --username="$PG_USER" \
69 --format=custom \
70 --compress=0 \
71 --no-owner --no-privileges \
72 "$db_name" \
73 | gzip -9 > "$local_file"; then
74 rm -f "$local_file"
75 die "pg_dump failed for ${db_name}"
76 fi
77
78 local size
79 size="$(stat -c%s "$local_file")"
80 log "ok ${db_name}: ${size} bytes → ${local_file}"
81
82 # Optional off-site (B2/R2 S3-compatible via minio/mc).
83 if [ -n "${BRIVEN_BACKUP_S3_ENDPOINT:-}" ] \
84 && [ -n "${BRIVEN_BACKUP_S3_BUCKET:-}" ] \
85 && [ -n "${BRIVEN_BACKUP_S3_ACCESS_KEY:-}" ] \
86 && [ -n "${BRIVEN_BACKUP_S3_SECRET_KEY:-}" ]; then
87 local object="s3://${BRIVEN_BACKUP_S3_BUCKET}/${db_name}/${STAMP}.dump.gz"
88 log "uploading ${object}"
89 local endpoint_no_scheme="${BRIVEN_BACKUP_S3_ENDPOINT#https://}"
90 endpoint_no_scheme="${endpoint_no_scheme#http://}"
91 if ! docker run --rm \
92 -v "${local_file}:/backup.dump.gz:ro" \
93 -e "MC_HOST_off=https://${BRIVEN_BACKUP_S3_ACCESS_KEY}:${BRIVEN_BACKUP_S3_SECRET_KEY}@${endpoint_no_scheme}" \
94 --entrypoint sh \
95 minio/mc:latest \
96 -c "mc cp /backup.dump.gz off/${BRIVEN_BACKUP_S3_BUCKET}/${db_name}/${STAMP}.dump.gz"; then
97 log "WARN: off-site upload failed for ${db_name} — local copy still safe"
98 UPLOAD_FAILURES=$((UPLOAD_FAILURES + 1))
99 UPLOAD_FAILURE_DBS="${UPLOAD_FAILURE_DBS:+${UPLOAD_FAILURE_DBS} }${db_name}"
100 else
101 log "off-site upload ok"
102 fi
103 else
104 log "off-site upload skipped (BRIVEN_BACKUP_S3_* unset) — Phase 0.1 still Not done"
105 fi
106}
107
108prune_local() {
109 log "pruning local dumps older than ${LOCAL_RETENTION_DAYS}d"
110 if [ -d "$LOCAL_BACKUP_ROOT" ]; then
111 find "$LOCAL_BACKUP_ROOT" -type f -name '*.dump.gz' -mtime +"$LOCAL_RETENTION_DAYS" -delete || true
112 find "$LOCAL_BACKUP_ROOT" -type d -empty -delete || true
113 fi
114}
115
116log "briven backup run starting (container=${PG_CONTAINER} dbs=${DBS[*]})"
117
118for db in "${DBS[@]}"; do
119 dump_one "$db"
120done
121
122prune_local
123
124if [ "$UPLOAD_FAILURES" -gt 0 ]; then
125 log "ERROR: ${UPLOAD_FAILURES} off-site upload(s) failed (dbs=${UPLOAD_FAILURE_DBS})"
126 echo "upload_failures=${UPLOAD_FAILURES}" > /run/briven-backup-status
127 echo "upload_failure_dbs=${UPLOAD_FAILURE_DBS}" >> /run/briven-backup-status
128 exit 1
129fi
130
131# Clear stale status on success
132rm -f /run/briven-backup-status
133log "briven backup run complete"