- backend/alembic/* — alembic infra (env.py, script.py.mako). versions/ empty for now; first migration goes in Stage 2a when models are finalized. - backend/Dockerfile: bake alembic.ini + alembic/ into the image so `docker compose exec backend alembic upgrade head` works in prod. - backend/db/init/99_drop_unused_extensions.sql + bind-mount in both compose files: drops postgis-image's TIGER/topology/fuzzystrmatch on fresh volumes. - .pre-commit-config.yaml + pre-commit in dev deps: ruff/prettier on commit to stop CI failures like UP035 from leaking out. - ops/backup.sh, ops/restore.sh: pg_dump cron script with optional Selectel S3 upload. 7-day local retention. Restore guard: requires RESTORE_CONFIRM=yes. - Makefile: new targets `make migration NAME=...`, `make pre-commit-install`. - backend/.env.example: SENTRY_DSN comment with sentry.io reference.
73 lines
2.7 KiB
Bash
73 lines
2.7 KiB
Bash
#!/usr/bin/env bash
|
|
# Daily Postgres backup script. Designed to run from cron on the prod VM.
|
|
#
|
|
# Usage (one-time setup on VM):
|
|
# sudo cp /opt/gendesign/ops/backup.sh /usr/local/bin/gendesign-backup
|
|
# sudo chmod +x /usr/local/bin/gendesign-backup
|
|
# crontab -e
|
|
# # Run daily at 03:30 Moscow time
|
|
# 30 3 * * * /usr/local/bin/gendesign-backup >> /var/log/gendesign-backup.log 2>&1
|
|
#
|
|
# Optional S3 upload — set these env vars in /etc/default/gendesign-backup
|
|
# (or as `Environment=` in a systemd timer). Without them, dumps stay local only.
|
|
#
|
|
# S3_ENDPOINT=https://s3.ru-1.storage.selcloud.ru # Selectel S3
|
|
# S3_BUCKET=gendesign-backups
|
|
# S3_ACCESS_KEY=...
|
|
# S3_SECRET_KEY=...
|
|
#
|
|
# Retention: deletes local dumps older than 7 days. S3 retention policy should
|
|
# be configured at the bucket level (lifecycle rule), not in this script.
|
|
|
|
set -euo pipefail
|
|
|
|
# --- config ---
|
|
COMPOSE_DIR=/opt/gendesign
|
|
COMPOSE_FILE=docker-compose.prod.yml
|
|
LOCAL_BACKUP_DIR=/opt/gendesign/backups
|
|
RETENTION_DAYS=7
|
|
|
|
# Optional S3 env. Loaded from /etc/default/gendesign-backup if present.
|
|
[[ -f /etc/default/gendesign-backup ]] && source /etc/default/gendesign-backup
|
|
|
|
# --- run ---
|
|
mkdir -p "$LOCAL_BACKUP_DIR"
|
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
|
DUMP_FILE="${LOCAL_BACKUP_DIR}/gendesign_${TIMESTAMP}.sql.gz"
|
|
|
|
cd "$COMPOSE_DIR"
|
|
|
|
# Read DB user/name from running compose env. Falls back to "gendesign" if env empty.
|
|
DB_USER=$(docker compose -f "$COMPOSE_FILE" exec -T postgres printenv POSTGRES_USER 2>/dev/null | tr -d '\r' || true)
|
|
DB_NAME=$(docker compose -f "$COMPOSE_FILE" exec -T postgres printenv POSTGRES_DB 2>/dev/null | tr -d '\r' || true)
|
|
DB_USER=${DB_USER:-gendesign}
|
|
DB_NAME=${DB_NAME:-gendesign}
|
|
|
|
echo "[$(date -Is)] Dumping ${DB_NAME} as ${DB_USER} → ${DUMP_FILE}"
|
|
|
|
docker compose -f "$COMPOSE_FILE" exec -T postgres \
|
|
pg_dump --clean --if-exists -U "$DB_USER" "$DB_NAME" \
|
|
| gzip -9 > "$DUMP_FILE"
|
|
|
|
DUMP_SIZE=$(stat -c '%s' "$DUMP_FILE")
|
|
echo "[$(date -Is)] Dump complete: ${DUMP_SIZE} bytes"
|
|
|
|
# --- optional S3 upload ---
|
|
if [[ -n "${S3_ENDPOINT:-}" && -n "${S3_BUCKET:-}" && -n "${S3_ACCESS_KEY:-}" && -n "${S3_SECRET_KEY:-}" ]]; then
|
|
echo "[$(date -Is)] Uploading to s3://${S3_BUCKET}/"
|
|
docker run --rm \
|
|
-e AWS_ACCESS_KEY_ID="$S3_ACCESS_KEY" \
|
|
-e AWS_SECRET_ACCESS_KEY="$S3_SECRET_KEY" \
|
|
-v "$LOCAL_BACKUP_DIR":/backup:ro \
|
|
amazon/aws-cli:latest \
|
|
--endpoint-url "$S3_ENDPOINT" \
|
|
s3 cp "/backup/$(basename "$DUMP_FILE")" "s3://${S3_BUCKET}/"
|
|
echo "[$(date -Is)] S3 upload OK"
|
|
else
|
|
echo "[$(date -Is)] S3 vars not set — backup stays local only"
|
|
fi
|
|
|
|
# --- prune old local dumps ---
|
|
find "$LOCAL_BACKUP_DIR" -name 'gendesign_*.sql.gz' -mtime "+${RETENTION_DAYS}" -print -delete
|
|
|
|
echo "[$(date -Is)] Backup done."
|