Commit 71a4a840 authored by Kourser's avatar Kourser
Browse files

Le .env se complète, il ne se réécrit plus par défaut

Devant un .env existant, l'installateur savait le garder ou le réécrire. Rien
entre les deux. Or c'est entre les deux que se trouve à peu près toujours ce
qu'on veut d'une mise à jour : garder ce qui est là, prendre ce qui est
nouveau.

Ce que réécrire coûtait
  Un .env en service porte des choses que ce script n'a pas écrites et ne sait
  pas redemander : la clé .p8 d'Apple, le compte de service Firebase, le mot
  de passe du relais SMTP, et toute valeur qu'un exploitant a ajustée à la
  main. La sauvegarde à côté les préservait, mais il fallait ensuite les
  recopier une à une — c'est-à-dire s'en souvenir.

  « Le compléter » devient la première porte, et le défaut.

Deux gestes, dans cet ordre
  Renommer d'abord. Une clé qui a changé de nom n'est pas une clé absente : la
  compléter poserait la valeur d'exemple à côté de la vraie, et comme la
  composition préfère le nouveau nom, c'est l'exemple qui l'emporterait. Le
  même piège que preparer-env.mjs a déjà rencontré, et pour la même raison.

  Ajouter ensuite ce que .env.example a et que le fichier n'a pas, en tirant
  au sort les secrets qui restent marqués — SESSION_SECRET en 32 octets, les
  autres en 24, le mot de passe de base en 16, et DATABASE_URL reconstruite à
  partir de ce que le fichier dit déjà plutôt que d'une base que personne n'a
  créée.

Ce qui n'est jamais touché
  Une valeur déjà présente. Et une clé que .env.example ne connaît pas : elle
  est signalée, jamais retirée, parce qu'elle vient peut-être d'un réglage
  local dont ce script n'a pas à décider.

Contrôlé
  Sur un .env réel d'avant le passage à l'anglais : 29 clés en entrée, 48 en
  sortie, 14 renommées et 19 ajoutées, aucune perdue, aucune valeur modifiée,
  aucun doublon, le mode 600 conservé. Relancer la migration ne trouve plus
  rien à faire et laisse le fichier à l'octet près. Un SESSION_SECRET retiré
  puis remigré revient en 64 caractères hexadécimaux tirés au sort, et non en
  valeur d'exemple. Les trois autres portes — garder, réécrire, ne rien faire
  — répondent toujours, et la réécriture reprend toujours les identifiants
  Postgres devant un volume existant.

Un défaut trouvé en chemin, dans ce commit même : « $_vieux→$_neuf » perdait
son nom de variable, sh happant le premier octet de la flèche — qui n'est pas
de l'ASCII — dans le nom qui la précède. Les accolades le règlent, et c'est le
genre de faute que seule l'exécution montre.

Co-Authored-By: default avatarClaude Opus 5 <noreply@anthropic.com>
parent cbc1b6fa
Loading
Loading
Loading
Loading
Loading
+157 −8
Changes for outils/installer.sh: 157 added lines, 8 removed lines.
Original line number Diff line number Diff line
@@ -197,6 +197,117 @@ valeur_fichier() { # valeur_fichier <fichier> <cle> — idem, hors du .env cou
  sed -n "s/^$2=//p" "$1" | tail -n 1
}

# ── Migration d'un .env existant ───────────────────────────────────────────
#
# Un .env en service porte des choses que ce script n'a pas écrites et ne sait
# pas redemander : la clé .p8 d'Apple, le compte de service Firebase, le mot de
# passe du relais SMTP, et les valeurs qu'un exploitant a ajustées à la main.
# Le réécrire les perd toutes. Le compléter les garde, et c'est presque
# toujours ce qu'on veut d'une mise à jour.
#
# Deux gestes, dans cet ordre. Renommer, parce qu'une clé qui a changé de nom
# n'est pas une clé absente : la compléter ajouterait la valeur d'exemple à
# côté de la vraie. Puis ajouter ce que .env.example a et que le fichier n'a
# pas, en tirant au sort les secrets qui restent à tirer.
#
# Ce qui n'est jamais touché : une valeur déjà présente, et une clé que
# .env.example ne connaît pas — elle est signalée, pas supprimée.

