docs(spec): secubox-picobrew — suite brassage/fermentation/distillation en LXC

Réaligne le paquet Debian (squelette hôte : 992 lignes de contrôleur de
fermentation à capteurs, aucun LXC) sur l'intention du portage OpenWrt, SANS
jeter l'existant : le contrôleur déménage dans le LXC et devient sbx-stillwatch,
étendu à la distillation.

Décisions : LXC Debian natif (pas Alpine, pas Docker) ; upstream picobrew_pico
(Pico S/C/Pro + Z + Zymatic) plutôt que le fork CyberMind limité au Zymatic ;
clone GitHub latest puis SHA figé, update explicite refusé pendant une session
(un changement upstream en plein brassage est inacceptable) ; drop-in DNS
picobrew.com actif par défaut, le cloud constructeur étant éteint depuis 2020 ;
héberger l'éprouvé (CraftBeerPi 4) et n'écrire que le manquant.

Limite de conception posée : stillwatch surveille et guide la distillation mais
n'actionne pas d'alambic — les coupes têtes/coeur/queues sont matérialisées et
tracées, la décision reste humaine. cuts.py est une fonction pure, testable sans
matériel.

Livraison en 3 phases utilisables seules, avec porte de réévaluation YAGNI sur
CraftBeerPi en phase 3. LXC alloué en 10.100.0.140 (motif appliances 1x0).

Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
CyberMind-FR 2026-07-23 16:55:11 +02:00
parent e45c9690c9
commit 58166f02d0

View File

