2026-10-08 02:01:58 +02:00
|
|
|
---
|
|
|
|
|
- name: créer le dossier de l'application
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ portail_dossier_app }}"
|
|
|
|
|
state: directory
|
|
|
|
|
owner: "{{ ansible_user }}"
|
|
|
|
|
group: "{{ ansible_user }}"
|
|
|
|
|
mode: "0755"
|
|
|
|
|
|
|
|
|
|
# storage/ et bootstrap/cache/ sont de l'état et du cache runtime, jamais du
|
|
|
|
|
# code : on ne les synchronise jamais depuis le Mac (un rsync --delete les
|
|
|
|
|
# effacerait sinon, y compris storage/app/ où atterriront plus tard les
|
|
|
|
|
# travaux d'élèves). On crée leur structure une fois, puis on laisse
|
|
|
|
|
# www-data (PHP-FPM) les posséder en continu.
|
|
|
|
|
- name: créer la structure storage/ et bootstrap/cache/ (une seule fois)
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ portail_dossier_app }}/{{ item }}"
|
|
|
|
|
state: directory
|
|
|
|
|
owner: www-data
|
|
|
|
|
group: www-data
|
|
|
|
|
mode: "0775"
|
|
|
|
|
loop:
|
|
|
|
|
- storage/app/public
|
|
|
|
|
- storage/framework/cache/data
|
|
|
|
|
- storage/framework/sessions
|
|
|
|
|
- storage/framework/views
|
|
|
|
|
- storage/logs
|
|
|
|
|
- bootstrap/cache
|
|
|
|
|
|
|
|
|
|
- name: synchroniser le code du portail (sans vendor/node_modules/.env/storage)
|
|
|
|
|
become: false
|
|
|
|
|
ansible.posix.synchronize:
|
|
|
|
|
src: "{{ playbook_dir }}/../portail/"
|
|
|
|
|
dest: "{{ portail_dossier_app }}/"
|
|
|
|
|
delete: true
|
|
|
|
|
rsync_opts:
|
|
|
|
|
- "--exclude=/vendor"
|
|
|
|
|
- "--exclude=/node_modules"
|
|
|
|
|
- "--exclude=/.env"
|
|
|
|
|
- "--exclude=/database/database.sqlite"
|
|
|
|
|
- "--exclude=/storage"
|
|
|
|
|
- "--exclude=/bootstrap/cache"
|
|
|
|
|
- "--exclude=/public/build"
|
2026-10-09 21:44:02 +02:00
|
|
|
# Produit sur le serveur par `npm run build` (scripts/copier-vendor.mjs),
|
|
|
|
|
# donc appartenant à root : ne jamais le synchroniser depuis le Mac.
|
|
|
|
|
- "--exclude=/public/vendor"
|
2026-10-08 02:01:58 +02:00
|
|
|
- "--exclude=/public/hot"
|
|
|
|
|
- "--exclude=/.git"
|
|
|
|
|
register: portail_code_synchronise
|
ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage
par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances
planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues
dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création
de stack ratée est supprimée et réessayée deux fois, puis la session passe
en échec, visible du prof.
Corrige aussi trois défauts des étapes précédentes : la console se coupait
à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas
rejoindre une séance, la déconnexion n'avait pas de jeton CSRF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:52:16 +02:00
|
|
|
notify: redémarrer horizon
|
2026-10-08 02:01:58 +02:00
|
|
|
|
|
|
|
|
- name: installer les dépendances PHP (composer)
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: composer install --no-dev --optimize-autoloader --no-interaction
|
|
|
|
|
args:
|
|
|
|
|
chdir: "{{ portail_dossier_app }}"
|
|
|
|
|
environment:
|
|
|
|
|
COMPOSER_ALLOW_SUPERUSER: "1"
|
|
|
|
|
register: portail_composer_install_app
|
|
|
|
|
changed_when: "'Nothing to install' not in portail_composer_install_app.stdout"
|
|
|
|
|
|
|
|
|
|
- name: vérifier si .env existe déjà
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: "{{ portail_dossier_app }}/.env"
|
|
|
|
|
register: portail_env_stat
|
|
|
|
|
|
|
|
|
|
- name: générer une clé d'application (une seule fois)
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: php artisan key:generate --show
|
|
|
|
|
args:
|
|
|
|
|
chdir: "{{ portail_dossier_app }}"
|
|
|
|
|
register: portail_app_key_generee
|
|
|
|
|
when: not portail_env_stat.stat.exists
|
|
|
|
|
changed_when: true
|
|
|
|
|
|
|
|
|
|
- name: déployer .env de production (une seule fois, jamais réécrit ensuite)
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: env.production.j2
|
|
|
|
|
dest: "{{ portail_dossier_app }}/.env"
|
|
|
|
|
owner: "{{ ansible_user }}"
|
|
|
|
|
group: www-data
|
|
|
|
|
mode: "0640"
|
|
|
|
|
vars:
|
|
|
|
|
portail_app_key: "{{ portail_app_key_generee.stdout }}"
|
|
|
|
|
portail_app_url: "http://{{ ansible_host }}"
|
|
|
|
|
when: not portail_env_stat.stat.exists
|
|
|
|
|
|
2026-10-08 10:14:24 +02:00
|
|
|
# Variables ajoutées après le premier déploiement (étapes 9 et 10) : mises à
|
|
|
|
|
# jour ligne par ligne, pour ne jamais retoucher le reste d'un .env déjà en
|
|
|
|
|
# place (APP_KEY en particulier, qui ne doit jamais changer sans le vouloir).
|
|
|
|
|
#
|
|
|
|
|
# Authentification par mot de passe, pas par application credential : Heat
|
|
|
|
|
# refuse de créer un trust Keystone à partir d'un jeton d'application
|
|
|
|
|
# credential (« Delegated tokens cannot manage trusts », constaté le
|
|
|
|
|
# 2026-10-08) — voir portail/config/openstack.php et docs/journal.md.
|
|
|
|
|
- name: lire les identifiants OpenStack du portail (étape 6)
|
2026-10-08 09:35:16 +02:00
|
|
|
ansible.builtin.slurp:
|
|
|
|
|
src: /etc/kolla/clouds-paillasse.yaml
|
|
|
|
|
register: portail_clouds_brut
|
|
|
|
|
|
2026-10-08 10:14:24 +02:00
|
|
|
- name: extraire le nom d'utilisateur et le mot de passe
|
2026-10-08 09:35:16 +02:00
|
|
|
ansible.builtin.set_fact:
|
2026-10-08 10:14:24 +02:00
|
|
|
portail_os_username: "{{ portail_clouds_contenu | regex_search('username:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
|
|
|
|
portail_os_password: "{{ portail_clouds_contenu | regex_search('password:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
|
|
|
|
portail_os_project_name: "{{ portail_clouds_contenu | regex_search('project_name:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
2026-10-08 09:35:16 +02:00
|
|
|
vars:
|
|
|
|
|
portail_clouds_contenu: "{{ portail_clouds_brut.content | b64decode }}"
|
|
|
|
|
no_log: true
|
|
|
|
|
|
|
|
|
|
- name: ajouter/mettre à jour les variables OpenStack dans .env
|
|
|
|
|
ansible.builtin.lineinfile:
|
|
|
|
|
path: "{{ portail_dossier_app }}/.env"
|
|
|
|
|
regexp: "^{{ item.cle }}="
|
|
|
|
|
line: "{{ item.cle }}={{ item.valeur }}"
|
|
|
|
|
loop:
|
|
|
|
|
- { cle: "OS_AUTH_URL", valeur: "http://10.10.0.1:5000" }
|
|
|
|
|
- { cle: "OS_NOVA_URL", valeur: "http://10.10.0.1:8774/v2.1" }
|
2026-10-08 10:14:24 +02:00
|
|
|
- { cle: "OS_HEAT_URL", valeur: "http://10.10.0.1:8004/v1" }
|
|
|
|
|
- { cle: "OS_USERNAME", valeur: "{{ portail_os_username }}" }
|
|
|
|
|
- { cle: "OS_PASSWORD", valeur: "{{ portail_os_password }}" }
|
|
|
|
|
- { cle: "OS_PROJECT_NAME", valeur: "{{ portail_os_project_name }}" }
|
|
|
|
|
- { cle: "OS_APPLICATION_CREDENTIAL_ID", valeur: "" }
|
|
|
|
|
- { cle: "OS_APPLICATION_CREDENTIAL_SECRET", valeur: "" }
|
2026-10-08 09:35:16 +02:00
|
|
|
- { cle: "REDIS_PREFIX", valeur: "" }
|
2026-10-08 10:14:24 +02:00
|
|
|
- { cle: "LABS_DOSSIER", valeur: "/opt/labs" }
|
2026-10-13 16:14:36 +02:00
|
|
|
# Banque de quiz officielle (quiz:synchroniser-banque) : contenu
|
|
|
|
|
# éditorial source (cours, TD, quiz), pas seulement les lab.yaml
|
|
|
|
|
# déployés — voir 08-banque-quiz.yml.
|
|
|
|
|
- { cle: "CONTENUS_DOSSIER", valeur: "/opt/paillasse-contenus" }
|
ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage
par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances
planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues
dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création
de stack ratée est supprimée et réessayée deux fois, puis la session passe
en échec, visible du prof.
Corrige aussi trois défauts des étapes précédentes : la console se coupait
à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas
rejoindre une séance, la déconnexion n'avait pas de jeton CSRF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:52:16 +02:00
|
|
|
# > timeout des superviseurs Horizon (300 s, config/horizon.php)
|
|
|
|
|
- { cle: "REDIS_QUEUE_RETRY_AFTER", valeur: "330" }
|
2026-10-09 10:39:50 +02:00
|
|
|
# Étape 12 : HTTPS. Cookies de session envoyés en HTTPS seulement.
|
|
|
|
|
- { cle: "APP_URL", valeur: "https://{{ portail_domaine }}" }
|
|
|
|
|
- { cle: "SESSION_SECURE_COOKIE", valeur: "true" }
|
2026-10-09 21:07:25 +02:00
|
|
|
# Étape 14 (RGPD) : journaux applicatifs par jour, gardés 14 jours
|
|
|
|
|
# (comme ceux de Nginx), au lieu d'un fichier unique jamais purgé.
|
|
|
|
|
- { cle: "LOG_STACK", valeur: "daily" }
|
|
|
|
|
- { cle: "LOG_DAILY_DAYS", valeur: "14" }
|
2026-10-12 21:12:43 +02:00
|
|
|
# Bloc A, self-healing : métrique pour Prometheus (collecteur textfile).
|
|
|
|
|
- { cle: "LABS_SURVEILLANCE_METRIQUES", valeur: "/var/lib/node_exporter/textfile/sante_vm.prom" }
|
2026-10-08 09:35:16 +02:00
|
|
|
no_log: true
|
|
|
|
|
register: portail_env_mis_a_jour
|
ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage
par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances
planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues
dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création
de stack ratée est supprimée et réessayée deux fois, puis la session passe
en échec, visible du prof.
Corrige aussi trois défauts des étapes précédentes : la console se coupait
à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas
rejoindre une séance, la déconnexion n'avait pas de jeton CSRF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:52:16 +02:00
|
|
|
# Horizon charge la configuration une seule fois au démarrage : sans
|
2026-10-08 21:01:08 +02:00
|
|
|
# redémarrage, il garderait l'ancien mot de passe après une rotation.
|
ajoute le stock chaud, la correction automatique et les nouveaux essais de création de stack
Stock de stacks prêtes par lab dans Redis (prise atomique en Lua, ménage
par comparaison avec Heat, disjoncteur), files gérées par Horizon, séances
planifiables. Correcteur Go sur l'hyperviseur : socket Unix, commandes lues
dans le lab.yaml uniquement, exécution par l'agent invité QEMU. Une création
de stack ratée est supprimée et réessayée deux fois, puis la session passe
en échec, visible du prof.
Corrige aussi trois défauts des étapes précédentes : la console se coupait
à la première frappe d'un navigateur (trames texte), l'élève ne pouvait pas
rejoindre une séance, la déconnexion n'avait pas de jeton CSRF.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:52:16 +02:00
|
|
|
notify: redémarrer horizon
|
2026-10-08 09:35:16 +02:00
|
|
|
|
2026-10-14 09:52:15 +02:00
|
|
|
# Bloc B, stockage S3 (2026-10-14) : fichier créé à la main par Adam sur le
|
|
|
|
|
# serveur, jamais par Ansible ni par Claude (CLAUDE.md, commandes et
|
|
|
|
|
# dépenses). Tant qu'il n'existe pas, ce bloc ne touche à rien : le dépôt de
|
|
|
|
|
# fichier des devoirs reste simplement masqué côté portail
|
|
|
|
|
# (StockageTravauxEleves::disponible()) — voir docs/runbooks/stockage-s3.md.
|
|
|
|
|
- name: vérifier si les identifiants S3 existent (étape 10, facultatif)
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: /etc/syllabus/s3-paillasse.yaml
|
|
|
|
|
register: portail_s3_stat
|
|
|
|
|
|
|
|
|
|
- name: lire les identifiants S3 du portail (étape 10)
|
|
|
|
|
ansible.builtin.slurp:
|
|
|
|
|
src: /etc/syllabus/s3-paillasse.yaml
|
|
|
|
|
register: portail_s3_brut
|
|
|
|
|
when: portail_s3_stat.stat.exists
|
|
|
|
|
|
|
|
|
|
- name: extraire les champs S3
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
portail_s3_access_key: "{{ portail_s3_contenu | regex_search('access_key:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
|
|
|
|
portail_s3_secret_key: "{{ portail_s3_contenu | regex_search('secret_key:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
|
|
|
|
portail_s3_bucket: "{{ portail_s3_contenu | regex_search('bucket:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
|
|
|
|
portail_s3_region: "{{ portail_s3_contenu | regex_search('region:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
|
|
|
|
portail_s3_endpoint: "{{ portail_s3_contenu | regex_search('endpoint:\\s*\"?([^\"\\n]+)', '\\1') | first }}"
|
|
|
|
|
vars:
|
|
|
|
|
portail_s3_contenu: "{{ portail_s3_brut.content | b64decode }}"
|
|
|
|
|
when: portail_s3_stat.stat.exists
|
|
|
|
|
no_log: true
|
|
|
|
|
|
|
|
|
|
- name: ajouter/mettre à jour les variables S3 dans .env
|
|
|
|
|
ansible.builtin.lineinfile:
|
|
|
|
|
path: "{{ portail_dossier_app }}/.env"
|
|
|
|
|
regexp: "^{{ item.cle }}="
|
|
|
|
|
line: "{{ item.cle }}={{ item.valeur }}"
|
|
|
|
|
loop:
|
|
|
|
|
- { cle: "AWS_ACCESS_KEY_ID", valeur: "{{ portail_s3_access_key }}" }
|
|
|
|
|
- { cle: "AWS_SECRET_ACCESS_KEY", valeur: "{{ portail_s3_secret_key }}" }
|
|
|
|
|
- { cle: "AWS_BUCKET", valeur: "{{ portail_s3_bucket }}" }
|
|
|
|
|
- { cle: "AWS_DEFAULT_REGION", valeur: "{{ portail_s3_region }}" }
|
|
|
|
|
- { cle: "AWS_ENDPOINT", valeur: "{{ portail_s3_endpoint }}" }
|
|
|
|
|
- { cle: "AWS_USE_PATH_STYLE_ENDPOINT", valeur: "false" }
|
|
|
|
|
when: portail_s3_stat.stat.exists
|
|
|
|
|
no_log: true
|
|
|
|
|
notify: redémarrer horizon
|
|
|
|
|
|
2026-10-08 02:01:58 +02:00
|
|
|
- name: installer les dépendances JS (npm)
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: npm ci
|
|
|
|
|
args:
|
|
|
|
|
chdir: "{{ portail_dossier_app }}"
|
|
|
|
|
register: portail_npm_ci
|
|
|
|
|
changed_when: true
|
|
|
|
|
|
|
|
|
|
- name: compiler les assets (vite build)
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: npm run build
|
|
|
|
|
args:
|
|
|
|
|
chdir: "{{ portail_dossier_app }}"
|
|
|
|
|
register: portail_npm_build
|
|
|
|
|
changed_when: true
|
|
|
|
|
|
|
|
|
|
- name: appliquer les migrations
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: php artisan migrate --force
|
|
|
|
|
args:
|
|
|
|
|
chdir: "{{ portail_dossier_app }}"
|
|
|
|
|
register: portail_migrate
|
|
|
|
|
changed_when: "'Nothing to migrate' not in portail_migrate.stdout"
|
|
|
|
|
|
|
|
|
|
- name: mettre en cache la configuration et les routes
|
|
|
|
|
ansible.builtin.shell:
|
|
|
|
|
cmd: php artisan config:cache && php artisan route:cache && php artisan view:cache
|
|
|
|
|
args:
|
|
|
|
|
chdir: "{{ portail_dossier_app }}"
|
|
|
|
|
changed_when: true
|