name: CI on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: jobs: test: runs-on: ubuntu-latest env: DATABASE_URL: sqlite:///./ci.db PYTHONUNBUFFERED: "1" steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" cache: pip - run: python -m pip install --upgrade pip - run: python -m pip install -r requirements.txt - run: python -m pytest -q - run: node --check app/static/admin.js deploy: if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'workflow_dispatch' needs: test runs-on: ubuntu-latest concurrency: group: production-deploy cancel-in-progress: false env: PROD_PATH: /srv/miem_workers RELEASE_NAME: miem-workers-release-${{ github.run_id }}.tar steps: - uses: actions/checkout@v4 - name: Prepare release id: prepare shell: bash run: | set -euo pipefail backend_version="$(sed -n 's/^BACKEND_VERSION = "\(.*\)"$/\1/p' app/version.py)" test -n "$backend_version" echo "backend_version=$backend_version" >> "$GITHUB_OUTPUT" tar --exclude=.git --exclude=.env -cf "$RUNNER_TEMP/$RELEASE_NAME" . printf '%s\n' "$PROD_SSH_KEY" > "$RUNNER_TEMP/deploy_key" printf '%s\n' "$PROD_KNOWN_HOSTS" > "$RUNNER_TEMP/known_hosts" chmod 600 "$RUNNER_TEMP/deploy_key" "$RUNNER_TEMP/known_hosts" env: PROD_SSH_KEY: ${{ secrets.PROD_SSH_KEY }} PROD_KNOWN_HOSTS: ${{ secrets.PROD_KNOWN_HOSTS }} - name: Upload release shell: bash run: | set -euo pipefail scp -i "$RUNNER_TEMP/deploy_key" \ -o BatchMode=yes \ -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ "$RUNNER_TEMP/$RELEASE_NAME" \ "$PROD_USER@$PROD_HOST:$RELEASE_NAME" env: PROD_HOST: ${{ secrets.PROD_HOST }} PROD_USER: ${{ secrets.PROD_USER }} - name: Deploy and verify shell: bash run: | set -euo pipefail ssh -i "$RUNNER_TEMP/deploy_key" \ -o BatchMode=yes \ -o UserKnownHostsFile="$RUNNER_TEMP/known_hosts" \ "$PROD_USER@$PROD_HOST" \ "PROD_PATH='$PROD_PATH' RELEASE_NAME='$RELEASE_NAME' EXPECTED_BACKEND_VERSION='$EXPECTED_BACKEND_VERSION' bash -s" <<'REMOTE' set -euo pipefail cd "$PROD_PATH" release="$HOME/$RELEASE_NAME" test -f "$release" archive_version="$(tar -xOf "$release" ./app/version.py | sed -n 's/^BACKEND_VERSION = "\(.*\)"$/\1/p')" test "$archive_version" = "$EXPECTED_BACKEND_VERSION" mkdir -p "$HOME/backups/miem_workers" backup="$HOME/backups/miem_workers/miem_workers-$(date +%Y%m%d-%H%M%S).sql" docker compose exec -T postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > "$backup" docker run --rm \ -v "$release:/src/release.tar:ro" \ -v "$PROD_PATH":/dst \ alpine:3.20 sh -c 'tar -xf /src/release.tar -C /dst' docker compose up -d --build runtime_version="$(docker compose exec -T api python -c 'from app.version import BACKEND_VERSION; print(BACKEND_VERSION)')" test "$runtime_version" = "$EXPECTED_BACKEND_VERSION" health="$(curl --fail --silent --show-error http://127.0.0.1:8000/api/health)" case "$health" in *"\"status\":\"ok\""*"\"backend_version\":\"$EXPECTED_BACKEND_VERSION\""*) ;; *) echo "Unexpected health response: $health" >&2; exit 1 ;; esac find "$HOME/backups/miem_workers" -maxdepth 1 -type f -name 'miem_workers-*.sql' ! -path "$backup" -delete docker compose ps rm -f "$release" REMOTE env: PROD_HOST: ${{ secrets.PROD_HOST }} PROD_USER: ${{ secrets.PROD_USER }} EXPECTED_BACKEND_VERSION: ${{ steps.prepare.outputs.backend_version }}