Files
bookshelf/server/deploy/backup.sh
T
claude 6c17e42037 Baseline: wave 1A server complete, wave 1B Android scaffold + design system green
assembleDebug, testDebugUnitTest, and recordPaparazziDebug all pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016bThmkmyUUdqQpy3MXFFe5
2026-09-06 01:58:37 +00:00

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.