Compose Postgres (PostGIS + pgvector épinglé par digest), scripts de bootstrap du premier SuperAdmin, de lancement local du service et de migration des données, et le README du flux en huit étapes. Le port est lié à 127.0.0.1 et non publié : le Postgres précédent, exposé par un simple "5432:5432" avec mym/mym, a encaissé 1 074 465 échecs d'authentification entre juin et octobre 2025. Le compte mym n'a jamais été tenté — c'est le seul hasard qui a sauvé la base. Les limites de log viennent du même serveur, où un conteneur avait atteint 516 Mo sur un disque de 20 Go. Le SuperAdmin passe par le token de réinitialisation plutôt que par un INSERT : les mots de passe étant hachés en scrypt, seule l'application peut produire le bon format, et personne d'autre que l'opérateur ne connaît le mot de passe. MigrationController l'exige et la migration n'en crée aucun, MongoDB n'ayant pas de champ Role. Éprouvé sur la préprod le 07/09 : 4 instances, 315 sections migrées et 20 signalées sur 335, 2383 ressources, tout réconcilié avec MongoDB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
160 lines
5.7 KiB
Markdown
160 lines
5.7 KiB
Markdown
# Provisionner une base MyInfoMate — préprod ou production
|
|
|
|
Le flux complet, du serveur nu à une base Postgres remplie avec les données MongoDB.
|
|
Éprouvé le **2026-09-07** sur la préprod `51.77.222.154` : 4 instances, 315 sections,
|
|
2383 ressources migrées sans perte.
|
|
|
|
> Ces scripts sont en PowerShell mais s'exécutent depuis `cmd` via `run.cmd` —
|
|
> dans `cmd`, un `.ps1` n'est pas exécuté, il est passé à l'association de fichier
|
|
> et ne fait rien.
|
|
>
|
|
> ```
|
|
> run.cmd 1-bootstrap-superadmin.ps1 -Email moi@exemple.be
|
|
> ```
|
|
|
|
---
|
|
|
|
## 0. Prérequis sur le serveur
|
|
|
|
```bash
|
|
docker image prune -f # 7,5 Go récupérés sur la préprod : 3 ans de builds
|
|
df -h / # il faut ~1 Go pour les images
|
|
```
|
|
|
|
## 1. La base
|
|
|
|
```bash
|
|
cp .env.example .env # puis remplir POSTGRES_PASSWORD
|
|
docker-compose -p myim-preprod -f docker-compose.postgres.yml up -d
|
|
```
|
|
|
|
**Le `-p` n'est pas optionnel.** Compose est en 1.21.0 (2018) sur ce serveur : il ne
|
|
connaît pas la clé `name:`, et sans `-p` il déduit le projet du dossier courant —
|
|
lancé depuis `/home/debian`, il réutiliserait le volume `debian_postgres-data`,
|
|
c'est-à-dire la base cassée d'octobre 2025.
|
|
|
|
Vérifier que le port n'est **pas** exposé :
|
|
|
|
```bash
|
|
docker port myim_pg # doit afficher 127.0.0.1:5432, jamais 0.0.0.0
|
|
```
|
|
|
|
## 2. Le schéma
|
|
|
|
Aucun `Migrate()` au démarrage : les migrations s'appliquent explicitement.
|
|
Depuis le poste de dev, à travers un tunnel SSH :
|
|
|
|
```bash
|
|
ssh -f -N -L 5433:127.0.0.1:5432 -p 55522 user@serveur
|
|
```
|
|
|
|
```powershell
|
|
$env:MIGRATIONS_CONNECTION = "Host=127.0.0.1;Port=5433;Database=my_info_mate;Username=myim;Password=..."
|
|
dotnet ef database update --project ManagerService
|
|
```
|
|
|
|
`MIGRATIONS_CONNECTION` est prévue pour ça dans `MyInfoMateDbContextFactory`.
|
|
Contrôler ensuite que `postgis` **et** `vector` sont bien installées :
|
|
|
|
```sql
|
|
select extname, extversion from pg_extension;
|
|
```
|
|
|
|
## 3. Le Mongo source
|
|
|
|
`MigrationController` lit un MongoDB **vivant**, pas des fichiers. Un export JSON
|
|
ne suffit donc pas : il faut le restaurer dans un Mongo jetable.
|
|
|
|
```bash
|
|
docker run -d --name myim_mongo_src -p 27018:27017 mongo:6
|
|
docker run --rm --network container:myim_mongo_src -v "$PWD:/src:ro" mongo:6 \
|
|
mongorestore --uri mongodb://localhost:27017 --gzip --archive=/src/dump.gz --drop
|
|
```
|
|
|
|
⚠️ **Ne jamais pointer sur le Mongo de production.** Les `DatabaseService` exposent
|
|
`InsertOne`/`ReplaceOne`/`DeleteOne`, et `dryRun` ne protège que Postgres.
|
|
|
|
## 4. Le compte SuperAdmin
|
|
|
|
`MigrationController` est `[Authorize(Policy = SuperAdmin)]`, et la migration n'en
|
|
crée aucun — MongoDB n'a pas de champ `Role`. C'est donc le préalable obligé.
|
|
|
|
```
|
|
run.cmd 1-bootstrap-superadmin.ps1 -Email moi@exemple.be
|
|
```
|
|
|
|
Il affiche deux `INSERT` à exécuter dans le client SQL (**penser à commiter**), puis
|
|
la commande de l'étape suivante. Les mots de passe étant hachés en scrypt, on laisse
|
|
l'application faire le hachage via `set-password` : personne d'autre que toi ne
|
|
connaît le mot de passe.
|
|
|
|
## 5. Le service, en local
|
|
|
|
```
|
|
run.cmd 2-run-service-local.ps1
|
|
```
|
|
|
|
Le brancher en local évite de builder une image, donc d'affronter au même moment le
|
|
`Dockerfile` de manager-app (Flutter 3.7 = Dart 2.19, alors que le `pubspec` exige
|
|
`>= 3.1`) et la source NuGet `git.dev-espaces-naturels.lu` qui répond 401.
|
|
|
|
Puis, dans une autre fenêtre :
|
|
|
|
```
|
|
run.cmd 3-set-password.ps1 -Token <le token de l'etape 4>
|
|
run.cmd 4-get-jwt.ps1 -Email moi@exemple.be
|
|
```
|
|
|
|
## 6. La migration
|
|
|
|
```
|
|
run.cmd 5-migrate.ps1 # simulation
|
|
run.cmd 5-migrate.ps1 -Apply -InstanceId <id> # pour de vrai, une instance
|
|
```
|
|
|
|
**Commencer par la plus petite instance.** Chaque appel est une transaction unique :
|
|
en cas d'échec, rien n'est écrit.
|
|
|
|
> ⚠️ **Un dry run ne peut détecter aucune violation de contrainte** : il n'écrit rien,
|
|
> donc `SaveChanges` n'est jamais appelé. Un `NOT NULL` violé ne sort qu'au run réel.
|
|
> C'est exactement ce qui est arrivé le 07/09/2026 — `LastName` null sur
|
|
> `test@email.be` — et commencer par la plus petite instance l'a fait apparaître
|
|
> en 3 secondes sur 1 utilisateur au lieu de 10.
|
|
|
|
Compter 2 à 5 min : la sonde HEAD interroge chaque blob Firebase, 30 à la fois.
|
|
Elle tourne **aussi** en dry run.
|
|
|
|
## 7. Le backfill des tailles
|
|
|
|
```
|
|
POST /api/Resource/backfill-storage?dryRun=true puis dryRun=false
|
|
```
|
|
|
|
La sonde HEAD est **instable sous charge** : une ressource peut arriver à
|
|
`SizeBytes = 0` alors que son blob existe. Le 07/09, le backfill a rattrapé une
|
|
ressource sur 2383 et confirmé 3 vrais 404. Un `SizeBytes` à 0 n'est donc jamais
|
|
une preuve de blob manquant.
|
|
|
|
## 8. Nettoyage et vérifications
|
|
|
|
```sql
|
|
-- rattacher le SuperAdmin à une vraie instance, puis supprimer l'échafaudage
|
|
update "Users" set "InstanceId" = '<instance réelle>' where "Id" = 'sa-bootstrap-0001';
|
|
delete from "Instances" where "Id" = 'sa-bootstrap';
|
|
```
|
|
|
|
Puis `pg_dump -Fc` (voir `../backup/`), et les vérifications qui ne se voient qu'à
|
|
l'œil : login manager, une app visiteur avec sa clé API, un parcours, une carte,
|
|
un PDF, un quiz avec ses questions.
|
|
|
|
---
|
|
|
|
## Ce qui a mal tourné, et qu'on ne refait pas
|
|
|
|
| | |
|
|
|---|---|
|
|
| **`5432:5432`** sur le Postgres précédent | exposé à l'internet avec `mym`/`mym` du 06/06 au 22/10/2025 : **1 074 465 échecs d'authentification** sur autant de noms distincts. Le compte `mym` n'a jamais été tenté — c'est le seul hasard qui a sauvé la base. D'où le `127.0.0.1` du compose |
|
|
| **Aucune limite de log** | un seul conteneur avait atteint 516 Mo sur un disque de 20 Go |
|
|
| **Image `postgres:16` nue** | sans PostGIS, la migration `UpdateCoordinatesToGeom` ne passe pas : le schéma était bloqué à 14 migrations sur 68 |
|
|
| **`mym`/`mym`** | ne jamais réutiliser ce couple |
|