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