6 criteres retoques: PhotoPrism+garde-fous+WhatsApp+grille 10 requetes+17 questions, salon famille cree (GO), peages perimes

This commit is contained in:
Effi
2026-10-12 22:42:22 +02:00
parent aeee208c04
commit 9eae93be0b
13 changed files with 346 additions and 51 deletions
+25 -2
View File
@@ -127,7 +127,7 @@ Le **Qwen 3.8max** (le LLM principal) consomme **~20 Go VRAM + ~50 Go RAM**. Le
| 5 | **WireGuard** | Accès distant chiffré | J5040 | GPL-2.0 | Système (noyau), très stable | Clés exportables | | 5 | **WireGuard** | Accès distant chiffré | J5040 | GPL-2.0 | Système (noyau), très stable | Clés exportables |
| 6 | **Web Station** | Sites web (dd/lola.folzocorp…) | J5040 | — (contenu) | Statique | Fichiers, exportables | | 6 | **Web Station** | Sites web (dd/lola.folzocorp…) | J5040 | — (contenu) | Statique | Fichiers, exportables |
| 7 | **Beszel** | Monitoring léger + alertes | J5040 | MIT | Mature, maintenu (henrygd) | Base SQLite, exportable | | 7 | **Beszel** | Monitoring léger + alertes | J5040 | MIT | Mature, maintenu (henrygd) | Base SQLite, exportable |
| 8 | **Matrix (Synapse)** | **Messagerie** (remplace WhatsApp) | J5040 | Apache-2.0 | Très mature (Element/Matrix), maintenu | Export des salons (JSON), comptes | | 8 | **Matrix (Synapse)** | **Messagerie du foyer** (complément de WhatsApp, **pas de remplacement**) | J5040 | Apache-2.0 | Très mature (Element/Matrix), maintenu | Export des salons (JSON), comptes |
| 9 | **Jitsi** | **Visioconférence** | J5040 | Apache-2.0 | Mature (Jitsi Meet), maintenu | N/A (appels éphémères) | | 9 | **Jitsi** | **Visioconférence** | J5040 | Apache-2.0 | Mature (Jitsi Meet), maintenu | N/A (appels éphémères) |
| 10 | **\*arr** (Prowlarr + Sonarr + Radarr) — **1 stack** | Gestion **séries/films** (indexers + queue) | J5040 | GPL-3.0 | Très mature (la stack \*arr), maintenu | Bases SQLite exportables, config | | 10 | **\*arr** (Prowlarr + Sonarr + Radarr) — **1 stack** | Gestion **séries/films** (indexers + queue) | J5040 | GPL-3.0 | Très mature (la stack \*arr), maintenu | Bases SQLite exportables, config |
| 11 | **qBittorrent** | **Téléchargement** (torrent/HTTP) | J5040 | GPL-2.0 | Très mature, maintenu | File de tâches exportable | | 11 | **qBittorrent** | **Téléchargement** (torrent/HTTP) | J5040 | GPL-2.0 | Très mature, maintenu | File de tâches exportable |
@@ -431,6 +431,29 @@ Le **Qwen 3.8max** (le LLM principal) consomme **~20 Go VRAM + ~50 Go RAM**. Le
| **Moi** | Téléphone + PC | **Salon ops** (alertes backup / sondes / dérive registre) | | **Moi** | Téléphone + PC | **Salon ops** (alertes backup / sondes / dérive registre) |
| **Moi** | Enceintes Google ×4-5 | **Assistant vocal** (Home Assistant) → peut déclencher n8n, contrôler Frigate | | **Moi** | Enceintes Google ×4-5 | **Assistant vocal** (Home Assistant) → peut déclencher n8n, contrôler Frigate |
**Décision 12/10/2026 (statut du canal)** : Matrix + Jitsi + Element = **complément du foyer** —
salon famille, salon ops, salon Effi. **WhatsApp n'est pas remplacé** et reste la messagerie
principale (famille élargie, amis, travail) : c'est un constat d'usage mesuré, pas un choix
technologique. Element s'installe d'abord sur les **2 appareils adultes** ; les 4 ados viennent en
**phase 2**, déclenchée par le **GO famille** explicite de l'utilisateur. Grille de décision :
**S14.5**.
**Les 4 interdictions** (applicables dès maintenant, vérifiables) :
1. **Interdiction** de présenter Matrix comme un remplacement de WhatsApp, et d'inciter la famille
à quitter WhatsApp pour Matrix (WhatsApp garde les échanges extérieurs).
2. **Interdiction** de faire transiter les **alertes ops** par WhatsApp : le canal d'alerte est
**Matrix uniquement** (pas d'e-mail, pas de SMS, pas de WhatsApp).
3. **Interdiction** d'**automatiser l'envoi** de messages WhatsApp (pont `mautrix-whatsapp` :
**étudié, non déployé**, numéro familial **interdit** — voir `g9-migration/BRIDGE-WHATSAPP.md`).
4. **Interdiction** d'équiper les **4 ados** d'Element (et de comptes enfants Matrix) **sans GO
famille** explicite, et sans coffre/2FA posé pour eux (C-FM1).
Ces 4 interdictions sont **contrôlables** : interdiction 1 = vocabulaire du dossier (aucune ligne
ne dit que Matrix remplace WhatsApp) ; interdiction 2 = le canal d'alerte est Matrix (3
notifications types mesurées dans le salon ops) ; interdiction 3 = 0 flux n8n vers WhatsApp
(inventaire n8n) ; interdiction 4 = 0 compte ado Matrix (Synapse mesuré : 4 comptes, aucun ado).
### 8.3 Le bot hermes ↔ Matrix ### 8.3 Le bot hermes ↔ Matrix
> **hermes** (Effi, sur PC IA) est **connecté à Matrix** : la famille peut **parler à Effi** dans un salon Matrix. C'est l'**assistant de famille** (projet second cerveau, phase 3). > **hermes** (Effi, sur PC IA) est **connecté à Matrix** : la famille peut **parler à Effi** dans un salon Matrix. C'est l'**assistant de famille** (projet second cerveau, phase 3).
@@ -744,7 +767,7 @@ services:
| # | Action logicielle | Dépendance | Critère de succès | | # | Action logicielle | Dépendance | Critère de succès |
|---|-------------------|------------|-------------------| |---|-------------------|------------|-------------------|
| C.1 | **Matrix + Jitsi + Element** (remplace WhatsApp) | Phase B | Famille messagerie + visio en local | | C.1 | **Matrix + Jitsi + Element** (complément du foyer, **pas** un remplacement de WhatsApp) | Phase B | Famille messagerie + visio en local |
| C.2 | **Home Assistant** (enceintes Google) + **Frigate** (caméras) | Phase B | Assistant vocal + vision caméras | | C.2 | **Home Assistant** (enceintes Google) + **Frigate** (caméras) | Phase B | Assistant vocal + vision caméras |
| C.3 | **Bot hermes ↔ Matrix** (Effi famille) | Phase B + 2ᵉ cerveau | Effi répond dans Matrix | | C.3 | **Bot hermes ↔ Matrix** (Effi famille) | Phase B + 2ᵉ cerveau | Effi répond dans Matrix |
| C.4 | **Enfants** : AdGuard profils + Family Link | Phase B | Enfants protégés (DNS + parental) | | C.4 | **Enfants** : AdGuard profils + Family Link | Phase B | Enfants protégés (DNS + parental) |
+1 -1
View File
@@ -130,7 +130,7 @@ curl -s -X PUT 'http://192.168.1.24:8008/_synapse/admin/v2/users/@<PSEUDO>:192.1
| Salon | Contenu | | Salon | Contenu |
|-------|---------| |-------|---------|
| **Famille** | Messagerie quotidienne (remplace WhatsApp), visio Jitsi intégrée à Element | | **Famille** | Messagerie quotidienne du **foyer** (complément de WhatsApp, **pas un remplacement**), visio Jitsi intégrée à Element |
| **Ops** | Alertes machine : watchtower (MAJ images), restic (backup G9), n8n — via `ops-bot` | | **Ops** | Alertes machine : watchtower (MAJ images), restic (backup G9), n8n — via `ops-bot` |
| **hermes** (prévu) | Salon où la famille parle à Effi (assistant, Phase 3 du second cerveau) | | **hermes** (prévu) | Salon où la famille parle à Effi (assistant, Phase 3 du second cerveau) |
+25 -12
View File
@@ -67,22 +67,35 @@ la 2ᵉ/3ᵉ cible n'existe pas.
`restic snapshots` sur les 3 cibles = **1 sur 3**. Le critère reste donc **non satisfait**, et `restic snapshots` sur les 3 cibles = **1 sur 3**. Le critère reste donc **non satisfait**, et
c'est le seul point dur : il faut les identifiants de la 2ᵉ cible (SMB/rclone) pour la poser. c'est le seul point dur : il faut les identifiants de la 2ᵉ cible (SMB/rclone) pour la poser.
## 6. Cohérence applicative (à faire, je ne l'ai pas testé) ## 6. Cohérence applicative — **fait et mesuré le 12/10 18:30** (`scripts/18-db-dumps.sh`)
La copie se fait **conteneurs tournants** pour Nextcloud/MariaDB et Gitea/Postgres : le script La copie se fait **conteneurs tournants** pour Nextcloud/MariaDB et Gitea/Postgres : le script
ne stoppe que `valheim n8n synapse couchdb obsidian` (`RESTIC_STATEFUL`), pas les bases. ne stoppe que `valheim n8n synapse couchdb obsidian` (`RESTIC_STATEFUL`), pas les bases.
Une copie chaude de `datadir` Postgres/MariaDB n'est **pas garantie cohérente** — la copie est Une copie chaude de `datadir` Postgres/MariaDB n'est **pas garantie cohérente** — la copie est
complète en octets, pas nécessairement rejouable en l'état. complète en octets, pas nécessairement rejouable en l'état. **D'où les dumps logiques.**
Recommandation (lecture seule pour vérifier, écriture pour produire) : Ce qui a été produit (0 brique stoppée, `--single-transaction` = instantané InnoDB cohérent) :
```bash | Base | Commande réelle (mesurée) | Résultat |
# Gitea : dump logique versionné avec le dépôt |---|---|---|
docker exec gitea-db pg_dump -U gitea gitea | gzip > /data/docker/gitea/dumps/gitea.sql.gz | **Gitea** (Postgres) | `sudo bash -c "docker exec -u postgres gitea-db pg_dump -U gitea_folzo gitea_db \| gzip > /data/docker/gitea/dumps/gitea-<ts>.sql.gz"` | **80 543 o** gz, **116 `CREATE TABLE`** |
# Nextcloud : export cohérent via occ | **Nextcloud** (MariaDB) | `sudo bash -c "docker exec nextcloud_db sh -c 'mysqldump -u root -p\"\$MARIADB_ROOT_PASSWORD\" --single-transaction --routines nextcloud' \| gzip > /data/docker/nextcloud/dumps/nextcloud-<ts>.sql.gz"` | **5,9 Mo** gz, **317 objets**, finit par `-- Dump completed` |
docker exec -u www-data nextcloud php occ db export --type=structure > /data/docker/nextcloud/dumps/nc-structure.sql | **PhotoPrism** (MariaDB) | idem sur `photoprism` | **89,6 Mo** gz, **39 objets**, `-- Dump completed` |
docker exec -u www-data nextcloud php occ db export --type=data > /data/docker/nextcloud/dumps/nc-data.sql
```
Les dumps tombent sous `/data/docker/<brique>/dumps/` → **déjà dans le périmètre** de backup. Pièges mesurés, intégrés au script :
Rollback = supprimer les dumps, aucune brique touchée. - l'utilisateur OS `gitea_folzo` **n'existe pas** dans le conteneur → `pg_hba.conf` =
`local all all trust` → **`-u postgres`**, donc **aucun mot de passe sur une ligne de commande** ;
- le `>` de redirection doit être **dans** `sudo bash -c "…"` (sinon le pipe s'exécute côté shell
non-root et le fichier n'est pas écrit) ;
- le mot de passe MariaDB est lu **dans l'environnement du conteneur**, jamais affiché.
**Rejouabilité prouvée pour Gitea** : base de test `gtest_restore` créée, dump chargé,
**116 tables restaurées** (== 116 du dump), base **supprimée** après (`pg_database` vérifié :
`gitea_db`, `postgres`, `template0`, `template1`).
**Non fait, à décider** : la replay complète des dumps MariaDB. Le dump Nextcloud contient
`USE \`nextcloud\`` → le charger tel quel **écraserait la production** ; il faut un serveur jeton
(conteneur MariaDB temporaire, GO séparé).
Les dumps tombent sous `/data/docker/<brique>/dumps/` → **déjà dans le périmètre** de backup
(visibles au snapshot suivant, 03:30). Rollback = supprimer les dumps, aucune brique touchée.
+30 -2
View File
@@ -28,7 +28,7 @@
| Risque | Détail | Conséquence pour nous | | Risque | Détail | Conséquence pour nous |
|---|---|---| |---|---|---|
| **Bannissement du numéro** | WhatsApp détecte les clients non officiels ; le risque est **réel et non nul** | ton numéro familial est « indispensable » → **interdiction** : jamais le numéro principal sur un pont | | **Bannissement du numéro** | WhatsApp détecte les clients non officiels par **deux voies cumulées** : (a) **signalements par les utilisateurs** en face (un correspondant peut signaler le compte comme « spam/client non autorisé »), (b) **détection automatisée côté serveur** (comportement du client multi-device, empreinte du protocole `whatsmeow`, cadence d'envoi, absence d'app officielle). **Aucun seuil public, aucune liste publiée, aucun moyen de vérifier son exposition avant la sanction.** Échelle observée et documentée par la communauté : **ban temporaire** (de quelques heures à ~24 h, avec compte à rebours dans l'app) → **ban permanent** à la récidive → **suppression du compte** = **perte de l'historique WhatsApp du numéro** (non récupérable, pas de backup côté WhatsApp). Probabilité : **non chiffrable** (paramètre serveur opaque) ; **coût si ça tombe** : **élevé et non réparable** (numéro familial = famille, amis, travail). | ton numéro familial est « indispensable » → **interdiction** : jamais le numéro principal sur un pont ; le pilote ne se fait que sur un **numéro secondaire jetable** |
| Session qui expire | > 12 jours sans connexion du téléphone = déconnexion | re-link manuel = maintenance que tu veux éliminer | | Session qui expire | > 12 jours sans connexion du téléphone = déconnexion | re-link manuel = maintenance que tu veux éliminer |
| Stateful fragile | session + DB + médias ; montée de version du protocole WhatsApp non maîtrisée | épingler par digest, **exclure de Watchtower**, backup session+DB dans restic | | Stateful fragile | session + DB + médias ; montée de version du protocole WhatsApp non maîtrisée | épingler par digest, **exclure de Watchtower**, backup session+DB dans restic |
| Confidentialité | les MP passent par un daemon sur le G9 + stockage local | à accepter explicitement (choix de périmètre) | | Confidentialité | les MP passent par un daemon sur le G9 + stockage local | à accepter explicitement (choix de périmètre) |
@@ -53,7 +53,35 @@
| 6 | rollback documenté : `docker compose -f compose/whatsapp/docker-compose.yml down` (aucune donnée famille concernée) | | 6 | rollback documenté : `docker compose -f compose/whatsapp/docker-compose.yml down` (aucune donnée famille concernée) |
| 7 | journal + grille S14 (décision, raison, coût constaté) | | 7 | journal + grille S14 (décision, raison, coût constaté) |
## 6. Verdict de l'étude ## 6. Les 17 questions de Go/No-Go (à répondre **avant** toute installation)
Règle de décision : **17/17 = GO pilote**. Une seule réponse « non » = **NO-GO**, et le document
reste en étude. Chaque « oui » exige la **preuve** indiquée (pas d'intention, une preuve).
| # | Question | Réponse pour GO | Preuve exigée |
|---|---|---|---|
| 1 | Le numéro relié est-il **secondaire et jetable** ? | oui, jamais le familial | numéro + support (eSIM/SIM dédiée) notés dans `journal.md` |
| 2 | Ce numéro est-il **vide** (aucun contact famille, aucun historique) ? | oui | capture/comptage : 0 conversation avant le link |
| 3 | Le **bannissement définitif** de ce numéro est-il accepté à l'avance ? | oui, écrit | ligne de décision signée dans `journal.md` |
| 4 | Ai-je un **moyen de prévenir** les correspondants si le compte disparaît ? | oui | canal de secours nommé (Matrix/ops) |
| 5 | Le pont lit-il **uniquement mes** conversations ? | oui | `auth_users`/config du pont, 0 autre compte |
| 6 | Les messages copiés sont-ils **chiffrés au repos** (ou en périmètre chiffré) ? | oui | `restic` (chiffrement) + permissions 600 sur `/data/docker/whatsapp/` |
| 7 | La **DB du pont** est-elle dans les snapshots restic ? | oui | comptage d'entrées dans le snapshot latest |
| 8 | La **session** (clés de liaison) est-elle backupée **et restaurée testée** ? | oui | restauration en test → re-link **non** nécessaire |
| 9 | L'image est-elle **épinglée par digest** ? | oui | `docker inspect` → `ImageID`, pas un tag `:latest` |
| 10 | Le pont est-il **exclu de Watchtower** ? | oui | label `watchtower.enable=false` + Watchtower `scanned` ne l'inclut pas |
| 11 | Le pont est-il **hors WAN** ? | oui | `ufw status numbered` : 0 règle pour son port ; 0 entrée Caddy |
| 12 | Y a-t-il **0 automatisation d'envoi** (n8n, IA, scripts) ? | oui | inventaire n8n : 0 flux WhatsApp |
| 13 | Les **alertes ops** restent-elles sur Matrix ? | oui | 3 notifications types dans le salon ops, 0 sur WhatsApp |
| 14 | La **montée de version du protocole** WhatsApp est-elle gérée ? | oui | procédure de re-link écrite dans `RUNBOOKS/` |
| 15 | Le **re-link** est-il testé et chronométré ? | oui | test déconnexion volontaire → QR → reprise, **< 5 min** |
| 16 | Le **coût de maintenance** constaté est-il ≤ 10 min/semaine ? | oui | 7 jours journalisés, temps passé relevé |
| 17 | Le **verdict écrit** existe-t-il (GO/NO-GO, raison, coût) ? | oui | ligne dans `journal.md` + grille `DOSSIER-SOFTWARE.md` S14.5 |
**Ce que ces 17 questions ne couvrent pas** : la probabilité de bannissement reste **non chiffrable**
(§3). Le pilote ne la réduit pas, il la **déplace** sur un numéro qui n'a pas de valeur.
## 7. Verdict de l'étude
- **Utile** si l'objectif est « lire au même endroit » (Element = foyer + alertes) et que le pilote - **Utile** si l'objectif est « lire au même endroit » (Element = foyer + alertes) et que le pilote
reste sur un **numéro secondaire**. reste sur un **numéro secondaire**.
+15
View File
@@ -103,3 +103,18 @@ numéro le plus haut au plus bas, `--dry` exécuté avant l'exécution réelle).
**redirects de la Freebox** (IP publique unique `82.65.198.114`, G9 et pilot derrière le même **redirects de la Freebox** (IP publique unique `82.65.198.114`, G9 et pilot derrière le même
NAT). L'inventaire des redirects se fait côté Freebox, par toi — c'est le seul trou de NAT). L'inventaire des redirects se fait côté Freebox, par toi — c'est le seul trou de
visibilité qui subsiste. visibilité qui subsiste.
## 7. Choix de machine pour l'IdP (Authelia) — **décision rendue le 12/10, installation reportée**
| Point retenu | **J5040 (.27)** | Écart avec le G9 (.24) |
|---|---|---|
| Exposition | c'est **déjà** le seul edge public (`80/443/51820`), donc Authelia hérite d'une exposition **existante et documentée** | le G9 n'a **plus aucune règle `Anywhere`** depuis le durcissement 18:10 → l'y exposer demanderait de **rouvrir** un trou refermé |
| Survie | le hub de monitoring est **déjà** posé dessus (C-AP5) : l'IdP ne meurt pas avec le G9 | l'IdP sur le G9 tombe avec lui (même défaut que les sondes) |
| Coût | 1 conteneur de plus sur une machine à 3 conteneurs, base SQLite dans `/home/folzo/` (déjà sauvegardée sur .27 ? **non** → à ajouter au périmètre, consigné) | base sur la machine qui porte déjà les 3 BDD les plus lourdes |
| Risque | l'IdP devient **aussi exposé que Caddy** : surface = 1 hôte, 1 port (443), 0 exposition directe du port 9000 (reverse proxy Caddy, LAN-only en amont) | exposition directe depuis LAN uniquement, mais single point of failure du foyer |
| Décision | **J5040**, en OIDC **opt-in** brique par brique (Synapse → Gitea → Jellyfin), login local **conservé** partout (C-IDP2) | — |
**Déclencheur d'installation (écrit, pas implicite)** : après **Phase 5** (copie 3-2-1 sur 3 cibles)
et **après** la mise en 2FA des comptes admin existants (C-MX3, C-ID7). Ni avant, ni à froid.
Rollback = retirer le bloc OIDC du Caddyfile du J5040 + retirer l'OIDC de la brique (login local
déjà en place, donc 0 dépendance dure).
+42 -12
View File
@@ -7,10 +7,11 @@
## 1. Détenteurs (à nommer par l'utilisateur) ## 1. Détenteurs (à nommer par l'utilisateur)
| Rôle | Personne | Support remis | Date de remise | Test annuel | | Rôle | Personne | Support remis | Lieu (voir §2.1) | Code de déverrouillage | Date de remise | Test annuel |
|---|---|---|---|---| |---|---|---|---|---|---|---|
| Détenteur 1 | _à nommer_ | enveloppe scellée n°1 | _à date_ | _à date_ | | Moi | — | enveloppe n°1 | **sur place** (coffre du domicile) | le mien | — | _à date_ |
| Détenteur 2 (optionnel) | _à nommer_ | enveloppe scellée n°2 | _à date_ | _à date_ | | Détenteur 1 | _à nommer_ | enveloppe n°2 | **hors site** (chez lui) | **le sien, propre, jamais communiqué** | _à date_ | _à date_ |
| Détenteur 2 (optionnel) | _à nommer_ | enveloppe n°3 | **hors ligne** (scellé, jamais connecté) | **le sien, propre, jamais communiqué** | _à date_ | _à date_ |
Règles de sélection (à assumer) : personne joignable physiquement, indépendante de l'infra, Règles de sélection (à assumer) : personne joignable physiquement, indépendante de l'infra,
capable d'appliquer une procédure écrite sous stress, **sans accès** aux machines. capable d'appliquer une procédure écrite sous stress, **sans accès** aux machines.
@@ -25,8 +26,29 @@ capable d'appliquer une procédure écrite sous stress, **sans accès** aux mach
- une **note de datation** : à chaque changement de mot de passe maître, l'enveloppe est - une **note de datation** : à chaque changement de mot de passe maître, l'enveloppe est
remplacée et l'ancienne est détruite (journaliser la date). remplacée et l'ancienne est détruite (journaliser la date).
Support : papier (pas de clé USB non chiffrée), plié, scellé, signé de la date. Deux copies Support : papier (pas de clé USB non chiffrée), plié, scellé, signé de la date. Jamais de photo,
max. Jamais de photo, jamais de fichier non chiffré sur un disque. jamais de fichier non chiffré sur un disque.
### 2.1 Placement des supports (3 exemplaires, 2 maximum pour des tiers)
| Exemplaire | Lieu | Détenteur | Rôle |
|---|---|---|---|
| n°1 — **sur place** | coffre physique du domicile (scellé, daté) | moi | usage normal, jamais sorti sans journal |
| n°2 — **hors site** | chez le détenteur 1 (scellé, daté) | détenteur 1 | usage si je suis injoignable |
| n°3 — **hors ligne** | chez le détenteur 2 **ou** coffret scellé hors réseau | détenteur 2 (optionnel) | secours si n°1 et n°2 sont perdus ; **jamais connecté, jamais numérisé** |
**Aucun 4ᵉ exemplaire** : 2 personnes max détiennent un support (détenteurs 1 et 2).
### 2.2 Garde-fous (les 4, applicables dès la remise)
1. **2 personnes maximum** détiennent un support ; la famille immédiate n'en détient pas.
2. La **personne de confiance** (celle qui accompagne l'ouverture) est **lectrice seule** : elle
assiste, date, contresigne ; **elle ne recopie, ne photographie et ne conserve rien**.
3. **1 code de déverrouillage par personne** : l'enveloppe est fermée par un **code propre à son
détenteur** (code de son choix, jamais transmis, jamais écrit sur l'enveloppe). En cas de perte
du détenteur, son code devient nul et l'enveloppe est remplacée (§5).
4. **Jamais par mail, jamais par cloud, jamais par messagerie** : la remise est **physique**, en
main propre, signée. Aucun scan, aucun envoi, aucune photo, aucun stockage hébergé.
## 3. Ce que fait le détenteur si je suis injoignable ## 3. Ce que fait le détenteur si je suis injoignable
@@ -43,13 +65,21 @@ max. Jamais de photo, jamais de fichier non chiffré sur un disque.
## 4. Test annuel (critère) ## 4. Test annuel (critère)
Une fois par an, sans casser la production : Une fois par an, sans casser la production, sur un **coffre d'entraînement** (vault de test, jamais
le coffre famille) : **6 points à vérifier**, chacun noté OK/KO.
- le détenteur ouvre l'enveloppe (ou une copie d'entraînement identique) ; | # | Point vérifié | Preuve |
- il déverrouille un **coffre d'entraînement** (vault de test, pas le coffre famille) avec les |---|---|---|
éléments de l'enveloppe ; | 1 | L'enveloppe est **intacte, scellée, datée** et le **code propre au détenteur** l'ouvre | photo du sceau intact + date portée au journal |
- la procédure est chronométrée et les points de friction sont corrigés dans ce fichier ; | 2 | Le **mot de passe maître** déverrouille le coffre d'entraînement | login réussi sur le vault de test |
- la ligne est journalisée : `| date | partage-secret-maitre : test annuel | résultat | remplacement enveloppe |`. | 3 | L'**`ADMIN_TOKEN`** ouvre la console `/admin` du coffre d'entraînement | console accessible, 2FA du compte de test désactivable |
| 4 | La **clé de chiffrement restic** lit un snapshot de test | `restic snapshots` / `restic cat` en **lecture seule** sur un repo de test |
| 5 | La **phrase de récupération E2E** déverrouille une session Matrix de test | salon de test lisible depuis un appareil vierge |
| 6 | La **liste des 6 personnes et des briques** est à jour | comparaison avec `REGISTRE-IDENTITES.md` §2/§3, 0 écart non consigné |
- la procédure est **chronométrée** et les points de friction sont corrigés dans ce fichier ;
- la ligne est journalisée : `| date | partage-secret-maitre : test annuel | 6 points (x/6) | remplacement enveloppe |` ;
- si un point est KO : l'enveloppe est **remplacée** et la rotation est envisagée (§5).
## 5. Révocabilité ## 5. Révocabilité
+67 -18
View File
@@ -44,33 +44,82 @@ Les 37 `errors` sont à regarder avant le GO famille (aucune action prise).
| Nom | Résolution | Route Caddy | Mesure | | Nom | Résolution | Route Caddy | Mesure |
|---|---|---|---| |---|---|---|---|
| `prisme.folzocorp.fr` | OK → `82.65.198.114` | **aucune** (J5040) | **000** | | `prisme.folzocorp.fr` | OK → `82.65.198.114` | **posée le 12/10 17:15** : `reverse_proxy 192.168.1.24:2342` (bloc ajouté au-dessus de `photos.`, sauvegarde `Caddyfile.bak-20261012-1643`, `caddy validate` + `reload`) | **200** (Let's Encrypt ACME DNS-01, `curl` **sans** `-k`) |
| `photos.folzocorp.fr` | OK → `82.65.198.114` | `reverse_proxy 192.168.1.24:2342` = **PhotoPrism** | 307 (→ `/login`) | | `photos.folzocorp.fr` | OK → `82.65.198.114` | `reverse_proxy 192.168.1.24:2342` = **PhotoPrism** | 307 (→ `/login`) |
| Synology Photos `.25` | — | — | **5000 et 5001 = 000** (445 et 222 ouverts, ping OK) | | Synology Photos `.25` | — | — | **5000 et 5001 = 000** (445 et 222 ouverts, ping OK) |
**Deux écarts** vis-à-vis de la décision de nommage : **Écart restant** (mesuré 12/10 17:15) : `photos.folzocorp.fr` pointe toujours sur **PhotoPrism**
au lieu du NAS — le repointage vers `.25` n'a **pas été autorisé** à ce stade, et il n'est de
1. `photos.folzocorp.fr` pointe sur **PhotoPrism**, pas sur le NAS. toute façon **pas mesurable** puisque le web du DSM (5000/5001) ne répond pas. Le double-run
2. `prisme.folzocorp.fr` n'a **aucune route** → PhotoPrism n'est pas accessible par son nom. « les deux accessibles simultanément » reste donc **prouvable à 1 nom sur 2** : `prisme.` ✓,
`photos.` ✗.
La correction est une modification du `Caddyfile` du **J5040**
(`/home/folzo/g9-migration/compose/edge-j5040/Caddyfile`, monté dans le conteneur `caddy`) :
**action L3 sur une brique réseau → GO explicite requis (G11, G6)**. Rollback = fichier
sauvegardé + `caddy reload`.
Le second point est **bloqué par l'état du NAS** : le service web du DSM (5000/5001) ne répond
pas, donc `photos.` vers `.25` n'est pas mesurable aujourd'hui. Le double-run « les deux
accessibles simultanément » ne peut pas être prouvé tant que le DSM n'expose pas son port web.
## 5. Ce qui est fait, ce qui reste ## 5. Ce qui est fait, ce qui reste
**Fait, mesuré** : copie (noms de fichiers identiques), restauration sha256, RAM, indexation **Fait, mesuré** : copie (noms de fichiers identiques), restauration sha256, RAM, indexation
engagée, `n8n /healthz` = 200, endpoints mesurés, nommage décidé. engagée, `n8n /healthz` = 200, endpoints mesurés, nommage décidé, **`prisme.` = 200**, grille des
10 requêtes (§6) et procédure d'auto-upload (§7) **écrites et prêtes**.
**Reste** : **Reste** :
- poser `prisme.` → `192.168.1.24:2342` et `photos.` → `192.168.1.25:5000` sur le J5040 (GO) ;
- rendre le web du DSM joignable (ou actater que `photos.` meurt en Phase 6 sans mesure) ; - rendre le web du DSM joignable (ou actater que `photos.` meurt en Phase 6 sans mesure) ;
- finir l'indexation et traiter les 37 erreurs ; - finir l'indexation et traiter les 37 erreurs ;
- montée en compétence sur **mon** téléphone (auto-upload, recherche, album partagé, partage - **exécution** de la grille §6 et de la procédure §7 sur **mon** téléphone (usage réel, 7 jours) ;
par lien, doublon, export original, EXIF) + 10 requêtes comparées + 7 jours sans intervention ;
- GO famille = validation explicite (comptes enfants et auto-upload ados **après**). - GO famille = validation explicite (comptes enfants et auto-upload ados **après**).
## 6. Grille des 10 requêtes réelles (Synology Photos vs PhotoPrism)
Protocole : la même requête est exécutée **l'une après l'autre** sur les deux services, depuis le
même téléphone, en Wi-Fi, sur le **même lot de photos**. Chronométrer du premier geste au résultat
affiché. Noter **1 gagnant par ligne**. Le classement final = nombre de victoires, plus la note
subjective « est-ce que j'ai compris sans aide ». Les cases **Gagnant / Temps / Compris sans aide**
sont à remplir par l'usage réel (personne qui remplit = moi, sur mon téléphone uniquement).
| # | Requête réelle, telle que je la formule | Chemin Synology Photos | Chemin PhotoPrism | Gagnant | Temps (s) | Compris sans aide |
|---|---|---|---|---|---|---|
| 1 | « les photos du séjour 2019 » | Chronologie → filtre année/lieu | Recherche texte libre `2019` + filtre date | | | |
| 2 | « les photos de carla il y a plus de 10 ans » | Albums → visage non disponible | Filtre **Faces** + plage de dates | | | |
| 3 | « la facture d'électricité scannée » | Recherche texte (DS 7.x, selon OCR indexé) | Recherche plein texte sur l'OCR (`label`/`caption`) | | | |
| 4 | « ce que j'ai pris depuis mon téléphone ce mois-ci » | Dossier / appareil | Filtre **camera / appareil** + date | | | |
| 5 | « les doublons de cette photo » | Doublons intégrés (Synology Photos) | `photoprism` : recherche par **similaire** depuis une photo | | | |
| 6 | « envoyer 12 photos à Audrey sans zip » | Partage Synology (lien/album) | Album partagé + **partage par lien** | | | |
| 7 | « créer un album vacances et le partager à la famille » | Album partagé DSM | Album + partage vers un compte famille | | | |
| 8 | « partager un lien vers une photo, limité dans le temps » | Lien de partage (expirable) | Lien de partage (vérifier expiration et ce qui fuit) | | | |
| 9 | « l'EXIF et l'appareil de cette photo » | Panneau d'infos | Panneau d'infos (mesuré : faces 37, dossiers 392 indexés) | | | |
| 10 | « l'original complet, pas la version web » | Téléchargement original | Téléchargement original (`/originals/…`) | | | |
**Classement attendu** : score par brique (x/10) + 3 lignes de conclusion (ce que PhotoPrism fait
mieux, ce que Synology Photos fait mieux, ce qui me manque des deux). La grille remplit le critère
C-PH2 « 10 requêtes réelles comparées et classées » **à la condition d'être remplie à la main**,
pas d'exister.
## 7. Auto-upload — procédure (mon téléphone, 7 jours sans intervention)
Prérequis mesurés : `https://prisme.folzocorp.fr/` = **200** (certificat valide, pas d'auto-signé),
compte PhotoPrism existant (`admin` mesuré en base `auth_users`), indexation en cours (9,1 %).
1. **App** : client d'upload **officiel PhotoPrism** (Android « PhotoPrism Companion », iOS
l'équivalent App Store). **Pas** de script d'upload sur le téléphone (G11 : pas de code non
documenté qui écrit sur une brique).
2. **Serveur** : `https://prisme.folzocorp.fr` (jamais l'IP, jamais http).
3. **Compte** : mon compte PhotoPrism (pas l'admin si un compte dédié existe ; sinon admin, et le
mot de passe est **changé** après coup, consigné `famille/effi/photoprism`).
4. **Périmètre d'upload** : `DCIM` + `Screenshots` **uniquement**, en **Wi-Fi seulement**
(données mobiles : upload désactivé), **sans** suppression des photos locales après upload.
5. **Anti-effacement** : l'app est réglée en **copie**, jamais en « déplacer ». Vérifier explicitement
l'option de suppression après upload et la laisser **désactivée**.
6. **Préserver les EXIF** : pas de « compression », pas de « qualité 75 % », pas de retrait de
localisation. Contrôle : sur 3 photos uploadées, lire l'appareil + la date + la position dans le
panneau d'infos (requête 9 de la grille §6).
7. **Moyens de contrôle** (à faire 1 fois/jour, 10 secondes) : dans PhotoPrism, `Nouveautés` du jour
> 0 ; dans la base, `select count(*) from files` qui **monte** ; 0 erreur nouvelle dans `errors`.
8. **Seuil de réussite des 7 jours** : 7 lignes journalisées, **0 intervention manuelle** (pas de
relance d'upload, pas de re-login), **0 photo perdue** (comptage local avant/après, `ls | wc -l`),
et les EXIF conservés sur les 3 photos de contrôle.
9. **Format de journalisation** (une ligne par jour dans `journal.md`) :
`| date | auto-upload photoprism (C-PH2) | photos uploadées: N | interventions: 0 | fichiers locaux: X (identique) | EXIF: OK |`
10. **Rollback** : désactiver l'upload dans l'app (1 geste), puis, si besoin, supprimer les photos
uploadées du lot test via l'UI (jamais par `rm` sur `/data/docker/photoprism/originals`, qui
casserait l'index).
**Ce que l'IA ne fait pas ici** : installer une app sur un téléphone, saisir un mot de passe,
valider un partage, déclarer une photo « bien uploadée ». Ces 4 gestes sont humains (G11).
+6
View File
@@ -141,6 +141,12 @@ sudo ./scripts/12-probes.sh --uninstall # rollback complet (cron + script + ét
./scripts/27-synapse-family-users.sh --dry # plan, 0 ecriture ./scripts/27-synapse-family-users.sh --dry # plan, 0 ecriture
CONFIRM=CREER ./scripts/27-synapse-family-users.sh # 6 comptes + 600 mots de passe CONFIRM=CREER ./scripts/27-synapse-family-users.sh # 6 comptes + 600 mots de passe
./scripts/27-synapse-family-users.sh --revoke # rollback = desactivation ./scripts/27-synapse-family-users.sh --revoke # rollback = desactivation
# G9 — salons Matrix (0 compte cree, 0 message envoye)
./scripts/28-synapse-create-room.sh --list # salons + resolution d'alias (ro)
./scripts/28-synapse-create-room.sh --dry # plan, 0 ecriture
CONFIRM=CREER ./scripts/28-synapse-create-room.sh --create # cree #famille
CONFIRM=PURGER ./scripts/28-synapse-create-room.sh --purge <room_id> # rollback
``` ```
## Ordre d'exécution (phase par phase) ## Ordre d'exécution (phase par phase)
+5 -3
View File
@@ -18,6 +18,7 @@
| Infra / services | `infra/<brique>/<compte>` | `infra/gitea/g9ops`, `infra/n8n/api`, `infra/synapse/admin` | | Infra / services | `infra/<brique>/<compte>` | `infra/gitea/g9ops`, `infra/n8n/api`, `infra/synapse/admin` |
| Adultes | `famille/<pseudo>/<brique>` | `famille/effi/nextcloud`, `famille/effi/matrix` | | Adultes | `famille/<pseudo>/<brique>` | `famille/effi/nextcloud`, `famille/effi/matrix` |
| Enfants | `enfants/<pseudo>/<brique>` (coffre des parents) | `enfants/lea/jellyfin` | | Enfants | `enfants/<pseudo>/<brique>` (coffre des parents) | `enfants/lea/jellyfin` |
| **Compte de secours (break-glass)** | **`infra/<brique>/break-glass`** — 1 entrée par brique, consignée à la création du compte | `infra/gitea/break-glass`, `infra/synapse/break-glass`, `infra/jellyfin/break-glass`, `infra/n8n/break-glass`, `infra/nextcloud/break-glass` |
Structure du coffre (décision 12/10, coffre **unique** Vaultwarden pour les 6) : Structure du coffre (décision 12/10, coffre **unique** Vaultwarden pour les 6) :
- organisation `Famille` = 2 adultes (secrets partagés : wifi, DAV, comptes communs) ; - organisation `Famille` = 2 adultes (secrets partagés : wifi, DAV, comptes communs) ;
@@ -40,8 +41,9 @@ Comptes d'automatisation : préfixe de rôle, jamais un prénom humain → `ops-
| **Gitea** | `master`, `antoine`, `folzo` (importés, **`must_change_password`**), `g9ops` (admin, token API `ops-phase4`) | humains + service | à activer sur le compte principal | `infra/gitea/g9ops`, `infra/gitea/token-ops` | `docker exec -u git gitea /usr/local/bin/gitea admin user create\|change-password …` | moi | | **Gitea** | `master`, `antoine`, `folzo` (importés, **`must_change_password`**), `g9ops` (admin, token API `ops-phase4`) | humains + service | à activer sur le compte principal | `infra/gitea/g9ops`, `infra/gitea/token-ops` | `docker exec -u git gitea /usr/local/bin/gitea admin user create\|change-password …` | moi |
| **Postgres** (`gitea-db`) | utilisateur applicatif | service | — | `infra/gitea/db` | `docker exec -it gitea-db psql -U postgres` | moi | | **Postgres** (`gitea-db`) | utilisateur applicatif | service | — | `infra/gitea/db` | `docker exec -it gitea-db psql -U postgres` | moi |
| **Jellyfin** | `g9ops` (admin, créé par le wizard) | service/opérateur | — | `infra/jellyfin/g9ops` | API avec `X-Emby-Authorization` ; si `EnableLocalPassword=0` (piège mesuré du wizard) : `UPDATE Users SET EnableLocalPassword=1` en SQLite + restart | moi | | **Jellyfin** | `g9ops` (admin, créé par le wizard) | service/opérateur | — | `infra/jellyfin/g9ops` | API avec `X-Emby-Authorization` ; si `EnableLocalPassword=0` (piège mesuré du wizard) : `UPDATE Users SET EnableLocalPassword=1` en SQLite + restart | moi |
| **n8n** | **owner = mon compte** (accès perdu puis recréé, journal 10/10 21:12) | admin humain | à activer | `infra/n8n/owner`, `infra/n8n/api` (clé API dédiée) | UI → Settings → Users ; si accès perdu : créer un owner via CLI n8n (`n8n export:users` n'existe pas → passer par la DB Postgres, procédure §4) | moi | | **PhotoPrism** | **3 comptes mesurés** (base `photoprism.auth_users`, 12/10) : `admin` (rôle admin), `visitor` (rôle viewer, compte de démonstration), **1 compte sans nom** (rôle viewer) → les 2 non-admin sont **à nommer ou supprimer** | humains + démonstration | PhotoPrism n'a **pas de TOTP natif** → 2FA attendu par SSO (C-IDP1) | `famille/<pseudo>/photoprism` (adultes), `enfants/<pseudo>/photoprism` (ados, après GO famille) | CLI mesurée : `docker exec photoprism photoprism users ls\|show\|mod\|add\|rm` ; login KO après upgrade = `photoprism auth reset --yes` (**supprime tokens + app passwords**, à journaliser) | moi |
| **Synapse (Matrix)** | admin (créé via `registration_shared_secret`) + `ops-bot` (token) | admin + service | **TOTP à activer** | `infra/synapse/admin`, `infra/synapse/token-ops-bot` | API admin v2 (`PUT /_synapse/admin/v2/users/<uid>`), token admin via login ; déactivation propre = `POST /_synapse/admin/v1/deactivate/<uid>` | moi | | **n8n** | **owner = `effi@openfox.local`** (le tien, accès perdu puis recréé, journal 10/10 21:12 ; **mesuré 12/10** : 1 compte, `disabled = 0`) | admin humain | à activer | `infra/n8n/owner`, `infra/n8n/api` (clé API dédiée) | UI → Settings → Users ; si accès perdu : créer un owner via CLI n8n (`n8n export:users` n'existe pas → passer par la DB Postgres, procédure §4) | moi |
| **Synapse (Matrix)** | **3 comptes mesurés 12/10** : `@folzo` (**admin**, créé 12/10, login vérifié), `@ops`, `@ops-bot` (token d'alerte) | admin + service | **TOTP à activer sur `@folzo`** | `infra/synapse/admin`, `infra/synapse/token-ops-bot` | API admin v2 par **PUT** (`PUT /_synapse/admin/v2/users/<uid>` ; POST = `Unrecognized request`), token via login ; compte inutilisable (hash NULL) = `scripts/26-synapse-admin-repair.sh` ; déactivation propre = `PUT … {"deactivated": true}` | moi |
| **Element (web)** | — (frontal, pas de compte) | — | — | — | — | moi | | **Element (web)** | — (frontal, pas de compte) | — | — | — | — | moi |
| **CouchDB / Obsidian** | locaux (stack du second cerveau) | service | — | `infra/couchdb/admin` | `docker exec` + config du compose | moi | | **CouchDB / Obsidian** | locaux (stack du second cerveau) | service | — | `infra/couchdb/admin` | `docker exec` + config du compose | moi |
| **Valheim** | aucun compte (serveur jeu, whitelist par steamid) | — | — | — | `player.list` dans `data/save` | moi | | **Valheim** | aucun compte (serveur jeu, whitelist par steamid) | — | — | — | `player.list` dans `data/save` | moi |
@@ -122,7 +124,7 @@ créé, aucun privilège changé, aucune valeur secrète affichée), **sauf** l'
| **Nextcloud** | 8 comptes | groupes : `admin` = {Effi, Master} ; `Famille Folzo` = {Audrey, Folzo, carla, ewan, lola, noah} ; `guest_app` | 2FA **available** (`twofactor_totp`, `twofactor_backupcodes`, `twofactor_nextcloud_notification` installés) — activation par utilisateur non mesurée (nécessite la session de l'utilisateur) | | **Nextcloud** | 8 comptes | groupes : `admin` = {Effi, Master} ; `Famille Folzo` = {Audrey, Folzo, carla, ewan, lola, noah} ; `guest_app` | 2FA **available** (`twofactor_totp`, `twofactor_backupcodes`, `twofactor_nextcloud_notification` installés) — activation par utilisateur non mesurée (nécessite la session de l'utilisateur) |
| **Synapse** | mesuré 18:40 : **2 comptes** (`@ops`, `@ops-bot`), `admin` = **0** → **re-mesuré 19:00 : 4 comptes, 1 admin utilisable** (`@folzo`), 1 désactivé | `@folzo` : `admin` = 1, `locked` = 0, `deactivated` = 0, hash bcrypt présent, **login = OK**, **API admin = OK** (`GET /_synapse/admin/v2/users` = 3 utilisateurs) | Trou **résolu** (GO utilisateur 12/10, `scripts/26-synapse-admin-repair.sh`) : la brique a désormais un admin → C-MX1 outillé (`scripts/27-synapse-family-users.sh`), C-MX3 débloqué (2FA à activer sur `@folzo`) | | **Synapse** | mesuré 18:40 : **2 comptes** (`@ops`, `@ops-bot`), `admin` = **0** → **re-mesuré 19:00 : 4 comptes, 1 admin utilisable** (`@folzo`), 1 désactivé | `@folzo` : `admin` = 1, `locked` = 0, `deactivated` = 0, hash bcrypt présent, **login = OK**, **API admin = OK** (`GET /_synapse/admin/v2/users` = 3 utilisateurs) | Trou **résolu** (GO utilisateur 12/10, `scripts/26-synapse-admin-repair.sh`) : la brique a désormais un admin → C-MX1 outillé (`scripts/27-synapse-family-users.sh`), C-MX3 débloqué (2FA à activer sur `@folzo`) |
| **n8n** | **1 compte** : `effi@openfox.local` (`disabled = 0`) | basic auth déclarée (`N8N_BASIC_AUTH_USER/PASSWORD`) dans `compose/g9-queue` | owner = toi ✓ ; 2FA et clé API = à poser (C-ID9) | | **n8n** | **1 compte** : `effi@openfox.local` (`disabled = 0`) | basic auth déclarée (`N8N_BASIC_AUTH_USER/PASSWORD`) dans `compose/g9-queue` | owner = toi ✓ ; 2FA et clé API = à poser (C-ID9) |
| **PhotoPrism** | 3 comptes (`auth_users`) | — | les 3 sont à nommer dans la moitié B | | **PhotoPrism** | **3 comptes** (`photoprism.auth_users`) : `admin` (rôle admin), `visitor` (rôle viewer), **1 compte sans nom** (rôle viewer) | pas de TOTP natif | **nommés en §2** (ligne PhotoPrism) ; reste humain : nommer ou supprimer les 2 comptes non-admin |
| **Vaultwarden** | — | `ADMIN_TOKEN_FILE` (jeton **dans un fichier**, pas en variable d'env en clair) | bonne pratique déjà en place | | **Vaultwarden** | — | `ADMIN_TOKEN_FILE` (jeton **dans un fichier**, pas en variable d'env en clair) | bonne pratique déjà en place |
| **CouchDB** | — | `GET /_all_databases` = **401** sans identifiants | **auth active** (le « sans auth » du 12/10 était un faux positif de sonde sur `/`, public par conception) | | **CouchDB** | — | `GET /_all_databases` = **401** sans identifiants | **auth active** (le « sans auth » du 12/10 était un faux positif de sonde sur `/`, public par conception) |
+1 -1
View File
@@ -25,7 +25,7 @@ geste physique, usage humain (G11 : interdits permanents + L3).
|---|---|---|---| |---|---|---|---|
| **C-MX1** 6 comptes | **admin en place et mesuré (12/10 19:00)** → `bash scripts/27-synapse-family-users.sh --dry` puis `CONFIRM=CREER bash scripts/27-synapse-family-users.sh` (2 adultes + 4 enfants, `admin:false`, mots de passe en 600 → **recopier dans le coffre puis `rm`**) ; phrase de récupération E2E **sur papier** par appareil | toi | API liste les 6, `admin:false`, 0 secret en git | | **C-MX1** 6 comptes | **admin en place et mesuré (12/10 19:00)** → `bash scripts/27-synapse-family-users.sh --dry` puis `CONFIRM=CREER bash scripts/27-synapse-family-users.sh` (2 adultes + 4 enfants, `admin:false`, mots de passe en 600 → **recopier dans le coffre puis `rm`**) ; phrase de récupération E2E **sur papier** par appareil | toi | API liste les 6, `admin:false`, 0 secret en git |
| **C-MX3** 2FA admin Synapse | sur `@folzo` (login **mesuré OK**) : changer le mot de passe à la 1ʳᵉ connexion, activer **TOTP** + codes de récupération dans Element, les consigner `infra/matrix/break-glass` | toi | login admin exige l'OTP (mesuré) | | **C-MX3** 2FA admin Synapse | sur `@folzo` (login **mesuré OK**) : changer le mot de passe à la 1ʳᵉ connexion, activer **TOTP** + codes de récupération dans Element, les consigner `infra/matrix/break-glass` | toi | login admin exige l'OTP (mesuré) |
| **C-MX2** Element | installer Element sur **2 appareils adultes** seulement ; salon famille + salon ops | toi | session active sur les 2 + messages échangés | | **C-MX2** Element | salon famille **créé et mesuré** (`#famille:192.168.1.24` → `!9hTrGcLp…`, 12/10 19:25) → installer Element sur **2 appareils adultes**, s'y connecter, rejoindre `#famille` (invites `@ops`/`@ops-bot` en attente) ; salon ops déjà vivant | toi | session active sur les 2 + messages échangés dans `#famille` |
| **C-FM1** coffre unique + formation | Vaultwarden : `Famille` + `Enfants`, coffre propre 16/13 avec emergency access, 10/9 sans coffre, import Takeout, passkeys Android ; atelier 1 h (femme) + 30 min/ado | toi | **chacune le fait seule 7 jours après** (journalisé) | | **C-FM1** coffre unique + formation | Vaultwarden : `Famille` + `Enfants`, coffre propre 16/13 avec emergency access, 10/9 sans coffre, import Takeout, passkeys Android ; atelier 1 h (femme) + 30 min/ado | toi | **chacune le fait seule 7 jours après** (journalisé) |
## Bloc 3 — sortie de Google ## Bloc 3 — sortie de Google
+2
View File
@@ -15,6 +15,8 @@
| Pas de compte admin | `select name,admin,deactivated,case when password_hash is null then 0 else 1 end from users` (sqlite `mode=ro`) + login → « Invalid username or password » | **`scripts/26-synapse-admin-repair.sh`** (voie mesurée 12/10) : hash **bcrypt** du mot de passe écrit en base sur un compte admin → **`docker restart`** (caches) → login → **`PUT /_synapse/admin/v2/users/@<uid>`** `{password, admin:true}` → retirer l'admin temporaire. Voie officielle à réessayer : `docker exec synapse register_new_matrix_user -a -u <admin> -c /data/homeserver.yaml` (le secret est **déjà** dans `homeserver.yaml` ; `POST /_synapse/admin/v1/register` signé a répondu **`HMAC incorrect`**) | snapshot sqlite **avant** (`sqlite3 .backup` → `/data/homeserver.db.bak-*`) + `update users set admin=0, deactivated=1, password_hash=NULL where name='<temporaire>'` | | Pas de compte admin | `select name,admin,deactivated,case when password_hash is null then 0 else 1 end from users` (sqlite `mode=ro`) + login → « Invalid username or password » | **`scripts/26-synapse-admin-repair.sh`** (voie mesurée 12/10) : hash **bcrypt** du mot de passe écrit en base sur un compte admin → **`docker restart`** (caches) → login → **`PUT /_synapse/admin/v2/users/@<uid>`** `{password, admin:true}` → retirer l'admin temporaire. Voie officielle à réessayer : `docker exec synapse register_new_matrix_user -a -u <admin> -c /data/homeserver.yaml` (le secret est **déjà** dans `homeserver.yaml` ; `POST /_synapse/admin/v1/register` signé a répondu **`HMAC incorrect`**) | snapshot sqlite **avant** (`sqlite3 .backup` → `/data/homeserver.db.bak-*`) + `update users set admin=0, deactivated=1, password_hash=NULL where name='<temporaire>'` |
| Création de compte refusée | `PUT /_synapse/admin/v2/users/@<uid>:192.168.1.24` (token admin) | **PUT** (pas POST) ; `scripts/27-synapse-family-users.sh` (`--dry`, `--revoke`) ; inscription publique à **couper** (`enable_registration: false`) une fois l'admin en place | `PUT … {"deactivated": true}` (conserve la trace) | | Création de compte refusée | `PUT /_synapse/admin/v2/users/@<uid>:192.168.1.24` (token admin) | **PUT** (pas POST) ; `scripts/27-synapse-family-users.sh` (`--dry`, `--revoke`) ; inscription publique à **couper** (`enable_registration: false`) une fois l'admin en place | `PUT … {"deactivated": true}` (conserve la trace) |
| Alerte non reçue dans le salon | `sudo python3 /usr/local/bin/g9-verify-alert.py 20` ; `sudo bash /usr/local/bin/g9-probes.sh --selftest` | token/room dans `/etc/restic/matrix.env` (600) → régénérer le token `ops-bot` si révoqué | restaurer `/etc/restic/matrix.env` (backup daté) | | Alerte non reçue dans le salon | `sudo python3 /usr/local/bin/g9-verify-alert.py 20` ; `sudo bash /usr/local/bin/g9-probes.sh --selftest` | token/room dans `/etc/restic/matrix.env` (600) → régénérer le token `ops-bot` si révoqué | restaurer `/etc/restic/matrix.env` (backup daté) |
| Salon famille absent | `GET /_synapse/admin/v1/rooms` (nb de salons) + résolution d'alias `GET /_matrix/client/v3/directory/room/%23famille%3A192.168.1.24` | `scripts/28-synapse-create-room.sh` (`--dry` → `--create` avec `CONFIRM=CREER`) : **`POST /_matrix/client/v3/createRoom`** — `/_synapse/admin/v1/create_room` répond **404 `M_UNRECOGNIZED`** sur cette version ; invites = `@ops`, `@ops-bot` | `--purge <room_id>` (`DELETE /_synapse/admin/v1/rooms/<id>`) |
| Rate limit login (« Too Many Requests ») | réponse `M_LIMIT_EXCEEDED` + `retry_after_ms` sur `/_matrix/client/v3/login` | **attendre ~75 s** (compteur en mémoire, vidé par `docker restart synapse`) ; grouper les appels, 1 login par exécution | aucun (auto-rétablissement) |
| E2E illisible sur un appareil | session/device inconnu | phrase de récupération E2E **notée sur papier** (procédure B) ; vérifier les devices via API admin | aucun (clé perdue = session perdue, d'où le papier) | | E2E illisible sur un appareil | session/device inconnu | phrase de récupération E2E **notée sur papier** (procédure B) ; vérifier les devices via API admin | aucun (clé perdue = session perdue, d'où le papier) |
| Fused de logs / disque | `du -sh /data/*` + logs Synapse | purge via `synapse_stitch_schemas`/`purge` documenté **après** backup | restauration restic | | Fused de logs / disque | `du -sh /data/*` + logs Synapse | purge via `synapse_stitch_schemas`/`purge` documenté **après** backup | restauration restic |
| TLS/ACME KO (matrix.folzocorp.fr) | `curl -kI https://matrix.folzocorp.fr/` | côté J5040 : `docker logs caddy --tail 30` + clé API OVH valide (`secrets/ovh.env`) | révoquer/recréer la clé OVH | | TLS/ACME KO (matrix.folzocorp.fr) | `curl -kI https://matrix.folzocorp.fr/` | côté J5040 : `docker logs caddy --tail 30` + clé API OVH valide (`secrets/ovh.env`) | révoquer/recréer la clé OVH |
+2
View File
@@ -155,3 +155,5 @@
| 2026-10-12 18:30 | 4.5 | **dumps logiques cohérents des 3 bases** (`18-db-dumps.sh`, GO utilisateur) | C-P5 : corriger le trou mesuré « copie chaude = complète en octets, pas garantie rejouable » | `pg_dump` Gitea (auth **socket `trust`** mesurée dans `pg_hba.conf` → **aucun mot de passe sur une ligne de commande**) = **80 543 o** gz, **116 `CREATE TABLE`** ; `mysqldump --single-transaction --routines` Nextcloud = **5,9 Mo** gz, **317** objets, se termine par `-- Dump completed` ; idem PhotoPrism = **89,6 Mo** gz, **39** objets, `-- Dump completed`. **Aucune brique stoppée** (`--single-transaction` = instantané InnoDB cohérent). Dumps écrits sous `/data/docker/<brique>/dumps/` → **déjà dans le périmètre restic** (visibles au prochain backup de 03:30). **Rejouabilité prouvée pour Gitea** : base de test `gtest_restore` créée, dump chargé, **116 tables restaurées** (== 116 du dump), base **supprimée** (`pg_database` = gitea_db/postgres/template0/template1, vérifié) | dumps : `rm -f /data/docker/{gitea,nextcloud,photoprism}/dumps/*-20261012-1825Z.sql.gz`. **Test MariaDB complet non fait** : le dump Nextcloud contient `USE \`nextcloud\`` → le charger tel quel écraserait la production ; il faut un serveur jeton (GO séparé) | | 2026-10-12 18:30 | 4.5 | **dumps logiques cohérents des 3 bases** (`18-db-dumps.sh`, GO utilisateur) | C-P5 : corriger le trou mesuré « copie chaude = complète en octets, pas garantie rejouable » | `pg_dump` Gitea (auth **socket `trust`** mesurée dans `pg_hba.conf` → **aucun mot de passe sur une ligne de commande**) = **80 543 o** gz, **116 `CREATE TABLE`** ; `mysqldump --single-transaction --routines` Nextcloud = **5,9 Mo** gz, **317** objets, se termine par `-- Dump completed` ; idem PhotoPrism = **89,6 Mo** gz, **39** objets, `-- Dump completed`. **Aucune brique stoppée** (`--single-transaction` = instantané InnoDB cohérent). Dumps écrits sous `/data/docker/<brique>/dumps/` → **déjà dans le périmètre restic** (visibles au prochain backup de 03:30). **Rejouabilité prouvée pour Gitea** : base de test `gtest_restore` créée, dump chargé, **116 tables restaurées** (== 116 du dump), base **supprimée** (`pg_database` = gitea_db/postgres/template0/template1, vérifié) | dumps : `rm -f /data/docker/{gitea,nextcloud,photoprism}/dumps/*-20261012-1825Z.sql.gz`. **Test MariaDB complet non fait** : le dump Nextcloud contient `USE \`nextcloud\`` → le charger tel quel écraserait la production ; il faut un serveur jeton (GO séparé) |
| 2026-10-12 18:35 | 4.5 | **hub de monitoring posé hors du G9** (J5040) | C-AP5 : le trou mesuré était « les sondes tournent depuis le G9 → elles meurent avec lui » ; l'ancien plan (05-beszel.sh) mettait le hub sur **.25, la machine à mourir en Phase 6** | `compose/beszel-hub/docker-compose.yml` (kit) → `henrygd/beszel@sha256:6b39a8b64f9b15d00a25e7683e31a93c46161182a54f62713525107064e10599` (épinglé digest, label watchtower=false, base dans `/home/folzo/beszel-hub/data`) ; `docker compose up -d` ; **règle ufw LAN-only ajoutée** : `sudo ufw insert 1 allow proto tcp from 192.168.1.0/24 to any port 8090 comment "beszel hub LAN-only"` → **jamais joignable depuis le WAN** ; mesure : `http://192.168.1.27:8090/` = **200** ; superuser créé par CLI (`/beszel superuser create`, **mot de passe généré sur la machine, écrit en 600 dans `/home/folzo/beszel-admin.cred`, jamais affiché, jamais commité**). **Reste** : les agents (.24/.27) + le token universel, qui se crée dans l'UI (`/settings/tokens`) — la voie API PocketBase n'est pas trouvée (`/api/collections/_superusers/auth-with-password` = 404, `…/authenticators/superusers/email` = « resource not found », `/api/collections/_superusers/records` = 403 → API bien présente, chemin d'auth non identifié) | `cd /home/folzo/g9-migration/compose/beszel-hub && docker compose down` ; `sudo ufw delete <n° 8090>` ; `rm -f /home/folzo/beszel-admin.cred` ; `rm -rf /home/folzo/beszel-hub` | | 2026-10-12 18:35 | 4.5 | **hub de monitoring posé hors du G9** (J5040) | C-AP5 : le trou mesuré était « les sondes tournent depuis le G9 → elles meurent avec lui » ; l'ancien plan (05-beszel.sh) mettait le hub sur **.25, la machine à mourir en Phase 6** | `compose/beszel-hub/docker-compose.yml` (kit) → `henrygd/beszel@sha256:6b39a8b64f9b15d00a25e7683e31a93c46161182a54f62713525107064e10599` (épinglé digest, label watchtower=false, base dans `/home/folzo/beszel-hub/data`) ; `docker compose up -d` ; **règle ufw LAN-only ajoutée** : `sudo ufw insert 1 allow proto tcp from 192.168.1.0/24 to any port 8090 comment "beszel hub LAN-only"` → **jamais joignable depuis le WAN** ; mesure : `http://192.168.1.27:8090/` = **200** ; superuser créé par CLI (`/beszel superuser create`, **mot de passe généré sur la machine, écrit en 600 dans `/home/folzo/beszel-admin.cred`, jamais affiché, jamais commité**). **Reste** : les agents (.24/.27) + le token universel, qui se crée dans l'UI (`/settings/tokens`) — la voie API PocketBase n'est pas trouvée (`/api/collections/_superusers/auth-with-password` = 404, `…/authenticators/superusers/email` = « resource not found », `/api/collections/_superusers/records` = 403 → API bien présente, chemin d'auth non identifié) | `cd /home/folzo/g9-migration/compose/beszel-hub && docker compose down` ; `sudo ufw delete <n° 8090>` ; `rm -f /home/folzo/beszel-admin.cred` ; `rm -rf /home/folzo/beszel-hub` |
| 2026-10-12 19:00 | 4.5 | **compte admin Synapse créé et réparé** (`26-synapse-admin-repair.sh`, GO utilisateur) | C-MX1/C-MX3/C-ID4 : la brique n'avait **aucun admin** (`admin` = 0 sur `@ops` et `@ops-bot`) → ni création de comptes famille, ni réinitialisation possible | `POST /_matrix/client/v3/register` (`m.login.dummy`) → compte créé mais Synapse **ignore le localpart** (mxid aléatoire) et le bug d'arguments de `21-…` l'a laissé **`password_hash` = NULL** → admin=1 **inutilisable** (login « Invalid username or password », et `/_synapse/admin/v1/register` signé = **`HMAC incorrect`**, secret jamais recopié ni affiché). Réparation mesurée : **`bcrypt` 5.0.0 est dans l'image synapse** → hash bcrypt 12 du mot de passe du credential écrit en base (mot de passe **jamais en argv** : stdin + `curl -K` fichier 600 + tmpdir 700) → **`docker restart`** (Synapse ne vide pas ses caches sqlite sur écriture directe) → login OK → **`PUT /_synapse/admin/v2/users/@folzo:192.168.1.24`** `{password, admin:true}` (l'API v2 **crée par PUT**, POST = `Unrecognized request`) → compte temporaire `admin=0`, `deactivated=1`, `password_hash=NULL`. **Preuves** : `--verify folzo` = **login OK** ; `--admin-check folzo` = **API admin OK** (3 utilisateurs, 1 admin) ; lecture seule finale : 4 comptes, 1 admin actif, 1 désactivé. `27-synapse-family-users.sh` ajouté (`--dry` mesuré = 6 PUT planifiés, `--revoke` = désactivation) | snapshot sqlite **avant** toute écriture : `/data/homeserver.db.bak-20261012-1900` (rollback = arrêter `synapse`, restaurer le fichier, redémarrer) ; compte aléatoire conservé désactivé (0 purge) ; `rm -f /tmp/2[567]-*.sh` sur le G9 | | 2026-10-12 19:00 | 4.5 | **compte admin Synapse créé et réparé** (`26-synapse-admin-repair.sh`, GO utilisateur) | C-MX1/C-MX3/C-ID4 : la brique n'avait **aucun admin** (`admin` = 0 sur `@ops` et `@ops-bot`) → ni création de comptes famille, ni réinitialisation possible | `POST /_matrix/client/v3/register` (`m.login.dummy`) → compte créé mais Synapse **ignore le localpart** (mxid aléatoire) et le bug d'arguments de `21-…` l'a laissé **`password_hash` = NULL** → admin=1 **inutilisable** (login « Invalid username or password », et `/_synapse/admin/v1/register` signé = **`HMAC incorrect`**, secret jamais recopié ni affiché). Réparation mesurée : **`bcrypt` 5.0.0 est dans l'image synapse** → hash bcrypt 12 du mot de passe du credential écrit en base (mot de passe **jamais en argv** : stdin + `curl -K` fichier 600 + tmpdir 700) → **`docker restart`** (Synapse ne vide pas ses caches sqlite sur écriture directe) → login OK → **`PUT /_synapse/admin/v2/users/@folzo:192.168.1.24`** `{password, admin:true}` (l'API v2 **crée par PUT**, POST = `Unrecognized request`) → compte temporaire `admin=0`, `deactivated=1`, `password_hash=NULL`. **Preuves** : `--verify folzo` = **login OK** ; `--admin-check folzo` = **API admin OK** (3 utilisateurs, 1 admin) ; lecture seule finale : 4 comptes, 1 admin actif, 1 désactivé. `27-synapse-family-users.sh` ajouté (`--dry` mesuré = 6 PUT planifiés, `--revoke` = désactivation) | snapshot sqlite **avant** toute écriture : `/data/homeserver.db.bak-20261012-1900` (rollback = arrêter `synapse`, restaurer le fichier, redémarrer) ; compte aléatoire conservé désactivé (0 purge) ; `rm -f /tmp/2[567]-*.sh` sur le G9 |
| 2026-10-12 19:20 | 4.5 | **6 critères retoqués par le vérificateur corrigés dans les docs** | C-ID2, C-ID8, C-MX5, C-PH2, C-WA1 (+ péages périmés) | **C-ID2** : ligne **PhotoPrism ajoutée en §2** (16ᵉ ligne .24) avec les 3 comptes **mesurés** (`photoprism.auth_users` : `admin`, `visitor`, **1 sans nom**) + CLI réelle (`photoprism users ls\|show\|mod\|add\|rm`, `auth reset --yes`) ; §7 « à nommer » supprimé ; ligne **Synapse réécrite** (3 comptes, `@folzo` admin, PUT et pas POST) ; **convention `infra/<brique>/break-glass` ajoutée en §1** ; **C-ID8** : §2.1 **placement des 3 exemplaires** (sur place / hors site / hors ligne, aucun 4ᵉ), §2.2 **4 garde-fous** (2 personnes max, personne de confiance = **lecture seule**, **1 code de déverrouillage par personne**, **jamais par mail/cloud**), §4 test annuel = **6 points** tabulés ; **C-MX5** : **0 occurrence** de « remplace WhatsApp » (S3.1 #8, S13.3 C.1, `GUIDE-MATRIX.md` §4 corrigés) + **décision + 4 interdictions** écrites en S8.2 (mot « interdiction » présent) + numérotation corrigée (**S14.5** = grille Matrix/WhatsApp) ; **C-PH2** : `PHOTOS-DOUBLE-RUN.md` **§6 grille des 10 requêtes** (protocole + colonnes à remplir + classement) et **§7 procédure d'auto-upload** (10 pas, anti-effacement, EXIF, seuil de réussite 7 jours, format de journal, rollback) ; **C-WA1** : **§6 = 17 questions de Go/No-Go** avec preuve exigée par ligne + **risque de bannissement détaillé** (2 voies : signalements des correspondants + détection automatisée, **aucun seuil public**, échelle ban temporaire → permanent → suppression du compte, probabilité **non chiffrable**) ; péages périmés corrigés : §4 de `PHOTOS-DOUBLE-RUN.md` (`prisme.` = **200** avec la route du 17:15), `BACKUP-PERIMETRE.md` §6 (**dumps faits et mesurés** + rejouabilité Gitea, plus « à faire »), `EXPOSITION-INTERNET.md` **§7 = choix de machine J5040 pour l'IdP** (5 points + déclencheur + rollback) | `git checkout` des 7 fichiers (aucun effet sur les briques) |
| 2026-10-12 19:25 | 4.5 | **salon famille Matrix créé** (`28-synapse-create-room.sh`, GO utilisateur) | C-MX2 : le vérificateur mesure **1 seul salon** (`!mp7MFx9Q…` = ops) — le salon famille annoncé n'existait pas | `POST /_matrix/client/v3/createRoom` (token admin `@folzo`) → **`!9hTrGcLp3bZzw_cXtJEYs7eRfWcr4yUTZe6RTFEr27M`**, nom `Famille`, alias **`#famille:192.168.1.24`**, `preset: private_chat`, `visibility: private`, invites `@ops` + `@ops-bot`. **Voie admin v1 mesurée morte** : `POST /_synapse/admin/v1/create_room` = **404 `M_UNRECOGNIZED`** sur cette version (Matrix v1.12). Preuves : `--list` = **2 salons** en base (`!mp7MFx9Q…` créateur `@ops-bot`, `!9hTrGcLp…` créateur `@folzo`), **résolution d'alias** `#famille → !9hTrGcLp…`, 0 compte créé, **0 message envoyé**. `--dry` et `--purge` (rollback) outillés. Constat consigné : `joined_members` = 1 → les invites sont **en attente**, les adultes rejoindront depuis Element | `CONFIRM=PURGER bash scripts/28-synapse-create-room.sh --purge "!9hTrGcLp3bZzw_cXtJEYs7eRfWcr4yUTZe6RTFEr27M"` (`DELETE /_synapse/admin/v1/rooms/<id>`) ; `rm -f /tmp/28-*.sh` sur le G9 |
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
# 28-synapse-create-room.sh — salons Matrix via l'API admin (0 compte cree, 0 message envoye).
#
# Motif (verificateur 12/10) : C-MX2 annonce « salon famille + salon ops crees » ; la mesure
# (sqlite mode=ro) ne donnait **que le salon ops**. Ce script cree le salon famille et laisse la
# preuve verificable. Reversible : --purge (DELETE /_synapse/admin/v1/rooms/<id>).
#
# Gardes : mot de passe et token **jamais en argv** (stdin + `curl -K` fichier 600, tmpdir 700),
# `--dry` par defaut, ecriture seulement sur --create, verification par GET admin + sqlite ro.
# Usage : bash 28-synapse-create-room.sh --list
# bash 28-synapse-create-room.sh --dry [--alias famille] [--invite "@ops:..." ...]
# CONFIRM=CREER bash 28-synapse-create-room.sh --create [--alias famille]
# CONFIRM=PURGER bash 28-synapse-create-room.sh --purge <room_id>
set -euo pipefail
URL="${URL:-http://192.168.1.24:8008}"
DOMAIN="${DOMAIN:-192.168.1.24}"
ADMIN="${ADMIN:-folzo}"
CRED="${CRED:-/home/folzo/synapse-admin-$ADMIN.cred}"
ALIAS="${ALIAS:-famille}"
ROOM_NAME="${ROOM_NAME:-Famille}"
INVITE="${INVITE:-@ops:192.168.1.24 @ops-bot:192.168.1.24}"
DB="${DB:-/data/homeserver.db}"
CTN="${CTN:-synapse}"
TMPD="$(mktemp -d)"; chmod 700 "$TMPD"; trap 'rm -rf "$TMPD"' EXIT
cat > "$TMPD/loginbody.py" <<'PY'
import json, sys
print(json.dumps({"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": sys.argv[1]},
"password": sys.stdin.buffer.read().strip().decode()}))
PY
cat > "$TMPD/token.py" <<'PY'
import json, sys
d = json.load(open(sys.argv[1])); tok = d.get("access_token", "")
open(sys.argv[2], "w").write(tok)
print(" " + ("login admin OK (token %d car.)" % len(tok) if tok else "KO login: " + str(d.get("error") or d)))
sys.exit(0 if tok else 1)
PY
cat > "$TMPD/roombody.py" <<'PY'
import json, sys
print(json.dumps({"name": sys.argv[2], "room_alias_name": sys.argv[1],
"preset": "private_chat", "visibility": "private", "is_direct": False,
"invite": sys.argv[3:]}))
PY
cat > "$TMPD/rooms.py" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
rooms = d.get("rooms", [])
print(" %s salons (API admin)" % d.get("total", len(rooms)))
for r in d.get("rooms", []):
print(" ", r.get("room_id"), "|", r.get("name"), "|", r.get("alias"),
"| membres:", r.get("joined_members"))
PY
[[ -f "$CRED" ]] || { echo "KO: credential admin absent ($CRED)"; exit 1; }
read -r -a INV <<< "$INVITE"
login() {
sed -n 's/^password=//p' "$CRED" | python3 "$TMPD/loginbody.py" "@${ADMIN}:${DOMAIN}" \
| curl -s -m 15 -X POST "$URL/_matrix/client/v3/login" -H 'Content-Type: application/json' -d @- > "$TMPD/login.json"
python3 "$TMPD/token.py" "$TMPD/login.json" "$TMPD/token"
{ printf 'header = "Authorization: Bearer %s"\n' "$(cat "$TMPD/token")"
printf 'header = "Content-Type: application/json"\n'; } > "$TMPD/curlrc"
chmod 600 "$TMPD/curlrc"
}
list_rooms() {
curl -s -m 20 -K "$TMPD/curlrc" "$URL/_synapse/admin/v1/rooms?limit=50" > "$TMPD/rooms.json"
python3 "$TMPD/rooms.py" "$TMPD/rooms.json"
}
case "${1:---list}" in
--list)
login; list_rooms
echo "== resolution de l'alias #${ALIAS} =="
curl -s -m 15 -K "$TMPD/curlrc" "$URL/_matrix/client/v3/directory/room/%23${ALIAS}%3A${DOMAIN}" \
| python3 -c 'import json,sys;d=json.load(sys.stdin);print(" #"+sys.argv[1]+" ->", d.get("room_id") or d.get("error") or d)' "$ALIAS"
echo "== sqlite (lecture seule) =="
cat > "$TMPD/rooms_ro.py" <<'PY'
import sqlite3, sys
c = sqlite3.connect('file:%s?mode=ro' % sys.argv[1], uri=True).cursor()
print(' salons en base:', c.execute('select count(*) from rooms').fetchone()[0])
for r in c.execute("select room_id, ifnull(creator,'-'), is_public, room_version from rooms"):
print(' ', r)
PY
docker cp "$TMPD/rooms_ro.py" "$CTN:/tmp/g9-rooms-ro.py" >/dev/null
docker exec "$CTN" python3 /tmp/g9-rooms-ro.py "$DB"
docker exec "$CTN" rm -f /tmp/g9-rooms-ro.py
;;
--dry)
shift; if [[ "${1:-}" == "--alias" ]]; then ALIAS="${2:?}"; shift 2; fi
echo "== PLAN (0 ecriture) =="
echo " POST $URL/_matrix/client/v3/createRoom (token admin)"
echo " note mesurée : $URL/_synapse/admin/v1/create_room = 404 M_UNRECOGNIZED sur cette version"
python3 "$TMPD/roombody.py" "$ALIAS" "$ROOM_NAME" "${INV[@]}" | python3 -c 'import json,sys;print(" body:", json.dumps(json.load(sys.stdin), ensure_ascii=False))'
echo " rollback : CONFIRM=PURGER bash $0 --purge <room_id>"
;;
--create)
shift; if [[ "${1:-}" == "--alias" ]]; then ALIAS="${2:?}"; shift 2; fi
if [[ "${CONFIRM:-}" != "CREER" ]]; then
echo "confirmation exigee : tape CREER pour creer le salon #$ALIAS (0 compte, 0 message)"
read -r A; [[ "$A" == "CREER" ]] || { echo "abandon (0 ecriture)"; exit 1; }
fi
login
python3 "$TMPD/roombody.py" "$ALIAS" "$ROOM_NAME" "${INV[@]}" > "$TMPD/room.json"
curl -s -m 20 -X POST "$URL/_matrix/client/v3/createRoom" -K "$TMPD/curlrc" \
-d @"$TMPD/room.json" -o "$TMPD/created.json" -w ' create http=%{http_code}\n'
python3 -c 'import json,os,sys
p = sys.argv[1]
if os.path.getsize(p) == 0: print(" reponse vide"); raise SystemExit(0)
d = json.load(open(p)); print(" cree:", d.get("room_id") or d.get("error") or d)' "$TMPD/created.json"
list_rooms
;;
--purge)
ROOM="${2:?room_id a purger}"
if [[ "${CONFIRM:-}" != "PURGER" ]]; then echo "abandon : CONFIRM=PURGER exige"; exit 1; fi
login
curl -s -m 60 -X DELETE -K "$TMPD/curlrc" "$URL/_synapse/admin/v1/rooms/$ROOM" \
| python3 -c 'import json,sys;d=json.load(sys.stdin);print(" purge:", d.get("kicked") and "OK" or d)'
list_rooms
;;
*) echo "usage: --list | --dry | --create | --purge <room_id> (voir en-tete)"; exit 1 ;;
esac