90 lines
3.2 KiB
Bash
Executable File
90 lines
3.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Back up Relata: MongoDB, the uploaded originals, and optionally the Qdrant
|
|
# vectors.
|
|
#
|
|
# MongoDB and the upload directory must be captured together — an item row that
|
|
# refers to a file missing from the backup is a broken record. Qdrant is derived
|
|
# data and can be rebuilt by re-embedding, so its snapshot is a convenience.
|
|
#
|
|
# ./scripts/backup.sh [destination-directory]
|
|
#
|
|
# Stop Relata first for a consistent snapshot. The workers shut down gracefully:
|
|
# in-flight jobs drain, and any that do not finish keep their leases and return
|
|
# to the queue on restart.
|
|
|
|
set -euo pipefail
|
|
|
|
# Backups contain the complete knowledge store and may include personal data.
|
|
# Do not let a permissive caller umask create group- or world-readable archives.
|
|
umask 077
|
|
|
|
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
|
DEST="${1:-backups}/${STAMP}"
|
|
COMPOSE="${COMPOSE:-docker compose}"
|
|
UPLOAD_DIR="${RELATA_UPLOAD_DIR:-./data/uploads}"
|
|
MONGO_DB="${RELATA_MONGO_DATABASE:-relata}"
|
|
QDRANT_URL="${RELATA_QDRANT_URL:-http://127.0.0.1:6333}"
|
|
QDRANT_COLLECTION="${RELATA_QDRANT_COLLECTION:-relata_chunks}"
|
|
|
|
mkdir -p "$DEST"
|
|
echo "==> Writing backup to $DEST"
|
|
|
|
# --- MongoDB: the irreplaceable part ---------------------------------------
|
|
echo "==> MongoDB ($MONGO_DB)"
|
|
$COMPOSE exec -T mongodb mongodump --db="$MONGO_DB" --archive --gzip \
|
|
> "$DEST/relata-mongo.archive.gz"
|
|
echo " $(du -h "$DEST/relata-mongo.archive.gz" | cut -f1)"
|
|
|
|
# --- Uploaded originals ------------------------------------------------------
|
|
if [ -d "$UPLOAD_DIR" ]; then
|
|
echo "==> Uploaded originals ($UPLOAD_DIR)"
|
|
tar -czf "$DEST/uploads.tar.gz" -C "$(dirname "$UPLOAD_DIR")" "$(basename "$UPLOAD_DIR")"
|
|
echo " $(du -h "$DEST/uploads.tar.gz" | cut -f1)"
|
|
else
|
|
echo "==> No upload directory at $UPLOAD_DIR; skipping"
|
|
fi
|
|
|
|
# --- Qdrant: derived, so failure here is not fatal ---------------------------
|
|
echo "==> Qdrant snapshot (optional — vectors are rebuildable)"
|
|
if curl -fsS -X POST "$QDRANT_URL/collections/$QDRANT_COLLECTION/snapshots" \
|
|
> "$DEST/qdrant-snapshot.json" 2>/dev/null; then
|
|
echo " snapshot requested; retrieve it from the Qdrant storage volume"
|
|
else
|
|
echo " Qdrant unreachable or snapshots disabled — re-embed after restoring instead"
|
|
rm -f "$DEST/qdrant-snapshot.json"
|
|
fi
|
|
|
|
# --- Configuration, minus secrets -------------------------------------------
|
|
if [ -f .env ]; then
|
|
echo "==> Configuration reference (secret values redacted)"
|
|
# Redact by assignment rather than dropping matching lines: keeping the key
|
|
# names is useful when restoring, and a comment that merely mentions
|
|
# "password" is not a secret.
|
|
sed -E 's/^([A-Za-z_][A-Za-z0-9_]*(PASSWORD|API_KEY|SECRET|TOKEN|URI))=.*/\1=[redacted]/' \
|
|
.env > "$DEST/config-reference.env"
|
|
fi
|
|
|
|
cat > "$DEST/RESTORE.md" <<'EOF'
|
|
# Restoring this backup
|
|
|
|
docker compose stop app
|
|
|
|
docker compose exec -T mongodb mongorestore --archive --gzip --drop \
|
|
< relata-mongo.archive.gz
|
|
|
|
rm -rf ./data/uploads
|
|
tar -xzf uploads.tar.gz -C ./data
|
|
|
|
docker compose start app # or: make dev
|
|
|
|
Then check the settings page. If "vectors stored" does not match "chunks in
|
|
MongoDB", use **Settings → Re-embed workspace** to rebuild them.
|
|
|
|
Full procedure: docs/BACKUP_AND_RESTORE.md
|
|
EOF
|
|
|
|
echo
|
|
echo "==> Backup complete: $DEST"
|
|
ls -lh "$DEST"
|