gabriel / musehub public
backup.sh bash
86 lines 3.9 KB
Raw
sha256:8a1389ae62d0a688d5e027763249bd8faedfc39ab00857d65da6620d1ab8a689 Merge 'feat/9a-4-f7-overseer-provenance' into 'dev' — propo… Human 3 days ago
1 #!/usr/bin/env bash
2 # Postgres secondary backup script — run via cron on the EC2 instance.
3 #
4 # As of 2026-09-08 both staging and production run on managed AWS RDS, which
5 # already provides daily automated backups and point-in-time recovery
6 # (7-day retention) natively — see docs/database-architecture.md. This
7 # script is now a belt-and-suspenders SECONDARY backup, not the primary
8 # mechanism: it exists to diversify failure modes RDS's own backups don't
9 # cover (e.g. accidental instance/snapshot deletion, an account-level
10 # incident). Runs weekly, not daily, since RDS already covers day-to-day
11 # recovery needs.
12 #
13 # Two-tier backup strategy:
14 # 1. Local dump → /opt/backups/musehub/ (fast restore, 14-day rotation)
15 # 2. R2 upload → r2://BACKUP_R2_BUCKET/musehub-db/ (off-disk, long retention)
16 #
17 # Connects via DATABASE_URL (works against RDS or self-hosted Postgres
18 # identically — it's just a connection string) rather than `docker exec`
19 # into a specific container, since there's no longer a local Postgres
20 # container to exec into on either environment.
21 #
22 # R2 upload requires:
23 # - rclone installed: sudo apt-get install rclone
24 # - rclone configured with an R2 remote named "r2":
25 # rclone config (add remote → S3-compatible → Cloudflare R2)
26 # - BACKUP_R2_BUCKET set in /opt/musehub/.env, e.g.:
27 # BACKUP_R2_BUCKET=musehub-backups
28 # If BACKUP_R2_BUCKET is unset or rclone is not installed, the script
29 # continues with local-only backup and emits a warning.
30 #
31 # Install (on EC2):
32 # sudo mkdir -p /opt/backups/musehub
33 # sudo chown ubuntu:ubuntu /opt/backups/musehub
34 # chmod +x /opt/musehub/deploy/backup.sh
35 # crontab -e
36 # # Add this line (runs weekly, Sunday 3 AM):
37 # 0 3 * * 0 /opt/musehub/deploy/backup.sh >> /var/log/musehub-backup.log 2>&1
38
39 set -euo pipefail
40
41 APP_DIR="/opt/musehub"
42 BACKUP_DIR="/opt/backups/musehub"
43 TIMESTAMP=$(date +%Y%m%d_%H%M%S)
44 BACKUP_FILE="$BACKUP_DIR/musehub_${TIMESTAMP}.sql.gz"
45 RETAIN_DAYS=14
46
47 mkdir -p "$BACKUP_DIR"
48
49 echo "[$(date)] Starting backup..."
50
51 # DATABASE_URL is written by deploy/secrets.sh (SSM-sourced). psql/pg_dump
52 # don't understand SQLAlchemy's "+asyncpg" driver suffix -- strip it.
53 DATABASE_URL=$(grep '^DATABASE_URL=' "$APP_DIR/.env" | cut -d'=' -f2- | sed 's/postgresql+asyncpg/postgresql/')
54 BACKUP_R2_BUCKET=$(grep '^BACKUP_R2_BUCKET=' "$APP_DIR/.env" 2>/dev/null | cut -d'=' -f2- || true)
55
56 sudo docker run --rm postgres:16 pg_dump "$DATABASE_URL" | gzip > "$BACKUP_FILE"
57
58 echo "[$(date)] Backup written: $BACKUP_FILE ($(du -sh "$BACKUP_FILE" | cut -f1))"
59
60 # ── Off-disk: sync to Cloudflare R2 ──────────────────────────────────────────
61 # Keeps backups on a separate storage medium — survives disk failure on the
62 # EC2 instance. rclone copy is idempotent (skips already-uploaded files).
63 if [[ -n "${BACKUP_R2_BUCKET:-}" ]] && command -v rclone &>/dev/null; then
64 echo "[$(date)] Syncing backup to R2 bucket: ${BACKUP_R2_BUCKET}..."
65 rclone copy "$BACKUP_FILE" "r2:${BACKUP_R2_BUCKET}/musehub-db/" \
66 --s3-chunk-size=128M \
67 --s3-upload-concurrency=4 \
68 --stats=30s
69 echo "[$(date)] R2 upload complete."
70
71 # Remove R2 copies older than 90 days (long-term retention).
72 rclone delete "r2:${BACKUP_R2_BUCKET}/musehub-db/" \
73 --min-age=90d \
74 --include "musehub_*.sql.gz" || true
75 echo "[$(date)] R2 old-backup rotation complete."
76 else
77 echo "[$(date)] WARNING: BACKUP_R2_BUCKET not set or rclone not installed — local-only backup."
78 echo "[$(date)] To enable off-disk backups: install rclone, configure an R2 remote,"
79 echo "[$(date)] and set BACKUP_R2_BUCKET=<your-bucket-name> in $APP_DIR/.env"
80 fi
81
82 echo "[$(date)] Removing local backups older than $RETAIN_DAYS days..."
83 find "$BACKUP_DIR" -name "musehub_*.sql.gz" -mtime "+$RETAIN_DAYS" -delete
84
85 echo "[$(date)] Backup complete. Local files kept:"
86 ls -lh "$BACKUP_DIR"
File History 17 commits
sha256:8a1389ae62d0a688d5e027763249bd8faedfc39ab00857d65da6620d1ab8a689 Merge 'feat/9a-4-f7-overseer-provenance' into 'dev' — propo… Human 3 days ago
sha256:bee12c5cbde2334f98421c6c209d768fa6b8004d6705c9ea798ce6c1651bc11f Merge 'infra/database-phase3-4-cleanup' into 'dev' — propos… Human 4 days ago
sha256:316e70bc7bfc59633679c76f96aee8b77de3e8b04f70f3bb38ba83df3cb1a5ed Merge 'infra/database-phase2-production-rds' into 'dev' — p… Human 4 days ago
sha256:cd5c2fcb91a44ac9e38e9c36176c27ce079a074586d9852296da628a29fb01ff Merge 'docs/aws-identity-and-deploy-fixes' into 'dev' — pro… Human 15 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 67 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 70 days ago
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 73 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 75 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 88 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 92 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 93 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 94 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 94 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 94 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 94 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 106 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 108 days ago