Widens the lint step from `ruff check app/` to `ruff check .`, since tests/ and
scripts/ had drifted to 11 findings while unchecked (fixed in 1c6ccce).
Widening alone would have been unsafe, and the check turned up something worse
than the drift: CI installs ruff unpinned, the repo had no [tool.ruff] config,
and ruff's default rule set is not stable across releases. Local 0.15.4 reports
0 findings in app/; 0.16.2 -- what `pip install ruff` resolves to today --
reports 376. 168 of those are B008 flagging FastAPI's `Depends()` in a signature
default, which is the framework's documented idiom, not a defect. So the lint
job would have failed the deploy pipeline on untouched code at the next push,
independent of this change.
Pinning the ruff version would freeze the bug in place. Pinning the *rule set*
is the actual fix: select = ["E4", "E7", "E9", "F"] in pyproject.toml, the set
the tree was already clean under, now enforced repo-wide. The ruff version can
float freely without changing what CI enforces.
Verified both scopes against both versions with caches disabled: `ruff check .`
passes under 0.15.4 and 0.16.2. Confirmed the pin actually binds rather than
passing by luck -- a probe file using `Depends()` in a default is clean under
the committed config and reports B008 + I001 under `--isolated` 0.16.2 defaults.
Full suite still 852 passed, 1 skipped.
Adding rules is welcome; do it in pyproject.toml with the fixes in the same
commit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
181 lines
5.8 KiB
YAML
181 lines
5.8 KiB
YAML
# Gitea Actions CI/CD pipeline: lint → test → deploy
|
|
# Triggers on push to main branch.
|
|
# Required variables and secrets (set in Gitea repo settings):
|
|
# Variables:
|
|
# DEPLOY_HOST — server IP or hostname
|
|
# DEPLOY_PATH — absolute path to the directory on the server
|
|
# DEPLOY_USER — SSH username on the server
|
|
# SSH_KNOWN_HOSTS — SSH known_host fingerprint for security
|
|
# SSH_PORT — SSH port of the server
|
|
# Secrets:
|
|
# SSH_PRIVATE_KEY — SSH private key for deployment
|
|
name: Deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
inputs:
|
|
run_setup_db:
|
|
description: "Run deploy/setup_db.sh to configure DB and run migrations"
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
|
|
# Serialize deploys so two quick pushes to main can't rsync/restart on top of
|
|
# each other. Don't cancel an in-flight deploy mid-restart.
|
|
concurrency:
|
|
group: deploy-main
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
cache: "pip"
|
|
- run: pip install ruff
|
|
# Whole repo, not just app/: tests/ and scripts/ drifted to 11 findings
|
|
# while unchecked. Rules are pinned in pyproject.toml, so the unpinned
|
|
# ruff above cannot change what this enforces.
|
|
- run: ruff check .
|
|
|
|
test:
|
|
needs: lint
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:16
|
|
env:
|
|
POSTGRES_DB: test_db
|
|
POSTGRES_USER: test_user
|
|
POSTGRES_PASSWORD: test_pass
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-python@v5
|
|
with:
|
|
python-version: "3.12"
|
|
cache: "pip"
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: "npm"
|
|
cache-dependency-path: frontend/package-lock.json
|
|
- run: pip install -e ".[dev]"
|
|
# The Postgres service exists only to validate the migrations against real
|
|
# Postgres (what prod runs). The test suite itself uses an in-memory SQLite
|
|
# engine (tests/conftest.py), so pytest doesn't touch this service.
|
|
- run: alembic upgrade head
|
|
env:
|
|
DATABASE_URL: postgresql+asyncpg://test_user:test_pass@postgres:5432/test_db
|
|
- run: pytest --tb=short
|
|
- run: |
|
|
cd frontend
|
|
npm ci
|
|
if node -e "require.resolve('vitest/package.json')" >/dev/null 2>&1; then
|
|
npm test
|
|
else
|
|
echo "vitest not configured; skipping frontend tests"
|
|
fi
|
|
npm run build
|
|
|
|
deploy:
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
env:
|
|
DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}
|
|
DEPLOY_USER: ${{ vars.DEPLOY_USER }}
|
|
DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}
|
|
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
|
|
SSH_KNOWN_HOSTS: ${{ vars.SSH_KNOWN_HOSTS }}
|
|
SSH_PORT: ${{ vars.SSH_PORT || '22' }}
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
cache: "npm"
|
|
cache-dependency-path: frontend/package-lock.json
|
|
|
|
- name: Build frontend
|
|
run: |
|
|
cd frontend
|
|
npm ci
|
|
npm run build
|
|
|
|
- name: Install deploy tools
|
|
run: sudo apt-get update -qq && sudo apt-get install -y -qq rsync openssh-client > /dev/null 2>&1 || true
|
|
|
|
- name: Set up SSH
|
|
run: |
|
|
mkdir -p ~/.ssh
|
|
chmod 700 ~/.ssh
|
|
echo "$SSH_PRIVATE_KEY" > ~/.ssh/deploy_key
|
|
chmod 600 ~/.ssh/deploy_key
|
|
echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
|
|
# known_hosts is supplied, so verify the host key instead of blindly
|
|
# trusting it (StrictHostKeyChecking=no would defeat the fingerprint).
|
|
echo "SSH_OPTS=-i $HOME/.ssh/deploy_key -o StrictHostKeyChecking=yes -p ${SSH_PORT}" >> "$GITHUB_ENV"
|
|
|
|
- name: Sync files to server
|
|
run: |
|
|
rsync -avz --delete \
|
|
--exclude '.git/' \
|
|
--exclude '.gitea/' \
|
|
--exclude '.env' \
|
|
--exclude '.venv/' \
|
|
--exclude '__pycache__/' \
|
|
--exclude '.pytest_cache/' \
|
|
--exclude 'logs/' \
|
|
--exclude '*.pyc' \
|
|
--exclude 'frontend/node_modules/' \
|
|
-e "ssh $SSH_OPTS" \
|
|
./ "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/"
|
|
|
|
- name: Install deps & run migrations
|
|
run: |
|
|
ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_HOST}" << REMOTE_SCRIPT
|
|
set -e
|
|
cd ${DEPLOY_PATH}
|
|
|
|
# Create venv if not exists
|
|
if [ ! -d ".venv" ]; then
|
|
python3 -m venv .venv
|
|
fi
|
|
|
|
source .venv/bin/activate
|
|
pip install --quiet --upgrade pip
|
|
pip install --quiet -e .
|
|
|
|
# Setup DB and run migrations
|
|
if [ "${{ inputs.run_setup_db }}" = "true" ] || [ "${{ github.event.inputs.run_setup_db }}" = "true" ]; then
|
|
chmod +x deploy/setup_db.sh
|
|
./deploy/setup_db.sh
|
|
else
|
|
alembic upgrade head
|
|
fi
|
|
REMOTE_SCRIPT
|
|
|
|
- name: Restart service & health check
|
|
run: |
|
|
ssh $SSH_OPTS "${DEPLOY_USER}@${DEPLOY_HOST}" << REMOTE_SCRIPT
|
|
set -e
|
|
sudo systemctl restart signalplatform.service
|
|
sleep 3
|
|
curl -fsS http://127.0.0.1:8998/api/v1/health > /dev/null \
|
|
|| { echo "✗ health check failed after restart"; exit 1; }
|
|
echo "✓ signalplatform deployed"
|
|
REMOTE_SCRIPT
|
|
|
|
- name: Clean up SSH key
|
|
if: always()
|
|
run: rm -f ~/.ssh/deploy_key
|