diff --git a/README.md b/README.md index 545f88c..75b504e 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ The tests use `mongomock`, so they need no database and no network. | `/brews` | The brew process: record a reading, review every run | | `/active` | What is in the fermenter right now — tap, gravity, projected strength | | `/history` | How every brew has gone, with charts and the standard-vs-legacy ABV comparison | +| `/analysis` | How consistently the brews ferment — attenuation, fermentation curves, brew timings | ### `/active` — the active beers page @@ -59,6 +60,27 @@ A run with only one reading and no recorded original gravity has no reliable estimate, so the alcohol figure shows a dash rather than a misleading number. Record the OG on the run (or take a second reading) and it appears. +### `/analysis` — how the brews ferment + +The page that turns the history into something you can brew better from: + +- **Attenuation by brew** — what fraction of the original gravity was + fermented away, for every finished brew. +- **Fermentation curves** — the typical shape of a fermentation, drawn as a + median curve with the min–max envelope behind it. Plotting every brew + individually gives sixteen near-identical lines and an unreadable legend. +- **Every finished brew** — OG, FG, ABV, attenuation, days in the fermenter, + days to reach 1.010, and the finish date. +- **Readings that need a second look** — readings outside the plausible + hydrometer range. +- Headline figures: average attenuation and its spread, average brew time, + average time to 1.010. + +A reading that is physically impossible — a final gravity below the original +gravity by more than the wort itself could have — is excluded from the +averages rather than silently distorting them, and the run is listed on the +page so it can be corrected. + ### Run metadata Some details cannot be derived from the readings, so they are stored per run in @@ -94,7 +116,6 @@ src/flask/ views/ blueprints: auth, brews, analysis mongo/repository.py Mongo access for the beer_data and beer_runs collections templates/ Jinja templates - static/ CSS, JS, fonts, logo (all served locally — no CDN) static/ CSS, JS, fonts, logo (all served locally - no CDN) tests/ pytest suite scripts/ operational scripts (the run-metadata backfill) diff --git a/RUNBOOK.md b/RUNBOOK.md index bca333f..e2e46cd 100644 --- a/RUNBOOK.md +++ b/RUNBOOK.md @@ -69,15 +69,53 @@ env: ## Deploying -Pushing to `master` triggers `.gitea/workflows/build_push.yml`, which runs the -test suite and then builds and pushes the image. **The workflow does not -currently deploy to the cluster** — apply the manifests yourself: +Pushing to `master` triggers `.gitea/workflows/build_push.yml`, which builds +the image, pushes it, **deploys it to the cluster and verifies it**. There is +no manual step. + +The workflow is a single job, in this order: + +1. **Checkout** +2. **Create Kubeconfig** — writes `secrets.KUBEC_CONFIG_BUILDX_NEW_2` to + `$HOME/.kube/config`. This is FIRST because the buildx `kubernetes` driver + needs it to create its builder pod in the cluster. +3. **Set up Docker Buildx** — `driver: kubernetes`, `namespace=gitea-runner`. +4. **Login to the registry** — `secrets.REG_PASSWORD`. +5. **Build and Push** — `linux/amd64` only. A multi-arch build runs qemu, + which crashes the buildkit pod on this NUC (exit 255, `SandboxChanged`) + and the build then hangs for many minutes. +6. **Deploy** — installs kubectl, applies `kube/*.yaml`, creates the + image-pull secret idempotently, then `rollout restart` + `rollout status`. + The rollout restart is required: the image tag in the manifest is + `:latest`, so `kubectl apply` alone does not roll the pods. +7. **Verify the deployed app** — exercises `/healthz` and `/readyz` from + inside the pod, using the pod's own python (there is no `curl` in the + slim image). + +> The deploy step does **not** delete the namespace. That would destroy the +> hand-provisioned `beer-db` and `beer-app` Secrets. + +### Deploying by hand + +If you need to deploy without going through CI — for example to test a +manifest change — do the same thing the workflow does: ```bash +kubectl apply -f kube/beer_namespace.yaml +kubectl apply -f kube/beer-deployment.yaml +kubectl apply -f kube/beer-service.yaml kubectl -n beer rollout restart deployment/beer-deployment kubectl -n beer rollout status deployment/beer-deployment --timeout=180s ``` +Validate the manifests before applying them. `kubectl apply` **silently +rejects** a manifest with a schema error, so a broken manifest leaves the +old Deployment in place while the job still reports success: + +```bash +kubectl apply --dry-run=client -f kube/ +``` + ### Verify the deploy actually took CI green and pods `Ready` do **not** prove the new code is running. Check the