# « nouveau ancien », par paires. L'ancrage sur « = » suffit à empêcher
# APNS_CLE de mordre dans APNS_CLE_ID.
NOMS_MIGRES="
KASTELL_PUBLIC_URL KASTELL_URL_PUBLIQUE
KASTELL_TRUSTED_PROXY KASTELL_PROXY_DE_CONFIANCE
KASTELL_PUSH_RELAY KASTELL_RELAIS_POUSSEE
KASTELL_RELAY_KEY KASTELL_RELAIS_CLE
CLAMAV_HOST CLAMAV_HOTE
LIVEKIT_INTERNAL_URL LIVEKIT_URL_INTERNE
LIVEKIT_WS_INTERNAL LIVEKIT_WS_INTERNE
LIVEKIT_KEY LIVEKIT_CLE
S3_ACCESS S3_ACCES
SESSION_SECRET SECRET_SESSION
APNS_KEY APNS_CLE
APNS_KEY_ID APNS_CLE_ID
APNS_TEAM_ID APNS_EQUIPE_ID
APNS_BUNDLE_ID APNS_PAQUET
APNS_ENVIRONMENT APNS_ENVIRONNEMENT
APNS_CRITICAL_ALERT APNS_ALERTE_CRITIQUE
FCM_PROJECT FCM_PROJET
FCM_ACCOUNT FCM_COMPTE
FCM_KEY FCM_CLE
"

migrer_env() {
  renommees=""
  ajoutees=""
  tirees=""
  mdp_ajout=""

  # ── Renommer sur place ──────────────────────────────────────────────────
  #
  # « sed » sans -i : BSD et GNU ne s'accordent pas sur sa syntaxe, et « cat »
  # dans le fichier d'origine en préserve le mode 600 et l'inode.
  set -- $NOMS_MIGRES
  while [ "$#" -ge 2 ]; do
    _neuf="$1"; _vieux="$2"; shift 2
    if grep -q "^$_vieux=" .env && ! grep -q "^$_neuf=" .env; then
      sed "s/^$_vieux=/$_neuf=/" .env > .env.migration
      cat .env.migration > .env
      rm -f .env.migration
      # Accolades obligatoires : « → » n'est pas de l'ASCII, et sh happe son
      # premier octet dans le nom de la variable qui le précède.
      renommees="$renommees ${_vieux}→${_neuf}"
    fi
  done

  # ── Ajouter ce qui manque ───────────────────────────────────────────────
  #
  # Lecture par redirection et non par tube : un « while read » dans un tube
  # tourne dans un sous-shell, et tout ce qu'on y accumule est perdu au bout.
  : > .env.ajouts
  while IFS= read -r _ligne; do
    case "$_ligne" in
      [A-Z]*=*) ;;
      *) continue ;;
    esac
    _cle="${_ligne%%=*}"
    grep -q "^$_cle=" .env && continue
    _valeur="${_ligne#*=}"
    case "$_valeur" in
      *CHANGEZ-MOI*)
        case "$_cle" in
          POSTGRES_PASSWORD)
            [ -n "$mdp_ajout" ] || mdp_ajout="$(secret 16)"
            _valeur="$mdp_ajout" ;;
          DATABASE_URL)
            # Reconstruite à partir de ce que le fichier dit déjà, sinon elle
            # désignerait une base que personne n'a créée.
            _u="$(valeur_env POSTGRES_USER)"; _b="$(valeur_env POSTGRES_DB)"
            _m="$(valeur_env POSTGRES_PASSWORD)"
            if [ -z "$_m" ]; then
              [ -n "$mdp_ajout" ] || mdp_ajout="$(secret 16)"
              _m="$mdp_ajout"
            fi
            _valeur="postgres://${_u:-kastell}:$_m@localhost:5432/${_b:-kastell}" ;;
          SESSION_SECRET) _valeur="$(secret 32)" ;;
          *) _valeur="$(secret 24)" ;;
        esac
        tirees="$tirees $_cle" ;;
    esac
    printf '%s=%s\n' "$_cle" "$_valeur" >> .env.ajouts
    ajoutees="$ajoutees $_cle"
  done < .env.example

  if [ -s .env.ajouts ]; then
    {
      echo ""
      echo "# ── Added by outils/installer.sh on $(date '+%Y-%m-%d at %H:%M') ──────────"
      cat .env.ajouts
    } >> .env
  fi
  rm -f .env.ajouts
}

