Commit e38ce436 authored by Kourser's avatar Kourser
Browse files

CI : Node vient du dépôt, plus de l'hôte

L'hôte du runner porte Node v18.19.1. Ce dépôt en exige 22, Docusaurus au
moins 20. Le pipeline échouait donc à la construction du site.

Pourquoi ni l'une ni l'autre des issues évidentes
  Mettre l'hôte à jour demande les droits d'administration — que je n'ai pas —
  et surtout déplace la vérité hors du dépôt : plus rien n'y dirait quelle
  version le projet attend, et la prochaine machine reposerait la question.

  Abaisser l'exigence à Node 18 reviendrait à construire une documentation de
  crise avec une version qui ne reçoit plus de correctifs de sécurité.

  Alors Node est installé dans le répertoire du projet, comme pnpm, sans rien
  toucher sur la machine. La version est écrite dans outils/node-ci.sh,
  versionnée avec le reste, et le cache l'évite au passage suivant.

L'archive est contrôlée avant d'être exécutée
  Une archive téléchargée puis exécutée sans contrôle serait une porte
  d'entrée dans la chaîne de construction — c'est par là que passent les
  compromissions d'écosystème. L'empreinte SHA-256 est inscrite dans le dépôt,
  relevée sur nodejs.org. Un écart fait échouer le travail, sans reprise.

Éprouvé, et pas seulement le chemin qui réussit
  L'empreinte inscrite a été confrontée à l'archive Linux x64 réelle : elles
  concordent. Le refus a été exercé avec une empreinte volontairement fausse :
  sortie non nulle, rien d'installé, rien laissé sur disque. Les deux travaux
  ont ensuite été rejoués sous ce Node 22 exact, jusqu'au « mv site/build
  public » — plus aucun avertissement de moteur, 26 pages dans les deux
  langues. Et le second passage se contente du cache.

Ce que la répétition locale ne prouve pas : sur macOS l'empreinte inscrite ne
correspond pas à l'archive téléchargée, le contrôle est donc annoncé comme
impossible et sauté. Sous Linux — le seul cas de l'intégration — il a lieu.

Signed-off-by: default avatarKourser <contact@kourser.bzh>
Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent e20c108f
Loading
Loading
Loading
Loading
Loading
+18 −7
Original line number Diff line number Diff line
@@ -5,8 +5,9 @@
# l'utilisateur gitlab-runner, pas dans un conteneur. Trois conséquences, qui
# expliquent tout ce qui suit :
#
#   — « image: » serait ignoré : on ne le déclare pas, pour ne pas laisser
#     croire que la version de Node est maîtrisée ici.
#   — « image: » serait ignoré : on ne le déclare pas. La version de Node
#     n'en est pas moins maîtrisée — elle est installée par le dépôt, dans le
#     dépôt : voir outils/node-ci.sh.
#   — « services: » serait ignoré aussi : aucune base, aucun cache, aucun
#     stockage objet n'est disponible. La suite complète de garanties ne peut
#     donc pas tourner ici — voir le travail « garanties » plus bas.
@@ -20,10 +21,16 @@ stages: [verifier, publier]

default:
  before_script:
    # pnpm installé dans le répertoire du projet, jamais globalement :
    # l'utilisateur de l'exécuteur n'écrit pas dans /usr/local. La version est
    # celle que déclare packageManager — npm ne résout pas les dépendances
    # « workspace:* » de ce dépôt, pnpm est donc requis.
    # L'hôte porte Node 18 ; ce dépôt en exige 22 et Docusaurus au moins 20.
    # Node est donc installé dans le répertoire du projet, à une version
    # inscrite dans le dépôt et contrôlée par empreinte. Le détail et ses
    # raisons sont dans outils/node-ci.sh.
    - sh outils/node-ci.sh
    - export PATH="$CI_PROJECT_DIR/.ci/node/bin:$PATH"
    # pnpm ensuite, avec ce Node-là, et jamais globalement : l'utilisateur de
    # l'exécuteur n'écrit pas dans /usr/local. La version est celle que déclare
    # packageManager — npm ne résout pas les dépendances « workspace:* » de ce
    # dépôt, pnpm est donc requis.
    - npm install --no-save --no-audit --no-fund --prefix .ci pnpm@9.12.3
    - export PATH="$CI_PROJECT_DIR/.ci/node_modules/.bin:$PATH"
    # Le premier échec suivant sera diagnosticable sans aller-retour.
@@ -32,7 +39,11 @@ default:
    - pnpm config set store-dir "$CI_PROJECT_DIR/.pnpm-store"

cache:
  key:
  # Deux caches séparés, chacun avec sa propre raison de devenir caduc : Node
  # quand sa version change, le magasin pnpm quand le verrou change.
  - key: node-22.23.2
    paths: [.ci/node]
  - key:
      files: [pnpm-lock.yaml]
    paths: [.pnpm-store]

outils/node-ci.sh

