fix: add missing slog import and remove unused imports #67
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: CI/CD Focus Project | |
| on: | |
| push: | |
| branches: | |
| - dev | |
| - stage | |
| - prod | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| packages: write | |
| concurrency: | |
| group: focus-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| compose-validate: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Create placeholder env files | |
| run: | | |
| touch .env | |
| mkdir -p backend | |
| touch backend/.env | |
| - name: Validate local Docker Compose | |
| run: docker compose config | |
| - name: Validate server Docker Compose | |
| env: | |
| BACKEND_IMAGE: ghcr.io/example/focus/backend:test | |
| DATABASE_URL: postgres://user:pass@db:5432/focus?sslmode=disable | |
| JWT_SECRET: test-secret | |
| run: docker compose -f deploy/docker-compose.server.yml config | |
| build-and-push-images: | |
| needs: compose-validate | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Login to GHCR | |
| uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Build and push backend | |
| uses: docker/build-push-action@v6 | |
| with: | |
| context: ./backend | |
| push: true | |
| tags: | | |
| ghcr.io/${{ github.repository }}/backend:${{ github.sha }} | |
| ghcr.io/${{ github.repository }}/backend:latest | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| deploy-to-server: | |
| needs: build-and-push-images | |
| if: github.ref == 'refs/heads/prod' | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: production | |
| env: | |
| SERVER_HOST: ${{ secrets.SERVER_IP }} | |
| SERVER_USER: ${{ secrets.SERVER_USER }} | |
| SERVER_PORT: ${{ secrets.SERVER_PORT }} | |
| DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }} | |
| SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} | |
| GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }} | |
| BACKEND_IMAGE: ghcr.io/${{ github.repository }}/backend:${{ github.sha }} | |
| GITHUB_ACTOR: ${{ github.actor }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Normalize deploy settings | |
| run: | | |
| server_host="$(printf '%s' "${SERVER_HOST}" | tr -d '\r' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')" | |
| server_user="$(printf '%s' "${SERVER_USER}" | tr -d '\r' | sed -E 's/[[:space:]]+$//')" | |
| server_port="$(printf '%s' "${SERVER_PORT}" | tr -d '\r' | sed -E 's/[[:space:]]+$//')" | |
| deploy_path="$(printf '%s' "${DEPLOY_PATH}" | tr -d '\r' | sed -E 's/[[:space:]]+$//')" | |
| server_host="${server_host#http://}" | |
| server_host="${server_host#https://}" | |
| server_host="${server_host%%/*}" | |
| if [[ "$server_host" == *"@"* ]]; then | |
| [ -z "$server_user" ] && server_user="${server_host%@*}" | |
| server_host="${server_host#*@}" | |
| fi | |
| if printf '%s' "$server_host" | grep -Eq '^[^:/]+:[0-9]+$'; then | |
| [ -z "$server_port" ] && server_port="${server_host##*:}" | |
| server_host="${server_host%:*}" | |
| fi | |
| : "${server_user:=devops}" | |
| : "${server_port:=22}" | |
| : "${deploy_path:=/opt/focus}" | |
| [ -z "$server_host" ] && { echo "SERVER_IP secret is empty or invalid" >&2; exit 1; } | |
| [ -z "$SSH_PRIVATE_KEY" ] && { echo "SSH_PRIVATE_KEY secret is empty" >&2; exit 1; } | |
| [ -z "$GHCR_TOKEN" ] && { echo "GHCR_TOKEN secret is empty" >&2; exit 1; } | |
| { | |
| echo "SERVER_HOST=$server_host" | |
| echo "SERVER_USER=$server_user" | |
| echo "SERVER_PORT=$server_port" | |
| echo "DEPLOY_PATH=$deploy_path" | |
| } >> "$GITHUB_ENV" | |
| - name: Prepare SSH | |
| run: | | |
| mkdir -p ~/.ssh | |
| printf '%s\n' "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa | |
| chmod 600 ~/.ssh/id_rsa | |
| ssh-keyscan -T 10 -p "$SERVER_PORT" -H "$SERVER_HOST" >> ~/.ssh/known_hosts || { | |
| echo "Could not read SSH host key from $SERVER_HOST:$SERVER_PORT" >&2 | |
| echo "Check that SERVER_IP and SERVER_PORT point to the SSH service, not a web panel port." >&2 | |
| exit 1 | |
| } | |
| - name: Upload deployment files | |
| run: | | |
| ssh -p "$SERVER_PORT" "$SERVER_USER@$SERVER_HOST" "mkdir -p '$DEPLOY_PATH'" | |
| scp -P "$SERVER_PORT" deploy/docker-compose.server.yml "$SERVER_USER@$SERVER_HOST:$DEPLOY_PATH/docker-compose.yml" | |
| - name: Render and upload env file | |
| run: | | |
| cat > deploy.env <<EOF | |
| BACKEND_IMAGE=${BACKEND_IMAGE} | |
| DOCKER_NETWORK=${{ secrets.DOCKER_NETWORK }} | |
| BACKEND_PORT=${{ secrets.BACKEND_PORT }} | |
| PORT=${{ secrets.PORT }} | |
| DATABASE_URL=${{ secrets.DATABASE_URL }} | |
| CORS_ORIGIN=${{ secrets.CORS_ORIGIN }} | |
| JWT_SECRET=${{ secrets.JWT_SECRET }} | |
| RESEND_API_KEY=${{ secrets.RESEND_API_KEY }} | |
| RESEND_FROM_EMAIL=${{ secrets.RESEND_FROM_EMAIL }} | |
| APP_URL=${{ secrets.APP_URL }} | |
| BACKEND_URL=${{ secrets.BACKEND_URL }} | |
| EOF | |
| scp -P "$SERVER_PORT" deploy.env "$SERVER_USER@$SERVER_HOST:$DEPLOY_PATH/.env" | |
| - name: Deploy on server | |
| run: | | |
| ssh -p "$SERVER_PORT" "$SERVER_USER@$SERVER_HOST" bash -s <<EOF | |
| set -euo pipefail | |
| cd "$DEPLOY_PATH" | |
| echo "$GHCR_TOKEN" | docker login ghcr.io -u "$GITHUB_ACTOR" --password-stdin | |
| docker compose --env-file .env pull backend migrate | |
| docker compose --env-file .env run --rm migrate | |
| docker compose --env-file .env up -d backend | |
| docker image prune -f | |
| EOF |