2026-10-09 10:39:50 +02:00
|
|
|
# Géré par Ansible (rôle portail) — ne pas modifier à la main.
|
2026-10-13 11:45:53 +02:00
|
|
|
{# Bloc A (ADR 0006) : quand le relais TURN est actif, le port public 443
|
|
|
|
|
est pris par le routeur Nginx par nom (stream, nginx.conf) — ces vhost
|
|
|
|
|
HTTPS n'écoutent alors plus qu'en interne, sur 127.0.0.1:8443, où ce
|
|
|
|
|
routeur leur transmet tout ce qui n'est pas pour coturn. #}
|
|
|
|
|
{%- macro listen443(default_server=False) -%}
|
|
|
|
|
{%- if portail_turn_actif | default(false) %}
|
|
|
|
|
listen 127.0.0.1:8443 ssl http2{{ ' default_server' if default_server }};
|
|
|
|
|
{%- else %}
|
|
|
|
|
listen 443 ssl http2{{ ' default_server' if default_server }};
|
|
|
|
|
listen [::]:443 ssl http2{{ ' default_server' if default_server }};
|
|
|
|
|
{%- endif -%}
|
|
|
|
|
{% endmacro -%}
|
2026-10-09 10:39:50 +02:00
|
|
|
{% macro bloc_portail() %}
|
2026-10-08 02:01:58 +02:00
|
|
|
root {{ portail_dossier_app }}/public;
|
|
|
|
|
index index.php;
|
|
|
|
|
|
|
|
|
|
client_max_body_size 20m;
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
try_files $uri $uri/ /index.php?$query_string;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 09:35:16 +02:00
|
|
|
# Relaie la console série (étape 9) en WebSocket vers le proxy Go. Le
|
|
|
|
|
# jeton après /console/ est à usage court (5 min), jamais l'URL Nova.
|
|
|
|
|
location /console/ {
|
|
|
|
|
proxy_pass http://127.0.0.1:8081/console/;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 02:01:58 +02:00
|
|
|
location ~ \.php$ {
|
|
|
|
|
fastcgi_pass unix:/run/php/php{{ portail_php_version }}-fpm.sock;
|
|
|
|
|
fastcgi_index index.php;
|
|
|
|
|
include fastcgi_params;
|
|
|
|
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-14 13:04:40 +02:00
|
|
|
# Bloc C : dossier de l'élève en WebDAV (rclone en local, voir
|
|
|
|
|
# 09-webdav.yml). "internal" : jamais appelable depuis l'extérieur,
|
|
|
|
|
# seulement par la sous-requête auth_request ci-dessous.
|
|
|
|
|
# $request_uri d'une sous-requête auth_request reflète la requête
|
|
|
|
|
# d'origine ("/webdav/…", pas "/interne/webdav-auth") : constaté le
|
|
|
|
|
# 2026-10-14 (404 systématique côté Laravel, qui routait donc sur le
|
|
|
|
|
# mauvais chemin). D'où REQUEST_URI écrit en dur ci-dessous — l'URI de
|
|
|
|
|
# cette location est fixe, il n'y a rien d'autre qu'elle ne pourrait
|
|
|
|
|
# valoir.
|
|
|
|
|
location = /interne/webdav-auth {
|
|
|
|
|
internal;
|
|
|
|
|
fastcgi_pass unix:/run/php/php{{ portail_php_version }}-fpm.sock;
|
|
|
|
|
fastcgi_index index.php;
|
|
|
|
|
include fastcgi_params;
|
|
|
|
|
fastcgi_param SCRIPT_FILENAME {{ portail_dossier_app }}/public/index.php;
|
|
|
|
|
fastcgi_param SCRIPT_NAME /index.php;
|
|
|
|
|
fastcgi_param REQUEST_URI /interne/webdav-auth;
|
|
|
|
|
fastcgi_param REQUEST_METHOD GET;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# "rewrite" s'exécute dans une phase antérieure à auth_request_set (le
|
|
|
|
|
# rewrite verrait donc toujours $eleve_dossier vide, et chaque élève
|
|
|
|
|
# atterrirait à la racine commune du dossier — constaté le 2026-10-14,
|
|
|
|
|
# un élève pouvait lire les fichiers d'un autre). proxy_pass avec le nom
|
|
|
|
|
# capturé par la regex de la location, lui, est évalué après coup.
|
|
|
|
|
location ~ ^/webdav/(?<chemin_webdav>.*)$ {
|
|
|
|
|
auth_request /interne/webdav-auth;
|
|
|
|
|
auth_request_set $eleve_dossier $upstream_http_x_eleve_dossier;
|
|
|
|
|
error_page 401 = @webdav_non_authentifie;
|
|
|
|
|
client_max_body_size 100m;
|
|
|
|
|
proxy_pass http://127.0.0.1:{{ portail_port_webdav }}/$eleve_dossier/$chemin_webdav;
|
|
|
|
|
proxy_http_version 1.1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Sans cela, un explorateur de fichiers (Windows/macOS) ne propose
|
|
|
|
|
# jamais de ressaisir les identifiants après un échec.
|
|
|
|
|
location @webdav_non_authentifie {
|
|
|
|
|
add_header WWW-Authenticate 'Basic realm="Syllabus WebDAV"' always;
|
|
|
|
|
return 401;
|
|
|
|
|
}
|
|
|
|
|
|
2026-10-08 02:01:58 +02:00
|
|
|
location ~ /\.(?!well-known) {
|
|
|
|
|
deny all;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
access_log /var/log/nginx/portail-access.log;
|
|
|
|
|
error_log /var/log/nginx/portail-error.log;
|
2026-10-09 10:39:50 +02:00
|
|
|
{% endmacro %}
|
|
|
|
|
{% if portail_https_actif %}
|
|
|
|
|
# HTTP : tout part en HTTPS. Les noms connus gardent leur nom ; une requête
|
|
|
|
|
# sur l'IP ou un nom inconnu est envoyée vers le domaine principal.
|
|
|
|
|
server {
|
|
|
|
|
listen 80;
|
|
|
|
|
listen [::]:80;
|
2026-10-13 11:45:53 +02:00
|
|
|
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 %}{% if portail_turn_actif | default(false) %} {{ portail_domaine_turn }}{% endif %};
|
2026-10-09 10:39:50 +02:00
|
|
|
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.
|
|
|
|
|
location / {
|
|
|
|
|
return 301 https://$host$request_uri;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
server {
|
|
|
|
|
listen 80 default_server;
|
|
|
|
|
listen [::]:80 default_server;
|
|
|
|
|
server_name _;
|
|
|
|
|
server_tokens off;
|
|
|
|
|
location / {
|
|
|
|
|
return 301 https://{{ portail_domaine }}$request_uri;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# www → domaine principal (une seule adresse canonique).
|
|
|
|
|
server {
|
2026-10-13 11:45:53 +02:00
|
|
|
{{ listen443() }}
|
2026-10-09 10:39:50 +02:00
|
|
|
server_name {{ portail_domaines_alias | join(' ') }};
|
|
|
|
|
server_tokens off;
|
|
|
|
|
include snippets/syllabus-tls.conf;
|
|
|
|
|
include snippets/syllabus-entetes.conf;
|
|
|
|
|
return 301 https://{{ portail_domaine }}$request_uri;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
server {
|
2026-10-13 11:45:53 +02:00
|
|
|
{{ listen443(default_server=True) }}
|
2026-10-09 10:39:50 +02:00
|
|
|
server_name {{ portail_domaine }};
|
|
|
|
|
server_tokens off;
|
|
|
|
|
include snippets/syllabus-tls.conf;
|
|
|
|
|
include snippets/syllabus-entetes.conf;
|
|
|
|
|
{{ bloc_portail() }}
|
|
|
|
|
}
|
|
|
|
|
{% if portail_statut_actif %}
|
|
|
|
|
|
|
|
|
|
# Page de statut publique d'Uptime Kuma, et RIEN d'autre : l'administration
|
|
|
|
|
# (tableau de bord, /socket.io, /metrics…) reste accessible par tunnel SSH
|
|
|
|
|
# uniquement. Chemins relevés dans server/routers/status-page-router.js.
|
|
|
|
|
server {
|
2026-10-13 11:45:53 +02:00
|
|
|
{{ listen443() }}
|
2026-10-09 10:39:50 +02:00
|
|
|
server_name {{ portail_domaine_statut }};
|
|
|
|
|
server_tokens off;
|
|
|
|
|
include snippets/syllabus-tls.conf;
|
|
|
|
|
include snippets/syllabus-entetes.conf;
|
|
|
|
|
|
|
|
|
|
location = / {
|
|
|
|
|
return 302 /status/{{ portail_statut_slug }};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
location ~ ^/(status/[a-z0-9-]+$|api/status-page/|assets/|upload/|icon\.svg$|favicon\.ico$|apple-touch-icon[a-z0-9-]*\.png$|robots\.txt$) {
|
|
|
|
|
limit_except GET HEAD {
|
|
|
|
|
deny all;
|
|
|
|
|
}
|
|
|
|
|
proxy_pass http://127.0.0.1:{{ portail_port_uptime_kuma }};
|
2026-10-09 10:50:30 +02:00
|
|
|
# Kuma 1.23 ne répond jamais pour un identifiant de page inconnu
|
|
|
|
|
# (constaté le 2026-10-09) : ne pas faire attendre le visiteur.
|
|
|
|
|
proxy_read_timeout 15s;
|
2026-10-09 10:39:50 +02:00
|
|
|
proxy_set_header Host $host;
|
|
|
|
|
proxy_set_header X-Forwarded-For $remote_addr;
|
|
|
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
location / {
|
|
|
|
|
return 404;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
access_log /var/log/nginx/statut-access.log;
|
|
|
|
|
error_log /var/log/nginx/statut-error.log;
|
|
|
|
|
}
|
|
|
|
|
{% endif %}
|
2026-10-12 12:35:35 +02:00
|
|
|
{% 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 {
|
2026-10-13 11:45:53 +02:00
|
|
|
{{ listen443() }}
|
2026-10-12 12:35:35 +02:00
|
|
|
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 %}
|
2026-10-09 10:39:50 +02:00
|
|
|
{% else %}
|
|
|
|
|
# Avant le premier certificat : HTTP seul, avec les noms du domaine pour que
|
|
|
|
|
# certbot (authentificateur nginx) trouve le bon bloc.
|
|
|
|
|
server {
|
|
|
|
|
listen 80 default_server;
|
|
|
|
|
listen [::]:80 default_server;
|
|
|
|
|
server_name {{ portail_domaine }} {{ portail_domaines_alias | join(' ') }} _;
|
|
|
|
|
server_tokens off;
|
|
|
|
|
{{ bloc_portail() }}
|
2026-10-08 02:01:58 +02:00
|
|
|
}
|
2026-10-09 10:39:50 +02:00
|
|
|
{% endif %}
|