0 → 100755
+116 −0
Original line number Diff line number Diff line
#!/bin/sh
# Installe la version de Node exigée par le dépôt — Kastell.
#
#   sh outils/node-ci.sh
#   export PATH="$PWD/.ci/node/bin:$PATH"
#
# Pourquoi cet outil existe
#   L'intégration continue tourne sur un exécuteur « shell » : les travaux
#   s'exécutent sur l'hôte, sous l'utilisateur du runner, sans conteneur. La
#   version de Node est donc celle de la machine — v18 sur la nôtre, quand ce
#   dépôt en exige 22 et Docusaurus au moins 20.
#
#   Les deux issues habituelles ne conviennent pas ici. Mettre l'hôte à jour
#   demande les droits d'administration et déplace la vérité hors du dépôt :
#   plus rien ne dirait quelle version le projet attend. Abaisser l'exigence
#   reviendrait à livrer une documentation de crise construite par une version
#   de Node qui n'est plus corrigée.
#
#   Alors Node est installé dans le répertoire du projet, comme pnpm, sans
#   toucher à la machine. La version est écrite ici, versionnée avec le reste.
#
# Sur la vérification d'empreinte
#   L'archive est comparée à une empreinte inscrite dans ce fichier, publiée
#   par nodejs.org et relevée à la main. Une archive téléchargée puis exécutée
#   sans contrôle serait une porte d'entrée dans la chaîne de construction :
#   c'est par là que passent les compromissions d'écosystème. Un écart fait
#   échouer le travail, il ne déclenche aucune reprise.
#
#   Pour changer de version : relever l'empreinte sur
#   https://nodejs.org/dist/vX.Y.Z/SHASUMS256.txt et modifier les deux
#   constantes ci-dessous ensemble.
#
# Licence : CC0 1.0 Universal, comme les autres outils de ce dossier.

set -eu

VERSION="22.23.2"                                                   # Node 22 « Jod », LTS
SOMME_X64="b294a556e639d64338823920e5866c21c02741742d2e1529ee1a225c1ec9252a"
SOMME_ARM64="013b59cfd2819703a6f4a14ab891fc46fc2a4e3f5bcd92de3fb4929b43e35b30"

racine="${CI_PROJECT_DIR:-$PWD}"
cible="$racine/.ci/node"

# Déjà là, et à la bonne version : le cache a fait son travail.
if [ -x "$cible/bin/node" ] && [ "v$VERSION" = "$("$cible/bin/node" --version 2>/dev/null || true)" ]; then
  echo "Node v$VERSION déjà présent dans .ci/node"
  exit 0
fi

case "$(uname -s)" in
  Linux) systeme="linux" ;;
  Darwin) systeme="darwin" ;;
  *)
    echo "outils/node-ci.sh : système « $(uname -s) » non prévu." >&2
    echo "Installez Node >= 22 vous-même, puis relancez sans cet outil." >&2
    exit 1 ;;
esac

case "$(uname -m)" in
  x86_64 | amd64) arch="x64"; somme="$SOMME_X64" ;;
  aarch64 | arm64) arch="arm64"; somme="$SOMME_ARM64" ;;
  *)
    echo "outils/node-ci.sh : architecture « $(uname -m) » non prévue." >&2
    exit 1 ;;
esac

# Les empreintes inscrites plus haut sont celles des archives Linux. Sur une
# autre plateforme — une répétition en local, par exemple — le contrôle n'a
# rien à quoi se comparer : on le dit, plutôt que de laisser croire qu'il a eu
# lieu. L'intégration continue, elle, est toujours sous Linux.
if [ "$systeme" != "linux" ]; then
  somme=""
  echo "Plateforme $systeme : empreinte non inscrite, contrôle impossible."
fi

archive="node-v$VERSION-$systeme-$arch.tar.gz"
adresse="https://nodejs.org/dist/v$VERSION/$archive"

echo "Node v$VERSION ($systeme-$arch) — téléchargement"
rm -rf "$racine/.ci/node-tmp" "$cible"
mkdir -p "$racine/.ci/node-tmp"

if command -v curl > /dev/null 2>&1; then
  curl -fsSL --retry 3 -o "$racine/.ci/node-tmp/$archive" "$adresse"
elif command -v wget > /dev/null 2>&1; then
  wget -q -O "$racine/.ci/node-tmp/$archive" "$adresse"
else
  echo "outils/node-ci.sh : ni curl ni wget sur cette machine." >&2
  exit 1
fi

if [ -n "$somme" ]; then
  if command -v sha256sum > /dev/null 2>&1; then
    obtenue="$(sha256sum "$racine/.ci/node-tmp/$archive" | cut -d' ' -f1)"
  elif command -v shasum > /dev/null 2>&1; then
    obtenue="$(shasum -a 256 "$racine/.ci/node-tmp/$archive" | cut -d' ' -f1)"
  else
    echo "outils/node-ci.sh : aucun outil d'empreinte disponible." >&2
    exit 1
  fi
  if [ "$obtenue" != "$somme" ]; then
    echo "outils/node-ci.sh : empreinte incorrecte pour $archive." >&2
    echo "  attendue : $somme" >&2
    echo "  obtenue  : $obtenue" >&2
    echo "L'archive n'est pas celle que ce dépôt a validée. Rien n'est installé." >&2
    rm -rf "$racine/.ci/node-tmp"
    exit 1
  fi
  echo "Empreinte vérifiée."
fi

mkdir -p "$cible"
tar -xzf "$racine/.ci/node-tmp/$archive" -C "$cible" --strip-components=1
rm -rf "$racine/.ci/node-tmp"

echo "Node installé dans .ci/node — ajoutez .ci/node/bin au PATH."