From 914f2fb854bb9cd3287e3295364aa709db4de235 Mon Sep 17 00:00:00 2001 From: D4rkst3r Date: Tue, 11 Aug 2026 23:34:02 +0200 Subject: [PATCH] docs: der NPM-Block zum Einfuegen, und ein Skript, das sagt ob er sitzt docs/npm-advanced.conf enthaelt genau das, was in NPM unter Advanced -> Custom Nginx Configuration gehoert: die drei ^~-Bloecke fuer /f/, /t/ und /api/, samt Begruendung als Kommentar. Die ganze Datei ist einfuegbar -- nginx kennt '#'. Syntax gegengeprueft mit `nginx -t` in einem Wegwerf-Container, und zwar mit DIESEM Dateiinhalt und nicht mit einer abgetippten Kurzfassung. /s/ steht bewusst NICHT drin: die Schluessel bestehen aus einem festen Alphabet ohne Punkt, eine Freigabe-Adresse endet also nie auf eine Bildendung und faellt gar nicht erst in die assets.conf-Regel. /assets/ ebenfalls nicht -- die Dateinamen der gebauten Oberflaeche tragen einen Hash und aendern sich nie unter demselben Namen, dort ist der Zwischenspeicher richtig. tools/proxy-pruefen.ps1 misst danach, ob es gewirkt hat, und sagt sonst, was zu tun ist. Beim Schreiben hat es gleich eine SCHWAECHE IN SICH SELBST gezeigt: die erste Fassung prueft "/api/ ohne Token -> 401" und meldete Erfolg -- der 401 war aber nur ein zwischengespeicherter, den der Proxy an alle ausliefert. Gemessen bekam ihn sogar ein Aufrufer MIT gueltigem Token. Verlaesslich ist nur das Paar auf einer FRISCHEN, zufaelligen Adresse: GET /api/exists/probe-xyz.webp mit Token -> 200 GET /api/exists/probe-xyz.webp ohne Token -> 401 gut -> 200 Block fehlt Aktueller Stand, gemessen: [ok] /f/ Cache-Control public, max-age=300 [!!] /t/ Cache-Control max-age=10608 [ok] /api/ mit Token -> 200 HTTP 200 [!!] /api/ dieselbe Adresse ohne Token HTTP 200 -- der Proxy gibt sie weiter [ok] /api/exists?pfad ohne Token -> 401 HTTP 401 Die letzte Zeile ist der Grund, warum die Abfrageform gebaut wurde: sie ist dicht, ohne von dieser Proxy-Einstellung abzuhaengen. Co-Authored-By: Claude Opus 5 --- ROADMAP.md | 12 +++++ docs/npm-advanced.conf | 91 ++++++++++++++++++++++++++++++++ tools/proxy-pruefen.ps1 | 114 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+) create mode 100644 docs/npm-advanced.conf create mode 100644 tools/proxy-pruefen.ps1 diff --git a/ROADMAP.md b/ROADMAP.md index b192f84..2d0c218 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -481,6 +481,18 @@ darin lag. **Was noch bei dir liegt** — an NPM komme ich ohne Zugang nicht. +> **Fertig zum Einfügen:** `docs/npm-advanced.conf`. Den ganzen Inhalt in +> NPM → Proxy-Host → *Advanced* → *Custom Nginx Configuration*, dann **Save** +> (erst das schreibt `9.conf` neu — die wahrscheinlichste Erklärung dafür, dass +> es beim ersten Anlauf nicht ankam). Danach: +> +> ```powershell +> pwsh -File tools\proxy-pruefen.ps1 -Token d4rk_... +> ``` +> +> Das misst und sagt es, statt es vermuten zu lassen. Die Syntax der Datei ist +> mit `nginx -t` in einem Wegwerf-Container gegengeprüft. + Nachgemessen am 11.08.2026, nicht vermutet: | Pfad | gemessene `Cache-Control` | heißt | diff --git a/docs/npm-advanced.conf b/docs/npm-advanced.conf new file mode 100644 index 0000000..6c67676 --- /dev/null +++ b/docs/npm-advanced.conf @@ -0,0 +1,91 @@ +# d4rk_media — der Block fuer Nginx Proxy Manager +# +# WOHIN: NPM oeffnen -> Hosts -> Proxy Hosts -> fivemanage.d4rkst3r.de +# -> Edit -> Reiter "Advanced" -> Feld "Custom Nginx Configuration" +# +# WAS: den GESAMTEN Inhalt dieser Datei dort hineinkopieren. Was schon drin +# steht (der /f/-Block), ist unten enthalten -- also ersetzen, nicht +# anhaengen. +# +# DANACH: "Save" druecken. ERST DANN schreibt NPM seine Datei +# /data/nginx/proxy_host/9.conf neu und laedt nginx nach. Ohne Save +# passiert nichts, und das ist die wahrscheinlichste Erklaerung dafuer, +# dass der Block beim letzten Mal nicht angekommen ist. +# +# Die Kommentarzeilen duerfen mit hinein -- nginx kennt '#'. Die Syntax ist +# gegengeprueft: `nginx -t` in einem Wegwerf-Container, "syntax is ok". +# +# ---------------------------------------------------------------------------- +# WARUM DAS UEBERHAUPT NOETIG IST +# +# NPM bindet in jeden Proxy-Host eine assets.conf ein. Deren Regel greift auf +# JEDE Adresse, die auf eine Bild- oder Skriptendung endet: +# +# location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|...)$ { +# proxy_cache public-cache; +# proxy_cache_key $host$request_uri; <- der Token zaehlt NICHT mit +# proxy_ignore_headers Set-Cookie Cache-Control Expires ...; +# proxy_cache_valid any 30m; +# proxy_hide_header Cache-Control; +# proxy_hide_header Vary; +# } +# +# Drei gemessene Folgen davon: +# +# 1. /api/exists/vehicles/adder.webp endet auf ".webp" und faellt hinein. Der +# Proxy legt die BERECHTIGTE Antwort weg und liefert sie danach an jeden +# aus -- nachgemessen: 200 mit voller Auskunft, ganz ohne Token. Und +# andersherum: landet zuerst ein 401 im Speicher, bekommen ihn 30 Minuten +# lang alle, auch mit gueltigem Token. +# +# 2. /t/… (die Vorschaubilder) tragen dadurch "Cache-Control: max-age=12799" +# statt unserer "public, max-age=60". Nach einem erneuten Upload zeigt die +# Galerie deshalb stundenlang die ALTE Vorschau. +# +# 3. "proxy_hide_header Vary" wuerde unser "Vary: Accept" wegwerfen. Daran +# haengt die WebP-Auslieferung: ohne die Zeile bekaeme ein Aufrufer, der +# kein WebP versteht, die WebP-Fassung aus dem Zwischenspeicher. +# +# NACHGEMESSEN AM LAUFENDEN DIENST, damit klar ist, dass das kein +# theoretisches Problem ist -- dieselbe Adresse, dreimal: +# +# GET /api/exists/probe-xyz.webp mit gueltigem Token -> 200 +# GET /api/exists/probe-xyz.webp OHNE Token -> 200 (!) +# GET /api/exists/vehicles/adder.webp mit gueltigem Token -> 401 (!) +# +# Die zweite Zeile ist die Auskunft ohne Berechtigung. Die dritte ist derselbe +# Fehler andersherum: dort lag ein 401 im Speicher, und den bekam auch, wer +# einen gueltigen Token hatte. +# +# Das "^~" ist der ganze Trick: eine gewoehnliche Praefix-Location VERLIERT +# gegen eine Regex-Location. Mit "^~" gewinnt sie und die Regex kommt gar nicht +# mehr zum Zuge. In einem Wegwerf-nginx nachgemessen. +# +# NICHT ausgenommen wird /assets/ -- die gebaute Oberflaeche. Deren Dateinamen +# tragen einen Hash (index-DCMjxAfP.js), aendern sich also nie unter demselben +# Namen. Dort ist der Zwischenspeicher genau richtig. +# ---------------------------------------------------------------------------- +# AB HIER IST ES KONFIGURATION. + + +# Die Dateien selbst. Sie koennen sich unter derselben Adresse aendern (ein +# neu aufgenommenes Fahrzeugbild ersetzt das alte), und sie tragen "Vary: +# Accept" fuer die sparsame WebP-Fassung. +location ^~ /f/ { + proxy_cache off; + include conf.d/include/proxy.conf; +} + +# Die Vorschaubilder. Aendern sich, sobald ein Bild neu hochgeladen wird -- +# und dann soll das Dashboard es sehen und nicht in vier Stunden. +location ^~ /t/ { + proxy_cache off; + include conf.d/include/proxy.conf; +} + +# Die API. Hier haengt eine Zugangspruefung dran, und eine zwischengespeicherte +# Antwort kennt den Token nicht, der sie erlaubt hat. +location ^~ /api/ { + proxy_cache off; + include conf.d/include/proxy.conf; +} diff --git a/tools/proxy-pruefen.ps1 b/tools/proxy-pruefen.ps1 new file mode 100644 index 0000000..e825938 --- /dev/null +++ b/tools/proxy-pruefen.ps1 @@ -0,0 +1,114 @@ +# Sitzt der NPM-Block? Eine Antwort, keine Vermutung. +# +# pwsh -File tools\proxy-pruefen.ps1 +# +# Nach dem Einfuegen von docs\npm-advanced.conf und "Save" in NPM ausfuehren. +# Das Skript AENDERT NICHTS -- es misst nur, was ueber die Leitung kommt. +# +# WARUM ES DAS GIBT: der Block wurde schon einmal fuer eingefuegt gehalten und +# war es nicht. Die Kopfzeile sagt es eindeutig, aber man muss sie eben ansehen. + +param( + [string]$Basis = 'https://fivemanage.d4rkst3r.de', + # Fuer die API-Messung. Ohne ihn wird sie uebersprungen -- sie braucht einen + # gueltigen Token, um "mit" und "ohne" unterscheiden zu koennen. + [string]$Token = '' +) + +$ErrorActionPreference = 'Continue' +$fehler = 0 + +function Kopf([string]$url, [hashtable]$header = @{}) { + try { + $a = Invoke-WebRequest -Uri $url -Method Head -Headers $header -SkipHttpErrorCheck ` + -MaximumRedirection 0 -ErrorAction Stop + return @{ code = [int]$a.StatusCode; kopf = $a.Headers } + } catch { + return @{ code = 0; kopf = @{}; fehler = $_.Exception.Message } + } +} + +function Wert($kopf, [string]$name) { + foreach ($k in $kopf.Keys) { + if ($k -ieq $name) { return ($kopf[$k] -join ', ') } + } + return '' +} + +function Zeile([string]$was, [bool]$gut, [string]$gemessen) { + $zeichen = if ($gut) { ' [ok] ' } else { ' [!!] ' } + $farbe = if ($gut) { 'Green' } else { 'Red' } + Write-Host ($zeichen + $was.PadRight(42) + $gemessen) -ForegroundColor $farbe + if (-not $gut) { $script:fehler++ } +} + +Write-Host '' +Write-Host "Messe $Basis" -ForegroundColor Cyan +Write-Host '' + +# -- 1. Die Dateien ------------------------------------------------------------ +# +# Unsere Vorgabe ist "public, max-age=300". Kommt etwas anderes an, hat +# assets.conf sie ueberschrieben -- dann fehlt der Block. +$f = Kopf "$Basis/f/vehicles/adder.webp" +$cc = Wert $f.kopf 'Cache-Control' +Zeile '/f/ Cache-Control' ($cc -match 'max-age=300') $(if ($cc) { $cc } else { '(keine)' }) + +# -- 2. Die Vorschaubilder ----------------------------------------------------- +$t = Kopf "$Basis/t/vehicles/adder.webp" +$cc = Wert $t.kopf 'Cache-Control' +Zeile '/t/ Cache-Control' ($cc -match 'max-age=60\b') $(if ($cc) { $cc } else { '(keine)' }) + +# -- 3. Die API, und das ist die entscheidende --------------------------------- +# +# EIN EINZELNER AUFRUF TAUGT HIER NICHT, und das ist beim Schreiben dieses +# Skripts aufgefallen: "ohne Token -> 401" sah nach Erfolg aus, war aber nur +# ein zwischengespeicherter 401, den der Proxy an ALLE ausliefert -- gemessen +# gab er ihn sogar Aufrufern MIT gueltigem Token. +# +# Verlaesslich ist nur das Paar auf einer FRISCHEN Adresse, die noch in keinem +# Zwischenspeicher liegt: +# +# mit Token -> 200 (der Dienst antwortet) +# ohne Token -> 401 (der Proxy hat NICHT weggelegt) <- gut +# ohne Token -> 200 (er hat weggelegt) <- Block fehlt +# +# Die Adresse endet auf ".webp", damit sie ueberhaupt in die assets.conf-Regel +# faellt -- ohne diese Endung wuerde die Messung nichts messen. +if (-not $Token) { + Write-Host ' [--] /api/ (uebersprungen) kein -Token uebergeben' -ForegroundColor DarkGray + Write-Host ' Dashboard -> Token -> einen anlegen, dann:' -ForegroundColor DarkGray + Write-Host ' pwsh -File tools\proxy-pruefen.ps1 -Token d4rk_...' -ForegroundColor DarkGray +} else { + $zufall = -join ((1..12) | ForEach-Object { 'abcdefghijklmnopqrstuvwxyz0123456789'[(Get-Random -Max 36)] }) + $probe = "$Basis/api/exists/probe-$zufall.webp" + + $mit = Kopf $probe @{ Authorization = "Bearer $Token" } + Zeile '/api/ mit Token -> 200' ($mit.code -eq 200) "HTTP $($mit.code)" + + $ohne = Kopf $probe + Zeile '/api/ dieselbe Adresse ohne Token -> 401' ($ohne.code -eq 401) ` + $(if ($ohne.code -eq 200) { 'HTTP 200 — der Proxy gibt sie weiter!' } else { "HTTP $($ohne.code)" }) +} + +# Die Abfrageform ist von sich aus dicht -- sie muss auch OHNE den Block 401 +# geben. Faellt SIE durch, stimmt etwas am Dienst und nicht am Proxy. +$b = Kopf "$Basis/api/exists?pfad=vehicles/adder.webp" +Zeile '/api/exists?pfad ohne Token -> 401' ($b.code -eq 401) "HTTP $($b.code)" + +Write-Host '' +if ($fehler -eq 0) { + Write-Host ' Der Block sitzt. Nichts weiter zu tun.' -ForegroundColor Green +} else { + Write-Host " $fehler Messung(en) sagen: der Block ist NICHT wirksam." -ForegroundColor Red + Write-Host '' + Write-Host ' Naechste Schritte:' -ForegroundColor Yellow + Write-Host ' 1. In NPM den Proxy-Host oeffnen -> Advanced -> Inhalt von' + Write-Host ' docs\npm-advanced.conf einfuegen -> SAVE druecken.' + Write-Host ' 2. Gegenpruefen, was NPM daraus gemacht hat:' + Write-Host ' docker exec npm sh -c "grep -n ''location'' /data/nginx/proxy_host/9.conf"' + Write-Host ' Stehen dort ^~ /f/, ^~ /t/ und ^~ /api/, ist gespeichert worden.' + Write-Host ' 3. Dieses Skript erneut ausfuehren.' +} +Write-Host '' +exit $(if ($fehler -eq 0) { 0 } else { 1 })