CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
restore.sh
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| 1a07e02 | 1 | #!/usr/bin/env bash |
| 2 | # | |
| 3 | # Disaster-recovery restore for the self-hosted VPS. Companion to | |
| 4 | # scripts/backup-offsite.sh. Proves the backups are actually restorable — | |
| 5 | # an untested backup is a hope, not a backup. | |
| 6 | # | |
| 7 | # Usage: | |
| 8 | # scripts/restore.sh repos <repos-YYYYMMDD-HHMMSS.tar.gz> [--dest DIR] | |
| 9 | # scripts/restore.sh db <db-YYYYMMDD-HHMMSS.dump> [--target-url URL] | |
| 10 | # | |
| 11 | # Modes: | |
| 12 | # repos Extract a repos snapshot into --dest (default: a scratch dir under | |
| 13 | # /tmp so you can diff/verify before swapping it into place). It does | |
| 14 | # NOT overwrite the live $GIT_REPOS_PATH unless you point --dest there. | |
| 15 | # db pg_restore a db dump into --target-url (default: $SCRATCH_DATABASE_URL, | |
| 16 | # NEVER $DATABASE_URL by default — restoring over prod must be explicit). | |
| 17 | # | |
| 18 | # Pull an artifact from offsite first if needed: | |
| 19 | # rclone copy $BACKUP_RCLONE_REMOTE/repos/repos-<ts>.tar.gz ./ | |
| 20 | set -euo pipefail | |
| 21 | ||
| 22 | log() { echo "$(date -Is) [restore] $*"; } | |
| 23 | die() { log "FATAL: $*"; exit 1; } | |
| 24 | ||
| 25 | mode="${1:-}"; artifact="${2:-}" | |
| 26 | shift 2 2>/dev/null || true | |
| 27 | ||
| 28 | [ -n "$mode" ] && [ -n "$artifact" ] || die "usage: restore.sh {repos|db} <artifact> [opts]" | |
| 29 | [ -f "$artifact" ] || die "artifact not found: $artifact" | |
| 30 | ||
| 31 | case "$mode" in | |
| 32 | repos) | |
| 33 | dest="/tmp/gluecron-restore-$(date +%s)" | |
| 34 | while [ $# -gt 0 ]; do case "$1" in --dest) dest="$2"; shift 2;; *) shift;; esac; done | |
| 35 | mkdir -p "$dest" | |
| 36 | tar -xzf "$artifact" -C "$dest" | |
| 37 | log "extracted repos snapshot into: $dest" | |
| 38 | log "verify a repo, e.g.: git -C \"$dest\"/repos/<owner>/<name>.git log -1" | |
| 39 | log "when satisfied, swap into place by stopping gluecron and rsync-ing over \$GIT_REPOS_PATH" | |
| 40 | ;; | |
| 41 | db) | |
| 42 | target="${SCRATCH_DATABASE_URL:-}" | |
| 43 | while [ $# -gt 0 ]; do case "$1" in --target-url) target="$2"; shift 2;; *) shift;; esac; done | |
| 44 | [ -n "$target" ] || die "no target: pass --target-url or set SCRATCH_DATABASE_URL (refusing to guess prod)" | |
| 45 | log "restoring $artifact into $target" | |
| 46 | pg_restore --clean --if-exists --no-owner --no-privileges --dbname "$target" "$artifact" | |
| 47 | log "restore complete. sanity-check: psql \"$target\" -c 'select count(*) from users;'" | |
| 48 | ;; | |
| 49 | *) | |
| 50 | die "unknown mode '$mode' (want: repos | db)" | |
| 51 | ;; | |
| 52 | esac |