| 1 | #!/usr/bin/env bash |
| 2 | # SPDX-License-Identifier: AGPL-3.0-or-later |
| 3 | # |
| 4 | # Daily logical backup. Run from cron (or a systemd timer) as the |
| 5 | # postgres user. We take a custom-format pg_dump of the shithub DB, |
| 6 | # stream it to Spaces, and keep one local copy in /var/backups for |
| 7 | # the operator to grab in a hurry. Lifecycle on the bucket prunes |
| 8 | # anything older than 30 days; PITR rolls forward from the WAL |
| 9 | # archive (see archive_command.sh). |
| 10 | # |
| 11 | # Exit non-zero on any failure so the systemd timer surfaces it |
| 12 | # (OnFailure= → alertmanager). |
| 13 | |
| 14 | set -euo pipefail |
| 15 | |
| 16 | DB="${SHITHUB_DB:-shithub}" |
| 17 | BUCKET="${SHITHUB_BACKUP_BUCKET:-spaces-prod:shithub-backups}" |
| 18 | LOCAL_DIR="${SHITHUB_BACKUP_LOCAL:-/var/backups/shithub}" |
| 19 | STAMP="$(date -u +%Y%m%dT%H%M%SZ)" |
| 20 | NAME="${DB}-${STAMP}.dump" |
| 21 | |
| 22 | mkdir -p "$LOCAL_DIR" |
| 23 | |
| 24 | # pg_dump as the postgres user via local-socket peer auth. |
| 25 | # Cron runs this script as root; sudo handles the user switch. |
| 26 | sudo -u postgres pg_dump --format=custom --compress=9 --no-owner --no-privileges \ |
| 27 | --file="$LOCAL_DIR/$NAME" "$DB" |
| 28 | |
| 29 | # Verify the dump is structurally sound before we ship it. |
| 30 | pg_restore --list "$LOCAL_DIR/$NAME" >/dev/null |
| 31 | |
| 32 | # --s3-no-check-bucket: skip the GetBucketLocation pre-check that |
| 33 | # requires a permission our scoped-RW Spaces key doesn't grant. |
| 34 | # The actual PUT works fine on a key with bucket-level readwrite. |
| 35 | rclone --config /etc/rclone-shithub.conf --s3-no-check-bucket \ |
| 36 | copyto "$LOCAL_DIR/$NAME" "$BUCKET/daily/$(date -u +%Y/%m/%d)/$NAME" |
| 37 | |
| 38 | # Local retention: keep the last 7 dumps; bucket lifecycle handles |
| 39 | # the long tail. |
| 40 | ls -1t "$LOCAL_DIR"/*.dump 2>/dev/null | tail -n +8 | xargs -r rm -f |