Commit 025de76b authored by Kourser's avatar Kourser
Browse files

L'image de chaque révision, publiée plutôt que reconstruite dans la salle (EX-46)

Construire l'artefact sur le poste de la salle coûte des minutes et exige
Internet au pire moment : le registre npm, Docker Hub, et que chaque paquet y
soit encore. Le travail « images » publie l'image de chaque révision de main,
en amd64 et arm64, étiquetée par l'empreinte du commit et par elle seule.

L'installateur tire l'image de la révision exacte qu'il a extraite, jamais
« la dernière », et seulement sur un arbre de travail propre : une
modification locale n'est pas dans l'image, et la tirer ferait tourner autre
chose que le code qu'on a sous les yeux. À défaut, il construit comme avant.

Pas encore actif : le registre de conteneurs n'est pas ouvert sur
git.cythin.eu. Le travail attend CI_REGISTRY et la variable KASTELL_IMAGES ;
l'installateur attend l'adresse du registre dans IMAGE_PUBLIEE, vide pour
l'instant.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent 2e8d6084
Loading
Loading
Loading
Loading
Loading
+41 −0
Changes for .gitlab-ci.yml: 41 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -154,3 +154,44 @@ pages:
    paths: [public]
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

# ── L'image de l'application, publiée par révision (EX-46) ────────────────
#
# Construire l'image sur le poste de la salle coûte des minutes et exige
# Internet au pire moment : le registre npm, Docker Hub, et que chaque paquet
# y soit encore. L'installateur express tire donc l'image de la révision qu'il
# a sous les yeux, quand elle existe, et ne la construit qu'à défaut.
#
# Étiquetée par l'empreinte du commit et par elle seule : l'installateur ne
# tire que la révision exacte qu'il a extraite, jamais « la dernière ». Une
# image qui ne correspondrait pas au code lisible à côté contredirait l'offre
# de code de l'AGPL §13 — et ce qu'on croit avoir relu.
#
# Deux conditions, dites plutôt que découvertes à l'échec : le registre de
# conteneurs doit être ouvert sur l'instance GitLab (sans lui, CI_REGISTRY
# n'existe pas), et l'hôte de l'exécuteur doit porter Docker avec buildx —
# plus l'émulation binfmt pour construire l'autre architecture. D'où la
# variable KASTELL_IMAGES, posée à la main quand ces conditions sont réunies,
# plutôt qu'un travail qui rougit à chaque passage.
images:
  stage: publier
  rules:
    - if: $CI_REGISTRY && $KASTELL_IMAGES && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  # Rien de Node ici : la construction a lieu dans l'image, comme chez
  # l'auto-hébergeur. Le before_script commun installerait Node pour rien.
  before_script: []
  cache: []
  variables:
    PLATEFORMES: linux/amd64,linux/arm64
  script:
    - echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
    - docker buildx inspect kastell-images > /dev/null 2>&1 || docker buildx create --name kastell-images
    - >-
      docker buildx build --builder kastell-images
      --platform "$PLATEFORMES"
      --file docker/Dockerfile
      --build-arg KASTELL_REVISION="$CI_COMMIT_SHA"
      --tag "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA"
      --push .
  after_script:
    - docker logout "$CI_REGISTRY" || true
+1 −0
Changes for cahier-des-charges.html: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -1070,6 +1070,7 @@ donc immuable, et la règle est portée par le schéma.</span></li>
    <li><span class="rid">EX-41</span><span class="rtx">Toute route que l&rsquo;interface appelle existe sur le serveur qui la sert. Des routes importées et jamais enregistrées ont laissé quatre écrans et le bandeau d&rsquo;agent répondre 404 pendant que la suite restait verte.</span></li>
    <li><span class="rid">EX-44</span><span class="rtx">Une instance de salle démarre sans relais de courriel. Rien ne part alors par courriel et rien n&rsquo;en est écrit dans les traces&nbsp;; ce qui n&rsquo;a de sens qu&rsquo;avec un courriel — inscription, lien de connexion, campagne de vérification — est refusé avant d&rsquo;écrire quoi que ce soit, et les écrans retirent ou signalent les voies qui en dépendent. On entre par la porte de jonction&nbsp;; un relais se pose ensuite depuis la console, sans redémarrer. Une instance exposée, elle, refuse toujours de démarrer sans relais.</span></li>
    <li><span class="rid">EX-45</span><span class="rtx">Une personne en crise, sur un poste où Docker tourne, lève une instance de salle en répondant à deux questions et en donnant l&rsquo;identité du premier compte. Tout ce que l&rsquo;installateur décide à sa place est affiché avant d&rsquo;agir&nbsp;; les téléchargements partent pendant les questions&nbsp;; ce qui est lourd ou dont la salle se passe — visioconférence, antivirus, transcription, courriel — est remis à plus tard et dit comme tel. Un <code>.env</code> déjà présent se démarre sur une seule question. Le temps écoulé est mesuré et affiché.</span></li>
    <li><span class="rid">EX-46</span><span class="rtx">L&rsquo;image de l&rsquo;application est publiée par l&rsquo;intégration continue, une par révision, étiquetée par l&rsquo;empreinte du commit et par elle seule. L&rsquo;installation tire l&rsquo;image de la révision exacte qu&rsquo;elle a extraite, jamais « la dernière », et seulement sur un arbre de travail propre&nbsp;; à défaut, elle construit sur place.</span></li>
  </ul>

  <div class="note">
