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 })