docs: monorepo README, cutover runbook, root agent instructions
This commit is contained in:
parent
2e753f2c6f
commit
210627f040
3 changed files with 174 additions and 0 deletions
27
CLAUDE.md
Normal file
27
CLAUDE.md
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
# thermograph monorepo — agent instructions
|
||||||
|
|
||||||
|
Reunified monorepo (2026-07-22): `backend/`, `frontend/`, `infra/`,
|
||||||
|
`observability/` — each a former split repo subtree-merged with history.
|
||||||
|
`thermograph-docs` remains a separate repo; cross-cutting decision docs and
|
||||||
|
operator runbooks belong there, not here.
|
||||||
|
|
||||||
|
- **Each domain keeps its own `CLAUDE.md`** — read the one for the domain you
|
||||||
|
touch. They still use repo-era wording ("this repo", sibling-repo names);
|
||||||
|
mentally map `thermograph-backend` → `backend/` etc., and fix wording as you
|
||||||
|
touch files near it.
|
||||||
|
- **CI lives only in root `.forgejo/workflows/`**, path-filtered per domain
|
||||||
|
(see README). Never re-add workflows under a domain's own `.forgejo/` — they
|
||||||
|
are inert there and become a trap.
|
||||||
|
- Images are `emi/thermograph/backend` and `emi/thermograph/frontend`
|
||||||
|
(`sha-<12hex>`). Deploy = `infra/deploy/deploy.sh` over SSH, per-service.
|
||||||
|
- The compose project name is pinned (`name: thermograph` in
|
||||||
|
`infra/docker-compose.yml`); LAN dev overrides via
|
||||||
|
`COMPOSE_PROJECT_NAME=thermograph-dev`. Don't remove either half.
|
||||||
|
- Cross-domain rules that still bind: FE/BE ship independently → keep the
|
||||||
|
`/api/version` contract and `PAYLOAD_VER` discipline (see
|
||||||
|
`backend/CLAUDE.md`); anything named "prefetch" must never spend the
|
||||||
|
Open-Meteo quota; Nominatim ≤ 1 req/s; secrets only via the SOPS vault
|
||||||
|
(`infra/deploy/secrets/`).
|
||||||
|
- `CUTOVER-NOTES.md` is the source of truth for what is and isn't live yet.
|
||||||
|
- Commits/PRs: concise and technical; never mention AI/assistants/automated
|
||||||
|
authorship.
|
||||||
113
CUTOVER-NOTES.md
Normal file
113
CUTOVER-NOTES.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# 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-backend `83c2e05` (Discord gateway bot landed, #7)
|
||||||
|
- `frontend/` ← thermograph-frontend `3a98146` (responsiveness hardening, #4)
|
||||||
|
- `infra/` ← thermograph-infra `99b4b3f` (mail docs, #12)
|
||||||
|
- `observability/` ← thermograph-observability `19e74af` (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 }}/app` derivation 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 a `domain` input (context = the domain dir, so Dockerfiles see the
|
||||||
|
same tree as in the split era).
|
||||||
|
- `pr-build.yml` — one `changes` job (git-diff domain detection) + per-domain
|
||||||
|
gated builds + a single always-reporting **`gate`** required 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 to `main`
|
||||||
|
touching `infra/**` → git-sync `/opt/thermograph` on beta+prod and re-render
|
||||||
|
secrets. Rolls no services (image tags are the app domains' axis).
|
||||||
|
- `observability-validate.yml` — port with `observability/` prefixes +
|
||||||
|
`workflow_call` so pr-build reuses it.
|
||||||
|
- `secrets-guard.yml` — glob is now `infra/deploy/secrets/*.yaml`; still runs
|
||||||
|
on everything (cheap backstop).
|
||||||
|
- `ops-cron.yml` — compose invocations `cd /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 under
|
||||||
|
`infra/deploy/`; image-path defaults renamed.
|
||||||
|
- `docker-compose.yml`: **`name: thermograph` pinned.** Critical: without it,
|
||||||
|
running compose from `…/infra` derives project "infra" — a silently NEW
|
||||||
|
project that would recreate the whole stack beside the running one.
|
||||||
|
`deploy-dev.sh` exports `COMPOSE_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 deploy` takes 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)
|
||||||
|
|
||||||
|
1. **Run the pending prod network-recreate runbook first.** Don't rebase the
|
||||||
|
deploy path on a prod that wedges on its next deploy.
|
||||||
|
2. **Prove Forgejo path filters + reusable-workflow inputs** with a throwaway
|
||||||
|
repo: one push touching only `backend/**` must fire only backend workflows;
|
||||||
|
a `uses: ./.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.
|
||||||
|
3. **Land the in-flight split-repo branches** (backend/frontend
|
||||||
|
`reconcile-dev-with-main`, infra `phase5/discord-bot-flag`, …) in the split
|
||||||
|
repos, then re-subtree or graft the deltas — this assembly is `origin/main`
|
||||||
|
as of 2026-07-22 and goes stale as those merge.
|
||||||
|
|
||||||
|
## Cutover runbook
|
||||||
|
|
||||||
|
1. Create `emi/thermograph` on Forgejo (a **new** repo — do not resurrect the
|
||||||
|
archived monorepo) and push `main`.
|
||||||
|
2. Actions config, once: secrets `REGISTRY_TOKEN`, `SSH_HOST/USER/KEY/PORT`
|
||||||
|
(beta), `PROD_SSH_*` (prod); variable `REGISTRY_HOST=git.thermograph.org`.
|
||||||
|
3. Branch protection on `dev` + `main`: required check `gate`, squash merges,
|
||||||
|
auto-merge on green.
|
||||||
|
4. Hosts (beta, then prod): clone the monorepo to `/opt/thermograph.new`;
|
||||||
|
**copy `deploy/.image-tags.env` from the old checkout into
|
||||||
|
`infra/deploy/`** (its path moved — losing it makes the next
|
||||||
|
single-service deploy roll the sibling onto `local`); swap directories
|
||||||
|
(running containers are untouched — they're images, not the checkout);
|
||||||
|
`/etc/thermograph*` files stay as-is.
|
||||||
|
5. LAN box: re-point `~/thermograph-dev` at the monorepo the same way.
|
||||||
|
6. First push builds the **new** image paths — old
|
||||||
|
`emi/thermograph-{backend,frontend}/app` packages stay in the registry for
|
||||||
|
rollback; GC after burn-in. deploy.sh's tag prune already targets the new
|
||||||
|
paths only.
|
||||||
|
7. One `workflow_dispatch` of `backend-deploy.yml` after its build-push
|
||||||
|
completes = the live end-to-end validation, same as the split-era wiring
|
||||||
|
test.
|
||||||
|
8. After burn-in: archive the four split repos (leave `thermograph-docs`
|
||||||
|
alone), 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. True `notifier/` domain (own image + compose service, singleton
|
||||||
|
lock retired) is a refactor for after the monorepo settles.
|
||||||
|
- Domain `CLAUDE.md`s still speak repo-era ("this repo", sibling-repo links) —
|
||||||
|
correct them as touched; root `CLAUDE.md` points the right way.
|
||||||
|
- `backend/MONOREPO-CUTOVER-PLAN.md` references (the *split* plan) are now
|
||||||
|
historical.
|
||||||
|
- Infra `Makefile` local-image targets still assume sibling checkouts — update
|
||||||
|
when first needed locally.
|
||||||
34
README.md
Normal file
34
README.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# thermograph
|
||||||
|
|
||||||
|
The Thermograph monorepo — the split repos reunified (2026-07-22) with full
|
||||||
|
history via subtree merges, while keeping everything the split was actually
|
||||||
|
for: **per-domain images, per-domain deploys, and an async FE/BE contract**.
|
||||||
|
|
||||||
|
## Domains
|
||||||
|
|
||||||
|
| Dir | What | CI |
|
||||||
|
|---|---|---|
|
||||||
|
| `backend/` | FastAPI graded-climate API, accounts, notifications (Discord bot, push, mail), data pipeline | `backend-build-push` → image `emi/thermograph/backend`; `backend-deploy[-prod\|-dev]` |
|
||||||
|
| `frontend/` | Public client: static JS/CSS + SSR pages | `frontend-*` mirrors of the above; image `emi/thermograph/frontend` |
|
||||||
|
| `infra/` | Compose, deploy scripts, terraform, SOPS secrets vault, ops cron | `infra-sync` (host checkout + secrets render), `secrets-guard`, `ops-cron` |
|
||||||
|
| `observability/` | Loki + Grafana + Alloy stack | `observability-validate` |
|
||||||
|
|
||||||
|
`thermograph-docs` deliberately **stays its own repo** (ADRs + runbooks, no
|
||||||
|
build artifacts, different change cadence).
|
||||||
|
|
||||||
|
## How CI stays decoupled
|
||||||
|
|
||||||
|
Every workflow in `.forgejo/workflows/` is **path-filtered to its domain**: a
|
||||||
|
push touching only `frontend/**` builds/deploys nothing else. Images stay
|
||||||
|
separate (`emi/thermograph/backend`, `emi/thermograph/frontend`, each tagged
|
||||||
|
`sha-<12hex>`), deploys stay per-service (`infra/deploy/deploy.sh
|
||||||
|
SERVICE=backend|frontend|all`), and the API version contract
|
||||||
|
(`GET /api/version`, `PAYLOAD_VER`) still lets FE and BE ship out of lockstep.
|
||||||
|
The one intentionally *coupled* piece is `pr-build.yml`: a single always-running
|
||||||
|
`gate` required check that builds only the domains a PR touches (a
|
||||||
|
path-filtered required check would deadlock auto-merge).
|
||||||
|
|
||||||
|
Branch model (unchanged from the split era): PRs → `dev`, `main` → beta,
|
||||||
|
`release` → prod; infra tracked via `main` on all hosts.
|
||||||
|
|
||||||
|
**Before pointing anything live at this repo, read `CUTOVER-NOTES.md`.**
|
||||||
Loading…
Reference in a new issue