assembleDebug, testDebugUnitTest, and recordPaparazziDebug all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016bThmkmyUUdqQpy3MXFFe5
84 lines
3.3 KiB
Bash
Executable File
84 lines
3.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# backup.sh — SQLite-safe backup of a running PocketBase pb_data directory.
|
|
#
|
|
# PocketBase's data.db and auxiliary.db are SQLite databases opened in WAL
|
|
# mode. Copying the .db files directly with `cp` while the server is running
|
|
# can grab an inconsistent snapshot if a checkpoint happens mid-copy. Using
|
|
# sqlite3's `.backup` command (or PocketBase's own "backups" API) takes a
|
|
# consistent snapshot safely without stopping the server.
|
|
#
|
|
# This script uses `sqlite3 .backup`, which is the simplest option that needs
|
|
# no PocketBase superuser credentials. It backs up pb_data (including
|
|
# uploaded files) into timestamped tarballs and prunes old ones.
|
|
#
|
|
# Usage:
|
|
# ./backup.sh [PB_DATA_DIR] [BACKUP_DIR]
|
|
# PB_DATA_DIR defaults to ../pb_data (relative to this script)
|
|
# BACKUP_DIR defaults to ./backups (relative to this script)
|
|
#
|
|
# Suggested cron (nightly at 3am, keep last 14):
|
|
# 0 3 * * * /opt/bookshelf/deploy/backup.sh >> /var/log/bookshelf-backup.log 2>&1
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PB_DATA_DIR="${1:-$SCRIPT_DIR/../pb_data}"
|
|
BACKUP_DIR="${2:-$SCRIPT_DIR/backups}"
|
|
KEEP_LAST="${KEEP_LAST:-14}"
|
|
|
|
log() { printf '[%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$*"; }
|
|
fail() { log "ERROR: $*"; exit 1; }
|
|
|
|
command -v sqlite3 >/dev/null 2>&1 || fail "sqlite3 is required (apt install sqlite3 / apk add sqlite)."
|
|
[[ -d "$PB_DATA_DIR" ]] || fail "pb_data directory not found: $PB_DATA_DIR"
|
|
|
|
TIMESTAMP="$(date -u '+%Y%m%d_%H%M%S')"
|
|
WORKDIR="$(mktemp -d)"
|
|
trap 'rm -rf "$WORKDIR"' EXIT
|
|
|
|
mkdir -p "$BACKUP_DIR"
|
|
|
|
log "Backing up pb_data from $PB_DATA_DIR"
|
|
|
|
# Consistent SQLite snapshots via the .backup command (safe for a live,
|
|
# WAL-mode database — it's the same mechanism `sqlite3 db.sqlite .backup`
|
|
# uses, which briefly locks for the copy but never corrupts).
|
|
for db in data.db auxiliary.db; do
|
|
src="$PB_DATA_DIR/$db"
|
|
if [[ -f "$src" ]]; then
|
|
log " snapshotting $db"
|
|
sqlite3 "$src" ".backup '$WORKDIR/$db'"
|
|
fi
|
|
done
|
|
|
|
# Everything else under pb_data that isn't a sqlite file/WAL/SHM (i.e.
|
|
# uploaded covers under storage/) gets copied as-is — these are immutable
|
|
# blobs once written, so a plain copy is safe.
|
|
log " copying uploaded files (storage/)"
|
|
if [[ -d "$PB_DATA_DIR/storage" ]]; then
|
|
cp -a "$PB_DATA_DIR/storage" "$WORKDIR/storage"
|
|
fi
|
|
|
|
ARCHIVE="$BACKUP_DIR/bookshelf-backup-$TIMESTAMP.tar.gz"
|
|
tar -czf "$ARCHIVE" -C "$WORKDIR" .
|
|
log "Wrote $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"
|
|
|
|
# Prune old backups, keep the most recent $KEEP_LAST.
|
|
mapfile -t old < <(ls -1t "$BACKUP_DIR"/bookshelf-backup-*.tar.gz 2>/dev/null | tail -n +$((KEEP_LAST + 1)))
|
|
if [[ ${#old[@]} -gt 0 ]]; then
|
|
log "Pruning ${#old[@]} backup(s) older than the last $KEEP_LAST"
|
|
rm -f "${old[@]}"
|
|
fi
|
|
|
|
log "Done."
|
|
|
|
# --- Restore ---------------------------------------------------------------
|
|
# 1. Stop the server: sudo systemctl stop bookshelf
|
|
# 2. Move aside the live data: mv /opt/bookshelf/pb_data /opt/bookshelf/pb_data.bak
|
|
# 3. Extract the chosen backup: mkdir /opt/bookshelf/pb_data && \
|
|
# tar -xzf bookshelf-backup-TIMESTAMP.tar.gz -C /opt/bookshelf/pb_data
|
|
# 4. Fix ownership: sudo chown -R bookshelf:bookshelf /opt/bookshelf/pb_data
|
|
# 5. Start the server again: sudo systemctl start bookshelf
|
|
# 6. Once you've confirmed it's healthy, delete pb_data.bak.
|