Files
padelnomics/.gitlab-ci.yml
Deeman c0c8607664 fix: migration atomicity + deploy hardening + Litestream R2
Migration atomicity:
- Remove conn.commit() and executescript() from all up() functions (0000,
  0011, 0012, 0013, 0014, 0015); executescript() issued implicit COMMITs
  which broke the batch-rollback guarantee of the migration runner
- Rewrite 0000 with individual conn.execute() calls (was a single
  executescript block)

Deploy hardening:
- Add pre-migration DB backup step to deploy.sh: saves
  app.db.pre-deploy-<timestamp> in the volume before every migration
- On health-check failure: restore the backup, then stop + exit
- On success: clean up old backups (keep last 3)

Litestream:
- Enable R2 as primary replica in litestream.yml (env-var placeholders)
- Add local /app/data/backups as secondary replica
- docker-compose: add auto-restore on empty volume (sh entrypoint runs
  'litestream restore' before 'litestream replicate' if app.db missing)
- Add LITESTREAM_R2_* vars to .gitlab-ci.yml .env block and .env.example

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 10:28:59 +01:00

58 lines
1.9 KiB
YAML

stages:
- test
- deploy
test:
stage: test
image: python:3.12-slim
before_script:
- pip install uv
script:
- cd padelnomics && uv sync
- uv run pytest tests/ -x -q
- uv run ruff check src/ tests/
rules:
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
deploy:
stage: deploy
image: alpine:latest
needs: [test]
rules:
- if: $CI_COMMIT_BRANCH == "master"
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
script:
- |
ssh "$DEPLOY_USER@$DEPLOY_HOST" "cat > /opt/padelnomics/padelnomics/.env" << ENVEOF
APP_NAME=$APP_NAME
SECRET_KEY=$SECRET_KEY
BASE_URL=$BASE_URL
DEBUG=false
ADMIN_PASSWORD=$ADMIN_PASSWORD
DATABASE_PATH=data/app.db
MAGIC_LINK_EXPIRY_MINUTES=${MAGIC_LINK_EXPIRY_MINUTES:-15}
SESSION_LIFETIME_DAYS=${SESSION_LIFETIME_DAYS:-30}
RESEND_API_KEY=$RESEND_API_KEY
EMAIL_FROM=${EMAIL_FROM:-hello@notifications.padelnomics.io}
ADMIN_EMAIL=${ADMIN_EMAIL:-}
RATE_LIMIT_REQUESTS=${RATE_LIMIT_REQUESTS:-100}
RATE_LIMIT_WINDOW=${RATE_LIMIT_WINDOW:-60}
PADDLE_API_KEY=$PADDLE_API_KEY
PADDLE_WEBHOOK_SECRET=$PADDLE_WEBHOOK_SECRET
PADDLE_PRICE_STARTER=$PADDLE_PRICE_STARTER
PADDLE_PRICE_PRO=$PADDLE_PRICE_PRO
LITESTREAM_R2_BUCKET=$LITESTREAM_R2_BUCKET
LITESTREAM_R2_ACCESS_KEY_ID=$LITESTREAM_R2_ACCESS_KEY_ID
LITESTREAM_R2_SECRET_ACCESS_KEY=$LITESTREAM_R2_SECRET_ACCESS_KEY
LITESTREAM_R2_ENDPOINT=$LITESTREAM_R2_ENDPOINT
ENVEOF
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "chmod 600 /opt/padelnomics/padelnomics/.env"
- ssh "$DEPLOY_USER@$DEPLOY_HOST" "cd /opt/padelnomics && git pull origin master && ./deploy.sh"