name: CI # Trigger on pushes to main and feature branches, and on proposals targeting main. on: push: branches: - main - "feat/*" - "fix/*" - "chore/*" pull_request: branches: - main jobs: test: name: Test & typecheck (Python 3.14) container: python:3.14-slim steps: - name: Install dependencies run: pip install -e ".[dev]" - name: Static type check run: mypy musehub/ - name: Run tests run: pytest tests/ -n auto --tb=short deploy: name: Deploy to musehub.ai container: python:3.14-slim needs: - test # Only deploy when merging to main (not on proposals or feature branches) if: "trigger == 'push' and branch == 'main'" env: EC2_HOST: ${{ secrets.EC2_HOST }} EC2_USER: ${{ secrets.EC2_USER }} EC2_SSH_KEY: ${{ secrets.EC2_SSH_KEY }} steps: - name: Install deploy tools run: | apt-get update -qq && apt-get install -y --no-install-recommends openssh-client rsync - name: Write SSH key run: | mkdir -p ~/.ssh echo "$EC2_SSH_KEY" > ~/.ssh/id_deploy chmod 600 ~/.ssh/id_deploy ssh-keyscan -H "$EC2_HOST" >> ~/.ssh/known_hosts 2>/dev/null - name: Rsync code to EC2 run: | rsync -az --delete \ --exclude='.muse' \ --exclude='__pycache__' \ --exclude='*.pyc' \ --exclude='.env' \ --exclude='docker-compose.override.yml' \ -e "ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no" \ . "$EC2_USER@$EC2_HOST:/opt/musehub/" - name: Reload nginx config run: | ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no "$EC2_USER@$EC2_HOST" \ "sudo cp /opt/musehub/deploy/nginx-ssl.conf /etc/nginx/sites-available/musehub \ && sudo nginx -t && sudo nginx -s reload" - name: Rebuild and restart containers run: | ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no "$EC2_USER@$EC2_HOST" \ "cd /opt/musehub && docker compose up -d --build" - name: Wait for health check run: | ssh -i ~/.ssh/id_deploy -o StrictHostKeyChecking=no "$EC2_USER@$EC2_HOST" \ "for i in \$(seq 1 24); do STATUS=\$(docker inspect --format='{{.State.Health.Status}}' musehub 2>/dev/null) echo \"Attempt \$i: \$STATUS\" [ \"\$STATUS\" = 'healthy' ] && exit 0 sleep 5 done echo 'Container did not become healthy in time' docker logs musehub --tail 30 exit 1" - name: Smoke test live site run: | CODE=\$(curl -s -o /dev/null -w "%{http_code}" https://musehub.ai/explore) echo "https://musehub.ai/explore → \$CODE" [ "\$CODE" = "200" ] || (echo "Smoke test failed" && exit 1)