From 7813a72285d52663539886a4e408a9640ce569c6 Mon Sep 17 00:00:00 2001 From: Abdikhani Abdirashid Date: Tue, 3 Nov 2026 10:01:15 +0100 Subject: [PATCH] infra: build, test and publish the dashboard image --- .github/workflows/pipeline.yml | 116 ++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 4ed57a3..2520bf8 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -128,6 +128,118 @@ jobs: push: true cache-from: type=gha + dashboard-test: + name: dashboard-test + runs-on: ubuntu-24.04 + timeout-minutes: 10 + defaults: + run: + working-directory: dashboard + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + # The dashboard runs on Node and npm, not Bun: decision 0005. The version comes + # from dashboard/.nvmrc, so CI, Docker and the laptop read it from one file. + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version-file: dashboard/.nvmrc + cache: npm + cache-dependency-path: dashboard/package-lock.json + # "ci", never "install": it installs exactly what package-lock.json names, so CI + # can never quietly resolve a version the laptop never tested. + - run: npm ci + # The runner has no browser. Playwright installs the exact build its own version + # expects, plus the system libraries Chromium needs. + - name: Install Chromium for Playwright + run: npx playwright install --with-deps chromium + - run: npm run lint + # "build" is tsc -b && vite build, so the type check fails here as its own step + # instead of being buried in a Docker build log. + - run: npm run build + # Playwright starts Vite itself (webServer in playwright.config.ts), so there is + # no separate "start the app" step. + - name: End-to-end tests + run: npm run test:e2e + + dashboard-image: + name: dashboard-image + needs: dashboard-test # never pack an image out of code that failed its tests + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: read + packages: write # only on main, in the push step below + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + - name: Log in to GHCR + if: github.event_name == 'push' + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + # Every PR and every merge: build into the runner's own Docker, so the image can be + # tested before anything is pushed. VITE_API_URL is not passed: the Dockerfile's + # default is already https://api.myrelay.tech. + # scope=dashboard: the gha cache scope is "buildkit" for everyone by default, so + # without this the API build and this one would overwrite each other's cache. + - name: Build the image + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: dashboard + platforms: linux/amd64 # the VPS is amd64; GitHub's runners build that natively + tags: relay-dashboard:smoke + load: true + cache-from: type=gha,scope=dashboard + cache-to: type=gha,mode=max,scope=dashboard + # Kubernetes decides a pod is ready by asking for /index.html. An image that + # cannot answer that would never become ready: the rollout would time out after + # 180 s and roll the API back with it. So we ask the same question here, on the PR. + - name: Smoke-test the image + shell: bash # GitHub then adds -e and pipefail + run: | + docker run -d --name smoke-dash -p 127.0.0.1:8099:8080 relay-dashboard:smoke + # nginx is not up the instant the container is: wait, but not forever. + for _ in $(seq 1 30); do + # -fs, not -fsS: a refused connection while nginx starts is expected here, + # and printing it would look like a failure in a run that went fine. + curl -fs -o /dev/null http://127.0.0.1:8099/index.html && break + sleep 1 + done + # curl -s exits 0 on a 404 too, so every check compares the code itself. + code=$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8099/index.html) + if [ "$code" != 200 ]; then + echo "::error::the image did not serve /index.html (HTTP $code)" + exit 1 + fi + # Every unknown path returns index.html (the single-page fallback in nginx.conf), + # so a 200 there proves little on its own. location /assets/ has try_files =404 and + # no fallback, so asking for the bundle index.html names is what proves the built + # files are really in the image: an image with an empty assets/ answers 404 here + # while still answering 200 above. + asset=$(curl -s http://127.0.0.1:8099/index.html | grep -oE '/assets/[^"]+\.js' | head -n 1) + if [ -z "$asset" ]; then + echo "::error::index.html names no built bundle" + exit 1 + fi + code=$(curl -s -o /dev/null -w '%{http_code}' "http://127.0.0.1:8099$asset") + if [ "$code" != 200 ]; then + echo "::error::$asset answered $code: the build is not in the image" + exit 1 + fi + echo "the image serves /index.html and $asset" + # Only after a merge to main: push the same image. Its layers come from the build + # above (the builder's cache), so this takes seconds and pushes what was tested. + - name: Push the image (main only) + if: github.event_name == 'push' + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: dashboard + platforms: linux/amd64 + tags: ghcr.io/${{ github.repository_owner }}/relay-dashboard:${{ github.sha }} + push: true + cache-from: type=gha,scope=dashboard + deploy-script: name: deploy-script-test runs-on: ubuntu-24.04 @@ -143,7 +255,9 @@ jobs: deploy: name: deploy - needs: [image, deploy-script] # a broken deploy script never reaches the server + # Every image this commit's manifests ask for must be on the shelf before the + # server is told to pull them, so deploy waits for both image jobs. + needs: [image, dashboard-image, deploy-script] # a broken deploy script never reaches the server if: github.event_name == 'push' # only after a merge to main, never on a PR runs-on: ubuntu-24.04 # The environment hands over DEPLOY_SSH_KEY, and only to workflows running on main.