Die Web-App rechnet im Browser statt auf dem Server

Bisher lief die Weboberfläche auf einem JDK-HttpServer: Uploads landeten als
temporäre Dateien auf dem Server, die Synopse entstand dort. Das kostete
Betrieb (systemd, Reverse Proxy, Rate-Limiting, Timeouts) und verlangte ein
Datenschutzversprechen, das nur zusicherbar, nicht nachprüfbar war — gerade
Entwurfstexte verließen den Rechner der Nutzer:innen.

Neu übersetzt `./mvnw -Pwasm package` dieselbe Pipeline mit GraalVM Web Image
(`native-image --tool:svm-wasm`) nach WebAssembly, PDFBox eingeschlossen.
Ausgeliefert werden nur noch statische Dateien; gerechnet wird im Browser.
Die erzeugte Synopse ist byteweise identisch mit der der Befehlszeile
(SHA-256 verglichen für IfSG 48/27/21 und BayJG 151/3/54).

Die Befehlszeile bleibt unberührt: `./mvnw package` erzeugt unverändert das
JAR, alle Optionen und Meldungstexte sind gleich, das Wasm-Profil ist rein
additiv und verlangt Oracle GraalVM 25.1+ (die CE hat kein Web Image).

Portabilitätsschnitt (nützt beiden Fassungen):

* `Quelle` (Name + Bytes) ersetzt `Path` in der Pipeline; nur die
  Befehlszeile kennt noch ein Dateisystem. Der Name trägt genau den
  bisherigen `getFileName()`-Text, damit Warnungen und Quellenzeile
  wortgleich bleiben.

* `DateiTyp` erkennt PDF/XML/Klartext an den Signaturbytes. Tika entfällt —
  eine schwergewichtige Abhängigkeit samt ServiceLoader- und
  XML-Konfiguration weniger, was der Wasm-Übersetzung unmittelbar zugutekommt.

Vier Eigenheiten von Web Image, die der Quelltext jeweils an Ort und Stelle
vermerkt:

* `java.util.zip.Inflater` ist nicht angebunden (GR-65205), ohne Inflate ist
  kein PDF lesbar. `InflaterErsatz` substituiert ihn durch jzlib.

* Typisierte JS-Felder lassen sich nicht nach `byte[]` umsetzen
  („byteArrayHub is not defined“); der Dateiinhalt wandert als Base64.

* JULs Standardformatter ruft `StackWalker`, den es dort nicht gibt.

* Im Worker fehlt `document.currentScript`, worauf die Laufzeit das
  Wasm-Modul neben `worker.js` sucht; die VM wird deshalb mit ausdrücklichem
  Pfad ein zweites Mal gestartet.

Die Reachability-Metadaten stammen aus einem Lauf des Tracing-Agents über die
Pipeline; die PDFBox- und FontBox-Ressourcen sind als Globs ergänzt, sonst
scheitern PDFs an „Could not find referenced cmap stream Identity-H“.

Entfallen: WebMain, UploadHandler, StaticHandler, Multipart und die
systemd-Unit. Die nginx-Vorlage liefert jetzt statische Dateien aus, und die
Datenschutzseite sagt, was nun stimmt: Die Dateien verlassen den Rechner
nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Change-Id: I38faf2ac0f764d601f080d4276babe4747773683
diff --git a/deploy/aendggner-web.service b/deploy/aendggner-web.service
deleted file mode 100644
index f87bbb8..0000000
--- a/deploy/aendggner-web.service
+++ /dev/null
@@ -1,41 +0,0 @@
-# systemd-Unit für den ÄndGgner-Webserver.
-#
-# Installation (Beispiel):
-#   sudo cp deploy/aendggner-web.service /etc/systemd/system/
-#   sudo useradd --system --home /opt/aendggner --shell /usr/sbin/nologin aendggner
-#   sudo cp -r . /opt/aendggner && sudo chown -R aendggner:aendggner /opt/aendggner
-#   sudo systemctl daemon-reload
-#   sudo systemctl enable --now aendggner-web
-#
-# Erwartet, dass zuvor "mvn package" gelaufen ist, sodass target/aendggner-*.jar
-# und target/libs/* existieren.
-
-[Unit]
-Description=AendGgner Web-App
-After=network.target
-
-[Service]
-Type=simple
-User=aendggner
-Group=aendggner
-WorkingDirectory=/opt/aendggner
-ExecStart=/bin/sh -c 'exec java -cp "target/aendggner-*.jar:target/libs/*" eu.mulk.aendggner.web.WebMain'
-Environment=AENDGGNER_WEB_PORT=8080
-Environment=AENDGGNER_WEB_BIND=127.0.0.1
-Restart=on-failure
-RestartSec=5
-
-# Ressourcen-Obergrenzen: hält den Dienst auf einem kleinen Server in Schach,
-# selbst wenn die Anwendungs- und Proxy-seitigen Limits einmal nicht greifen.
-MemoryMax=512M
-CPUQuota=150%
-TasksMax=128
-
-# Härtung
-NoNewPrivileges=true
-PrivateTmp=true
-ProtectSystem=strict
-ProtectHome=true
-
-[Install]
-WantedBy=multi-user.target
diff --git a/deploy/nginx-aendggner.conf b/deploy/nginx-aendggner.conf
index f4667fb..46eacb5 100644
--- a/deploy/nginx-aendggner.conf
+++ b/deploy/nginx-aendggner.conf
@@ -1,9 +1,8 @@
-# Reverse-Proxy-Vorlage für den ÄndGgner-Webserver. Übernimmt TLS-Terminierung und
-# Rate-Limiting; der Java-Prozess selbst bindet nur an 127.0.0.1 (siehe aendggner-web.service).
+# 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.
 #
-# Vor dem Einsatz anpassen: server_name, ssl_certificate(_key) (z. B. via certbot).
-
-limit_req_zone $binary_remote_addr zone=aendggner:10m rate=10r/m;
+# Vor dem Einsatz anpassen: server_name, ssl_certificate(_key) (z. B. via certbot), root.
 
 server {
     listen 80;
@@ -20,20 +19,29 @@
     ssl_certificate     /etc/letsencrypt/live/aendggner.example.org/fullchain.pem;
     ssl_certificate_key /etc/letsencrypt/live/aendggner.example.org/privkey.pem;
 
-    # Muss zum Upload-Limit von UploadHandler (MAX_BODY_BYTES, aktuell 40 MB) passen.
-    client_max_body_size 40m;
+    # Inhalt von target/web/ — ohne aendggner.js.wat, das nur ein Zwischenschritt
+    # des Übersetzers ist.
+    root /var/www/aendggner;
+    index index.html;
+
+    # Ohne diesen Typ lehnt der Browser die Streaming-Instanziierung des Moduls ab.
+    types {
+        application/wasm wasm;
+    }
+
+    gzip on;
+    gzip_types application/wasm application/javascript text/css text/html;
+    gzip_min_length 1024;
+
+    # 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";
+    }
 
     location / {
-        limit_req zone=aendggner burst=5 nodelay;
-
-        proxy_pass http://127.0.0.1:8080;
-        proxy_set_header Host $host;
-        proxy_set_header X-Real-IP $remote_addr;
-        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-        proxy_set_header X-Forwarded-Proto $scheme;
-
-        # Etwas großzügiger als der 30s-Timeout der Anwendung, damit deren eigene
-        # 504-Antwort den Nutzer erreicht statt eines nginx-eigenen Timeouts.
-        proxy_read_timeout 35s;
+        try_files $uri $uri/ =404;
     }
 }