feat(01-04): create PostgreSQL backup script with 30-day retention
- Add backup-postgres.sh with pg_dump custom format (-Fc) - Verify backup integrity via pg_restore --list - Compress backups with gzip for storage efficiency - Delete backups older than 30 days (configurable via RETENTION_DAYS) - Weekly restore test on Mondays to validate backup usability - Add cron configuration for daily 2 AM backups - Add .gitignore for pycache, env files, and backup files
This commit is contained in:
parent
cd94d99c62
commit
09f89617e7
3 changed files with 144 additions and 0 deletions
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
.coverage
|
||||||
|
htmlcov/
|
||||||
|
.pytest_cache/
|
||||||
|
.mypy_cache/
|
||||||
|
|
||||||
|
# Database backups (should not be in repo)
|
||||||
|
*.dump
|
||||||
|
*.dump.gz
|
||||||
|
/backups/
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
83
scripts/backup-postgres.sh
Executable file
83
scripts/backup-postgres.sh
Executable file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# PostgreSQL backup script for Debate platform
|
||||||
|
# Runs daily, keeps 30 days of backups
|
||||||
|
# Verifies backup integrity after creation
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
BACKUP_DIR="${BACKUP_DIR:-/var/backups/debate/postgres}"
|
||||||
|
RETENTION_DAYS="${RETENTION_DAYS:-30}"
|
||||||
|
CONTAINER_NAME="${CONTAINER_NAME:-debate-postgres}"
|
||||||
|
DB_NAME="${DB_NAME:-debate}"
|
||||||
|
DB_USER="${DB_USER:-debate}"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.dump"
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
log() {
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create backup directory
|
||||||
|
mkdir -p "$BACKUP_DIR"
|
||||||
|
|
||||||
|
log "Starting backup of database: $DB_NAME"
|
||||||
|
|
||||||
|
# Create backup using pg_dump custom format (-Fc)
|
||||||
|
# Custom format is compressed and allows selective restore
|
||||||
|
docker exec "$CONTAINER_NAME" pg_dump \
|
||||||
|
-U "$DB_USER" \
|
||||||
|
-Fc \
|
||||||
|
-b \
|
||||||
|
-v \
|
||||||
|
"$DB_NAME" > "$BACKUP_FILE" 2>/dev/null
|
||||||
|
|
||||||
|
log "Backup created: $BACKUP_FILE"
|
||||||
|
|
||||||
|
# Verify backup integrity using pg_restore --list
|
||||||
|
# This reads the archive table of contents without restoring
|
||||||
|
# We pipe the backup into the container since pg_restore is only available there
|
||||||
|
log "Verifying backup integrity..."
|
||||||
|
cat "$BACKUP_FILE" | docker exec -i "$CONTAINER_NAME" pg_restore --list > /dev/null 2>&1 || {
|
||||||
|
log "ERROR: Backup verification failed!"
|
||||||
|
rm -f "$BACKUP_FILE"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get backup size
|
||||||
|
BACKUP_SIZE=$(du -h "$BACKUP_FILE" | cut -f1)
|
||||||
|
log "Backup size: $BACKUP_SIZE"
|
||||||
|
|
||||||
|
# Compress backup (pg_dump -Fc includes compression, but gzip adds more)
|
||||||
|
gzip -f "$BACKUP_FILE"
|
||||||
|
log "Compressed: ${BACKUP_FILE}.gz"
|
||||||
|
|
||||||
|
# Clean up old backups
|
||||||
|
log "Removing backups older than $RETENTION_DAYS days..."
|
||||||
|
find "$BACKUP_DIR" -name "${DB_NAME}_*.dump.gz" -mtime +$RETENTION_DAYS -delete
|
||||||
|
REMAINING=$(find "$BACKUP_DIR" -name "${DB_NAME}_*.dump.gz" | wc -l)
|
||||||
|
log "Remaining backups: $REMAINING"
|
||||||
|
|
||||||
|
# Weekly restore test (every Monday)
|
||||||
|
if [ "$(date +%u)" -eq 1 ]; then
|
||||||
|
log "Running weekly restore test..."
|
||||||
|
TEST_DB="${DB_NAME}_backup_test"
|
||||||
|
|
||||||
|
# Create test database
|
||||||
|
docker exec "$CONTAINER_NAME" createdb -U "$DB_USER" "$TEST_DB" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Restore to test database
|
||||||
|
gunzip -c "${BACKUP_FILE}.gz" | docker exec -i "$CONTAINER_NAME" pg_restore \
|
||||||
|
-U "$DB_USER" \
|
||||||
|
-d "$TEST_DB" \
|
||||||
|
--clean \
|
||||||
|
--if-exists 2>&1 || true
|
||||||
|
|
||||||
|
# Drop test database
|
||||||
|
docker exec "$CONTAINER_NAME" dropdb -U "$DB_USER" "$TEST_DB" 2>/dev/null || true
|
||||||
|
|
||||||
|
log "Weekly restore test completed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Backup completed successfully"
|
||||||
5
scripts/cron/postgres-backup
Normal file
5
scripts/cron/postgres-backup
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
# PostgreSQL daily backup at 2 AM
|
||||||
|
# Install: sudo cp scripts/cron/postgres-backup /etc/cron.d/debate-postgres-backup
|
||||||
|
# Requires: /var/log/debate directory to exist
|
||||||
|
|
||||||
|
0 2 * * * root /home/mikkel/repos/debate/scripts/backup-postgres.sh >> /var/log/debate/postgres-backup.log 2>&1
|
||||||
Loading…
Add table
Reference in a new issue