diff options
Diffstat (limited to 'deploy')
| -rw-r--r-- | deploy/nginx-aendggner.conf | 101 | ||||
| -rwxr-xr-x | deploy/webpaket.sh | 73 |
2 files changed, 140 insertions, 34 deletions
diff --git a/deploy/nginx-aendggner.conf b/deploy/nginx-aendggner.conf index 46eacb5..78677b5 100644 --- a/deploy/nginx-aendggner.conf +++ b/deploy/nginx-aendggner.conf @@ -1,47 +1,80 @@ -# Vorlage für die Auslieferung der ÄndGgner-Browserfassung. Es gibt keinen -# Anwendungsprozess mehr: Der Server liefert nur statische Dateien aus -# (Inhalt von target/web/), gerechnet wird im Browser. +# Vorlage für die Auslieferung der ÄndGgner-Browserfassung unter +# https://matthias.benkard.de/aendggner/. # -# Vor dem Einsatz anpassen: server_name, ssl_certificate(_key) (z. B. via certbot), root. +# Es gibt keinen Anwendungsprozess: Der Server liefert nur statische Dateien aus +# (Inhalt von target/web/), gerechnet wird im Browser. Weil der Dienst unter einem +# Unterpfad einer bestehenden Domain läuft, ist dies kein eigener server-Block, +# sondern ein Schnipsel zum Einfügen in den vorhandenen — Zertifikat, HTTP→HTTPS +# und Domainname bleiben, wie sie sind: +# +# server { ... include /etc/nginx/snippets/aendggner.conf; ... } +# +# Ausgerollt wird nach /var/www/aendggner (siehe README.adoc, Abschnitt „Ausrollen“). -server { - listen 80; - listen [::]:80; - server_name aendggner.example.org; - return 301 https://$host$request_uri; +# Ohne diesen Schritt liefe /aendggner ins Leere, und alle relativen Verweise der +# Seite gingen von / statt von /aendggner/ aus. +location = /aendggner { + return 301 /aendggner/; } -server { - listen 443 ssl http2; - listen [::]:443 ssl http2; - server_name aendggner.example.org; - - ssl_certificate /etc/letsencrypt/live/aendggner.example.org/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/aendggner.example.org/privkey.pem; - - # Inhalt von target/web/ — ohne aendggner.js.wat, das nur ein Zwischenschritt - # des Übersetzers ist. - root /var/www/aendggner; +location /aendggner/ { + # Bewusst „root /var/www“ (mit dem Verzeichnis /var/www/aendggner) statt + # „alias /var/www/aendggner/“: In einer Präfix-Location löst try_files $uri + # gegen den root auf, nicht gegen den alias, und liefert dann 404 für alles. + root /var/www; index index.html; - # Ohne diesen Typ lehnt der Browser die Streaming-Instanziierung des Moduls ab. + try_files $uri $uri/ =404; + + # Ohne den ersten Typ lehnt der Browser die Streaming-Instanziierung des Moduls + # ab. Ein types-Block in einer location ersetzt die Zuordnung vollständig, statt + # sie zu ergänzen — deshalb stehen hier alle vorkommenden Endungen. types { application/wasm wasm; + text/html html; + text/css css; + text/javascript js; + image/svg+xml svg; + text/plain txt; + application/gzip gz; } + default_type application/octet-stream; - gzip on; - gzip_types application/wasm application/javascript text/css text/html; - gzip_min_length 1024; + # Das Wasm-Modul ist rund 24 MB groß. deploy/webpaket.sh legt beim Bauen .gz + # und .br daneben; ausgeliefert wird die fertige Datei, statt sie je Abruf neu + # zu packen. gzip_static bringen die üblichen Distributionspakete mit; + # brotli_static setzt das Modul ngx_brotli voraus — fehlt es, die Zeile + # streichen, gzip_static genügt. + gzip_static on; + brotli_static on; - # Das Wasm-Modul ist einige Megabyte groß und ändert sich nur mit einer neuen - # Fassung; vorkomprimierte .gz/.br daneben zu legen lohnt sich (ngx_brotli bzw. - # gzip_static). - location ~ \.(wasm|js|css)$ { - expires 7d; - add_header Cache-Control "public"; - } + # Kein „expires 7d“: Die Dateinamen tragen keine Fassungskennung. Ein Browser + # mit altem app.js und neuem .wasm (oder umgekehrt) bekäme eine Mischfassung, + # die es nie gegeben hat. Stattdessen Revalidierung bei jedem Abruf; für das + # unveränderte Modul kostet das ein 304 ohne Rumpf. + add_header Cache-Control "no-cache" always; - location / { - try_files $uri $uri/ =404; - } + # add_header in einer location verdrängt sämtliche vom server-Block geerbten + # Header. Alles, was hier gelten soll, muss deshalb hier stehen — auch ein + # dort gesetztes HSTS. + # add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; + + # 'wasm-unsafe-eval' ist für die Instanziierung des Moduls nötig; echtes eval + # oder new Function kommt in der erzeugten aendggner.js nicht vor. + # + # style-src braucht 'unsafe-inline': Die fertige Synopse wird als blob:-Dokument + # geöffnet, und ein blob:-Dokument erbt die CSP des Dokuments, das es erzeugt + # hat. Der HtmlRenderer bettet sein Stylesheet als <style> ein — ohne die + # Freigabe erschiene die Synopse unformatiert. Skripte bleiben streng. + 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; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer" always; + add_header Permissions-Policy "geolocation=(), camera=(), microphone=(), usb=()" always; } + +# Eine robots.txt unter /aendggner/ wäre wirkungslos: Maßgeblich ist allein die im +# Wurzelverzeichnis der Domain. Wer die Seite von der Indizierung ausnehmen will, +# trage dort ein: +# +# User-agent: * +# Disallow: /aendggner/ diff --git a/deploy/webpaket.sh b/deploy/webpaket.sh new file mode 100755 index 0000000..2003134 --- /dev/null +++ b/deploy/webpaket.sh @@ -0,0 +1,73 @@ +#!/bin/sh +# Macht aus dem Übersetzerergebnis ein auslieferbares Verzeichnis. Läuft in der Phase +# „package“ des Profils -Pwasm, unmittelbar nach native-image. +# +# 1. Der Textzwischenschritt aendggner.js.wat (mehrere hundert Megabyte) wird entfernt. +# 2. Der Quelltext der gebauten Fassung wird als Tarball beigelegt — AGPLv3 §13 verlangt +# beim Netzwerkbetrieb einen Quellcode-Zugang für die Nutzer:innen, und der Footer der +# Startseite verweist darauf. +# 3. Die großen Dateien werden vorkomprimiert, damit nginx sie mit gzip_static bzw. +# brotli_static ausliefern kann, statt 24 MB je Abruf neu zu packen. +# +# Aufruf: deploy/webpaket.sh <ausgabeverzeichnis> (Standard: target/web) + +set -eu + +ziel="${1:-target/web}" +quelle="$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)" + +if [ ! -d "$ziel" ]; then + echo "webpaket: $ziel gibt es nicht — erst -Pwasm package laufen lassen." >&2 + exit 1 +fi + +# 1. Zwischenschritt fort. +rm -f "$ziel/aendggner.js.wat" + +# 2. Quelltext der gebauten Fassung. +# +# Ein veränderter Arbeitsbaum bricht den Bau ab: Der angebotene Quelltext muss der +# ausgelieferten Fassung entsprechen, sonst ist die Auflage aus §13 verfehlt. Wer nur +# ausprobieren will, setzt QUELLTEXT_UNGEPRUEFT=1. +if ! git -C "$quelle" rev-parse --git-dir >/dev/null 2>&1; then + echo "webpaket: $quelle ist kein Git-Arbeitsbaum; ohne ihn lässt sich der Quelltext" >&2 + echo " der gebauten Fassung nicht beilegen (AGPLv3 §13)." >&2 + exit 1 +fi + +if [ -n "$(git -C "$quelle" status --porcelain)" ] && [ "${QUELLTEXT_UNGEPRUEFT:-}" != 1 ]; then + echo "webpaket: Der Arbeitsbaum trägt uneingecheckte Änderungen. Der beigelegte Quelltext" >&2 + echo " wäre dann nicht der gebaute. Erst einchecken — oder für einen Probelauf" >&2 + echo " QUELLTEXT_UNGEPRUEFT=1 setzen (dann nicht ausliefern)." >&2 + exit 1 +fi + +fassung="$(git -C "$quelle" rev-parse HEAD)" +git -C "$quelle" archive --format=tar.gz --prefix=aendggner/ HEAD \ + -o "$(CDPATH='' cd -- "$ziel" && pwd)/aendggner-quelltext.tar.gz" + +{ + echo "ÄndGgner — Quelltext der ausgelieferten Fassung" + echo + echo "Commit: $fassung" + echo "Gebaut: $(date -u '+%Y-%m-%dT%H:%M:%SZ')" + echo + echo "Vollständig in aendggner-quelltext.tar.gz; Bauanleitung darin in README.adoc." + echo "Fortlaufend: https://gerrit.benkard.de/plugins/gitiles/aendggner" +} > "$ziel/quelltext-fassung.txt" + +# 3. Vorkompression. Der Tarball ist bereits gepackt und bleibt außen vor. +for datei in "$ziel"/*.wasm "$ziel"/*.js "$ziel"/*.css "$ziel"/*.html "$ziel"/*.svg \ + "$ziel"/*.txt; do + [ -f "$datei" ] || continue + gzip -9 -f -k -- "$datei" + if command -v brotli >/dev/null 2>&1; then + brotli -f -- "$datei" + fi +done + +if ! command -v brotli >/dev/null 2>&1; then + echo "webpaket: brotli nicht gefunden — nur .gz angelegt." >&2 +fi + +echo "webpaket: $ziel ist auslieferbar (Fassung $(git -C "$quelle" rev-parse --short HEAD))." |
