Installation¶
Prérequis¶
- Ubuntu 24.04 ou plus récent, 26.04 sur le serveur du CHU. Le script d'installation utilise
apt,systemdetdpkg; aucune autre distribution n'est prise en charge. - Python 3, présent d'origine sur Ubuntu. Le script d'installation lui confie la lecture des
valeurs déclarées dans les fichiers d'environnement, par le
contexte.pyque le disque de livraison apporte à côté de lui ; l'outil de sauvegarde en a besoin lui aussi. - Un GPU NVIDIA. Le script s'arrête si aucun GPU NVIDIA n'est détecté. La configuration de référence est une carte de 16 Go de VRAM, sur laquelle gemma4:12b et Whisper large-v3 cohabitent.
- Un accès Internet pendant l'installation, même lent : dépôts Ubuntu, Docker et NVIDIA pour les paquets du système, puis le registre pour les images du backend et du frontend, et Hugging Face pour le cache du modèle d'embedding, qui pèse 553 Mo et se reconstruit seul. Avec un disque de livraison, ni les modèles ni les images des trois moteurs ne sont téléchargés : ils viennent du disque, et c'est ce qui rend l'installation possible sur un lien de 1 à 2 Mb/s.
- Un jeton de lecture du registre. Les images du backend et du frontend sont des paquets privés : sans jeton, le tirage est refusé. Voir Le jeton du registre.
- Un accès au dépôt Git, pour une installation depuis les sources seulement. Le dépôt est privé et la machine reçoit alors une clé de déploiement en lecture seule. Un serveur installé depuis un disque de livraison n'a besoin d'aucun des deux : le script d'installation est sur le disque, à côté de ce qu'il installe.
- Une soixantaine de gigaoctets libres : les modèles occupent trente gigaoctets dans
~/.cause/, dont 4,4 pour le second modèle de transcription, et les images vingt-quatre de plus sous/var/lib/docker, dont seize et demi pour Whisper et Ollama et 6,9 pour Qwen3-ASR. Ce dernier chiffre est ce que les fichiers de l'image posent une fois dépliés, et non les 10,5 Go quedocker imagesannonce, qui sont la somme de ses couches. - Un jeton deSEC pour le certificat HTTPS, voir HTTPS. Sans certificat, l'application n'est pas joignable.
- Optionnel : une interface Wi-Fi capable du mode point d'accès, si vous voulez le hotspot intégré, et une autre voie d'accès à la machine que cette interface, câble ou seconde carte. Le point d'accès prend l'interface pour lui ; le script refuse de prendre celle qui porte votre session.
Pilote NVIDIA¶
Si aucun pilote n'est présent, le script installe nvidia-driver-550. Sur les cartes récentes
de génération Blackwell, par exemple la RTX 5060 Ti, ce pilote fermé ne fonctionne pas : seul le
module ouvert convient, installé en DKMS pour survivre aux mises à jour du noyau. Installez dans
ce cas le pilote vous-même avant de lancer le script, en suivant Dépannage GPU.
Accès au dépôt¶
Cette étape ne concerne que les machines qui construisent les images depuis les sources. Sur un serveur livré, passez directement à Installer depuis un disque de livraison.
Générez une clé dédiée sur le serveur, sans phrase de passe, et déclarez-la comme clé de déploiement en lecture seule sur le dépôt GitHub.
ssh-keygen -t ed25519 -C "cause-deploy" -f ~/.ssh/cause_deploy
cat >> ~/.ssh/config << 'EOF'
Host github.com
IdentityFile ~/.ssh/cause_deploy
IdentitiesOnly yes
EOF
cat ~/.ssh/cause_deploy.pub
ssh -T git@github.com
Lancer l'installation¶
Sur un serveur client, branchez le disque de livraison et lancez le script qui est dessus :
sudo bash /media/<vous>/<disque>/livraison-cause-<version>/setup.sh
Il n'y a rien d'autre à récupérer : le disque porte le script, les modèles et la version à installer. Voir Installer depuis un disque de livraison.
Depuis les sources, pour une machine de développement ou pour le laboratoire de l'éditeur :
git clone git@github.com:zero-filtre/cause.git
cd cause
git fetch --tags
git checkout v<version>
sudo ./scripts/setup.sh
La version à installer est le dernier tag publié. Le script est idempotent, dans les deux cas : le relancer ne casse rien, mais il réécrit certains fichiers qu'il gère, listés dans Exploitation.
Le script enchaîne :
- Docker et le plugin Compose, installés s'ils manquent. Votre utilisateur est ajouté au
groupe
docker, ce qui impose un redémarrage. - Pilote NVIDIA, voir ci-dessus. Un redémarrage est demandé si le pilote vient d'être posé.
Dans les deux cas le script s'arrête et vous demande de le relancer après
sudo reboot. - NVIDIA Container Toolkit, puis un test
docker run --gpus allavecnvidia-smi. - Répertoires sous
~/.cause/, et écriture deCAUSE_HOMEdans le fichier.envdu dépôt, ou dans~/.cause/.envquand le script est lancé depuis un disque. Seule cette ligne est réécrite : sur un serveur livré, le même fichier porte la version et le modèle. - Matériel de livraison : lancé depuis un disque, le script reconnaît le sien et ne demande
rien. Lancé depuis le dépôt, il cherche un disque externe portant un dossier
livraison-cause-<version>, propose ce qu'il trouve, et accepte aussi un chemin saisi à la main ; laisser vide construit les images depuis les sources, ce qui est le mode de développement. Voir Installer depuis un disque de livraison. - Variables backend : le fichier
.env.backendest généré à partir de votre environnement, et votre fichier de réglages.env.backend.overridesest créé s'il n'existe pas, puis laissé intact, voir Régler l'application. Puis la remontée des évaluations : le script nomme votre installation avec le numéro de licence de la machine, lu dans~/.cause/licence.lic, et ne vous demande ce nom que si la licence n'est pas encore émise. Il vous demande ensuite les deux clés du stockage de l'éditeur, la secrète en saisie masquée, et les pose dans le.env.backendqu'il génère, où il les reprend à chaque passage suivant. Votre fichier de réglages n'est pas touché : il ne doit porter que ce que vous y avez écrit. Un second passage ne vous demande rien et ne change rien. Sans personne devant la machine, rien n'est demandé et rien n'est écrit ;CAUSE_SANS_QUESTIONS=1supprime ces questions pour une installation en série. Voir Configuration. - Portainer sur
https://127.0.0.1:9443, dont le compte se crée au premier accès. - Certificat HTTPS si le fichier
desec.iniest présent, voir HTTPS. - Construction et démarrage des quatre images, ou, sur un serveur livré, chargement des moteurs depuis le disque et tirage du backend et du frontend depuis le registre. La première construction prend cinq à dix minutes. Le script attend ensuite que chaque service soit en bonne santé, dans l'ordre Whisper, Ollama, backend, frontend, puis que le modèle Ollama soit téléchargé, ce qui peut prendre jusqu'à une heure selon la connexion.
- Sources d'empreinte machine : le script vérifie que les trois fichiers d'identité sont lisibles dans le conteneur backend et le dit. Une source manquante n'est qu'un avertissement, deux sont un échec : en production le serveur refuserait de démarrer. Voir La licence machine.
- Hotspot Wi-Fi nommé
Cause, sans mot de passe, avec des QR codes imprimables dans~/.cause/qrcodes/print.html. Le script reconnaît le gestionnaire réseau de la machine, NetworkManager ou netplan avecsystemd-networkd, et refuse de reprendre l'interface qui porte votre session SSH ou la route par défaut. Désactivez l'étape avecCAUSE_NO_HOTSPOT=1. Voir Hotspot Wi-Fi.
Installer depuis un disque de livraison¶
C'est la procédure normale pour un serveur client. Le disque est préparé par l'éditeur et porte ce que la connexion du client ne rattraperait pas : les modèles de langue et de transcription, les natifs d'inférence, et les images des trois moteurs, Whisper, Qwen3-ASR et Ollama. Leurs archives, celles qui voyagent sur le disque, font dix gigaoctets à elles trois, 3,0 Go pour Whisper, 3,4 pour Qwen3-ASR et 3,5 pour Ollama, soit onze à vingt-deux heures de tirage sur un lien de 1 à 2 Mb/s. Elles sont presque entièrement de l'amont : 7,5 Go de base CUDA, 5 Go d'amont Ollama, 3,4 Go de bibliothèques NVIDIA pour Qwen3-ASR, et quelques centaines de mégaoctets d'assemblage.
Les images du backend et du frontend, elles, sont tirées du registre à la version que le disque déclare. Elles changent à chaque version, et ne pèsent que la couche du jar et celle du bundle, soit une vingtaine de minutes sur le même lien. C'est ce partage qui permet de mettre à jour l'application sans retourner chez le client, voir Mettre à jour.
Le script d'installation est sur le disque, à la racine du dossier de livraison, avec le
contexte.py dont il se sert pour lire les valeurs déclarées dans les fichiers d'environnement.
Le serveur n'a donc ni dépôt à cloner ni clé de déploiement à recevoir, et ces deux fichiers sont
tout le code source qui parte chez le client. Lancé de là, le script reconnaît son propre disque
et ne demande rien. Il enchaîne alors :
- Il contrôle les fichiers du disque, par leur nom et par leur taille, à partir du fichier
TAILLES.txtque le disque porte. C'est instantané, 0,08 seconde pour trente gigaoctets. Un fichier absent, ou dont la taille a changé, est nommé puis l'installation continue : c'est à vous de décider. Voir Vérifier un disque sans rien installer. - Il met les modèles en place dans
~/.cause/, sans jamais approcherconsultations/,patients/ni aucun autre dossier métier. Le script sert donc aussi bien à installer qu'à mettre à jour une machine qui soigne déjà. Seul ce qui manque ou a changé est recopié : les dates de modification viennent du disque, qui les a préservées, et disent ce qui est déjà en place. - Il charge les images des moteurs depuis le disque, une à deux minutes chacune, puis les
étiquette à la version livrée. L'archive, elle, ne porte pas la version dans son nom :
images/cause-whisper.tar,images/cause-qwen-asr.tar,images/cause-ollama.tar, les couches des moteurs ne changeant pas quand l'application change. Une image déjà présente n'est pas relue, et une montée de version qui retrouve les couches déjà chargées ne fait que poser l'étiquette neuve. Une archive absente est nommée et l'installation continue, comme pour le reste du disque. - Il pose le compose du client en
~/.cause/docker-compose.yml, avec son fichier.env, auquel il ajoute le chemin deCAUSE_HOME. Sur une machine qui a aussi le dépôt, ledocker-compose.ymldu dépôt n'est pas touché : ungit pullle réécrirait, et plus personne ne saurait lequel des deux fait foi. - Il déclare
COMPOSE_FILEdans votre profil, pour que les commandesdocker composedes manuels visent le compose du client où que vous les lanciez. Voir Quel compose fait foi. - Il se connecte au registre, puis tire le backend et le frontend au lieu de les
construire, et démarre. Les trois
moteurs ne sont cherchés dans aucun registre : leur service porte
pull_policy: never, parce que la machine est leur seule source. Si l'image manque, le démarrage s'arrête en la nommant.
Le disque peut porter plusieurs modèles, celui qui tourne et des replis. Le modèle actif est la
ligne OLLAMA_MODEL du fichier .env, et en changer ne demande rien d'autre que de modifier cette
ligne puis docker compose -f ~/.cause/docker-compose.yml up -d.
Ce même .env porte deux lignes de version, et ce n'est pas un doublon : CAUSE_VERSION est
celle du backend et du frontend, qui se tirent du registre, et CAUSE_MOTEURS celle des trois
images que le disque a apportées. Les deux ne changent pas au même rythme.
Il porte enfin KAUZE_DOMAINE, le nom du site, par exemple chu.kauze.tech. Le domaine n'est pas
écrit dans l'image du frontend, de sorte qu'une seule image serve tous les sites : c'est cette
ligne qui dit vers quel nom nginx redirige, et c'est pour ce nom que le certificat est émis. En
changer demande un docker compose up -d et un certificat réémis, voir HTTPS.
Le second moteur de transcription, livré à l'arrêt¶
Qwen3-ASR transcrit les actes dictés, Whisper restant sur les actes interactifs. Son image voyage
sur le disque comme les deux autres et le script la charge, ses 4,4 Go de poids sont posés dans
~/.cause/qwen-asr/hub par la mise en place, mais le compose du client le déclare derrière le
profil qwen : docker compose up -d ne le démarre pas. C'est la VRAM qui décide et elle se
mesure sur place : les deux cartes de la machine du CHU montent déjà à 26,5 Go au pic sous charge,
quand le moteur demande 5,2 Go avec sa fenêtre audio bornée à une minute.
Le réveiller ne demande donc aucun téléchargement, une seule commande :
docker compose --profile qwen up -d qwen-asr
Démarré, le moteur ne transcrit encore rien : c'est ASR_DICTATED_PROVIDER qui lui envoie la
dictée, voir Configuration.
Deux réglages du fichier .env comptent :
QWEN_MAX_SECONDS, la fenêtre audio, qui décide à elle seule si le moteur tient dans ce qu'il
reste de carte, et QWEN_DEVICE, qui nomme la carte, par exemple cuda:1, plutôt que de laisser
auto prendre la première. Pour le rendormir : docker compose stop qwen-asr.
La banque audio de mesure¶
Le disque porte aussi test-audio/, six cent quatre-vingt-cinq mégaoctets de voix de
volontaires dont se sert la campagne de charge jouée sur la machine installée. Elles voyagent
toujours avec le disque, le lien du client ne les rapatriant pas.
L'installation ne les pose pas : un serveur qui soigne n'a aucune raison de recevoir du
matériel d'essai. Pour les installer dans ~/.cause/test-audio, il faut le demander :
CAUSE_AVEC_TEST_AUDIO=1 sudo bash /media/<vous>/<disque>/livraison-cause-<version>/setup.sh
La campagne de qualité, elle, n'a besoin de rien sur la machine : les consultations qu'elle fait rédiger partent dans le corps de la requête.
Le jeton du registre¶
Les images du backend et du frontend sont des paquets privés, le jar portant les prompts et tout le code du produit. Le serveur a donc besoin d'un jeton, qui n'a que le droit de lire les paquets.
Créez sur GitHub un jeton personnel portant le seul droit read:packages, puis posez-le sur le
serveur, avant la première installation :
umask 077
echo CAUSE_GHCR_USER=<compte> > ~/.cause/.env.ghcr
echo CAUSE_GHCR_TOKEN=<jeton> >> ~/.cause/.env.ghcr
L'umask compte : le script retire les droits des autres comptes s'il en trouve, et le dit. Il
ne les élargit jamais, un fichier déjà plus fermé reste tel quel. Sans ce fichier et sans
connexion déjà en place, l'installation s'arrête avant le tirage en nommant le fichier
attendu et le droit à donner au jeton.
Ce fichier ne part jamais sur le disque de livraison, qui passe d'un client à l'autre : la
préparation du disque n'y dépose que ce qu'elle y écrit elle-même. Un serveur déjà connecté au
registre par un sudo docker login ghcr.io n'a pas à garder ce fichier : le script le constate
et passe.
Le script remplit deux trousseaux : celui de root, qui tire les images pendant l'installation,
et celui de votre compte, qui les tirera à chaque mise à jour. Sans le second, le docker compose
pull de la section « Mettre à jour » répond « unauthorized ».
Quand le jeton expire, le remplacer dans ce fichier suffit ; la mise à jour suivante s'y reconnecte toute seule. Si le registre refuse le jeton du fichier alors que le serveur y est déjà connecté, l'installation continue avec la connexion en place et le signale.
Vérifier un disque sans rien installer¶
cd /media/<vous>/<disque>/livraison-cause-<version>
cat VERSION
while read -r o c; do [ "$(stat -Lc %s "$c")" = "$o" ] || echo "ECART $c"; done < TAILLES.txt
VERSION nomme la version livrée, le modèle actif, ses replis, les deux images à tirer du
registre et les trois que le disque apporte.
TAILLES.txt déclare la taille attendue de chaque fichier, une ligne par fichier, dans le format
de sha256sum : la taille, deux espaces, le chemin. Cela se lit à l'oeil autant que par la boucle
ci-dessus, qui ne nomme que les écarts et ne demande rien d'autre que les outils de base du
système. Le script d'installation et son contexte.py sont inventoriés comme le reste du disque.
Le disque ne porte aucune liste de condensés : les blobs d'Ollama et de Hugging Face portent leur condensé en guise de nom, et un doute sur l'un d'eux se lève avec le fichier seul :
sha256sum modeles/ollama/models/blobs/sha256-a8bdd07a...
Si le condensé calculé n'est pas le nom du fichier, ce blob est abîmé.
Déposer la licence¶
En production, le serveur ne démarre qu'avec une licence émise pour cette machine. Après la
première installation, relevez l'empreinte affichée par l'écran d'accueil ou par
curl -s http://localhost:8090/api/licence, transmettez-la à l'éditeur, déposez le fichier reçu
en ~/.cause/licence.lic puis docker compose restart backend. La procédure complète, y compris
après un remplacement de matériel, est dans La licence machine.
Premier compte administrateur¶
Au premier démarrage, si aucun compte ADMIN n'existe, le backend crée l'utilisateur admin
avec un mot de passe aléatoire de douze caractères et l'écrit dans ses logs :
docker compose logs backend | grep 'Mot de passe'
Changez ce mot de passe dès la première connexion, depuis l'onglet Utilisateurs de l'administration. Il reste lisible dans le fichier de log du jour tant que celui-ci existe.
Si un utilisateur nommé admin existe déjà avec un autre rôle, aucun compte n'est créé et un
avertissement apparaît dans les logs.
Mettre à jour¶
Sur un serveur livré, la mise à jour applicative est une ligne à changer dans
~/.cause/.env, puis deux commandes :
sed -i 's/^CAUSE_VERSION=.*/CAUSE_VERSION=<version>/' ~/.cause/.env
docker compose pull
docker compose up -d
Le tirage demande le jeton du registre : un « denied » à cette étape veut dire que le jeton manque, a expiré ou a perdu son droit, voir Le jeton du registre.
Seuls le backend et le frontend sont tirés, parce que le compose dit des moteurs qu'ils ne sont dans aucun registre. Et seules les couches qui ont changé descendent : les bases Java et nginx sont épinglées et déjà sur la machine, donc comptez la couche du jar et celle du bundle, une vingtaine de minutes sur un lien de 1 à 2 Mb/s.
Changer de moteur est une autre affaire : dix gigaoctets d'archives ne passent pas
par cette connexion. Cela demande un nouveau disque, ou un portable branché sur le réseau de
l'établissement, puis le script du disque, qui charge l'image et met CAUSE_MOTEURS à jour. Seule
l'image qui a bougé voyage, et elle se compte en gigaoctets, pas en minutes.
Sur une machine installée depuis les sources, chaque version publiée porte un tag git, et la mise à jour consiste à se placer sur le tag voulu et à reconstruire :
git fetch --tags
git checkout v<version>
docker compose build
docker compose up -d
Les données de ~/.cause/ ne sont pas touchées, dans les deux cas. Les fichiers de consultation
sont migrés à la lecture si leur format a évolué.
Les mises à jour des paquets du système sont une autre opération, et elles ne sont pas automatiques : voir Mises à jour du système.