# ── Ports ──────────────────────────────────────────────────────────────────
#
# La composition réserve une poignée de ports côté hôte — LiveKit en prend la
@@ -532,17 +643,55 @@ if [ -f .env ]; then
  dire "  instance en service : ce script ne l'écrase pas sans votre accord." \
       "  in service: this script will not overwrite it without your consent."
  echo
  dire "    1) Le garder tel quel, et lever la composition" \
       "    1) Keep it as is, and bring the composition up"
  dire "    2) Le sauvegarder à côté, puis en écrire un neuf" \
       "    2) Save it alongside, then write a fresh one"
  dire "    3) Ne rien faire" \
       "    3) Do nothing"
  dire "    1) Le compléter : vos valeurs sont gardées, ce qui manque est ajouté" \
       "    1) Complete it: your values are kept, what is missing is added"
  dire "    2) Le garder tel quel, et lever la composition" \
       "    2) Keep it as is, and bring the composition up"
  dire "    3) Le sauvegarder à côté, puis en écrire un neuf" \
       "    3) Save it alongside, then write a fresh one"
  dire "    4) Ne rien faire" \
       "    4) Do nothing"
  echo
  demander_b "Votre choix" "Your choice" "1"
  case "$REPONSE" in
    1) ecrire_env="non" ;;
    2)
    1)
      ecrire_env="non"
      sauvegarde=".env.sauvegarde-$(date '+%Y%m%d-%H%M%S')"
      cp -p .env "$sauvegarde"
      migrer_env
      echo
      dire "  ✓ .env complété — copie d'avant sous $sauvegarde" \
           "  ✓ .env completed — copy of the previous one under $sauvegarde"
      if [ -n "$renommees" ]; then
        dire "    Renommées, valeurs conservées :$renommees" \
             "    Renamed, values kept:$renommees"
      fi
      if [ -n "$ajoutees" ]; then
        dire "    Ajoutées :$ajoutees" \
             "    Added:$ajoutees"
      fi
      if [ -n "$tirees" ]; then
        dire "    Tirées au sort :$tirees" \
             "    Drawn at random:$tirees"
      fi
      if [ -z "$renommees$ajoutees" ]; then
        dire "    Rien à compléter : le fichier était déjà à jour." \
             "    Nothing to complete: the file was already up to date."
      fi
      # Une clé que .env.example ne connaît pas est signalée, jamais retirée :
      # elle vient peut-être d'un réglage local que ce script ignore.
      inconnues=""
      for _c in $(sed -n 's/^\([A-Z][A-Z0-9_]*\)=.*/\1/p' .env); do
        grep -q "^$_c=" .env.example || inconnues="$inconnues $_c"
      done
      if [ -n "$inconnues" ]; then
        echo
        dire "  ⚠  Clés que .env.example ne connaît pas, laissées en place :$inconnues" \
             "  ⚠  Keys .env.example does not know, left in place:$inconnues"
      fi
      ;;
    2) ecrire_env="non" ;;
    3)
      sauvegarde=".env.sauvegarde-$(date '+%Y%m%d-%H%M%S')"
      cp -p .env "$sauvegarde"
      ancien_env="$sauvegarde"