GUIDE DE MISE EN PRODUCTION — RH CONNECT Migration MySQL sur serveur Linux avec Docker Compose Date de préparation : 30/08/2026 ====================================================================== 0. OBJECTIF ET PRINCIPE DE SÉCURITÉ ====================================================================== Objectif : déployer la nouvelle base RH préparée et validée localement, sans écraser immédiatement la base actuellement utilisée en production. Principe retenu : bascule « bleu/vert ». - L'ancienne base de production reste intacte. - Le dump final est importé dans une NOUVELLE base. - Les contrôles sont effectués avant la bascule. - Le backend est ensuite orienté vers la nouvelle base. - En cas de problème, le backend peut revenir rapidement sur l'ancienne base. IMPORTANT : - Ne jamais importer directement dans app_db. - Ne jamais lancer DROP DATABASE sur app_db. - Ne jamais transmettre le fichier .env.production ou ses secrets. - Le dump contient des données RH sensibles : pas de Git, mail ou Teams. - Exécuter cette procédure pendant une fenêtre de maintenance validée. - Les commandes supposent que les services s'appellent db, backend, frontend et alert-mail-scheduler. Les adapter après l'étape 2 si besoin. - Les commandes supposent que Docker Compose est lancé avec --env-file .env.production. Valeurs finales attendues dans la nouvelle base : - Salariés au total : 872 - Salariés actifs selon la base : 174 - Incohérences du statut Actif : 0 - COS dupliqués : 0 - Contrats orphelins : 0 - Identifiants métier de contrat dupliqués : 0 - Visites médicales historiques orphelines connues : 11 ====================================================================== 1. FICHIERS À TRANSMETTRE AU RESPONSABLE DU SERVEUR ====================================================================== Fichiers obligatoires : 1. rh_connect_production_.sql Dump SQL final généré depuis app_db_candidate_20260828. 2. rh_connect_production_.sql.sha256 Empreinte permettant de contrôler l'intégrité du dump. Ne pas transmettre les fichiers Access à la place du dump SQL. Déposer les fichiers dans un répertoire temporaire sécurisé, par exemple : /opt/rhconnect/migration/ Création possible par l'administrateur : sudo mkdir -p /opt/rhconnect/migration sudo chown "$USER":"$USER" /opt/rhconnect/migration chmod 700 /opt/rhconnect/migration Après transfert : chmod 600 /opt/rhconnect/migration/rh_connect_production_*.sql* ls -lh /opt/rhconnect/migration/ ====================================================================== 2. CONTRÔLE EN LECTURE SEULE DE LA PRODUCTION ====================================================================== Se placer dans le dossier contenant le fichier compose.yaml : cd /CHEMIN/DU/PROJET/RH-CONNECT Vérifier le dossier et les services : pwd docker compose --env-file .env.production config --services docker compose --env-file .env.production ps Contrôler la base actuellement demandée par le backend : docker compose --env-file .env.production exec -T backend \ printenv MYSQL_DATABASE Contrôler la configuration utile sans afficher les secrets : docker compose --env-file .env.production exec -T backend \ printenv SSO_MODE docker compose --env-file .env.production exec -T backend \ printenv ENTRA_REDIRECT_URI docker compose --env-file .env.production exec -T backend \ printenv DOCUMENT_TEMPLATES_ROOT docker compose --env-file .env.production exec -T backend \ printenv PAYSLIP_ROOT_PATH Si le service existe : docker compose --env-file .env.production exec -T alert-mail-scheduler \ printenv ALERT_MAIL_SCHEDULE_MODE Vérifier que l'URI Entra ID de production correspond exactement à l'URI enregistrée par la DSI. URI normalement attendue pour le projet : https://rhconnect.envie2enord.com/api/auth/microsoft/callback STOP si l'URI configurée dans l'application et celle enregistrée dans Entra ID ne sont pas identiques. ====================================================================== 3. VÉRIFICATION DU DUMP TRANSMIS ====================================================================== Définir le fichier réel, sans laisser le joker si plusieurs dumps sont présents : MIG_DIR=/opt/rhconnect/migration DUMP_FILE="$MIG_DIR/rh_connect_production_.sql" CHECKSUM_FILE="$DUMP_FILE.sha256" Contrôler leur présence : test -s "$DUMP_FILE" || { echo "ERREUR : dump absent ou vide"; exit 1; } test -s "$CHECKSUM_FILE" || { echo "ERREUR : SHA-256 absent"; exit 1; } ls -lh "$DUMP_FILE" "$CHECKSUM_FILE" Calculer l'empreinte réelle : sha256sum "$DUMP_FILE" Afficher l'empreinte fournie : cat "$CHECKSUM_FILE" Les deux valeurs hexadécimales doivent être strictement identiques. Le fichier .sha256 peut contenir un ancien chemin Windows. Dans ce cas, ne pas utiliser directement « sha256sum -c » : comparer les deux valeurs. Vérifier que le dump ne force pas une autre base : grep -nE '^(CREATE DATABASE|USE )' "$DUMP_FILE" | head -20 Résultat normalement attendu : aucune ligne. STOP si le dump contient CREATE DATABASE ou USE app_db_candidate_20260828. Il faut alors le corriger ou adapter l'import avant de continuer. ====================================================================== 4. VÉRIFICATION DU CODE À DÉPLOYER ====================================================================== La version de l'application déployée doit être celle validée avec la candidate, notamment avec le calcul corrigé du statut Actif. Ne pas faire un « git pull » aveugle en production. Identifier le tag ou le commit validé, puis construire/déployer exactement cette version selon la procédure habituelle de l'équipe. Afficher la version actuellement présente si le projet est un dépôt Git : git status --short git branch --show-current git rev-parse HEAD STOP en cas de modifications locales inattendues sur le serveur. ====================================================================== 5. PRÉPARATION DES VARIABLES DE LA PROCÉDURE ====================================================================== Depuis le dossier Docker du projet : COMPOSE="docker compose --env-file .env.production" CURRENT_DB="$($COMPOSE exec -T backend printenv MYSQL_DATABASE | tr -d '\r')" TARGET_DB="app_db_prod_20260830" TIMESTAMP="$(date +%Y%m%d_%H%M%S)" MIG_DIR=/opt/rhconnect/migration DUMP_FILE="$MIG_DIR/rh_connect_production_.sql" Contrôler les valeurs avant toute écriture : printf 'Base actuelle : %s\nNouvelle base : %s\nDump : %s\n' \ "$CURRENT_DB" "$TARGET_DB" "$DUMP_FILE" Sécurité sur les noms : case "$CURRENT_DB" in ''|*[!A-Za-z0-9_]*) echo "Nom CURRENT_DB invalide"; exit 1 ;; esac case "$TARGET_DB" in ''|*[!A-Za-z0-9_]*) echo "Nom TARGET_DB invalide"; exit 1 ;; esac Vérifier impérativement que : - CURRENT_DB correspond bien à la base de production actuelle ; - TARGET_DB est différent de CURRENT_DB ; - DUMP_FILE désigne le dump final contrôlé à l'étape 3. test "$CURRENT_DB" != "$TARGET_DB" || { echo "ERREUR : la cible ne doit pas être la base actuelle" exit 1 } ====================================================================== 6. SAUVEGARDE OBLIGATOIRE DE LA BASE ACTUELLE ====================================================================== Récupérer temporairement le mot de passe root MySQL depuis le conteneur : ROOT_PASSWORD="$($COMPOSE exec -T db printenv MYSQL_ROOT_PASSWORD | tr -d '\r')" test -n "$ROOT_PASSWORD" || { echo "Mot de passe root introuvable"; exit 1; } Créer le dump de sauvegarde : BACKUP_FILE="$MIG_DIR/backup_${CURRENT_DB}_avant_bascule_${TIMESTAMP}.sql" $COMPOSE exec -T db mysqldump \ -uroot "-p${ROOT_PASSWORD}" \ --single-transaction \ --routines --triggers --events \ --default-character-set=utf8mb4 \ "$CURRENT_DB" > "$BACKUP_FILE" Contrôler immédiatement la sauvegarde : test -s "$BACKUP_FILE" || { echo "ERREUR : sauvegarde absente ou vide" unset ROOT_PASSWORD exit 1 } ls -lh "$BACKUP_FILE" sha256sum "$BACKUP_FILE" | tee "$BACKUP_FILE.sha256" STOP si mysqldump retourne une erreur ou si le fichier est vide. ====================================================================== 7. SAUVEGARDE DES COMPTES ET DROITS SSO DE PRODUCTION ====================================================================== Le dump candidat peut contenir les comptes de l'environnement local. Les comptes, périmètres et permissions réellement configurés sur le serveur doivent être préservés avant la bascule. Tables concernées : - auth_users - auth_user_permissions - auth_user_sectors - auth_user_establishments - auth_user_categories - auth_user_contract_types Vérifier quelles tables existent dans la base actuelle : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -N -e "SELECT table_name FROM information_schema.tables WHERE table_schema='${CURRENT_DB}' AND table_name LIKE 'auth_user%' ORDER BY table_name;" Pour chaque table existante, relever le nombre de lignes : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -e "SELECT 'auth_users' AS table_name, COUNT(*) AS rows_count FROM \`${CURRENT_DB}\`.auth_users;" Sauvegarder toutes les tables listées ci-dessus si elles existent toutes : AUTH_BACKUP="$MIG_DIR/auth_production_${TIMESTAMP}.sql" $COMPOSE exec -T db mysqldump \ -uroot "-p${ROOT_PASSWORD}" \ --no-create-info --skip-triggers --complete-insert \ "$CURRENT_DB" \ auth_users \ auth_user_permissions \ auth_user_sectors \ auth_user_establishments \ auth_user_categories \ auth_user_contract_types > "$AUTH_BACKUP" test -s "$AUTH_BACKUP" || { echo "ERREUR : sauvegarde des comptes SSO vide" unset ROOT_PASSWORD exit 1 } sha256sum "$AUTH_BACKUP" | tee "$AUTH_BACKUP.sha256" Si une table n'existe pas, ne pas exécuter cette commande telle quelle : adapter la liste aux tables réellement présentes. ====================================================================== 8. DÉBUT DE LA FENÊTRE DE MAINTENANCE ====================================================================== Informer les utilisateurs, puis arrêter les composants capables d'écrire dans la base. Adapter la liste aux services affichés à l'étape 2. $COMPOSE stop backend alert-mail-scheduler Le frontend peut rester affiché, mais aucune opération métier ne doit être possible tant que le backend est arrêté. Vérifier : $COMPOSE ps ====================================================================== 9. CRÉATION DE LA NOUVELLE BASE SUR LE SERVEUR ====================================================================== Vérifier d'abord que TARGET_DB n'existe pas : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -N -e "SELECT schema_name FROM information_schema.schemata WHERE schema_name='${TARGET_DB}';" Résultat attendu : aucune ligne. Si une base de ce nom existe déjà, STOP. Choisir un nouveau nom ou demander explicitement quoi faire. Ne pas la supprimer automatiquement. Créer la nouvelle base : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -e "CREATE DATABASE \`${TARGET_DB}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" Récupérer et contrôler le nom de l'utilisateur applicatif : APP_USER="$($COMPOSE exec -T db printenv MYSQL_USER | tr -d '\r')" case "$APP_USER" in ''|*[!A-Za-z0-9_]*) echo "Nom MYSQL_USER invalide"; exit 1 ;; esac Accorder les droits à l'utilisateur déjà existant : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -e "GRANT ALL PRIVILEGES ON \`${TARGET_DB}\`.* TO '${APP_USER}'@'%'; FLUSH PRIVILEGES;" ====================================================================== 10. IMPORT DU DUMP DANS LA NOUVELLE BASE ====================================================================== Importer uniquement dans TARGET_DB : $COMPOSE exec -T db mysql \ -uroot "-p${ROOT_PASSWORD}" \ --default-character-set=utf8mb4 \ "$TARGET_DB" < "$DUMP_FILE" Contrôler le code retour immédiatement : IMPORT_STATUS=$? test "$IMPORT_STATUS" -eq 0 || { echo "ERREUR : import SQL échoué. Ne pas basculer le backend." unset ROOT_PASSWORD exit 1 } Contrôler que les tables principales existent : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -e "SHOW TABLES FROM \`${TARGET_DB}\`;" ====================================================================== 11. PRÉSERVATION DES COMPTES SSO DE PRODUCTION ====================================================================== Cette opération doit être effectuée seulement après vérification que les six tables d'habilitation ont la même structure dans CURRENT_DB et TARGET_DB. Comparer leurs structures : for TABLE in auth_users auth_user_permissions auth_user_sectors \ auth_user_establishments auth_user_categories auth_user_contract_types do echo "===== $TABLE =====" $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -N -e "SELECT table_schema, column_name, column_type, is_nullable FROM information_schema.columns WHERE table_schema IN ('${CURRENT_DB}','${TARGET_DB}') AND table_name='${TABLE}' ORDER BY column_name, table_schema;" done STOP si les structures diffèrent. Dans ce cas, faire une fusion adaptée au schéma au lieu d'un remplacement automatique. Si les structures sont strictement identiques et que l'administrateur valide la conservation des comptes de production, vider les données locales de la cible puis y restaurer les comptes de production : AUTH_CLEAR="$MIG_DIR/clear_target_auth_${TIMESTAMP}.sql" Créer ce fichier avec le contenu suivant en remplaçant __TARGET_DB__ par la valeur réelle de TARGET_DB : SET FOREIGN_KEY_CHECKS=0; DELETE FROM `__TARGET_DB__`.`auth_user_permissions`; DELETE FROM `__TARGET_DB__`.`auth_user_sectors`; DELETE FROM `__TARGET_DB__`.`auth_user_establishments`; DELETE FROM `__TARGET_DB__`.`auth_user_categories`; DELETE FROM `__TARGET_DB__`.`auth_user_contract_types`; DELETE FROM `__TARGET_DB__`.`auth_users`; SET FOREIGN_KEY_CHECKS=1; Exécuter le nettoyage ciblé : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ "$TARGET_DB" < "$AUTH_CLEAR" Puis restaurer AUTH_BACKUP dans la base cible : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ "$TARGET_DB" < "$AUTH_BACKUP" Contrôler à nouveau le nombre de comptes et de droits dans les deux bases. Ils doivent être cohérents avant la bascule. ====================================================================== 12. VALIDATION SQL AVANT BASCULE ====================================================================== Exécuter les contrôles principaux : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -e "SELECT 'employees_total' AS controle, COUNT(*) AS valeur FROM \`${TARGET_DB}\`.employes UNION ALL SELECT 'employees_active_stored', COUNT(*) FROM \`${TARGET_DB}\`.employes WHERE Actif=1 UNION ALL SELECT 'employees_null_cos', COUNT(*) FROM \`${TARGET_DB}\`.employes WHERE COS IS NULL UNION ALL SELECT 'employees_duplicate_cos', COUNT(*) FROM ( SELECT COS FROM \`${TARGET_DB}\`.employes WHERE COS IS NOT NULL GROUP BY COS HAVING COUNT(*) > 1 ) AS duplicates UNION ALL SELECT 'contracts_orphan_employee_cos', COUNT(*) FROM \`${TARGET_DB}\`.contrats c LEFT JOIN \`${TARGET_DB}\`.employes e ON e.COS=c.Id_Salarie WHERE c.Id_Salarie IS NOT NULL AND e.COS IS NULL;" Valeurs obligatoires : - employees_total = 872 - employees_active_stored = 174 - employees_null_cos = 0 - employees_duplicate_cos = 0 - contracts_orphan_employee_cos = 0 Contrôler aussi les comptes SSO : $COMPOSE exec -T db mysql -uroot "-p${ROOT_PASSWORD}" \ -e "SELECT COUNT(*) AS auth_users FROM \`${TARGET_DB}\`.auth_users;" Si les scripts de validation préparés avec la migration sont disponibles, exécuter également leur version adaptée à TARGET_DB et archiver le rapport. STOP si une valeur obligatoire est différente ou si le compte permettant le test SSO n'existe pas dans TARGET_DB. ====================================================================== 13. FICHIERS NON CONTENUS DANS MYSQL ====================================================================== Le dump SQL ne contient pas les fichiers binaires des modèles de documents ni les PDF de fiches de paie. 13.1 Modèles de documents ------------------------- Emplacement normalement utilisé dans le conteneur : /data/document-templates Contrôle : $COMPOSE run --rm --no-deps backend \ sh -lc 'printf "Racine : %s\n" "${DOCUMENT_TEMPLATES_ROOT:-/data/document-templates}"; find "${DOCUMENT_TEMPLATES_ROOT:-/data/document-templates}" \ -maxdepth 2 -type f | head -50' Si aucun fichier n'est présent, les métadonnées SQL pourront apparaître dans l'application, mais les modèles ne pourront pas être générés. Il faut alors transférer séparément l'archive des modèles validés vers le volume Docker de production avant l'ouverture aux utilisateurs. 13.2 Fiches de paie ------------------- Emplacement normalement utilisé dans le conteneur : /data/paies Ce chemin doit être un montage en lecture seule du partage sécurisé contenant les PDF. Contrôle : $COMPOSE run --rm --no-deps backend \ sh -lc 'printf "Racine : %s\n" "${PAYSLIP_ROOT_PATH:-/data/paies}"; find "${PAYSLIP_ROOT_PATH:-/data/paies}" \ -maxdepth 3 -type f -name "*.pdf" | head -20' Ne pas copier les fiches de paie dans Git ou dans le dump MySQL. Si aucun PDF n'apparaît, faire corriger le montage PAYSLIP_HOST_PATH par l'administrateur. ====================================================================== 14. BASCULE DU BACKEND VERS LA NOUVELLE BASE ====================================================================== Sauvegarder d'abord la configuration : cp -a .env.production ".env.production.avant_bascule_${TIMESTAMP}" chmod 600 ".env.production.avant_bascule_${TIMESTAMP}" Modifier uniquement la ligne MYSQL_DATABASE dans .env.production : MYSQL_DATABASE=app_db_prod_20260830 Ne modifier ni MYSQL_PASSWORD, ni les secrets Entra ID. Contrôler sans afficher les secrets : grep '^MYSQL_DATABASE=' .env.production Recréer le backend avec la nouvelle configuration : $COMPOSE up -d --build --force-recreate backend Recréer le scheduler s'il existe, mais ne l'activer qu'après les tests : $COMPOSE up -d --build --force-recreate alert-mail-scheduler Si le nouveau code frontend doit également être déployé : $COMPOSE up -d --build --force-recreate frontend Vérifier la base réellement utilisée par le backend : $COMPOSE exec -T backend printenv MYSQL_DATABASE Contrôle supplémentaire sans afficher le mot de passe de DATABASE_URL : $COMPOSE exec -T backend node -e \ 'const u=new URL(process.env.DATABASE_URL); console.log(u.pathname.slice(1))' Les deux commandes doivent afficher : app_db_prod_20260830 ====================================================================== 15. TESTS FONCTIONNELS APRÈS BASCULE ====================================================================== Surveiller les journaux pendant les tests : $COMPOSE logs --since=10m backend $COMPOSE logs -f --tail=100 backend Tests obligatoires : [ ] La page de connexion s'affiche. [ ] La connexion SSO Microsoft aboutit sans erreur. [ ] Aucun message Prisma « column does not exist » n'apparaît. [ ] Le tableau de bord affiche 174 salariés en cours. [ ] La liste contient 872 salariés au total lorsque tous sont affichés. [ ] Les deux nouveaux salariés attendus sont présents. [ ] Une fiche salarié existante s'ouvre. [ ] Ses contrats, avenants, absences et alertes sont visibles. [ ] Les droits et périmètres du compte de production sont conservés. [ ] Le module Documents voit les modèles et peut générer un document test. [ ] Le module Fiches de paie voit les PDF via /data/paies. [ ] Aucun envoi réel de mail d'alerte n'est déclenché pendant les tests. Faire uniquement des créations/modifications de test explicitement identifiées et les retirer ensuite selon la procédure métier. Après validation métier et technique, réactiver le scheduler selon la configuration décidée pour la production. ====================================================================== 16. RETOUR ARRIÈRE EN CAS DE PROBLÈME ====================================================================== Déclencher le retour arrière si : - le SSO ne fonctionne plus ; - les données attendues sont absentes ou incohérentes ; - Prisma indique une incompatibilité de schéma ; - les permissions utilisateurs sont perdues ; - une fonctionnalité majeure ne fonctionne plus. Procédure : 1. Arrêter les composants qui écrivent : $COMPOSE stop backend alert-mail-scheduler 2. Restaurer la valeur précédente de MYSQL_DATABASE dans .env.production : MYSQL_DATABASE= Ou restaurer le fichier de configuration sauvegardé après vérification : cp -a ".env.production.avant_bascule_${TIMESTAMP}" .env.production 3. Recréer les services : $COMPOSE up -d --force-recreate backend $COMPOSE up -d --force-recreate alert-mail-scheduler 4. Contrôler : $COMPOSE exec -T backend printenv MYSQL_DATABASE $COMPOSE logs --since=5m backend Ne pas supprimer TARGET_DB après le retour arrière : elle servira au diagnostic. ====================================================================== 17. FIN DE L'INTERVENTION ====================================================================== Une fois la production validée : - conserver l'ancienne base pendant la durée décidée avec l'administrateur ; - conserver la sauvegarde SQL et son SHA-256 dans un stockage sécurisé ; - conserver le dump final et les rapports de validation selon la politique RH ; - noter la date, l'heure, le commit déployé et les personnes ayant validé ; - supprimer les copies temporaires seulement après validation et selon la politique de conservation des données de l'entreprise ; - retirer la variable sensible de la session shell : unset ROOT_PASSWORD Ne supprimer ni l'ancienne base ni les sauvegardes le jour de la bascule. ====================================================================== 18. INFORMATIONS À FOURNIR AVANT D'EXÉCUTER LES ÉTAPES D'ÉCRITURE ====================================================================== Avant de commencer l'étape 6, communiquer au responsable de la migration : - le chemin exact du projet Docker sur le serveur ; - la sortie de « docker compose ... config --services » ; - la base actuellement affichée par MYSQL_DATABASE ; - le chemin exact du dump transféré ; - le résultat SHA-256 ; - le tag ou commit applicatif qui sera déployé ; - la confirmation de l'URI de redirection Entra ID ; - l'état des montages /data/document-templates et /data/paies ; - le créneau de maintenance et les personnes autorisant la bascule. FIN DU GUIDE