gendesign/ops/backup.sh
lekss361 4f034bd86b ops: alembic baseline, pre-commit, TIGER cleanup, pg_dump scripts
- 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.
2026-04-26 13:08:51 +03:00

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."