Adds the pages that were missing: an "Active Beers" view of what is in the fermenter right now, and the per-run metadata (canonical name, tap number, original gravity, brew notes) that could not be derived from the readings. Why the metadata is needed -------------------------- The `beer_data` collection stores a beer name on *each reading*, and that name drifts within a run - run 4 was entered as "Necatron-Christmas Beer", then "Nocturna the Christmas beer", then "Nocturna Christmas beer". There was nowhere to record a tap number, a confirmed original gravity, or a note about the brew. The new `beer_runs` collection holds one document per run with exactly those details. It starts empty; `scripts/backfill_run_metadata.py` populates it from the existing readings. The script is idempotent, only ever inserts into `beer_runs`, never touches `beer_data`, and never overwrites a name you have already set by hand. The active beers page --------------------- A card per brew that is still in the fermenter, showing the tap number, the current gravity, the projected alcohol, how many days it has been brewing, a sparkline of the gravity so far, and the latest comment on a reading. 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 or take a second reading and it appears. The same applies to the existing reads, where the legacy app's assumed OG of 1012 produced *negative* alcohol for a perfectly normal OG. Also adds the history page: alcohol by brew, brews per month, every run in a table, a comparison of the standard ABV formula against the legacy one, and a list of readings that fall outside the plausible hydrometer range. Changes to the existing pages ----------------------------- - The brew-process page and the run detail page now show the canonical run name and the latest comment, and the run detail page lets you edit the metadata inline. - Gravity is displayed as a decimal reading (1.039) with the point scale beneath it (39 points), which is the way a brewer reads it. Labelling the stored `1039` as "1039 points" would have been wrong. - A run's display name now prefers the name on its *final* reading - the one written when the brew was finished, so the name the operator settled on - falling back to the most-used name. - `upsert_run_metadata` now clears a field when it is passed as `None`, so a name or tap number can be deleted. `upsert_run_metadata_partial` is the non-clearing variant, used by the backfill. Bugs found and fixed along the way ---------------------------------- - The chart script was included both in the base template and again by each page, so every chart was drawn twice. A test now asserts it is loaded exactly once per page. - The chart library was never referenced by the base template, so all charts were silently dead. That was fixed in the previous PR. Tests ----- 91 (up from 74). New coverage: the active page contents, the tap number, the projected alcohol, the "needs 2nd reading" case, metadata saving and clearing, the stored name overriding the drifting reading name, the next free tap number, the partial-update semantics, the history page, the outlier list, and the point-scale formatting. Data safety ----------- `beer_data` is untouched by this change. The backfill script only inserts into `beer_runs` and skips any run whose name has already been set by hand.
203 lines
6.2 KiB
Markdown
203 lines
6.2 KiB
Markdown
# RUNBOOK — Brew Log (beer_data)
|
||
|
||
Operational notes for the `beer` namespace deployment.
|
||
|
||
## What runs where
|
||
|
||
| | |
|
||
|---|---|
|
||
| Namespace | `beer` |
|
||
| Deployment | `beer-deployment` (3 replicas) |
|
||
| Image | `git.aridgwayweb.com/armistace/beer-data:latest` |
|
||
| Service | `beer-service`, NodePort **30015** → container port 5000 |
|
||
| Database | MongoDB at `192.168.178.229`, database `beer_db` |
|
||
| Secret | `beercred` (registry pull credentials) |
|
||
|
||
Access: `http://<any-cluster-node>:30015/`
|
||
|
||
The pods are pinned to `archlinux-k3s-3` (`.161`) via `nodeSelector`.
|
||
|
||
## Environment variables
|
||
|
||
| Variable | Value | Source |
|
||
|---|---|---|
|
||
| `MONGO_HOST` | `192.168.178.229` | Deployment manifest |
|
||
| `MONGO_USER` | `root` | Deployment manifest |
|
||
| `MONGO_PASS` | *(see below)* | Deployment manifest |
|
||
| `SECRET_KEY` | *(see below)* | Deployment manifest — Flask session signing key. **Required** |
|
||
| `FLASK_ENV` | `production` | Dockerfile |
|
||
| `FLASK_DEBUG` | `0` | Dockerfile |
|
||
|
||
`SECRET_KEY` and `MONGO_PASS` must be set in the live Deployment. They are
|
||
**not** committed to this repo — read them from the running cluster:
|
||
|
||
```bash
|
||
kubectl -n beer get deploy beer-deployment -o jsonpath='{.spec.template.spec.containers[0].env}'
|
||
```
|
||
|
||
> **The app refuses to start without a real `SECRET_KEY`.** There is no default
|
||
> and no placeholder: a missing or placeholder value raises
|
||
> `ImproperlyConfigured` and the container exits. A known session-signing key
|
||
> would let anyone forge a login cookie, so a misconfigured deployment must
|
||
> fail loudly rather than run insecurely.
|
||
>
|
||
> Generate one with:
|
||
> ```bash
|
||
> python -c 'import secrets; print(secrets.token_hex(32))'
|
||
> ```
|
||
|
||
### Moving the credentials into a Kubernetes Secret
|
||
|
||
The live Deployment currently carries `MONGO_PASS` as a plain environment
|
||
value. Moving it to a Secret is the better shape and is **not** done here
|
||
because it would mean replacing the live hand-provisioned values — a change
|
||
that needs the operator's sign-off. When it is done:
|
||
|
||
```yaml
|
||
env:
|
||
- name: MONGO_PASS
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: beer-db
|
||
key: mongo_pass
|
||
- name: SECRET_KEY
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: beer-app
|
||
key: secret_key
|
||
```
|
||
|
||
## 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:
|
||
|
||
```bash
|
||
kubectl -n beer rollout restart deployment/beer-deployment
|
||
kubectl -n beer rollout status deployment/beer-deployment --timeout=180s
|
||
```
|
||
|
||
### Verify the deploy actually took
|
||
|
||
CI green and pods `Ready` do **not** prove the new code is running. Check the
|
||
running digest against the registry:
|
||
|
||
```bash
|
||
# what the pod is running
|
||
kubectl -n beer get pod -l app=beer-data \
|
||
-o jsonpath='{.items[0].status.containerStatuses[0].imageID}'
|
||
|
||
# what the registry has as :latest
|
||
curl -s -u "armistace:$REG_PASSWORD" -I \
|
||
"https://git.aridgwayweb.com/v2/armistace/beer-data/manifests/latest" \
|
||
-H "Accept: application/vnd.docker.distribution.manifest.v2+json" \
|
||
| grep -i docker-content-digest
|
||
```
|
||
|
||
If they differ, the registry `:latest` is stale — re-run the build rather than
|
||
re-applying the manifest.
|
||
|
||
### Check the app is alive
|
||
|
||
```bash
|
||
kubectl -n beer get pods
|
||
kubectl -n beer logs deploy/beer-deployment --tail=50
|
||
curl -s http://<node>:30015/healthz # -> {"status":"ok"}
|
||
curl -s http://<node>:30015/readyz # -> {"status":"ready"}
|
||
```
|
||
|
||
`/healthz` is the **liveness** probe and never touches Mongo — it answers "is
|
||
the process up". A database outage must not make the kubelet restart a healthy
|
||
process.
|
||
|
||
`/readyz` is the **readiness** probe and pings Mongo, returning 503 when the
|
||
database is unreachable. Wire it to the Service so a pod with no database is
|
||
pulled out of the endpoints rather than serving errors:
|
||
|
||
```yaml
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /readyz
|
||
port: 5000
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 10
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /healthz
|
||
port: 5000
|
||
initialDelaySeconds: 10
|
||
periodSeconds: 20
|
||
```
|
||
|
||
## The run metadata collection
|
||
|
||
The `beer_runs` collection holds one document per brew run with the details
|
||
that cannot be derived from the readings: the canonical name, the tap number,
|
||
the recorded original gravity and the brew notes.
|
||
|
||
It starts empty on a fresh install. Populate it from the existing readings:
|
||
|
||
```bash
|
||
# from the app's source tree, with the usual env vars set
|
||
python scripts/backfill_run_metadata.py # dry run
|
||
python scripts/backfill_run_metadata.py --apply # write it
|
||
```
|
||
|
||
The script is idempotent and only ever inserts into `beer_runs`. It does not
|
||
touch `beer_data`.
|
||
|
||
To see what is in it:
|
||
|
||
```bash
|
||
mongosh "mongodb://root:$MONGO_PASS@192.168.178.229:27017/beer_db" \
|
||
--eval 'db.beer_runs.find().pretty()'
|
||
```
|
||
|
||
## Data safety
|
||
|
||
The `beer_db.beer_data` collection holds the actual brewing records. There is
|
||
no migration step and no schema version — documents are read as they are
|
||
written, and every field is treated as optional.
|
||
|
||
To take a backup before any risky change:
|
||
|
||
```bash
|
||
mongodump --uri="mongodb://root:$MONGO_PASS@192.168.178.229:27017/beer_db" \
|
||
--out=/tmp/beer_db_backup_$(date +%Y%m%d)
|
||
```
|
||
|
||
## Common problems
|
||
|
||
### The login page loads but nothing else does
|
||
|
||
The Mongo connection failed. Check the app logs for an auth error and confirm
|
||
the credentials:
|
||
|
||
```bash
|
||
kubectl -n beer logs deploy/beer-deployment --tail=100 | grep -i mongo
|
||
```
|
||
|
||
### A run shows a negative alcohol figure
|
||
|
||
That was the old formula (`(max - min) / 7.36 + 0.5`) applied to a run whose
|
||
current gravity is above the assumed original gravity of 1012. Fixed in the
|
||
current version: the standard `(OG - FG) × 131.25` formula is used, and a run
|
||
with no known OG shows a dash instead of a figure.
|
||
|
||
### The pod will not start after a deploy
|
||
|
||
The most likely cause is a missing `SECRET_KEY` — the app exits rather than
|
||
run with a default. Check the logs:
|
||
|
||
```bash
|
||
kubectl -n beer logs deploy/beer-deployment --tail=20
|
||
# ImproperlyConfigured: SECRET_KEY is not set. ...
|
||
```
|
||
|
||
### The chart on the run page is blank
|
||
|
||
The chart library is served from `/static/js/chart.umd.js` — it is vendored in
|
||
the repo, not loaded from a CDN, so the container works with no outbound
|
||
network. Check the browser console for a 404 on that path.
|