@ -0,0 +1,144 @@
# secubox-picobrew — suite de brassage, fermentation et distillation (LXC) — Design
**Date :** 2026-07-23
**Statut :** validé (design), prêt pour le plan d'implémentation
**Portage de :** `luci-app-picobrew` + `secubox-app-picobrew` (SecuBox-OpenWrt)
---
## Objectif
Redonner vie à un appareil PicoBrew dont le cloud constructeur est éteint depuis 2020, et lui
adjoindre les outils nécessaires pour gérer **fermentation** et **distillation** de bout en bout —
le tout dans un seul LXC, piloté depuis un panel SecuBox unique.
## Contexte (vérifié, 2026-07-23)
### Une divergence à corriger
| | Ce que ça fait |
|---|---|
| OpenWrt `secubox-app-picobrew` | `picobrewctl` crée un LXC (rootfs Alpine) et y clone un serveur PicoBrew |
| Debian `secubox-picobrew` v1.0.0 | **Squelette hôte** : `api/main.py` (992 lignes) = contrôleur de fermentation à capteurs 1-Wire/I2C/USB. **Aucun LXC, aucun serveur PicoBrew** |
Les deux ne partagent que le nom. Ce design réaligne le paquet Debian sur son intention d'origine
**sans jeter** le contrôleur de fermentation : il déménage dans le LXC et gagne un volet distillation.
### Faits d'environnement
- LXC existants sur gk2 ; motif des appliances en `10.100.0.1x0` (lyrion .100, mqtt .110,
peertube .120, photoprism .130) → **`10.100.0.140` libre et cohérent** pour picobrew.
- Upstream retenu : **`chiefwigms/picobrew_pico`** (Flask, actif 05/2026, 155 ★) — couvre
**Pico S/C/Pro, Z Series, Zymatic**. Le fork `CyberMind-FR/picobrew-server` est écarté : Zymatic
uniquement, dernier commit 01/2026.
- L'upstream **exige une réécriture DNS** de `picobrew.com` (`address=/picobrew.com/<IP serveur>`) :
l'appareil ne sait parler qu'à ce domaine.
- La **série Z impose HTTPS** → terminaison TLS devant Flask (qui écoute en clair sur `:80`).
## Décisions actées
1. **Tout dans un LXC Debian** (pas Alpine comme l'OpenWrt, pas Docker comme l'upstream) : service
natif avec sa propre unit systemd, conformément à la doctrine du dépôt.
2. **Depuis GitHub latest, puis figé.** On clone la dernière version *à l'installation*, puis on
**enregistre le SHA**. Aucune mise à jour implicite : uniquement sur `picobrewctl update`
explicite. Un changement upstream ne doit jamais survenir **en plein brassage**, sur une machine
qui chauffe du moût.
3. **Drop-in DNS actif par défaut.** Le cloud PicoBrew est mort : réécrire `picobrew.com` localement
ne casse rien, il n'y a plus rien à casser. Réserve notée : si le domaine était un jour
re-déposé par un tiers, l'override le masquerait — mais l'appareil est inutilisable sans lui.
4. **Le contrôleur de fermentation n'est pas jeté** : il devient `sbx-stillwatch` dans le LXC.
5. **Héberger l'éprouvé, écrire le manquant** : picobrew_pico + CraftBeerPi 4 pour les fonctions
connues ; nous n'écrivons que la couche capteurs/coupes.
6. **Point d'entrée unique** : le panel SecuBox agrège l'état des services et renvoie vers les UI
tierces par vhosts. On les encadre, on ne les réécrit pas.
## Architecture
```text
Appareil PicoBrew ──"picobrew.com"──► Unbound ──► LXC picobrew (10.100.0.140)
(drop-in actif) │
├─ picobrew_pico : l'appareil (Flask + TLS série Z)
├─ CraftBeerPi 4 : fermentation (recettes, relais)
└─ sbx-stillwatch : NOUS (capteurs + coupes)
Admin ──► admin.gk2/picobrew/ ──► panel ──sudo──► picobrewctl ──lxc-attach──► LXC
```
Le webui n'effectue **jamais** d'action privilégiée en direct : il délègue à `picobrewctl` via un
sudoers à commande exacte (règle du dépôt : panel non privilégié → ctl root unique et audité).
## Composants
| Chemin | Rôle |
|---|---|
| `sbin/picobrewctl` | ctl root audité : `create` / `start` / `stop` / `status` / `update` / `logs` |
| `lib/picobrew/install-lxc.sh` | Provisionne le LXC Debian, venv Python, units systemd internes |
| `api/main.py` | **Réécrit** : API de gestion fine, délègue au ctl ; expose l'état agrégé |
| `www/picobrew/index.html` | Panel : état des 3 services, actions, capteurs, coupes |
| `nginx/picobrew.conf` | Reverse-proxy du panel + vhosts vers les UI tierces |
| `conf/unbound-picobrew.conf` | Drop-in DNS `picobrew.com``10.100.0.140` (actif par défaut) |
| `sudoers.d/secubox-picobrew` | Grant exact panel → `picobrewctl` |
### `sbx-stillwatch` — la seule brique que nous écrivons
Reprend les 992 lignes existantes et les étend. Découpage en unités testables isolément :
| Fichier | Rôle | Dépendances |
|---|---|---|
| `sensors.py` | Détection + lecture 1-Wire DS18B20 / I2C / USB | matériel |
| `sessions.py` | Sessions, courbes, journalisation | stockage |
| `alerts.py` | Seuils et alarmes | sessions |
| `cuts.py` | **Fonction pure** : découpage têtes/cœur/queues, ABV | aucune |
`cuts.py` est pur (température + volume + temps → verdict de coupe), donc entièrement testable sans
matériel ni alambic — c'est là que vit la logique délicate.
## Distillation : surveiller et guider, ne pas actionner
**Limite posée délibérément.** CraftBeerPi actionne les relais de **fermentation** : une cuve qui
dérive de 2 °C donne une bière moyenne, le risque est acceptable. Sur une **colonne à distiller**,
une actuation autonome qui se trompe est un incident. `stillwatch` matérialise les coupes, calcule
l'ABV, déclenche les alarmes et **trace** tout ; la décision de coupe reste humaine.
Les premières fractions concentrent le méthanol et les congénères indésirables : un outil qui
afficherait une simple température sans matérialiser les coupes donnerait une fausse assurance.
La matérialisation des coupes est donc une **exigence**, pas un confort.
La réglementation de la distillation domestique variant fortement selon les pays, le module n'en
présume rien et n'active aucun comportement spécifique à une juridiction.
## Livraison en trois phases
Chaque phase est utilisable seule.
1. **L'appareil revit** : LXC + picobrew_pico + TLS série Z + drop-in DNS + panel + ctl.
2. **`stillwatch` fermentation** : migration des capteurs, sessions, alertes dans le LXC.
3. **Distillation + CraftBeerPi 4** : `cuts.py`, ABV, alarmes ; puis CraftBeerPi.
**Porte de réévaluation** en début de phase 3 : si `stillwatch` couvre déjà le besoin de
fermentation à l'usage, CraftBeerPi n'est pas installé (YAGNI).
## Tests
- **`cuts.py`** (le cœur) : jeux de courbes réelles — montée en température, palier de cœur,
bascule en queues ; cas dégradés (capteur qui décroche, valeurs aberrantes, redémarrage en cours
de session).
- **`sensors.py`** : détection sans matériel (mocks), lecture d'un DS18B20 simulé, capteur absent.
- **`picobrewctl`** : idempotence de `create`, `status` sans LXC, `update` qui refuse de tourner
pendant une session active.
- **DNS** : le drop-in résout bien `picobrew.com` vers le LXC et ne casse aucune autre zone.
## Hors périmètre (YAGNI)
Recettes BeerXML, pilotage du PicoStill, compte cloud PicoBrew, actuation autonome d'un alambic,
intégration Tilt/iSpindel (à réévaluer après la phase 2).
## Risques connus
| Risque | Traitement |
|---|---|
| Upstream qui change en plein brassage | SHA figé ; `update` explicite, refusé si session active |
| Série Z sans TLS valide | Certificat auto-signé généré à l'install, terminaison nginx dans le LXC |
| `picobrew.com` re-déposé par un tiers | Override local assumé ; l'appareil est inutilisable sans |
| 3 UI hétérogènes | Panel SecuBox = point d'entrée unique ; les UI tierces sont encadrées, pas réécrites |
| Fausse assurance en distillation | Coupes matérialisées et tracées ; aucune actuation autonome |
| Capteur qui décroche en session | `cuts.py` traite les valeurs aberrantes ; alarme plutôt que verdict silencieux |