ajoute la visio de classe : Jitsi auto-hébergé, jetons du portail, prof seul modérateur

Jitsi (docker-jitsi-meet, version figée) derrière Nginx sur meet.syllabuslab.fr,
accès par jeton JWT signé par le portail, modération du micro, mains levées
synchronisées avec l'application. Pare-feu : UDP 10000 et pont interne
br-visio (accords d'Adam). Jicofo n'authentifie plus lui-même : il donnait
le rôle de propriétaire aux élèves. Capacité ramenée à 20 VM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Adam Belkacemi 2026-10-12 12:35:35 +02:00
parent dd4f9c3c55
commit ec4eae3744
36 changed files with 1406 additions and 18 deletions

View file

@ -16,3 +16,6 @@ portail_domaine_statut: status.syllabuslab.fr
portail_statut_slug: syllabus
portail_ip_publique: 163.172.4.195
portail_port_uptime_kuma: 3001
# Étape 15 : visio (Jitsi, rôle visio), activée quand ce nom résout vers le serveur.
portail_domaine_visio: meet.syllabuslab.fr
portail_port_visio: 8090

View file

@ -26,12 +26,20 @@
changed_when: false
failed_when: false
- name: noter si la page de statut peut être activée
- name: vérifier le DNS du sous-domaine de la visio
ansible.builtin.command: getent ahostsv4 {{ portail_domaine_visio }}
register: portail_dns_visio
changed_when: false
failed_when: false
- name: noter si la page de statut et la visio peuvent être activées
ansible.builtin.set_fact:
portail_statut_actif: "{{ portail_ip_publique in portail_dns_statut.stdout }}"
portail_visio_actif: "{{ portail_ip_publique in portail_dns_visio.stdout }}"
portail_noms_certificat: >-
{{ [portail_domaine] + portail_domaines_alias
+ ([portail_domaine_statut] if portail_ip_publique in portail_dns_statut.stdout else []) }}
+ ([portail_domaine_statut] if portail_ip_publique in portail_dns_statut.stdout else [])
+ ([portail_domaine_visio] if portail_ip_publique in portail_dns_visio.stdout else []) }}
- name: vérifier si le certificat existe déjà
ansible.builtin.stat:

View file

@ -40,7 +40,7 @@
server {
listen 80;
listen [::]:80;
server_name {{ portail_domaine }} {{ portail_domaines_alias | join(' ') }}{% if portail_statut_actif %} {{ portail_domaine_statut }}{% endif %};
server_name {{ portail_domaine }} {{ portail_domaines_alias | join(' ') }}{% if portail_statut_actif %} {{ portail_domaine_statut }}{% endif %}{% if portail_visio_actif | default(false) %} {{ portail_domaine_visio }}{% endif %};
server_tokens off;
# Dans une location (pas au niveau server) : la location exacte que
# certbot ajoute pour le défi ACME au renouvellement reste prioritaire.
@ -117,6 +117,48 @@ server {
error_log /var/log/nginx/statut-error.log;
}
{% endif %}
{% if portail_visio_actif | default(false) %}
# Visio (Jitsi Meet, rôle visio). Pas l'extrait d'en-têtes commun : son
# X-Frame-Options et sa Permissions-Policy interdiraient l'intégration dans
# le portail et la caméra. Intégration permise au seul portail.
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name {{ portail_domaine_visio }};
server_tokens off;
include snippets/syllabus-tls.conf;
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "frame-ancestors https://{{ portail_domaine }}" always;
# Pas de page d'accueil Jitsi : on n'entre dans une salle que depuis le portail.
location = / {
return 302 https://{{ portail_domaine }}/;
}
location /xmpp-websocket {
proxy_pass http://127.0.0.1:{{ portail_port_visio }};
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 1h;
}
location / {
proxy_pass http://127.0.0.1:{{ portail_port_visio }};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto https;
}
access_log /var/log/nginx/visio-access.log;
error_log /var/log/nginx/visio-error.log;
}
{% endif %}
{% else %}
# Avant le premier certificat : HTTP seul, avec les noms du domaine pour que
# certbot (authentificateur nginx) trouve le bon bloc.

View file

@ -6,5 +6,7 @@ add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" alway
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
# Caméra, micro et partage d'écran : délégués à la seule visio intégrée
# (étape 15) ; refusés à tout autre site.
add_header Permissions-Policy "camera=(self \"https://{{ portail_domaine_visio }}\"), microphone=(self \"https://{{ portail_domaine_visio }}\"), display-capture=(self \"https://{{ portail_domaine_visio }}\"), geolocation=(), payment=()" always;
add_header Cross-Origin-Opener-Policy "same-origin" always;