# Déploiement Production — Diagnostics multi-serveurs (T19/T20) > Statut : préparation (aucun déploiement réel effectué). Document de référence pour le > futur déploiement contrôlé du Diagnostic Center central et de l'API métier/mobile chez > chaque client. Aucune valeur de secret (clé, mot de passe) n'est contenue ici. --- ## 1. Responsabilités des composants (§30) | Composant | Rôle | Installation | |---|---|---| | **Omika.Api** | API métier/mobile installée **chez chaque client** (licence, bootstrap, métier, `POST /api/diagnostics`). Le mobile ne connaît QUE cette API. | Serveur du client | | **Omika.Diagnostics.Api** | API **centrale**, installée **uniquement sur le serveur Omika**. Reçoit les lots des serveurs clients (`POST /api/v1/diagnostics/batches`), authentifie (ClientCode + ApiKey), impose le ClientCode authentifié, écrit le spool central. Aucune logique métier. | Serveur central Omika | | **Omika.Diagnostics.Worker** | Import du spool central JSONL → PostgreSQL (`diagnostic_event`). Déduplication par `event_id`. | Serveur central Omika | | **Omika.Diagnostics.Web** | Interface de consultation/administration (Dashboard, Diagnostics, Erreurs, Télémétrie, Référentiel, **Clients Diagnostics**, Système). | Serveur central Omika | Le **mobile** ne connaît jamais `Omika.Diagnostics.Api/Worker/Web`, ni PostgreSQL Diagnostics, ni aucune clé central. Le **serveur client** ne connaît jamais la ConnectionString PostgreSQL centrale (§43) : seul le composant central autorisé détient cette configuration. --- ## 2. Architecture de flux ``` APPAREILS MOBILES ──HTTPS──▶ SERVEUR DU CLIENT Omika.Api (+ Omika.Worker métier) └─▶ Spool local borné (DiagnosticsForwarding.Spool) └─▶ DiagnosticsForwarder (BackgroundService dans Omika.Api) └─ HTTPS ──▶ SERVEUR OMIKA CENTRAL Omika.Diagnostics.Api └─▶ Spool central (Diagnostics:Central) └─▶ Omika.Diagnostics.Worker └─▶ PostgreSQL omika_diagnostics └─▶ Omika.Diagnostics.Web ``` --- ## 3. Services Windows cibles (§51) ### Serveur client - `OmikaApi` (process Omika.Api ; contient le forwarder de diagnostics — **aucun service dédié**). - `OmikaWorker` (Worker de synchronisation métier, service réel existant). - API Licence / service existant (inchangé). ### Serveur central Omika - `OmikaDiagnosticsApi` (API centrale, Kestrel). - `OmikaDiagnosticsWorker` (import JSONL → PostgreSQL). - `OmikaDiagnosticsWeb` (interface, Kestrel derrière reverse proxy Apache). - PostgreSQL (`omika_diagnostics`). Les noms de services sont posés par les scripts d'activation serveur (`sc.exe` / `New-Service`) et doivent correspondre aux valeurs `UseWindowsService(...)` du code. --- ## 4. Ports (§52) | Service | Port local Développement | Port Production (à confirmer selon convention existante) | |---|---|---| | Omika.Api (client) | 5097 | défini par `Kestrel:Endpoints` / `appsettings.Production.json` (loopback derrière Apache) | | Omika.Diagnostics.Api (central) | 5098 | à définir (loopback Kestrel derrière Apache/HTTPS) | | Omika.Diagnostics.Web (central) | 5080 | à définir (loopback Kestrel derrière Apache/HTTPS) | | PostgreSQL | 5432 | 5432 (loopback central uniquement) | **Règle** : les services web sont exposés en **HTTPS** via le reverse proxy Apache ; Kestrel reste en HTTP sur loopback. Aucun port n'est choisi arbitrairement : la convention Apache/Kestrel existante de `omika.applilogik.fr` s'applique. --- ## 5. HTTPS (§37/§38) - Le flux **serveur client → API centrale** est **HTTPS uniquement** en Production. - `DiagnosticsForwarding:Endpoint` doit être une URL **`https://…`**. - **Règle T20** : si l'environnement est **Production** et que l'Endpoint est en **`http://`**, le forwarder **refuse l'envoi** (log explicite une fois), le **métier continue** (fail-open), le spool local est conservé. `Omika.Api` n'est **jamais** arrêté à cause de Diagnostics. - Le certificat TLS réel n'est **pas** configuré dans ce document (à poser sur le reverse proxy). --- ## 6. Configuration Omika.Api (§31/§32/§33/§34/§35) Section `DiagnosticsForwarding` (dans `src/Omika.Api/appsettings.json`, surchargée par variables d'environnement / `appsettings.Production.json` **non versionné**) : | Clé | Défaut | Rôle | |---|---|---| | `DiagnosticsForwarding:Enabled` | `false` | Kill switch. **`DiagnosticsForwarding__Enabled=true` uniquement lors du déploiement serveur client.** | | `DiagnosticsForwarding:Endpoint` | `""` | URL centrale absolue **HTTPS** (ex. `https://…/api/v1/diagnostics/batches`). Laisser vide tant qu'aucune URL réelle. | | `DiagnosticsForwarding:ApiKey` | `""` | **Secret** à installer sur le serveur client via `DiagnosticsForwarding__ApiKey`. Jamais dans Git/log/rapport. | | `DiagnosticsForwarding:TimeoutSeconds` | `10` | Timeout réseau d'un envoi (le forwarder est background : jamais de blocage métier). | | `DiagnosticsForwarding:BatchSize` | `100` | Événements max par lot. | | `DiagnosticsForwarding:MaxBatchBytes` | `524288` | Taille max d'un lot (premier seuil). | | `DiagnosticsForwarding:RetryInitialSeconds` | `15` | Backoff initial. | | `DiagnosticsForwarding:RetryMaxMinutes` | `30` | Plafond du backoff. | | `DiagnosticsForwarding:Spool:MaxFileSizeMb` | `10` | Rotation de fichier. | | `DiagnosticsForwarding:Spool:MaxTotalSizeMb` | `1024` | Quota disque total. | | `DiagnosticsForwarding:Spool:RetentionDays` | `14` | Rétention. | Le défaut `Enabled=false` est **volontaire** (sécurité, opt-in). Aucun placeholder fictif (`https://diagnostics.exemple.fr`) ne doit devenir une valeur réelle accidentellement. --- ## 7. Configuration Omika.Diagnostics.Api (§36) Section `Diagnostics` (dans `src/Omika.Diagnostics.Api/appsettings.json`, surchargée par variables) : | Clé | Défaut | Rôle | |---|---|---| | `ConnectionStrings:DiagnosticsDatabase` (ou `Diagnostics:ConnectionString`) | `""` | PostgreSQL central (secret, injecté serveur). | | `Diagnostics:Central:Directory` | `logs/diagnostics-central` | **Spool central** — en Production, chemin explicite (voir §9). | | `Diagnostics:Central:MaxRequestBodyBytes` | `524288` | Corps de requête borné. | | `Diagnostics:Central:MaxEventsPerBatch` | `100` | Événements max par lot. | | `Diagnostics:Central:SchemaVersion` | `1` | Version de schéma de transport. | URL/port : via `ASPNETCORE_URLS` (ou `Kestrel:Endpoints`) derrière le reverse proxy HTTPS. --- ## 8. Spool client — emplacement Production (§40) Le service Windows peut démarrer depuis `C:\Windows\System32` : **ne jamais dépendre du current working directory**. Utiliser un chemin explicite configurable : ``` C:\ProgramData\Omika\Diagnostics\Spool ``` (via `Observability:Remote:Directory` / `DiagnosticsForwarding` — à poser dans la configuration serveur client). Le quota (`MaxTotalSizeMb`) et la rétention (`RetentionDays`) évitent un remplissage disque : à quota atteint, les fichiers les plus anciens sont supprimés (log du nombre), jamais le fichier actif, jamais d'échec métier. --- ## 9. Spool central — emplacement Production (§41) Même réflexion pour `Omika.Diagnostics.Api` : ``` C:\ProgramData\Omika\Diagnostics\Central ``` (via `Diagnostics:Central:Directory`). Le Worker central lit ce dossier (import JSONL → PostgreSQL, checkpoint `import-checkpoint.json`). --- ## 10. Permissions Windows (§42) Principe de moindre privilège (jamais Administrateur inutilement) : | Compte service | Dossier | Droits | |---|---|---| | `OmikaApi` | `C:\ProgramData\Omika\Diagnostics\Spool` | lecture + écriture | | `OmikaDiagnosticsApi` | `C:\ProgramData\Omika\Diagnostics\Central` | lecture + écriture | | `OmikaDiagnosticsWorker` | `C:\ProgramData\Omika\Diagnostics\Central` | lecture (checkpoint → écriture du checkpoint selon le dossier) | --- ## 11. PostgreSQL (§43/§54) - Base dédiée `omika_diagnostics`. PostgreSQL **ne doit jamais être exposé** aux serveurs clients : seuls les composants centraux autorisés y accèdent (firewall : loopback central uniquement). - Rôles : voir migrations `003`/`004`/`009`/`010` (`omika_diagnostics_owner`, `_worker`, `_web`, `_api`). - `omika_diagnostics_api` (LOGIN) : **Omika.Diagnostics.Api** — `SELECT` + `UPDATE(last_seen_at_utc)` sur `diagnostics_clients` uniquement. **Aucun** droit sur `diagnostics_users`. - `omika_diagnostics_web` (LOGIN) : **Omika.Diagnostics.Web** — `SELECT` sur `diagnostic_event` ; `SELECT`/`INSERT`/`UPDATE` sur `diagnostics_clients` (admin `/Clients`) ; `SELECT`/`INSERT`/`UPDATE` (colonnes `password_hash`, `role`, `enabled`, `updated_at_utc`, `password_changed_at_utc`, `last_login_at_utc`) + `USAGE` séquence sur `diagnostics_users` (admin `/Users` + login). - `omika_diagnostics_worker` (LOGIN) : **Omika.Diagnostics.Worker** — `INSERT`/`SELECT`/`DELETE` sur `diagnostic_event` ; **aucun** droit sur `diagnostics_clients` ni `diagnostics_users`. - Le serveur client **NE DOIT PAS** connaître la ConnectionString PostgreSQL centrale. --- ## 12. Migrations — inventaire (§44/§45/§46) Ordre d'application sur le serveur central : `001 → 002 → 003 → 004 → 005 → 006 → 007 → 008 → 009 → 010`. | Fichier | But | Développement appliqué ? | Production nécessaire ? | |---|---|---|---| | `001_diagnostics_schema.sql` | `diagnostic_event` + `telemetry_metric_hourly` + index + déduplication `event_id`. | Oui | Oui | | `002_actor_key.sql` | Colonnes `actor_key`/`actor_key_version` (HMAC pseudonymisation). | Oui | Oui | | `003_diagnostics_roles.sql` | Rôles/grants serveur (`owner`/`worker`/`web`), moindre privilège. | **Non** (à exécuter sur le serveur PostgreSQL Production, en superuser) | Oui | | `004_diagnostics_retention.sql` | Grant `SELECT, DELETE` au worker pour la rétention bornée. | **Non** (idem, serveur Production) | Oui | | `005_diagnostics_client_context.sql` | Colonnes `client_code`/`server_instance`/`environment`. | Oui | Oui | | `006_diagnostics_device_license.sql` | Colonnes appareil/licence. | Oui | Oui | | `007_diagnostics_usage.sql` | Colonnes `screen_code`/`feature_code`. | Oui | Oui | | `008_diagnostics_clients.sql` | Table `diagnostics_clients` (registre des serveurs clients, hash SHA-256). | Oui | Oui | | `009_diagnostics_clients_grants.sql` | Ownership + GRANT moindre privilège de `diagnostics_clients` (`_api`/`_web`), rôle `omika_diagnostics_api`. | **Non** (serveur PostgreSQL Production, superuser) | Oui | | `010_diagnostics_web_users.sql` | Table `diagnostics_users` (utilisateurs Web) + ownership `omika_diagnostics_owner` + GRANT moindre privilège `_web` uniquement (aucun droit API/Worker). | **Non** (serveur PostgreSQL Production, superuser) | Oui | **003/004/009/010** : contiennent des `CREATE ROLE` / `ALTER ROLE` / `GRANT` au niveau **serveur/instance**. Elles ne sont **pas** appliquées sur le PC de développement (elles exigent le superuser PostgreSQL Production et fixent les comptes de service). Elles devront être exécutées **sur le serveur PostgreSQL Production**, dans l'ordre, **avant** le pointage des comptes de service Worker/Web/Api (`omika_diagnostics_worker` / `omika_diagnostics_web` / `omika_diagnostics_api`), puis les mots de passe posés manuellement (`\password`, jamais dans un script/log). Ne pas les exécuter localement « pour du vert ». > **T22** : `008` crée `diagnostics_clients` **après** `003`/`004`, donc sans ownership ni GRANT > applicatif. `009` transfère la propriété à `omika_diagnostics_owner` (même propriétaire logique > que `diagnostic_event`/`telemetry_metric_hourly`), crée le rôle `omika_diagnostics_api` (absent du > modèle `003`), et pose les GRANT minimum identifiés par l'audit des consommateurs réels : > API = `SELECT` + `UPDATE(last_seen_at_utc)` ; Web = `SELECT`/`INSERT`/`UPDATE(enabled, api_key_hash, > key_prefix, key_created_at_utc)` + `USAGE` séquence ; Worker = aucun. `009` est idempotent > (re-exécutable sans erreur) et utilise `current_database()` pour `GRANT CONNECT` (portable, > testé sur `omika_diagnostics_preprod`). --- ## 13. Authentification ApiKey — audit final (§55/§56) - Clé : **32 octets aléatoires**, **Base64Url**, préfixe **`omkdiag_`**. - Stockage : **SHA-256 hexadécimal (64)** uniquement (`diagnostics_clients.api_key_hash`) + `key_prefix`. - Vérification : `CryptographicOperations.FixedTimeEquals` ; hash factice si client inconnu (anti-timing). - **401** : client inconnu OU mauvaise clé (indistinguables). **403** : client désactivé. - **Tenant autoritaire** : tout ClientCode payload ≠ authentifié → **400** + anomalie (jamais de confiance au payload). - Réponses sans fuite : jamais de hash, de clé, de stacktrace, ni l'existence précise d'un client. --- ## 14. Procédure — nouveau client Production (§73) 1. `Omika.Diagnostics.Web` → **Clients Diagnostics** (Admin). 2. **Nouveau client**. 3. Saisir le **CodeClient exact** (ex. `BATIK.01`, normalisé `UPPER(TRIM)`). 4. **Créer le client** → la clé est générée. 5. **Copier immédiatement** la clé (bouton « Copier »). 6. Installer la clé sur le serveur client : variable `DiagnosticsForwarding__ApiKey`. 7. Configurer `DiagnosticsForwarding__Endpoint` (URL centrale **HTTPS**). 8. Activer : `DiagnosticsForwarding__Enabled=true`. 9. Redémarrer `OmikaApi`. 10. Vérifier `last_seen_at_utc` dans Clients Diagnostics. 11. Vérifier un diagnostic réel (Dashboard / Diagnostics). --- ## 15. Clé perdue (§74) La clé **n'est pas récupérable** (hash seul stocké). Si elle n'a pas été copiée : faire **Régénérer la clé**, puis mettre à jour le serveur client avec la nouvelle clé. --- ## 16. Rotation de clé (§75) Version V1 : **régénération → ancienne clé immédiatement invalide**. Pendant l'intervalle où le serveur client n'a pas encore la nouvelle clé, les envois échouent en `401` et le **spool local s'accumule** (backlog borné, reprise automatique). Acceptable. --- ## 17. Panne centrale (§76) - **Ne pas arrêter** `OmikaApi` (le métier continue). - **Ne pas vider** le spool manuellement (les événements seront renvoyés). - Vérifier l'espace disque (quota/rétention bornent le spool). - Vérifier les logs du forwarder. - Attendre le rétablissement : reprise automatique (backoff borné 15 s → 30 min). --- ## 18. Disque spool (§77) - `MaxTotalSizeMb` (défaut 1024) : quota disque total ; à dépassement, suppression des fichiers les plus anciens (jamais le fichier actif, log du nombre supprimé). - `RetentionDays` (défaut 14) : purge des fichiers plus anciens. - `MaxFileSizeMb` (défaut 10) : rotation de fichier. - **Fail-open absolu** : aucune de ces opérations ne bloque le métier. > **Validation empirique T22** (pipeline réel, panne centrale simulée) : > - Quota `MaxTotalSizeMb=1` : 6000 événements fake (~3,6 Mo) → `DiagnosticsForwarder a abandonné > 14 fichier(s) de spool`, spool final 768 Ko (≤ 1 Mo), fichier actif préservé, `/health` 200 > (aucun crash métier), Working Set stable ~115–125 Mo (backlog sur disque ≠ RAM). > - Rétention `RetentionDays=1` : fichier ancien (-2 j) supprimé, fichier récent + fichier actif > conservés, central DOWN pendant tout le test (la maintenance ne dépend pas du central). > - **Correctif T22** : `EnforceBounds` (quota/rétention) était appelé uniquement quand le spool > était vide ; pendant une panne centrale (backlog jamais vide), le quota n'était donc pas > appliqué. Il est désormais appelé à chaque passe du forwarder, indépendamment du backlog. > Test : `ForwardOnce_BacklogOverQuota_StillEnforcesBounds`. --- ## 19. Ordre de démarrage (reprise/rollback) **Démarrage central** : PostgreSQL → `OmikaDiagnosticsWorker` → `OmikaDiagnosticsApi` → `OmikaDiagnosticsWeb`. **Démarrage client** : `OmikaWorker` (métier) → `OmikaApi` (métier + forwarder). **Rollback** : `DiagnosticsForwarding__Enabled=false` + redémarrage `OmikaApi` coupe immédiatement tout envoi central sans toucher au métier ni au spool (aucune perte de données). --- ## 20. Dépendance SQLite (mobile / Desktop local) > Mise à jour **T22** — audit + correctif CVE. - **Version retenue** : `Microsoft.Data.Sqlite 10.0.10` (inchangé) + **`SQLitePCLRaw` 2.1.12** épinglé transitivement (`Directory.Packages.props`, `CentralPackageTransitivePinningEnabled=true`). - **Raison** : `SQLitePCLRaw.lib.e_sqlite3 2.1.11` (transitif de `Microsoft.Data.Sqlite 10.0.10`) embarque une bibliothèque SQLite native < 3.50.2, vulnérable à **CVE-2025-6965 / GHSA-2m69-gcr7-jv3q** (troncature d'entier dans `findOrCreateAggInfoColumn`, `resolve.c`). Correctif = SQLite native **3.50.2**, livré par le patch **2.1.12** (famille 2.x, drop-in, ABI-compatible). **Pas** de saut vers SQLitePCLRaw 3.x (breaking) ni vers `Microsoft.Data.Sqlite 11` (majeur). - **Projets concernés** : `Omika.App.Local` (offline-first : cache métier, brouillons, sync, `DiagnosticOutboxSqliteStore`) → `Omika.App` → têtes `Omika.App.Desktop` / `Omika.App.Android` / `Omika.App.iOS`. Aucun impact sur `Omika.Api`, `Omika.Worker` ni les projets Diagnostics. - **Validation** : - `dotnet restore` OK ; `dotnet list package --include-transitive --vulnerable` → **« aucun package vulnérable »** ; arbre confirmé `SQLitePCLRaw.* = 2.1.12`. - Tests SQLite/Outbox ciblés : **148/148**. - Builds Release : `Omika.App.Desktop` 0 erreur, `Omika.App.Android` 0 erreur. - `Omika.App.Desktop` (Debug, flux dev) démarré réellement : ouverture `omika.db` OK + bootstrap `DEV BOOTSTRAP` OK (aucun crash SQLite). > **Surface réellement exposée** : la vulnérabilité nécessite une requête SQL construite avec un > nombre extrême de termes d'agrégation ; Omika n'exécute que des requêtes applicatives fixes et > paramétrées (jamais de SQL utilisateur), sur une base **locale** non accessible à distance. Le > correctif 2.1.12 est néanmoins appliqué (moindre coût, aucun risque de régression constaté). --- ## 21. Gestion des utilisateurs Diagnostics.Web (T23) ### 21.1 Distinction fondamentale | Concept | Définition | Identifiant | Stockage | |---|---|---|---| | **Utilisateur Diagnostics** | personne qui ouvre `Omika.Diagnostics.Web` | `username` | `diagnostics_users` (hash PBKDF2-SHA256) | | **Client Diagnostics** | serveur client qui envoie des événements | `ClientCode` | `diagnostics_clients` (hash SHA-256) | Les deux registres sont **totalement séparés** et ne doivent jamais être confondus. ### 21.2 Modèle utilisateur - Table `diagnostics_users` (migration `010`) : `id`, `username` (unique, insensible à la casse), `password_hash`, `role` (`Support` / `Admin`), `enabled`, `created_at_utc`, `updated_at_utc`, `password_changed_at_utc`, `last_login_at_utc`. - **Aucun mot de passe en clair** : seule la colonne `password_hash` existe. Le hash est produit par `DiagnosticPasswordHasher` (PBKDF2-SHA256, 100 000 itérations, sel aléatoire). - **Aucune suppression physique** (V1) : un utilisateur est **désactivé** (`enabled=false`), jamais `DELETE`. ### 21.3 Rôles | Rôle | Accès | |---|---| | **Support** | Dashboard, Diagnostics, Erreurs, Traces, Synchronisation, Telemetry, Parc, Versions, Référentiel, Mon compte. | | **Admin** | Tout Support + **Clients Diagnostics** + **Utilisateurs** + **Système**. | `/System`, `/Clients` et `/Users` sont **Admin uniquement**. Un Support y obtient un refus (redirection `/AccessDenied`). Un Admin conserve tous les droits de consultation Support. ### 21.4 Actions d'administration (`/Users`, Admin uniquement) - **Création** : saisie `username` + `password` + `confirmation` + `rôle`. Le Web hache automatiquement (`DiagnosticPasswordHasher.Hash`) ; l'Admin ne génère/copie **jamais** un hash. - **Désactivation / Réactivation** (`enabled=false/true`). - **Changement de rôle** `Support → Admin` / `Admin → Support`. - **Réinitialisation du mot de passe** (nouveau mot de passe + confirmation → hash + UPDATE). ### 21.5 Protections - **Dernier Admin** : impossible de désactiver ou rétrograder le dernier Admin actif (« Impossible de retirer le dernier administrateur actif. »). - **Auto-désactivation** : un Admin ne peut pas désactiver son propre compte. - **Auto-rétrogradation** : un Admin ne peut pas retirer son propre rôle Admin. ### 21.6 Règles de validation - **Username** : obligatoire, `trim`, unicité insensible à la casse, borné (≤ 64), caractères `lettres / chiffres / . / - / _`. - **Mot de passe** : non vide, **≥ 12 caractères**, confirmation identique. Pas de politique de complexité superflue (V1). ### 21.7 Connexion (login hybride) 1. Si le `username` existe dans `diagnostics_users` → la DB est **autoritaire** (jamais de fallback). - Vérification PBKDF2 (`DiagnosticPasswordHasher.Verify`) ; `enabled=false` → refus. 2. Sinon → fallback opérateurs `DiagnosticsAuth:Operators` (bootstrap temporaire). 3. Message d'échec **générique** : « Identifiants invalides. » (jamais « inconnu » / « désactivé » / « mauvais mot de passe »). - `last_login_at_utc` est mis à jour (best-effort) après un login DB réussi. - Anti-bruteforce léger (in-memory, mono-instance) : 5 échecs / 5 min → blocage 10 min (clé = username + IP). Aucun Redis, aucune session distribuée. - Anti-timing : hash factice calculé quand le username est inconnu (même principe que l'ApiKey). - Cookie : `HttpOnly`, `SameSite=Lax`, `Secure=Always` hors Développement, expiration 12 h (glissante). Production = HTTPS. - Un utilisateur désactivé avec un cookie existant est re-déconnecté à la prochaine re-vérification DB (fréquence bornée ~60 s, cache mémoire mono-instance) ; au pire ~1 minute de grâce, jamais « plusieurs jours ». ### 21.8 « Mon compte » (`/Account`) - Tout utilisateur authentifié peut changer **son propre** mot de passe : `mot de passe actuel` (vérifié réellement) + `nouveau` + `confirmation`. En cas de mot de passe actuel faux → refus **sans** modifier le hash. - Les opérateurs bootstrap (configuration) ne peuvent pas changer leur mot de passe ici. --- ## 22. Bootstrap du premier Admin Production (T23) Production démarre **sans** utilisateur DB. Conserver la possibilité de configurer temporairement un opérateur bootstrap (`DiagnosticsAuth:Operators`) pour créer le premier Admin DB. ### 22.1 Générer le hash bootstrap (hors ligne, pas d'endpoint Web) Outil console dédié (jamais un endpoint Web anonyme) : ``` dotnet run --project tools/Omika.DiagnosticHashTool -- "" # ou, pour éviter de laisser le mot de passe dans l'historique du shell : dotnet run --project tools/Omika.DiagnosticHashTool -- --stdin ``` > ⚠️ Le **mot de passe** bootstrap ne doit **jamais** être dans Git, dans le rapport, ni dans un > `appsettings` versionné. Seul le **hash** est placé dans la configuration / le secret store > Production. ### 22.2 Procédure premier déploiement (12 étapes) 1. Déployer `Omika.Diagnostics.Web` (services + reverse proxy HTTPS, cf. §3-§5). 2. Configurer **temporairement** l'opérateur bootstrap Admin (hash seulement) : `DiagnosticsAuth__Operators__0__Username` / `...__PasswordHash` / `...__Roles__0=Admin`. 3. Démarrer le service `OmikaDiagnosticsWeb`. 4. Se connecter avec l'opérateur bootstrap. 5. Aller sur **`/Users`**. 6. Créer l'**Admin DB permanent** (username + mot de passe + rôle `Admin`). 7. Se **déconnecter**. 8. Tester le login avec l'**Admin DB** (plus le bootstrap). 9. **Retirer** l'opérateur bootstrap de la configuration (`DiagnosticsAuth:Operators = []`). 10. Redémarrer le service `OmikaDiagnosticsWeb`. 11. Confirmer que le **bootstrap est refusé** (login → « Identifiants invalides. »). 12. Confirmer que l'**Admin DB fonctionne** (Dashboard / Diagnostics / Clients / Users). ### 22.3 Installation Production neuve Si PostgreSQL Diagnostics Production est créé **de zéro** : appliquer **`001 → 010`** dans l'ordre (jamais uniquement les dernières). Si une installation existe déjà : appliquer **uniquement les migrations manquantes**, dans l'ordre.