blob: 174543817fb86708e6b5e61bd3f53be0265de9c9 [file] [log] [blame]
Matthias Andreas Benkard7d46f072026-08-24 21:43:05 +02001# SPDX-FileCopyrightText: 2026 Matthias Andreas Benkard <code@mail.matthias.benkard.de>
Matthias Andreas Benkarda5d55442026-08-23 08:31:49 +02002# SPDX-License-Identifier: AGPL-3.0-or-later
3
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +02004# Vorlage für die Auslieferung der ÄndGgner-Browserfassung unter
Matthias Andreas Benkard2ef19ec2026-08-29 20:39:11 +02005# https://aendggner.kellertomaten.de/.
Matthias Andreas Benkard7b455cd2026-08-09 08:33:37 +02006#
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +02007# Es gibt keinen Anwendungsprozess: Der Server liefert nur statische Dateien aus
8# (Inhalt von target/web/), gerechnet wird im Browser. Weil der Dienst unter einem
9# Unterpfad einer bestehenden Domain läuft, ist dies kein eigener server-Block,
10# sondern ein Schnipsel zum Einfügen in den vorhandenen — Zertifikat, HTTP→HTTPS
11# und Domainname bleiben, wie sie sind:
12#
13# server { ... include /etc/nginx/snippets/aendggner.conf; ... }
14#
Matthias Andreas Benkard8cbbaaa2026-08-23 13:20:48 +020015# Ausgerollt wird nach /var/www/aendggner (siehe README.md, Abschnitt „Ausrollen“).
Matthias Andreas Benkard7b455cd2026-08-09 08:33:37 +020016
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020017# Ohne diesen Schritt liefe /aendggner ins Leere, und alle relativen Verweise der
18# Seite gingen von / statt von /aendggner/ aus.
19location = /aendggner {
20 return 301 /aendggner/;
Matthias Andreas Benkard7b455cd2026-08-09 08:33:37 +020021}
22
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020023location /aendggner/ {
24 # Bewusst „root /var/www“ (mit dem Verzeichnis /var/www/aendggner) statt
25 # „alias /var/www/aendggner/“: In einer Präfix-Location löst try_files $uri
26 # gegen den root auf, nicht gegen den alias, und liefert dann 404 für alles.
27 root /var/www;
Matthias Andreas Benkard2b1841e2026-08-15 10:51:11 +020028 index index.html;
29
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020030 try_files $uri $uri/ =404;
31
32 # Ohne den ersten Typ lehnt der Browser die Streaming-Instanziierung des Moduls
33 # ab. Ein types-Block in einer location ersetzt die Zuordnung vollständig, statt
34 # sie zu ergänzen — deshalb stehen hier alle vorkommenden Endungen.
Matthias Andreas Benkard2b1841e2026-08-15 10:51:11 +020035 types {
36 application/wasm wasm;
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020037 text/html html;
38 text/css css;
39 text/javascript js;
40 image/svg+xml svg;
41 text/plain txt;
42 application/gzip gz;
Matthias Andreas Benkard2b1841e2026-08-15 10:51:11 +020043 }
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020044 default_type application/octet-stream;
Matthias Andreas Benkard2b1841e2026-08-15 10:51:11 +020045
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020046 # Das Wasm-Modul ist rund 24 MB groß. deploy/webpaket.sh legt beim Bauen .gz
47 # und .br daneben; ausgeliefert wird die fertige Datei, statt sie je Abruf neu
48 # zu packen. gzip_static bringen die üblichen Distributionspakete mit;
49 # brotli_static setzt das Modul ngx_brotli voraus — fehlt es, die Zeile
50 # streichen, gzip_static genügt.
51 gzip_static on;
52 brotli_static on;
Matthias Andreas Benkard2b1841e2026-08-15 10:51:11 +020053
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020054 # Kein „expires 7d“: Die Dateinamen tragen keine Fassungskennung. Ein Browser
55 # mit altem app.js und neuem .wasm (oder umgekehrt) bekäme eine Mischfassung,
56 # die es nie gegeben hat. Stattdessen Revalidierung bei jedem Abruf; für das
57 # unveränderte Modul kostet das ein 304 ohne Rumpf.
58 add_header Cache-Control "no-cache" always;
Matthias Andreas Benkard7b455cd2026-08-09 08:33:37 +020059
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020060 # add_header in einer location verdrängt sämtliche vom server-Block geerbten
61 # Header. Alles, was hier gelten soll, muss deshalb hier stehen — auch ein
62 # dort gesetztes HSTS.
63 # add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
64
65 # 'wasm-unsafe-eval' ist für die Instanziierung des Moduls nötig; echtes eval
66 # oder new Function kommt in der erzeugten aendggner.js nicht vor.
67 #
68 # style-src braucht 'unsafe-inline': Die fertige Synopse wird als blob:-Dokument
69 # geöffnet, und ein blob:-Dokument erbt die CSP des Dokuments, das es erzeugt
70 # hat. Der HtmlRenderer bettet sein Stylesheet als <style> ein — ohne die
71 # Freigabe erschiene die Synopse unformatiert. Skripte bleiben streng.
72 add_header Content-Security-Policy "default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; worker-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'" always;
73 add_header X-Content-Type-Options "nosniff" always;
74 add_header Referrer-Policy "no-referrer" always;
75 add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), usb=()" always;
Matthias Andreas Benkard7b455cd2026-08-09 08:33:37 +020076}
Matthias Andreas Benkard2d33bf72026-08-16 06:40:07 +020077
78# Eine robots.txt unter /aendggner/ wäre wirkungslos: Maßgeblich ist allein die im
79# Wurzelverzeichnis der Domain. Wer die Seite von der Indizierung ausnehmen will,
80# trage dort ein:
81#
82# User-agent: *
83# Disallow: /aendggner/