Feat: Deploy to the VM after every merge to main #45
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 | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] | |
| # Cancel outdated pull request runs to save runner time and energy. Runs on | |
| # main are never cancelled, so a deploy is not stopped halfway; a newer | |
| # merge waits for the running one instead. | |
| concurrency: | |
| group: ci-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| # Apache serves this folder at /project2 (see docs/ci-cd.md). | |
| WEB_DIR: /var/www/html/project2 | |
| # The backend runs from here, outside the runner's work folder, which is | |
| # wiped by the next checkout. | |
| APP_DIR: /srv/ufound-it | |
| SERVICE: ufound-it-backend | |
| jobs: | |
| # Deploying is part of this job, not a separate one, because a new job | |
| # starts with a fresh checkout and would have to build again. The job | |
| # keeps its name since branch protection requires a check named "checks". | |
| checks: | |
| runs-on: self-hosted | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 22 | |
| cache: npm | |
| # npm ci installs exactly what package-lock.json says, so CI matches | |
| # what we run locally. | |
| - run: npm ci | |
| - run: npm run lint | |
| - run: npm run format:check | |
| - run: npm test | |
| - run: npm run build | |
| # Only pushes to main deploy. Pull requests stop after the checks. | |
| - name: Deploy frontend | |
| if: github.event_name == 'push' | |
| run: | | |
| find "$WEB_DIR" -mindepth 1 -delete | |
| cp -r frontend/dist/. "$WEB_DIR"/ | |
| - name: Deploy backend | |
| if: github.event_name == 'push' | |
| run: | | |
| # npm ci needs the workspace layout, so both package.json files are | |
| # copied even though only the backend runs here. backend/.env is | |
| # created once by hand on the VM and is left alone. | |
| mkdir -p "$APP_DIR/backend" "$APP_DIR/frontend" | |
| cp package.json package-lock.json "$APP_DIR"/ | |
| cp backend/package.json "$APP_DIR/backend/" | |
| cp frontend/package.json "$APP_DIR/frontend/" | |
| rm -rf "$APP_DIR/backend/dist" | |
| cp -r backend/dist "$APP_DIR/backend/" | |
| (cd "$APP_DIR" && npm ci --omit=dev --workspace backend) | |
| # Node is only installed in the runner's tool cache, so the service | |
| # gets its own copy of the same version CI tested with. Renaming | |
| # over the old binary works while the service is still running it. | |
| cp "$(command -v node)" "$APP_DIR/node.new" | |
| mv -f "$APP_DIR/node.new" "$APP_DIR/node" | |
| sudo -n systemctl restart "$SERVICE" | |
| - name: Check the deployed site | |
| if: github.event_name == 'push' | |
| run: | | |
| # The backend needs a moment to start, so retry before failing. | |
| for attempt in 1 2 3 4 5 6 7 8 9 10; do | |
| if curl -fsS http://localhost/project2/graphql \ | |
| -H 'content-type: application/json' \ | |
| --data '{"query":"{ health }"}' | grep -q '"ok"'; then | |
| curl -fsS -o /dev/null http://localhost/project2/ | |
| echo "Deployed and responding." | |
| exit 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "The backend did not answer through Apache." >&2 | |
| sudo -n systemctl status "$SERVICE" --no-pager || true | |
| exit 1 |