+46 −4
Changes for outils/installer.sh: 46 added lines, 4 removed lines.
Original line number Diff line number Diff line
@@ -61,6 +61,12 @@ cd "$racine"

COMPOSE="docker/compose.yaml"

# L'image de l'application publiée par l'intégration continue, une par
# révision (EX-46) — voir le travail « images » de .gitlab-ci.yml. Vide tant
# que le registre de conteneurs de l'instance GitLab n'est pas ouvert : on
# construit alors l'artefact sur place, comme toujours.
IMAGE_PUBLIEE="${KASTELL_IMAGE_PUBLIEE:-}"

# ── Langue ─────────────────────────────────────────────────────────────────
#
# Devinée avant tout affichage : les refus de prérequis tombent avant la
@@ -955,6 +961,22 @@ case "${1:-}" in
  *)              choix_parcours="" ;;
esac

# tirer_image_publiee <révision> — 0 si l'image de CETTE révision a été tirée
# du registre, et posée sous le nom que la composition lui aurait donné en la
# construisant.
#
# Seulement sur un arbre propre : une modification locale n'est pas dans
# l'image publiée, et la tirer ferait tourner autre chose que le code qu'on a
# sous les yeux — y compris celui dont l'AGPL oblige à offrir la source.
tirer_image_publiee() {
  [ -n "$IMAGE_PUBLIEE" ] && [ -n "$1" ] || return 1
  [ -z "$(git status --porcelain 2>/dev/null)" ] || return 1
  docker pull "$IMAGE_PUBLIEE:$1" > /dev/null 2>&1 || return 1
  # Le relais sort du même Dockerfile, avec une autre commande : même image.
  docker tag "$IMAGE_PUBLIEE:$1" "${projet}-app"
  docker tag "$IMAGE_PUBLIEE:$1" "${projet}-relais"
}

# precharger <fichier d'environnement> — tirer les images et construire
# l'application en tâche de fond, pendant que les questions se posent.
#
@@ -968,10 +990,17 @@ precharger() {
  (
    docker compose -f "$COMPOSE" --env-file "$1" pull postgres redis minio &
    _tirage=$!
    _construction=""
    if tirer_image_publiee "$_revision"; then
      # Le constat passe par un fichier : ce sous-shell ne peut pas poser une
      # variable dans le shell qui l'a lancé.
      : > "$journal_prechargement.publiee"
    else
      KASTELL_REVISION="$_revision" docker compose -f "$COMPOSE" --env-file "$1" build app &
      _construction=$!
    fi
    _s1=0; wait "$_tirage" || _s1=$?
    _s2=0; wait "$_construction" || _s2=$?
    _s2=0; [ -z "$_construction" ] || wait "$_construction" || _s2=$?
    [ "$_s1" -eq 0 ] && [ "$_s2" -eq 0 ]
  ) > "$journal_prechargement" 2>&1 &
  prechargement=$!
@@ -3148,10 +3177,23 @@ if [ -n "$prechargement" ]; then
  fi
fi

# $services est découpé volontairement : c'est une liste de noms construite
# plus haut, jamais une saisie de l'utilisateur.
# L'image publiée de cette révision, si elle a pu être tirée : rien à
# construire alors. Sinon « --build », comme toujours.
construire="--build"
if [ -n "$journal_prechargement" ] && [ -f "$journal_prechargement.publiee" ]; then
  construire=""
elif [ -z "$prechargement" ] && tirer_image_publiee "$revision"; then
  construire=""
fi
if [ -z "$construire" ]; then
  dire "  Image publiée de la révision $revision : rien à construire." \
       "  Published image of revision $revision: nothing to build."
fi

# $services et $construire sont découpés volontairement : ce sont des mots
# construits plus haut, jamais une saisie de l'utilisateur.
# shellcheck disable=SC2086
KASTELL_REVISION="$revision" docker compose "$@" up -d --build $services
KASTELL_REVISION="$revision" docker compose "$@" up -d $construire $services

# « up » ne relance pas un conteneur dont seul un fichier monté a changé : le
# serveur média garderait l'ancienne adresse jusqu'à son prochain redémarrage.
+5 −0
Changes for site/docs/installation.md: 5 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -50,6 +50,11 @@ Les téléchargements partent pendant les questions. `sh outils/installer.sh
express` saute la question « en crise ? », `sh outils/installer.sh complet`
ouvre directement le questionnaire entier.

Quand l'intégration continue publie l'image de l'application, le script tire
celle de la révision exacte qu'il a sous les yeux au lieu de la construire —
seulement si l'arbre de travail est propre : une modification locale n'est pas
dans l'image publiée.

Un `.env` existant n'est jamais écrasé sans votre accord : le script propose de
le compléter — vos valeurs gardées, ce qui manque ajouté —, de le garder tel
quel, ou de le sauvegarder à côté avant d'en écrire un neuf. Ce fichier porte
+4 −0
Changes for site/i18n/en/docusaurus-plugin-content-docs/current/installation.md: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -50,6 +50,10 @@ Downloads start while the questions are asked. `sh outils/installer.sh
express` skips the "in a crisis?" question, `sh outils/installer.sh complet`
goes straight to the full questionnaire.

When continuous integration publishes the application image, the script pulls
the one for the exact revision it has checked out instead of building it —
only on a clean working tree: a local change is not in the published image.

An existing `.env` is never overwritten without your say-so: the script offers to
complete it — your values kept, what is missing added —, to keep it as it stands,
or to save it alongside before writing a new one. That file may hold the secrets