musing has two independent deploy targets: the frontend (GitHub Pages) and the optional
musing-ai-service backend (Cloud Run). Neither requires the other — the frontend works with
no AI service configured.
- Repo Settings → Pages → Build and deployment: source GitHub Actions.
- Settings → Secrets and variables → Actions → add repository secrets if you want cloud
sync on the live site (same values as
.env.local):VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYThe build completes without them; the published app then behaves like local dev with no Supabase config (local-only persistence in the browser).
- Push to
main. The branch ruleset requiresverify.yml's checks (stack-docs drift,npm audit, lint, format check, typecheck, coverage, build) to pass before a PR can merge; Deploy to GitHub Pages (.github/workflows/deploy-pages.yml) then runs independently on that same push (npm ci,npm run build, copydist/index.html→dist/404.html, publishdist). Supabase keepalive is scheduled from the default branch as well; it only performs the health ping when both Supabase secrets above are set (otherwise it skips).
For a user site (https://<username>.github.io from a repo named <username>.github.io),
vite.config.ts uses base path / automatically when GITHUB_ACTIONS and
GITHUB_REPOSITORY indicate that naming convention.
Simulate a Pages build locally:
GITHUB_ACTIONS=true GITHUB_REPOSITORY=yourname/yourrepo npm run buildOptional: VITE_BASE_PATH=/custom/ when building.
service/ is a separate backend (Express + TypeScript) providing the AI second-brain
layer — semantic search, summaries, related pages — over musing's notes. It deploys
independently to Cloud Run via .github/workflows/deploy-cloud-run.yml, triggered by pushes
to main that touch service/**. Like Supabase keepalive, this workflow skips
(doesn't fail) until it's configured:
- A GCP service account (deploys as, and Cloud Run runs the container as) with: Cloud Run Admin, Service Account User, Secret Manager Secret Accessor, Cloud Build Editor (source builds run through Cloud Build), Artifact Registry Administrator (the first deploy creates the repo, not just pushes to it — plain Writer isn't enough), and Storage Admin (source upload needs a Cloud Storage staging bucket created on first deploy).
- Repository secrets:
GCP_PROJECT_ID,GCP_REGION,GCP_SA_KEY(that service account's JSON key), plus non-sensitive config —CHAT_MODEL,EMBEDDING_MODEL,AI_MONTHLY_TOKEN_CAP,AI_MONTHLY_REQUEST_CAP,AI_MAX_QPS,VOYAGE_MONTHLY_TOKEN_CAP,VOYAGE_MONTHLY_REQUEST_CAP,SUPABASE_URL, andALLOWED_ORIGINS(comma-separated list of origins allowed to call the API — see below). - GCP Secret Manager secrets (same names):
SUPABASE_SERVICE_ROLE_KEY,ANTHROPIC_API_KEY,VOYAGE_API_KEY— these are the actual sensitive values, kept out of plaintext env vars on the Cloud Run revision. - The musing frontend and
service/are unrelated hosts (GitHub Pages vs. Cloud Run) on different origins — the frontend callsservice/cross-origin directly (no same-origin proxy), so the API enforces CORS viaALLOWED_ORIGINSrather than relying on shared-domain routing. - Cloud Run is deployed with
--allow-unauthenticated— the actual access boundary is app-level (the API verifies each request's Supabase JWT), not GCP IAM, since callers are end-user browsers with no GCP identity to present.
Without that setup, the app runs exactly as described in the root README with no AI features — this is an optional layer on top of the core note-taking app.
Once deployed, weekly probe smoke (.github/workflows/weekly-probe-smoke.yml) checks
GET /health on service/ every Monday, reusing the VITE_AI_SERVICE_URL secret already
set for the frontend build — a rare drift check, not a chatty uptime monitor, so it won't
wake a scale-to-zero Cloud Run revision more than once a week.