name: deploy on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: jobs: test-backend: runs-on: ubuntu-latest container: image: rust:1-slim services: postgres: image: postgres:16-alpine env: POSTGRES_USER: mangalord POSTGRES_PASSWORD: mangalord POSTGRES_DB: mangalord options: >- --health-cmd "pg_isready -U mangalord" --health-interval 5s --health-timeout 5s --health-retries 10 env: DATABASE_URL: postgres://mangalord:mangalord@postgres:5432/mangalord steps: - uses: actions/checkout@v4 - name: Install build deps run: | apt-get update apt-get install -y --no-install-recommends pkg-config libssl-dev ca-certificates - name: Cache cargo registry and target uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git backend/target key: cargo-${{ runner.os }}-${{ hashFiles('backend/Cargo.lock') }} restore-keys: | cargo-${{ runner.os }}- - name: cargo test working-directory: backend run: cargo test --locked test-frontend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '22' cache: npm cache-dependency-path: frontend/package-lock.json - name: npm ci working-directory: frontend run: npm ci - name: vitest working-directory: frontend run: npm test build-and-push: runs-on: ubuntu-latest needs: [test-backend, test-frontend] # PRs only run the test jobs; build + deploy are reserved for # post-merge pushes to main. Without this gate every PR would push # a tagged image to the registry and SSH-deploy to prod. if: github.event_name != 'pull_request' outputs: image_tag: ${{ steps.meta.outputs.image_tag }} version: ${{ steps.meta.outputs.version }} steps: - uses: actions/checkout@v4 - name: Resolve image tags id: meta run: | version="$(grep -m1 '^version' backend/Cargo.toml | cut -d'"' -f2)" frontend_version="$(grep -m1 '"version"' frontend/package.json | cut -d'"' -f4)" if [ "$version" != "$frontend_version" ]; then echo "Version mismatch: backend=$version frontend=$frontend_version" >&2 exit 1 fi echo "image_tag=${GITHUB_SHA}" >> "$GITHUB_OUTPUT" echo "version=${version}" >> "$GITHUB_OUTPUT" - uses: docker/setup-buildx-action@v3 - name: docker login uses: docker/login-action@v3 with: registry: ${{ secrets.REGISTRY_URL }} username: ${{ secrets.REGISTRY_USERNAME }} password: ${{ secrets.REGISTRY_PASSWORD }} - name: Build & push backend uses: docker/build-push-action@v5 with: context: ./backend push: true tags: | ${{ secrets.REGISTRY_URL }}/mangalord-backend:latest ${{ secrets.REGISTRY_URL }}/mangalord-backend:${{ steps.meta.outputs.image_tag }} ${{ secrets.REGISTRY_URL }}/mangalord-backend:${{ steps.meta.outputs.version }} cache-from: type=gha,scope=backend cache-to: type=gha,mode=max,scope=backend - name: Build & push frontend uses: docker/build-push-action@v5 with: context: ./frontend push: true tags: | ${{ secrets.REGISTRY_URL }}/mangalord-frontend:latest ${{ secrets.REGISTRY_URL }}/mangalord-frontend:${{ steps.meta.outputs.image_tag }} ${{ secrets.REGISTRY_URL }}/mangalord-frontend:${{ steps.meta.outputs.version }} cache-from: type=gha,scope=frontend cache-to: type=gha,mode=max,scope=frontend deploy: runs-on: ubuntu-latest needs: build-and-push if: github.event_name != 'pull_request' steps: - name: SSH deploy uses: appleboy/ssh-action@v1.0.3 with: host: ${{ secrets.SSH_HOST }} username: ${{ secrets.SSH_USER }} key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT || 22 }} envs: REGISTRY_URL,REGISTRY_USERNAME,REGISTRY_PASSWORD,IMAGE_TAG,DEPLOY_PATH script_stop: true script: | set -euo pipefail cd "$DEPLOY_PATH" echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USERNAME" --password-stdin export REGISTRY_URL IMAGE_TAG docker compose -f docker-compose.yml -f docker-compose.prod.yml pull docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d docker image prune -f docker logout "$REGISTRY_URL" env: REGISTRY_URL: ${{ secrets.REGISTRY_URL }} REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }} DEPLOY_PATH: ${{ vars.DEPLOY_PATH }}