6 criteres retoques: PhotoPrism+garde-fous+WhatsApp+grille 10 requetes+17 questions, salon famille cree (GO), peages perimes
This commit is contained in:
+25
-2
@@ -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 |
|
||||
| 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 |
|
||||
| 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) |
|
||||
| 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 |
|
||||
@@ -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** | 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
|
||||
|
||||
> **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 |
|
||||
|---|-------------------|------------|-------------------|
|
||||
| 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.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) |
|
||||
|
||||
+1
-1
@@ -130,7 +130,7 @@ curl -s -X PUT 'http://192.168.1.24:8008/_synapse/admin/v2/users/@<PSEUDO>:192.1
|
||||
|
||||
| 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` |
|
||||
| **hermes** (prévu) | Salon où la famille parle à Effi (assistant, Phase 3 du second cerveau) |
|
||||
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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
|
||||
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
|
||||
# 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
|
||||
# Nextcloud : export cohérent via occ
|
||||
docker exec -u www-data nextcloud php occ db export --type=structure > /data/docker/nextcloud/dumps/nc-structure.sql
|
||||
docker exec -u www-data nextcloud php occ db export --type=data > /data/docker/nextcloud/dumps/nc-data.sql
|
||||
```
|
||||
| Base | Commande réelle (mesurée) | Résultat |
|
||||
|---|---|---|
|
||||
| **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** (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` |
|
||||
| **PhotoPrism** (MariaDB) | idem sur `photoprism` | **89,6 Mo** gz, **39 objets**, `-- Dump completed` |
|
||||
|
||||
Les dumps tombent sous `/data/docker/<brique>/dumps/` → **déjà dans le périmètre** de backup.
|
||||
Rollback = supprimer les dumps, aucune brique touchée.
|
||||
Pièges mesurés, intégrés au script :
|
||||
- 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.
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
| 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 |
|
||||
| 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) |
|
||||
@@ -53,7 +53,35 @@
|
||||
| 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é) |
|
||||
|
||||
## 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
|
||||
reste sur un **numéro secondaire**.
|
||||
|
||||
@@ -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
|
||||
NAT). L'inventaire des redirects se fait côté Freebox, par toi — c'est le seul trou de
|
||||
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).
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
## 1. Détenteurs (à nommer par l'utilisateur)
|
||||
|
||||
| Rôle | Personne | Support remis | Date de remise | Test annuel |
|
||||
|---|---|---|---|---|
|
||||
| Détenteur 1 | _à nommer_ | enveloppe scellée n°1 | _à date_ | _à date_ |
|
||||
| Détenteur 2 (optionnel) | _à nommer_ | enveloppe scellée n°2 | _à date_ | _à date_ |
|
||||
| Rôle | Personne | Support remis | Lieu (voir §2.1) | Code de déverrouillage | Date de remise | Test annuel |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Moi | — | enveloppe n°1 | **sur place** (coffre du domicile) | le mien | — | _à 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,
|
||||
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
|
||||
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
|
||||
max. Jamais de photo, jamais de fichier non chiffré sur un disque.
|
||||
Support : papier (pas de clé USB non chiffrée), plié, scellé, signé de la date. Jamais de photo,
|
||||
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
|
||||
|
||||
@@ -43,13 +65,21 @@ max. Jamais de photo, jamais de fichier non chiffré sur un disque.
|
||||
|
||||
## 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) ;
|
||||
- il déverrouille un **coffre d'entraînement** (vault de test, pas le coffre famille) avec les
|
||||
éléments de l'enveloppe ;
|
||||
- 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 | résultat | remplacement enveloppe |`.
|
||||
| # | Point vérifié | Preuve |
|
||||
|---|---|---|
|
||||
| 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 |
|
||||
| 2 | Le **mot de passe maître** déverrouille le coffre d'entraînement | login réussi sur le vault de test |
|
||||
| 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é
|
||||
|
||||
|
||||
@@ -44,33 +44,82 @@ Les 37 `errors` sont à regarder avant le GO famille (aucune action prise).
|
||||
|
||||
| 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`) |
|
||||
| Synology Photos `.25` | — | — | **5000 et 5001 = 000** (445 et 222 ouverts, ping OK) |
|
||||
|
||||
**Deux écarts** vis-à-vis de la décision de nommage :
|
||||
|
||||
1. `photos.folzocorp.fr` pointe sur **PhotoPrism**, pas sur le NAS.
|
||||
2. `prisme.folzocorp.fr` n'a **aucune route** → PhotoPrism n'est pas accessible par son nom.
|
||||
|
||||
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.
|
||||
**É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
|
||||
toute façon **pas mesurable** puisque le web du DSM (5000/5001) ne répond pas. Le double-run
|
||||
« les deux accessibles simultanément » reste donc **prouvable à 1 nom sur 2** : `prisme.` ✓,
|
||||
`photos.` ✗.
|
||||
|
||||
## 5. Ce qui est fait, ce qui reste
|
||||
|
||||
**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** :
|
||||
- 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) ;
|
||||
- finir l'indexation et traiter les 37 erreurs ;
|
||||
- montée en compétence sur **mon** téléphone (auto-upload, recherche, album partagé, partage
|
||||
par lien, doublon, export original, EXIF) + 10 requêtes comparées + 7 jours sans intervention ;
|
||||
- **exécution** de la grille §6 et de la procédure §7 sur **mon** téléphone (usage réel, 7 jours) ;
|
||||
- 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).
|
||||
|
||||
@@ -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
|
||||
CONFIRM=CREER ./scripts/27-synapse-family-users.sh # 6 comptes + 600 mots de passe
|
||||
./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)
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
| Infra / services | `infra/<brique>/<compte>` | `infra/gitea/g9ops`, `infra/n8n/api`, `infra/synapse/admin` |
|
||||
| Adultes | `famille/<pseudo>/<brique>` | `famille/effi/nextcloud`, `famille/effi/matrix` |
|
||||
| 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) :
|
||||
- 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 |
|
||||
| **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 |
|
||||
| **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 |
|
||||
| **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 |
|
||||
| **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 |
|
||||
| **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 |
|
||||
| **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 |
|
||||
@@ -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) |
|
||||
| **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) |
|
||||
| **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 |
|
||||
| **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) |
|
||||
|
||||
|
||||
@@ -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-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é) |
|
||||
|
||||
## Bloc 3 — sortie de Google
|
||||
|
||||
@@ -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>'` |
|
||||
| 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é) |
|
||||
| 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) |
|
||||
| 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 |
|
||||
|
||||
@@ -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: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: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
|
||||
Reference in New Issue
Block a user