Skip to content

fix(mcp-setup): stop summary from clobbering the client name #197

fix(mcp-setup): stop summary from clobbering the client name

fix(mcp-setup): stop summary from clobbering the client name #197

Workflow file for this run

name: Test & Deploy
on:
push:
branches:
- master
jobs:
test:
name: Backend tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies
working-directory: backend
run: uv sync --extra dev
- name: Run tests
working-directory: backend
run: uv run pytest -v
frontend:
name: Frontend audit + build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install dependencies
working-directory: frontend
run: npm ci
# `npm audit` flags vulnerabilities in production dependencies.
# Threshold = high so low/moderate findings (transitive dev-only
# CVEs that don't reach prod, advisories with no fix yet, etc.)
# don't block legitimate deploys. High + critical findings DO
# block — those are real and need a fix or a documented
# `--omit=optional` / override / waiver.
#
# `--omit=dev` skips devDependencies because they don't ship
# to production; the prod bundle is what reaches a user.
- name: npm audit (production deps only, high+critical)
working-directory: frontend
run: npm audit --audit-level=high --omit=dev
# Build now so a syntax or type error fails CI here rather than
# mid-deploy. Catches the same class of bug as backend pytest.
- name: Build production bundle
working-directory: frontend
run: npm run build
deploy:
name: Deploy to Fly.io
runs-on: ubuntu-latest
needs: [test, frontend]
concurrency:
group: deploy
cancel-in-progress: true
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
# Why two-step (build → machine update) instead of plain `fly deploy`:
#
# We run a single Fly Machine with a single persistent volume
# (`opensentry_data` at /data, holding the SQLite DB). `fly deploy`
# for this topology is non-deterministic: sometimes it sees the
# existing machine and updates it in place, sometimes it decides
# the image config has "drifted enough" and tries to provision a
# NEW machine alongside the old. The new-machine path errors
# immediately because the volume only has one attachment slot:
# "creating a new machine in group 'app' requires an
# unattached 'opensentry_data' volume."
# We hit this on consecutive runs 2026-04-28 with strategy=rolling
# AND strategy=immediate, and `max_unavailable` is rolling-only so
# it didn't help either.
#
# `fly machine update --image …` is the explicit in-place API.
# It targets a specific machine ID, restarts it on the new image,
# and the volume stays attached throughout. It cannot try to
# create a new machine. ~30-60s of downtime per deploy (same as
# `strategy = "immediate"` on a good day) but reliably works.
#
# Note: `--depot=false` is intentional — depot.dev timed out
# twice in a row on 2026-04-28 (5 min each), wedging CI for
# ~10 min before failing. Standard remote builder is slower
# (~3 min cached vs ~30s depot) but reliable.
- name: Build + push image to Fly registry
id: build
run: |
set -e
# --build-only --push: build the image and push to Fly's
# registry, but do NOT touch any machines. The image tag is
# printed on a line like:
# image: registry.fly.io/opensentry-command:deployment-XXXX
# Capture it so the next step can target it explicitly.
OUT=$(flyctl deploy --remote-only --depot=false --build-only --push 2>&1 | tee /dev/stderr)
IMAGE=$(echo "$OUT" | grep -oP 'image:\s+\K[^\s]+' | tail -1)
if [ -z "$IMAGE" ]; then
echo "::error::Could not find image tag in flyctl output"
exit 1
fi
echo "Captured image: $IMAGE"
echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
- name: Update machine in place
run: |
set -e
# We currently run exactly one machine. If we ever scale to
# multiple machines (or migrate to LiteFS / Postgres so we
# don't need a single volume), this script needs to loop.
MACHINE=$(flyctl machines list -a opensentry-command --json | jq -r '.[0].id')
if [ -z "$MACHINE" ] || [ "$MACHINE" = "null" ]; then
echo "::error::No machines found for opensentry-command"
exit 1
fi
echo "Updating machine $MACHINE to $IMAGE"
flyctl machine update "$MACHINE" --image "$IMAGE" --yes -a opensentry-command
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
IMAGE: ${{ steps.build.outputs.image }}