6.2 KiB
6.2 KiB
Monorepo cutover notes (assembled 2026-07-22)
Scratch assembly for inspection — nothing live points here yet. Built by
subtree-merging each split repo's origin/main (full history, 380 commits):
backend/← thermograph-backend83c2e05(Discord gateway bot landed, #7)frontend/← thermograph-frontend3a98146(responsiveness hardening, #4)infra/← thermograph-infra99b4b3f(mail docs, #12)observability/← thermograph-observability19e74af(validator CI, #3)
What changed vs. the split repos
CI (all in root .forgejo/workflows/; the subtree copies were deleted —
Forgejo only reads root workflows, but dead copies are a trap):
backend-build-push.yml/frontend-build-push.yml— the old per-repo build-push, path-filtered (paths: ['backend/**']etc.), image paths now explicit:emi/thermograph/backend|frontend(the old${{ github.repository }}/appderivation collides in a monorepo). Per-domain concurrency groups. Caveat:v*.*.*tag pushes carry no paths context, so a version tag builds both images (intended: a release names the set).backend|frontend-deploy[.yml|-prod.yml|-dev.yml]— ports of the SSH deploy workflows, path-filtered, script path now/opt/thermograph/infra/deploy/deploy.sh. Same secret names (SSH_*beta,PROD_SSH_*prod) — but now one secret set on one repo.build.yml— the build-only sanity check, now a single reusable workflow taking adomaininput (context = the domain dir, so Dockerfiles see the same tree as in the split era).pr-build.yml— onechangesjob (git-diff domain detection) + per-domain gated builds + a single always-reportinggaterequired check. Deliberately not path-filtered: a required check that never fires blocks auto-merge forever.infra-sync.yml— new (the infra domain's own pipeline): push tomaintouchinginfra/**→ git-sync/opt/thermographon beta+prod and re-render secrets. Rolls no services (image tags are the app domains' axis).observability-validate.yml— port withobservability/prefixes +workflow_callso pr-build reuses it.secrets-guard.yml— glob is nowinfra/deploy/secrets/*.yaml; still runs on everything (cheap backstop).ops-cron.yml— compose invocationscd /opt/thermograph/infra.
Deploy scripts / compose (hosts see a monorepo checkout at
/opt/thermograph):
deploy.sh:INFRA_DIR="$APP_DIR/infra"; git ops at the checkout root,cd "$INFRA_DIR"for all compose work; lock/tags/render paths underinfra/deploy/; image-path defaults renamed.docker-compose.yml:name: thermographpinned. Critical: without it, running compose from…/infraderives project "infra" — a silently NEW project that would recreate the whole stack beside the running one.deploy-dev.shexportsCOMPOSE_PROJECT_NAME=thermograph-dev(env wins over the file key), preserving LAN dev's separate project.deploy/stack/deploy-stack.sh+thermograph-stack.yml: same path/image renames (prod is stack-mode;docker stack deploytakes an explicit stack name, so no project-name concern there).
Verified here: every workflow + compose file parses (PyYAML); all three
deploy scripts pass bash -n; no emi/thermograph-{backend,frontend}/app
stragglers in sh/yml. Not verified (impossible locally): Forgejo actually
honoring on.push.paths and workflow_call inputs on our version; any live
deploy.
Before cutover (in order)
- Run the pending prod network-recreate runbook first. Don't rebase the deploy path on a prod that wedges on its next deploy.
- Prove Forgejo path filters + reusable-workflow inputs with a throwaway
repo: one push touching only
backend/**must fire only backend workflows; auses: ./.forgejo/workflows/build.yml+with:job must pass its input. If paths aren't honored, fall back to the pr-build pattern (diff job + gated jobs) for the push workflows too. - Land the in-flight split-repo branches (backend/frontend
reconcile-dev-with-main, infraphase5/discord-bot-flag, …) in the split repos, then re-subtree or graft the deltas — this assembly isorigin/mainas of 2026-07-22 and goes stale as those merge.
Cutover runbook
- Create
emi/thermographon Forgejo (a new repo — do not resurrect the archived monorepo) and pushmain. - Actions config, once: secrets
REGISTRY_TOKEN,SSH_HOST/USER/KEY/PORT(beta),PROD_SSH_*(prod); variableREGISTRY_HOST=git.thermograph.org. - Branch protection on
dev+main: required checkgate, squash merges, auto-merge on green. - Hosts (beta, then prod): clone the monorepo to
/opt/thermograph.new; copydeploy/.image-tags.envfrom the old checkout intoinfra/deploy/(its path moved — losing it makes the next single-service deploy roll the sibling ontolocal); swap directories (running containers are untouched — they're images, not the checkout);/etc/thermograph*files stay as-is. - LAN box: re-point
~/thermograph-devat the monorepo the same way. - First push builds the new image paths — old
emi/thermograph-{backend,frontend}/apppackages stay in the registry for rollback; GC after burn-in. deploy.sh's tag prune already targets the new paths only. - One
workflow_dispatchofbackend-deploy.ymlafter its build-push completes = the live end-to-end validation, same as the split-era wiring test. - After burn-in: archive the four split repos (leave
thermograph-docsalone), update its runbooks to the monorepo paths.
Known gaps / deferred
- Discord/notifier extraction (phase 2): discord still ships inside the
backend image (
backend/notifications/); a discord tweak = a backend deploy. Truenotifier/domain (own image + compose service, singleton lock retired) is a refactor for after the monorepo settles. - Domain
CLAUDE.mds still speak repo-era ("this repo", sibling-repo links) — correct them as touched; rootCLAUDE.mdpoints the right way. backend/MONOREPO-CUTOVER-PLAN.mdreferences (the split plan) are now historical.- Infra
Makefilelocal-image targets still assume sibling checkouts — update when first needed locally.