fix(security): apply CRITICAL fixes from Codex code review #2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Build and Deploy | |
| on: | |
| push: | |
| branches: [main] | |
| schedule: | |
| - cron: '0 2 * * 0' # Weekly rebuild (Sunday 2am UTC) for base image security patches | |
| workflow_dispatch: # Manual trigger option | |
| env: | |
| REGISTRY: ghcr.io | |
| IMAGE_NAME: ${{ github.repository }} | |
| # Prevent overlapping deployments | |
| concurrency: | |
| group: deploy-main | |
| cancel-in-progress: true | |
| jobs: | |
| build-and-deploy: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| packages: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | |
| - name: Log in to GitHub Container Registry | |
| uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0 | |
| with: | |
| registry: ${{ env.REGISTRY }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Extract metadata | |
| id: meta | |
| uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1 | |
| with: | |
| images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | |
| tags: | | |
| type=sha,prefix={{branch}}- | |
| type=raw,value=latest | |
| - name: Build and push Docker image | |
| uses: docker/build-push-action@4f58ea79222b3b9dc2c8bbdd6debcef730109a75 # v6.9.0 | |
| with: | |
| context: . | |
| push: true | |
| tags: ${{ steps.meta.outputs.tags }} | |
| labels: ${{ steps.meta.outputs.labels }} | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Deploy to server | |
| uses: appleboy/ssh-action@029f5b4aeeeb58fdfe1410a5d17f967dacf36262 # v1.0.3 | |
| with: | |
| host: ${{ secrets.SERVER_HOST }} | |
| username: ${{ secrets.SERVER_USER }} | |
| key: ${{ secrets.SSH_PRIVATE_KEY }} | |
| fingerprint: ${{ secrets.SSH_HOST_FINGERPRINT }} | |
| script: | | |
| cd /opt/oews | |
| # Validate secrets exist | |
| set -euo pipefail | |
| : "${AZURE_INFERENCE_ENDPOINT:?AZURE_INFERENCE_ENDPOINT secret not set}" | |
| : "${AZURE_INFERENCE_CREDENTIAL:?AZURE_INFERENCE_CREDENTIAL secret not set}" | |
| : "${TAVILY_API_KEY:?TAVILY_API_KEY secret not set}" | |
| : "${CORS_ORIGINS:?CORS_ORIGINS secret not set}" | |
| # Write .env file atomically | |
| cat > .env.tmp << 'EOF' | |
| AZURE_INFERENCE_ENDPOINT=${{ secrets.AZURE_INFERENCE_ENDPOINT }} | |
| AZURE_INFERENCE_CREDENTIAL=${{ secrets.AZURE_INFERENCE_CREDENTIAL }} | |
| TAVILY_API_KEY=${{ secrets.TAVILY_API_KEY }} | |
| CORS_ORIGINS=${{ secrets.CORS_ORIGINS }} | |
| DATABASE_ENV=dev | |
| SQLITE_DB_PATH=/app/data/oews.db | |
| API_HOST=0.0.0.0 | |
| API_PORT=8000 | |
| API_RELOAD=false | |
| EOF | |
| # Set secure permissions and move atomically | |
| chmod 600 .env.tmp | |
| mv .env.tmp .env | |
| # Pull new image and restart (restart all services to pick up config changes) | |
| docker compose pull | |
| docker compose up -d | |
| # Wait for health check | |
| echo "Waiting for containers to be healthy..." | |
| sleep 15 | |
| # Verify deployment | |
| docker compose ps | |
| docker compose logs --tail=20 oews-api | |
| # Check if container is running | |
| if ! docker compose ps | grep -q "oews-api.*running"; then | |
| echo "ERROR: oews-api container not running!" | |
| docker compose logs oews-api | |
| exit 1 | |
| fi | |
| echo "✓ Deployment complete" |