#!/usr/bin/env bash # Pull a full Pak Jabit backend backup to the house. # - pg_dump (custom format) of spree_production # - ActiveStorage volume, rsync'd mirror (~208MB, only deltas after the first run) # - .env (SECRET_KEY_BASE lives here — without it every signed image URL and # the admin API token die on restore) # # Usage: backup-pak-jabit.sh # normal run # backup-pak-jabit.sh --verify # also list the dump's table counts set -euo pipefail HOST=ijat@35.247.170.3 KEY=/root/.ssh/google_compute_engine SSH=(ssh -i "$KEY" -o ConnectTimeout=15 -o BatchMode=yes) DEST=/root/Backups/pak-jabit KEEP=14 # dated dumps to retain STAMP=$(date +%Y%m%d-%H%M) STORAGE_REMOTE=/var/lib/docker/volumes/pak-jabit_storage_data/_data/ mkdir -p "$DEST"/{db,storage} chmod 700 "$DEST" log() { printf '%s %s\n' "$(date +%H:%M:%S)" "$*"; } # --- 1. database ------------------------------------------------------------- log "pg_dump → db/spree-$STAMP.dump" "${SSH[@]}" "$HOST" \ 'docker exec pak-jabit-postgres-1 pg_dump -U postgres -Fc spree_production' \ > "$DEST/db/spree-$STAMP.dump.part" mv "$DEST/db/spree-$STAMP.dump.part" "$DEST/db/spree-$STAMP.dump" # A truncated dump is the classic silent-success backup. Refuse to keep one. if ! pg_restore -l "$DEST/db/spree-$STAMP.dump" >/dev/null 2>&1; then rm -f "$DEST/db/spree-$STAMP.dump" echo "FATAL: dump failed pg_restore -l — not a usable archive, deleted." >&2 exit 1 fi log " ok $(du -h "$DEST/db/spree-$STAMP.dump" | cut -f1)" # --- 2. ActiveStorage -------------------------------------------------------- log "rsync storage/" rsync -a --delete --info=stats2 \ -e "ssh -i $KEY -o BatchMode=yes" \ --rsync-path='sudo rsync' \ "$HOST:$STORAGE_REMOTE" "$DEST/storage/" | tail -n 6 # --- 3. secrets & compose ---------------------------------------------------- log "env + compose" "${SSH[@]}" "$HOST" 'cat ~/pak-jabit/.env' > "$DEST/env-$STAMP" "${SSH[@]}" "$HOST" 'cat ~/pak-jabit/docker-compose.yml' > "$DEST/docker-compose.yml" chmod 600 "$DEST"/env-* ln -sfn "env-$STAMP" "$DEST/env.latest" # --- 3b. custom app code ----------------------------------------------------- # The Spree image is prebuilt and mounts no app code, so every customisation # (Image Studio, the API body-limit initializer, the patched routes.rb) exists # ONLY as bind-mounted files on this VM. The database dump does not contain them # and a rebuilt box would come back without them. log "custom code" rsync -a --delete \ -e "ssh -i $KEY -o BatchMode=yes" \ --include='config/' --include='config/routes.rb' \ --include='config/initializers/***' \ --include='app/' --include='app/controllers/***' --include='app/views/***' \ --exclude='*' \ "$HOST:pak-jabit/" "$DEST/custom/" find "$DEST/custom" -type f | sed "s|$DEST/custom/| |" # --- 4. rotate --------------------------------------------------------------- ls -1t "$DEST"/db/spree-*.dump 2>/dev/null | tail -n +$((KEEP + 1)) | xargs -r rm -v ls -1t "$DEST"/env-* 2>/dev/null | tail -n +$((KEEP + 1)) | xargs -r rm -v if [[ ${1:-} == --verify ]]; then log "dump contents:" pg_restore -l "$DEST/db/spree-$STAMP.dump" | grep -c 'TABLE DATA' \ | xargs printf ' %s tables with data\n' fi log "done → $DEST ($(du -sh "$DEST" | cut -f1) total)"