Cartographie complète (liste_taches/coherence_taches), briefs tacheN + gates validation_tacheN, design tâche 2 (docs/design/tache2/), specs/plans jalon 1-2 et tâche 1.9/2 (Phase 1, Phase 2, SJ-0→3). Validations consignées (1.9 ✅, 2-8 🟡). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
10 KiB
30 — Scripts personnalisés / post-install : modèle moteur
Axe E + livrables §4.1/§4.7. Conçu pour cocher
validation_tache2.md §8(« scripts post-install et profils personnalisés »). Le catalogue détaillé est renvoyé à la tâche 4 ; ce document pose le mécanisme moteur, les manifestes, les champs dynamiques et les garde-fous.
1. Principe UX et absence d'interactivité SSH
- Interdiction stricte des questions interactives au milieu d'un script SSH. Toute question nécessaire devient un champ de formulaire dans la webapp avant exécution.
- Les profils post-install sont cochables ; cocher un profil déplie ses champs obligatoires.
- Chaque profil fournit un manifeste :
id,label,description,fields, valeurs par défaut, validations, prévisualisation,risk,requiresConfirmation. - Le bouton d'exécution reste désactivé tant que les champs requis des profils cochés ne sont pas valides.
- La webapp propose une preview du template rendu avant exécution (
preview_template), avec masquage des secrets et signalement des changements réseau/reboot. - Les scripts s'exécutent en mode non interactif ; s'ils détectent une décision non fournie, ils échouent avec une erreur structurée au lieu de bloquer.
Stockage : install_profiles (catalogue + manifest_json), install_recipes/install_recipe_versions (scripts versionnés + sha256), machine_profile_state (état par machine, variables non sensibles), script_variables_presets (préréglages réutilisables). Cf. tache1.9.md §9.
2. Profils post-install attendus (composables)
| Profil | Rôle | Risque | Confirmation |
|---|---|---|---|
bootstrap_root |
Première prépa après DHCP/su - : installe sudo, resolvconf, ca-certificates, curl ; ajoute l'opérateur au groupe sudo ; vérifie sudo. |
low | non |
identity_network |
Hostname, domaine/search .home, /etc/hosts, IP statique dans /etc/network/interfaces. |
network_change | oui |
base_tools |
Outils de base sans vim : nano, less, bash-completion, tmux, screen, htop, iotop, ncdu, tree, rsync, unzip, zip, tar. |
low | non |
network_tools |
iproute2, iputils-ping, dnsutils, traceroute, net-tools(opt), tcpdump, nmap, mtr-tiny, lsof, netcat-openbsd. |
low | non |
dev_git |
git, curl, wget, jq, yq, gnupg, lsb-release ; build-essential optionnel. |
low | non |
sharing |
samba, nfs-kernel-server, avahi-daemon, libnss-mdns, configurables séparément. |
medium | oui |
docker_official |
Docker Engine depuis le dépôt officiel Debian : docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin, docker-compose-plugin ; ajout user au groupe docker ; dossier Compose dans le home ; reboot/reconnexion si nécessaire. |
medium | oui |
vm_guest_tools |
qemu-guest-agent ou open-vm-tools selon hyperviseur choisi. |
low | non |
| Optionnels (non installés par défaut) | security_basic, backup_tools, monitoring, mail_notify, time_sync, storage_tools. |
variable | selon profil |
Les scripts hardware/drivers/benchmark ne sont jamais installés par défaut et exigent validation (voir
60-profils-os-machine.md).
3. Champs dynamiques générés
identity_network:newHostname,domain/search,interfaceName,staticAddress(CIDR, ex.10.0.x.y/22),gateway(défaut10.0.0.1),dnsNameservers(défaut10.0.0.1,10.0.0.10),reconnectHost.docker_official:dockerUser(ex.gilles),dockerHomeDir/composeRoot(ex./home/gilles/docker),installComposePlugin,rebootAfterInstall.sharing: choix séparé Samba/NFS/mDNS, noms de partages, chemins autorisés, utilisateurs/groupes si nécessaire.vm_guest_tools: type d'hyperviseur / paquet cible.
Les champs peuvent être préremplis depuis la machine (machine.name, IP DHCP, interface primaire détectée par machine_probe, utilisateur SSH), mais restent modifiables avant validation.
Exemple de manifeste (attendu dans la spec)
{
"id": "identity_network",
"label": "Hostname + IP statique",
"requiresConfirmation": true,
"risk": "network_change",
"fields": [
{ "name": "newHostname", "type": "hostname", "required": true },
{ "name": "domain", "type": "string", "required": true, "default": "home" },
{ "name": "interfaceName", "type": "select", "required": true, "defaultFrom": "detected.primaryInterface" },
{ "name": "staticAddress", "type": "ipv4_cidr", "required": true },
{ "name": "gateway", "type": "ipv4", "required": true, "default": "10.0.0.1" },
{ "name": "dnsNameservers", "type": "ipv4_list", "required": true, "default": ["10.0.0.1", "10.0.0.10"] },
{ "name": "reconnectHost", "type": "ipv4", "required": true, "defaultFrom": "staticAddress.ip" }
]
}
Types de champ proposés : string, hostname, ipv4, ipv4_cidr, ipv4_list, select, bool, int, path, secret (jamais sérialisé en clair, jamais envoyé à Hermes/MCP). defaultFrom référence une valeur détectée par machine_probe.
4. Templates custom attendus (pseudo-shell)
Tous : LC_ALL=C, DEBIAN_FRONTEND=noninteractive, marqueurs ===SU:CUSTOM_*===, ===SU:EXIT=N===, sortie parsable, log brut archivé. Échec contrôlé si décision manquante.
4.1 custom/bootstrap-root.sh.tpl
#!/bin/sh
export LC_ALL=C
export DEBIAN_FRONTEND=noninteractive
echo "===SU:CUSTOM_BOOTSTRAP==="
apt-get update -qq 2>&1
apt-get install -y sudo resolvconf ca-certificates curl 2>&1
CODE=$?
# Ajoute l'opérateur au groupe sudo (variable de formulaire, non secret).
usermod -aG sudo "{{operatorUser}}" 2>&1 || echo "WARN usermod"
# Vérifie sudo
su - "{{operatorUser}}" -c 'sudo -n true' 2>&1 && echo "SUDO_OK" || echo "SUDO_CHECK_PENDING"
echo "===SU:EXIT=${CODE}==="
4.2 custom/identity-network.sh.tpl
#!/bin/sh
export LC_ALL=C
echo "===SU:CUSTOM_IDENTITY==="
# Sauvegardes avant modification.
cp -a /etc/hosts "/etc/hosts.su.bak.$(date +%s)" 2>/dev/null
cp -a /etc/network/interfaces "/etc/network/interfaces.su.bak.$(date +%s)" 2>/dev/null
OLD_IP="{{dhcpEndpoint}}"
echo "OLD_ENDPOINT=${OLD_IP}"
hostnamectl set-hostname "{{newHostname}}" 2>&1 || echo "hostname_failed"
# Réécrit /etc/network/interfaces pour {{interfaceName}} en statique {{staticAddress}}.
# (rendu détaillé en tâche 4 ; échoue proprement si interface absente)
echo "NEW_ENDPOINT={{reconnectHost}}"
echo "RECONNECT_REQUIRED=1"
echo "===SU:EXIT=0==="
Le script ne coupe jamais la connexion sans stratégie de reconnexion planifiée par la webapp. Si reboot requis → mécanisme reboot_verified. Après application, la webapp vérifie la reconnexion sur la nouvelle IP/hostname et met à jour la machine si le retour est confirmé. Erreurs distinguées : network_config_invalid, interface_not_found, dns_config_failed, reconnect_failed, hostname_failed, sudo_setup_failed.
4.3 custom/install-package-groups.sh.tpl
#!/bin/sh
export LC_ALL=C
export DEBIAN_FRONTEND=noninteractive
echo "===SU:CUSTOM_PKGGROUPS==="
# {{packages}} rendu comme liste shell-safe par le backend (jamais de vim par défaut).
apt-get update -qq 2>&1
apt-get install -y {{packages}} 2>&1
CODE=$?
echo "===SU:EXIT=${CODE}==="
4.4 custom/docker-official-debian.sh.tpl
Suit la doc officielle Docker Debian (https://docs.docker.com/engine/install/debian/, https://docs.docker.com/compose/install/linux/) : clé GPG dans /etc/apt/keyrings, docker.sources, puis paquets.
#!/bin/sh
export LC_ALL=C
export DEBIAN_FRONTEND=noninteractive
echo "===SU:CUSTOM_DOCKER==="
apt-get update -qq 2>&1
apt-get install -y ca-certificates curl 2>&1
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc 2>&1
chmod a+r /etc/apt/keyrings/docker.asc
# docker.sources écrit selon codename détecté (non secret).
apt-get update -qq 2>&1
apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 2>&1
CODE=$?
usermod -aG docker "{{dockerUser}}" 2>&1 || echo "WARN docker group"
mkdir -p "{{composeRoot}}" 2>&1
echo "DOCKER_GROUP_RELOGIN_REQUIRED=1"
{{#rebootAfterInstall}}echo "REBOOT_REQUESTED=1"{{/rebootAfterInstall}}
echo "===SU:EXIT=${CODE}==="
4.5 custom/sharing.sh.tpl et 4.6 custom/vm-guest-tools.sh.tpl
Installent Samba/NFS/mDNS selon les choix (sans config dangereuse par défaut) / l'agent invité choisi. Mêmes conventions de marqueurs et d'échec contrôlé. Détail des configs renvoyé à la tâche 4.
Sources citées : Docker Debian https://docs.docker.com/engine/install/debian/ · Compose plugin https://docs.docker.com/compose/install/linux/ · Debian network https://wiki.debian.org/NetworkConfiguration · Debian Handbook https://www.debian.org/doc/manuals/debian-handbook/sect.network-config · resolvconf https://packages.debian.org/stable/net/resolvconf
5. JSON canonique post-install
ExecutionResult reçoit un bloc optionnel postInstall (voir 40-contrats-json.md) listant : profils exécutés, variables non sensibles utilisées, fichiers modifiés, paquets installés, services activés/démarrés, reboots demandés, erreurs. Secrets/tokens jamais inclus (variables sérialisées, logs UI, rapports, MCP). Les changements réseau/Docker sont marqués dans le rapport Markdown avec les prochaines actions attendues (reconnexion, logout/login groupe Docker, reboot).
6. Insertion dans la webapp existante
- Même mécanique que les autres actions : templates versionnés, preview, exécution SSH (
runScriptSudo), WebSocket terminal,executions, rapport Markdown, log brut. - Valeurs réutilisables conservées dans
script_variables_presets(scopeglobal/machine/profile) ; état par machine dansmachine_profile_state. Le provisioning peut être un assistant ponctuel ou stocké par machine. - Hermes peut proposer des profils ou expliquer un échec, mais ne reçoit que le JSON réduit et ne déclenche jamais les actions à risque sans validation webapp.
- Découpage en sous-jalons indépendants : bootstrap/sudo, identité+réseau, paquets de base, Docker officiel, partage réseau, outils VM/monitoring (voir
80-sous-jalons.md).