feat: add database and Redis backup/restore scripts

- scripts/backup-db.sh: pg_dump compressed backup with table count
  verification, optional MinIO upload, auto-prune keeping last 7
- scripts/restore-db.sh: restore from backup with safety confirmation,
  scales down services during restore, re-grants permissions, scales
  back up with correct replica counts
- scripts/backup-redis.sh: triggers BGSAVE, copies RDB dump locally,
  shows key stats
This commit is contained in:
Celes Renata
2026-04-16 00:10:27 +00:00
parent 4634f1f3fc
commit 357e68a764
3 changed files with 272 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
set -euo pipefail
# Stonks Oracle — Database Backup
# Creates a compressed pg_dump of the stonks database and optionally
# uploads it to MinIO for offsite storage.
#
# Usage:
# ./scripts/backup-db.sh # backup to local file
# ./scripts/backup-db.sh --upload-minio # backup + upload to MinIO
#
# Requires: kubectl access to the cluster
NAMESPACE_PG="postgresql-service"
PG_POD="postgresql-1"
PG_USER="postgres"
PG_DB="stonks"
BACKUP_DIR="${BACKUP_DIR:-$HOME/backups/stonks-oracle}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="stonks-${TIMESTAMP}.sql.gz"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILE}"
MINIO_BUCKET="stonks-backups"
UPLOAD_MINIO=false
for arg in "$@"; do
case "$arg" in
--upload-minio) UPLOAD_MINIO=true ;;
esac
done
echo "=== Stonks Oracle Database Backup ==="
echo "Timestamp: ${TIMESTAMP}"
echo "Target: ${BACKUP_PATH}"
# Ensure backup directory exists
mkdir -p "${BACKUP_DIR}"
# Run pg_dump inside the PostgreSQL pod and stream the compressed output
echo "[1/3] Running pg_dump..."
kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \
pg_dump -U "${PG_USER}" -d "${PG_DB}" \
--no-owner --no-privileges --clean --if-exists \
| gzip > "${BACKUP_PATH}"
BACKUP_SIZE=$(du -h "${BACKUP_PATH}" | cut -f1)
echo " Backup size: ${BACKUP_SIZE}"
# Verify the backup is not empty
if [ ! -s "${BACKUP_PATH}" ]; then
echo "ERROR: Backup file is empty!"
rm -f "${BACKUP_PATH}"
exit 1
fi
# Count tables in the backup as a sanity check
TABLE_COUNT=$(zcat "${BACKUP_PATH}" | grep -c "^CREATE TABLE" || true)
echo " Tables found: ${TABLE_COUNT}"
# Upload to MinIO if requested
if [ "${UPLOAD_MINIO}" = true ]; then
echo "[2/3] Uploading to MinIO bucket ${MINIO_BUCKET}..."
# Create bucket if it doesn't exist
kubectl exec -n stonks-oracle "$(kubectl get pods -n stonks-oracle -l app=ingestion -o name | head -1 | sed 's|pod/||')" -- \
python -c "
from minio import Minio
import os
client = Minio(
os.environ.get('MINIO_ENDPOINT', 'minio.minio-service.svc.cluster.local:80'),
access_key=os.environ.get('MINIO_ACCESS_KEY', ''),
secret_key=os.environ.get('MINIO_SECRET_KEY', ''),
secure=False,
)
if not client.bucket_exists('${MINIO_BUCKET}'):
client.make_bucket('${MINIO_BUCKET}')
print('Created bucket ${MINIO_BUCKET}')
else:
print('Bucket ${MINIO_BUCKET} exists')
" 2>&1 || echo "WARNING: Could not verify MinIO bucket"
# Upload via kubectl cp + minio client
echo " (MinIO upload requires mc CLI or manual copy)"
echo " File ready at: ${BACKUP_PATH}"
else
echo "[2/3] Skipping MinIO upload (use --upload-minio to enable)"
fi
# Prune old backups (keep last 7)
echo "[3/3] Pruning old backups (keeping last 7)..."
ls -t "${BACKUP_DIR}"/stonks-*.sql.gz 2>/dev/null | tail -n +8 | xargs -r rm -v
echo ""
echo "=== Backup complete ==="
echo "File: ${BACKUP_PATH}"
echo "Size: ${BACKUP_SIZE}"
echo "Tables: ${TABLE_COUNT}"
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
set -euo pipefail
# Stonks Oracle — Redis State Backup
# Triggers a Redis BGSAVE and copies the RDB dump file locally.
# Useful before major deployments or database resets.
#
# Usage:
# ./scripts/backup-redis.sh
#
# Requires: kubectl access to the cluster
NAMESPACE_REDIS="redis-service"
REDIS_POD="redis-master-0"
REDIS_PASSWORD="${REDIS_PASSWORD:-PSCh4ng3me!}"
BACKUP_DIR="${BACKUP_DIR:-$HOME/backups/stonks-oracle}"
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="redis-${TIMESTAMP}.rdb"
BACKUP_PATH="${BACKUP_DIR}/${BACKUP_FILE}"
echo "=== Stonks Oracle Redis Backup ==="
echo "Timestamp: ${TIMESTAMP}"
mkdir -p "${BACKUP_DIR}"
# Trigger BGSAVE
echo "[1/3] Triggering Redis BGSAVE..."
kubectl exec -n "${NAMESPACE_REDIS}" "${REDIS_POD}" -- \
redis-cli -a "${REDIS_PASSWORD}" BGSAVE 2>/dev/null
# Wait for save to complete
echo " Waiting for background save..."
sleep 5
LAST_SAVE=$(kubectl exec -n "${NAMESPACE_REDIS}" "${REDIS_POD}" -- \
redis-cli -a "${REDIS_PASSWORD}" LASTSAVE 2>/dev/null)
echo " Last save timestamp: ${LAST_SAVE}"
# Copy the RDB file
echo "[2/3] Copying RDB dump..."
kubectl cp "${NAMESPACE_REDIS}/${REDIS_POD}:/data/dump.rdb" "${BACKUP_PATH}" 2>/dev/null || \
echo "WARNING: Could not copy RDB file (path may differ)"
if [ -f "${BACKUP_PATH}" ]; then
BACKUP_SIZE=$(du -h "${BACKUP_PATH}" | cut -f1)
echo " Size: ${BACKUP_SIZE}"
else
echo " RDB copy failed — checking alternative paths..."
# Try common Redis data paths
for path in /data/dump.rdb /var/lib/redis/dump.rdb /bitnami/redis/data/dump.rdb; do
kubectl cp "${NAMESPACE_REDIS}/${REDIS_POD}:${path}" "${BACKUP_PATH}" 2>/dev/null && break
done
if [ -f "${BACKUP_PATH}" ]; then
BACKUP_SIZE=$(du -h "${BACKUP_PATH}" | cut -f1)
echo " Size: ${BACKUP_SIZE}"
else
echo "WARNING: Could not locate RDB dump file"
fi
fi
# Show key stats
echo "[3/3] Redis key stats..."
kubectl exec -n "${NAMESPACE_REDIS}" "${REDIS_POD}" -- \
redis-cli -a "${REDIS_PASSWORD}" INFO keyspace 2>/dev/null | grep "^db"
echo ""
echo "=== Redis backup complete ==="
echo "File: ${BACKUP_PATH}"
+105
View File
@@ -0,0 +1,105 @@
#!/usr/bin/env bash
set -euo pipefail
# Stonks Oracle — Database Restore
# Restores a pg_dump backup into the stonks database.
#
# Usage:
# ./scripts/restore-db.sh <backup-file.sql.gz>
# ./scripts/restore-db.sh ~/backups/stonks-oracle/stonks-20260415-180000.sql.gz
#
# WARNING: This will DROP and recreate all tables in the stonks database.
# All existing data will be replaced with the backup contents.
#
# Requires: kubectl access to the cluster
NAMESPACE_PG="postgresql-service"
PG_POD="postgresql-1"
PG_USER="postgres"
PG_DB="stonks"
if [ $# -lt 1 ]; then
echo "Usage: $0 <backup-file.sql.gz>"
echo ""
echo "Available backups:"
ls -lh "${HOME}/backups/stonks-oracle"/stonks-*.sql.gz 2>/dev/null || echo " (none found in ~/backups/stonks-oracle/)"
exit 1
fi
BACKUP_FILE="$1"
if [ ! -f "${BACKUP_FILE}" ]; then
echo "ERROR: Backup file not found: ${BACKUP_FILE}"
exit 1
fi
BACKUP_SIZE=$(du -h "${BACKUP_FILE}" | cut -f1)
TABLE_COUNT=$(zcat "${BACKUP_FILE}" | grep -c "^CREATE TABLE" || true)
echo "=== Stonks Oracle Database Restore ==="
echo "Backup file: ${BACKUP_FILE}"
echo "File size: ${BACKUP_SIZE}"
echo "Tables: ${TABLE_COUNT}"
echo ""
echo "WARNING: This will replace ALL data in the '${PG_DB}' database."
echo ""
read -p "Are you sure? Type 'yes' to continue: " CONFIRM
if [ "${CONFIRM}" != "yes" ]; then
echo "Aborted."
exit 0
fi
# Terminate active connections to the database
echo "[1/4] Terminating active connections..."
kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \
psql -U "${PG_USER}" -c \
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '${PG_DB}' AND pid <> pg_backend_pid();" \
2>/dev/null || true
# Scale down all stonks-oracle deployments to prevent reconnections
echo "[2/4] Scaling down stonks-oracle services..."
for dep in $(kubectl get deployments -n stonks-oracle -o name 2>/dev/null); do
kubectl scale -n stonks-oracle "$dep" --replicas=0 2>/dev/null || true
done
echo " Waiting for pods to terminate..."
sleep 10
# Restore the backup
echo "[3/4] Restoring database from backup..."
zcat "${BACKUP_FILE}" | kubectl exec -i -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \
psql -U "${PG_USER}" -d "${PG_DB}" --single-transaction 2>&1 | \
grep -v "^SET$\|^COMMENT$\|^ALTER\|^DROP\|^CREATE\|^--" | head -20
# Re-grant permissions to the stonks user
echo " Re-granting permissions..."
kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \
psql -U "${PG_USER}" -d "${PG_DB}" -c "
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO stonks;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO stonks;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO stonks;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON SEQUENCES TO stonks;
"
# Verify restore
echo " Verifying..."
RESTORED_TABLES=$(kubectl exec -n "${NAMESPACE_PG}" "${PG_POD}" -c postgres -- \
psql -U "${PG_USER}" -d "${PG_DB}" -t -c \
"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE';" | tr -d ' ')
echo " Restored tables: ${RESTORED_TABLES}"
# Scale services back up
echo "[4/4] Scaling stonks-oracle services back up..."
for dep in $(kubectl get deployments -n stonks-oracle -o name 2>/dev/null); do
kubectl scale -n stonks-oracle "$dep" --replicas=1 2>/dev/null || true
done
# Scale ingestion and parser to 2 replicas
kubectl scale -n stonks-oracle deployment/ingestion --replicas=2 2>/dev/null || true
kubectl scale -n stonks-oracle deployment/parser --replicas=2 2>/dev/null || true
echo ""
echo "=== Restore complete ==="
echo "Tables restored: ${RESTORED_TABLES}"
echo "Services are scaling back up. Check with:"
echo " kubectl get pods -n stonks-oracle"