Aller au contenu

Exploitation et mises à niveau

Cette page est le guide de l'opérateur pour exploiter Turbo EA en production : comment fonctionnent les mises à niveau et les migrations de base de données, comment sauvegarder et revenir en arrière, quels environnements mettre en place, et les pièges qui guettent les équipes à grande échelle.

Images de production et épinglage de version

Les images publiées sur ghcr.io/vincentmakes/turbo-ea/* sont la manière recommandée d'exploiter la production — le docker-compose.yml fourni les récupère par défaut, et la compilation depuis les sources relève du développement. Au-delà de la commodité, les images publiées offrent des garanties de chaîne d'approvisionnement qu'une compilation locale n'a pas : chaque publication est multi-architecture (amd64 + arm64), signée avec cosign (OIDC sans clé, vérifiable par rapport à l'identité du workflow GitHub Actions) et attestée avec une provenance SLSA et un SBOM. Les images sont bloquées à la publication en cas de CVE critique, re-scannées quotidiennement une fois en ligne, et reconstruites chaque semaine contre des dépôts Alpine à jour, de sorte que les correctifs des images de base arrivent automatiquement. Si votre organisation impose la vérification des signatures d'images à l'admission, les signatures cosign s'y intègrent directement — voir Chaîne d'approvisionnement pour les commandes de vérification.

L'habitude la plus importante : épinglez votre version. Le tag :latest est repositionné lors des versions publiées et de la reconstruction hebdomadaire — pas à chaque commit — et peut donc évoluer selon un calendrier que vous ne contrôlez pas. Définissez un tag explicite dans votre .env :

TURBO_EA_TAG=2.23.1

Voir Épingler une version pour les bases et Versions pour l'arborescence complète des tags et la politique des canaux de préversion.

PostgreSQL managé

Dans les environnements d'entreprise disposant d'un service PostgreSQL managé — Azure Database for PostgreSQL, Amazon RDS / Aurora, Google Cloud SQL ou équivalent — exécuter Turbo EA sur ce service est la configuration recommandée. Le conteneur db fourni est un choix par défaut sans dépendance, pas une exigence : pointez le backend vers votre instance avec les variables POSTGRES_* et laissez de côté le service fourni (voir Utiliser un PostgreSQL existant).

Ce que le service managé prend en charge à votre place :

  • Sauvegardes et restauration à un instant donné (PITR) — automatisées, avec rétention gérée et restaurables à tout moment ; exactement ce dont la stratégie de retour en arrière ci-dessous a besoin.
  • Haute disponibilité et bascule — redondance zonale ou régionale sans gérer votre propre réplication.
  • Correctifs du moteur, chiffrement au repos, isolation réseau — assurés selon le référentiel de conformité de votre organisation (points de terminaison privés, intégration IAM).

Trois choses ne changent pas : le backend exécute toujours lui-même ses migrations Alembic au démarrage (le modèle de mise à niveau de cette page est identique), le volume backend_data a toujours besoin de sa propre sauvegarde (les pièces jointes et les extensions ne résident pas dans PostgreSQL), et la garde du SECRET_KEY vous incombe toujours. L'image fournie embarque PostgreSQL 18 — toute version majeure récente proposée par votre fournisseur convient.

Vérifier la limite de connexions

Le seul paramètre à confirmer avant de basculer est la limite de connexions. Le backend s'exécute dans un processus unique et ouvre jusqu'à DB_POOL_SIZE + DB_MAX_OVERFLOW connexions — 30 par défaut. Le conteneur db intégré en autorise 100 : le problème ne se pose donc jamais sur l'installation par défaut. Les offres managées d'entrée de gamme plafonnent souvent la base plus bas, et PostgreSQL répond alors too many connections for database "turboea".

SELECT datname, datconnlimit FROM pg_database WHERE datname = 'turboea';
SELECT rolname, rolconnlimit FROM pg_roles    WHERE rolname = 'turboea';
SHOW max_connections;

-1 signifie « aucune limite spécifique ». Si le plafond effectif est inférieur à 30, relevez-le ou réduisez le pool dans .env, en laissant quelques connexions disponibles pour les sauvegardes et les sessions d'administration :

DB_POOL_SIZE=8
DB_MAX_OVERFLOW=2
DB_POOL_TIMEOUT=30

Pour voir ce qui est réellement connecté à un instant donné :

SELECT state, count(*) FROM pg_stat_activity WHERE datname = 'turboea' GROUP BY state;

Comment fonctionnent les mises à niveau : les migrations Alembic

La compatibilité du schéma de base de données est gérée automatiquement via Alembic. Au démarrage, le backend exécute alembic upgrade head : chaque migration en attente entre votre schéma actuel et la nouvelle version est appliquée — dans l'ordre — avant que l'application ne serve du trafic.

Les migrations sont numérotées séquentiellement et cumulatives, ce qui rend les sauts de version sûrs : si vous passez, par exemple, de 2.10 à 2.23, toutes les migrations intermédiaires s'exécutent dans l'ordre. Il n'est pas nécessaire de passer par chaque version mineure.

Quelques comportements à connaître :

Situation Ce qui se passe au démarrage
Base de données neuve Les tables sont créées directement et la base est estampillée à head — aucun rejeu de migrations.
Base de données existante Les migrations en attente s'exécutent automatiquement avant que l'API ne soit disponible.
RESET_DB=true Toutes les tables sont supprimées, recréées et réensemencées. À ne jamais activer en production.

Au sein d'une même ligne de version majeure, les migrations restent additives et rétrocompatibles à la mise à niveau — voir la Politique de compatibilité pour le contrat complet.

Ne jamais exécuter un backend plus ancien sur un schéma plus récent

Alembic ne migre que vers l'avant au démarrage. Un code ancien face à un schéma plus récent est un comportement indéfini — c'est la contrainte clé du retour en arrière (voir ci-dessous).

La procédure de mise à niveau

  1. Lisez le changelog. Passez en revue les entrées de CHANGELOG.md entre votre version actuelle et la cible. Les changements incompatibles augmentent la version majeure.
  2. Sauvegardez la base de données et le volume de données (voir ci-dessous).
  3. Montez le tag et récupérez les images :

    TURBO_EA_TAG=2.24.0 docker compose pull
    docker compose up -d
    
  4. Surveillez les journaux de démarrage et confirmez que les migrations se terminent proprement avant que l'API ne serve du trafic :

    docker compose logs -f backend
    

Fenêtres de maintenance

Les migrations sont généralement rapides, mais sur de grands inventaires certaines migrations de données peuvent prendre quelques minutes, pendant lesquelles le backend ne répond pas. Planifiez les mises à niveau dans une fenêtre de maintenance.

Savoir qu'une mise à niveau est disponible

Plutôt que de surveiller le dépôt, laissez l'instance vous prévenir : Turbo EA vérifie chaque jour l'existence d'une version plus récente et notifie les administrateurs dans la cloche de notifications, avec le numéro de version et les notes de version. Rien n'est jamais téléchargé ni installé — l'étape 1 ci-dessus commence toujours par la lecture du changelog. La vérification est active par défaut et peut être désactivée dans Administration → Paramètres → Général, ce qui supprime aussi la requête sortante sur les installations isolées. Une fois la mise à niveau effectuée, chaque utilisateur est informé via la cloche de notifications que l'application a été mise à jour et peut lire le changelog des versions franchies. Chaque notification continue d'afficher la version qu'elle a annoncée — les notes proviennent du changelog embarqué dans l'image —, si bien qu'un ancien avis n'est jamais accompagné des notes de la dernière version.

Sauvegardes

Effectuez une sauvegarde avant chaque mise à niveau, et automatisez-en une chaque nuit dans tous les cas :

docker compose exec db pg_dump -U turboea turboea > backup-$(date +%F).sql

Ajustez l'utilisateur et le nom de la base si vous avez modifié POSTGRES_USER / POSTGRES_DB. Un instantané du volume postgres_data est une alternative équivalente. Sur un service PostgreSQL managé, préférez les sauvegardes automatisées et la restauration à un instant donné du fournisseur aux dumps artisanaux — un pg_dump occasionnel reste utile comme copie portable et indépendante du fournisseur.

Sauvegardez également le volume backend_data — il contient les pièces jointes, les extensions installées et les bundles de transfert d'espace de travail qui ne résident pas dans PostgreSQL.

Deux points supplémentaires sur la posture de reprise :

  • Testez vos restaurations périodiquement. Une sauvegarde jamais restaurée est un espoir, pas un plan.
  • Les fiches archivées sont supprimées de manière réversible avec une fenêtre de 30 jours avant la purge définitive — c'est votre filet de sécurité pour les erreurs de données, distinct de la reprise d'infrastructure.

Retour en arrière et reprise

Les migrations de schéma sont effectivement à sens unique en production : Alembic prend certes en charge les rétrogradations, mais les migrations portant des données ne peuvent pas toujours être inversées sans perte, et l'application n'exécute jamais de rétrogradation automatiquement. La stratégie de retour en arrière fiable est la suivante :

  1. Arrêtez la pile.
  2. Restaurez la sauvegarde de base de données prise avant la mise à niveau (sur PostgreSQL managé : restauration à l'instant précédant immédiatement la mise à niveau).
  3. Remettez TURBO_EA_TAG sur la version précédente.
  4. docker compose up -d — la base restaurée correspond au schéma de l'ancien code, tout est cohérent.

Ne jamais revenir en arrière sur l'image seule

Revenir à l'image précédente tout en gardant la base de données migrée est la seule combinaison contre laquelle le système de migration automatique ne peut pas vous protéger. La sauvegarde de la base et le tag de l'image évoluent ensemble.

Récupérer l'accès administrateur

Turbo EA refuse les deux modifications qui verrouillent le plus souvent un administrateur hors de son instance : vous ne pouvez pas changer votre propre rôle pour un rôle non administrateur, et vous ne pouvez pas désactiver votre propre compte (voir Utilisateurs et rôles). Reste le cas ordinaire — un mot de passe oublié, ou une instance dont l'unique administrateur a quitté l'entreprise. Parcourez cette liste dans l'ordre ; seule la dernière étape touche à la base de données.

  1. Demandez à un autre administrateur. Admin → Utilisateurs → icône de modification → Mot de passe définit un nouveau mot de passe sur n'importe quel compte local. C'est la voie normale, et elle ne demande aucun accès au serveur.
  2. Utilisez la réinitialisation en libre-service. Le lien Mot de passe oublié de la page de connexion envoie un lien de réinitialisation par e-mail. Il ne fonctionne que pour les comptes locaux, et uniquement si SMTP est configuré — une instance sans serveur de messagerie n'a pas de réinitialisation en libre-service. Les comptes SSO n'ont pas de mot de passe à réinitialiser ; corrigez-les chez le fournisseur d'identité.
  3. Réinitialisez-le dans la base de données. Uniquement lorsque plus personne ne peut se connecter en tant qu'administrateur.

Réinitialiser un mot de passe directement dans la base de données

Générez l'empreinte avec la fonction de hachage de l'application elle-même, afin que la valeur stockée corresponde exactement à ce qu'attend la vérification de connexion. Ne la bricolez pas avec htpasswd ou openssl :

docker compose exec -it backend python -c \
  "from app.core.security import hash_password; print(hash_password(input('New password: ')))"

Saisir le mot de passe à l'invite le tient à l'écart de l'historique de votre shell. Copiez la ligne $2b$… affichée, puis :

docker compose exec -T db psql -v ON_ERROR_STOP=1 -U turboea -d turboea -c \
  "UPDATE users
      SET password_hash = '<empreinte collée ici>',
          auth_provider = 'local',
          is_active = true,
          password_setup_token = NULL
    WHERE lower(email) = lower('[email protected]');"

Chaque clause a sa raison d'être, car la connexion les vérifie toutes :

Clause Sa raison d'être
password_hash Le nouvel identifiant secret. Gardez-le entre apostrophes simples — les empreintes bcrypt contiennent $, que le shell développerait sinon.
auth_provider = 'local' Un mot de passe n'authentifie jamais un compte marqué SSO.
is_active = true Un compte inactif est refusé après la vérification du mot de passe, ce qui ressemble exactement à un mot de passe erroné.
password_setup_token = NULL Annule tout lien d'invitation encore en circulation, pour qu'il ne puisse pas écraser plus tard le mot de passe que vous venez de définir.

Si le compte a également été rétrogradé, rétablissez le rôle dans la même instruction en ajoutant role = 'admin'.

Attendez-vous à UPDATE 1. UPDATE 0 signifie qu'aucune ligne ne correspondait — les adresses e-mail sont stockées telles qu'elles ont été saisies, et c'est le lower() des deux côtés qui rend la comparaison insensible à la casse. Pour trouver la bonne ligne au préalable :

docker compose exec -T db psql -U turboea -d turboea -c \
  "SELECT email, role, is_active, auth_provider FROM users ORDER BY email;"

Deux points supplémentaires à connaître :

  • Adaptez la connexion. Utilisez vos propres POSTGRES_USER / POSTGRES_DB si vous les avez modifiés. Sur un service PostgreSQL géré, il n'y a pas de conteneur db — connectez-vous directement avec psql.
  • Les autres sessions restent connectées. Les sessions sont des JWT sans révocation côté serveur : les jetons émis avant la réinitialisation restent valides jusqu'à leur expiration (ACCESS_TOKEN_EXPIRE_MINUTES, 24 heures par défaut). Changer un mot de passe ne déconnecte personne.

L'exception admise à « ne modifiez pas la base de données »

Modifier directement la base de données est par ailleurs un piège, et à juste titre. Cette procédure est sûre parce qu'elle met à jour quelques colonnes d'une seule ligne users : elle ne change aucun schéma, elle ne peut donc pas entrer en conflit avec une future migration, et c'est le seul état que l'interface et l'API ne peuvent par définition pas réparer — il faut un accès pour rétablir l'accès.

Environnements et gouvernance des versions

Pour la plupart des organisations, deux environnements (Staging + Production) suffisent, car les mises à niveau sont des images publiées par l'éditeur, pas des builds personnalisés — vous validez, vous ne développez pas. Une chaîne complète Dev/SIT/UAT/Prod n'apporte de la valeur que si vous construisez des extensions personnalisées ou des intégrations lourdes.

Environnement Rôle Remarques
Dev / bac à sable (optionnel) Essayer des changements de métamodèle, démos SEED_DEMO=true pour le jeu de données de démonstration ; RESET_DB=true repart de zéro.
Staging Valider les nouvelles versions en premier Données proches de la production ; reçoit les nouveaux tags en premier.
Production Tag épinglé, sauvegardes, mises à niveau en fenêtre de maintenance Jamais latest, jamais RESET_DB.

Deux bonnes façons d'amener des données réalistes en staging :

  • Transfert d'espace de travail : exportez l'espace de travail de production sous forme de bundle .zip et importez-le en staging. Les secrets (identifiants SMTP, SSO, IA, ServiceNow) sont retirés par conception et ne quittent jamais l'instance.
  • Restauration de base de données : restaurez un pg_dump de production dans la base de staging (sur un service managé, un clone ou une restauration à un instant donné de l'instance de production convient très bien). Les secrets chiffrés en base sont dérivés de SECRET_KEY ; le staging a donc besoin du même SECRET_KEY, sinon vous y ressaisissez les identifiants d'intégration.

Côté gouvernance :

  • Traitez le fichier .env et le TURBO_EA_TAG épinglé comme de la configuration-as-code — conservez-les dans votre Git interne et faites des mises à niveau un changement revu (une pull request qui monte le tag).
  • Comme le staging et la production tirent le même tag GHCR épinglé, vous validez l'artefact identique au bit près que vous allez promouvoir.
  • Mettre à niveau le staging → laisser reposer quelques jours → promouvoir le même tag en production.

Pièges courants

  1. Exploiter latest sans épinglage — un docker compose pull de routine devient une mise à niveau imprévue avec des migrations imprévues, au rythme des publications plutôt qu'au vôtre.
  2. Mettre à niveau sans sauvegarde — les migrations sont à sens unique ; la sauvegarde est votre retour en arrière.
  3. Perdre ou changer SECRET_KEY — il signe les JWT et dérive la clé de chiffrement des secrets stockés (identifiants SMTP, SSO, ServiceNow). Le changer rend les secrets stockés indéchiffrables. Traitez-le comme un identifiant de base de données : dans un coffre, stable, sauvegardé.
  4. RESET_DB=true oublié dans un fichier d'environnement — il fait exactement ce qu'il dit, à chaque démarrage.
  5. Modifier la base de données directement — l'état du schéma appartient à Alembic, et du DDL manuel entrera en collision avec les migrations futures. Idem pour les données : passez par l'API ou l'interface afin que les permissions, les événements d'audit et le recalcul de la qualité des données restent corrects. La seule exception admise est la récupération de l'accès administrateur, pour le cas où plus personne ne peut se connecter pour utiliser l'API ou l'interface.
  6. Ne pas persister les volumespostgres_data et backend_data doivent survivre à la recréation des conteneurs ; vérifiez que vos outils d'instantané et de sauvegarde couvrent les deux.
  7. Revenir en arrière sur l'image sans restaurer la base — voir Retour en arrière et reprise.
  8. Pointer vers un PostgreSQL managé sans vérifier sa limite de connexions — le backend a besoin de 30 connexions au maximum par défaut. Un plafond inférieur se manifeste par too many connections for database "turboea" en usage normal ; voir la section « Vérifier la limite de connexions » ci-dessus.