freesailarr: im Gateway-Modus fehlt die Umleitung fuer den fehlenden Schraegstrich hinter /qbittorrent #118

Closed
opened 2026-10-12 21:25:06 +02:00 by jlab-bot · 5 comments
Member

In freesailarr 3.2.0+up1.0.0 fehlt im Gateway-Modus die Regel, die den fehlenden Schrägstrich hinter /qbittorrent ergänzt. Im Ingress-Modus leistet das ein configuration-snippet; in der gerenderten HTTPRoute hat es keine Entsprechung.

Gefunden am 12.10.2026 beim Umschwenken von freesailarr.jk-dev.de auf die Gateway API (jlab-cluster/cluster-config #19). Der Host ist deshalb nicht migriert worden.

Was gerendert wird

rules:
  - matches:
      - path: { type: PathPrefix, value: /qbittorrent }
    filters:
      - type: URLRewrite
        urlRewrite:
          path: { type: ReplacePrefixMatch, replacePrefixMatch: / }
    backendRefs:
      - name: freesailarr-freesailarr-qbittorrent-service
        port: 8080
  # … vier weitere Apps …
  - matches:
      - path: { type: PathPrefix, value: / }
    backendRefs:
      - name: freesailarr-freesailarr-seerr-service
        port: 5055

Das Praefix-Abschneiden ist damit sauber gelöst — und der Kommentar in den Werten beschreibt genau das zu Recht als besser als vorher, weil der Filter je Regel gilt und das zweite Ingress-Objekt entfällt.

Was fehlt

Eine Anfrage auf /qbittorrent ohne abschließenden Schrägstrich:

  1. trifft PathPrefix /qbittorrent
  2. wird auf / umgeschrieben, qBittorrent liefert seine Anmeldeseite
  3. die Dokument-URL im Browser bleibt /qbittorrent — es gibt keine Umleitung
  4. relative Verweise der Seite lösen damit gegen die Wurzel auf: css/login.css → /css/login.css
  5. das trifft die /-Regel und landet bei Seerr

Das ist genau das Fehlerbild, das die Werte-Datei im ingress-Block beschreibt — nur dass es dort durch das Snippet verhindert wird:

if ($uri = "/qbittorrent") { return 301 $scheme://$host/qbittorrent/; }

Im Gateway-Modus gibt es dafür keinen Ersatz, und die Werte erwähnen die Lücke nicht. Der ingress-Block warnt ausdrücklich, dass Annotationen im Gateway-Modus wegfallen — aber bei dieser einen ist der Wegfall nicht neutral, sondern eine Verhaltensänderung.

Die Entsprechung gibt es, sie wird nur nicht gerendert

Ein RequestRedirect-Filter an einer Regel mit Exact-Pfad leistet dasselbe und gewinnt nach den Vorrangregeln gegen beide PathPrefix-Regeln:

  - matches:
      - path: { type: Exact, value: /qbittorrent }
    filters:
      - type: RequestRedirect
        requestRedirect:
          path: { type: ReplaceFullPath, replaceFullPath: /qbittorrent/ }
          statusCode: 301

Dasselbe gilt für jede andere App, die über <app>.ingress.host auf einen Unterpfad gelegt wird und keinen Basispfad kennt — die Regel ist nicht qBittorrent-spezifisch, sondern gehört zu „App ohne Basispfad auf einem Unterpfad".

Zu prüfen wäre dabei, ob 301 oder 308 richtig ist: 308 erhält die Methode. Für einen Aufruf der Oberfläche ist das gleichgültig, für eine API unter demselben Präfix nicht.

Warum nicht im Cluster umgangen

Eine handgeschriebene HTTPRoute mit dieser einen Regel neben dem Chart wäre möglich (der Namensraum steht in der Allowlist des Listeners, ein Geschwister-Bundle wäre frei). Dagegen spricht, dass der Chart im Ingress-Modus die Sache selbst löst: Die Asymmetrie gehört in den Chart, nicht in das Cluster-Repository — sonst steht dort ein Objekt, das beim nächsten Chart-Upgrade wieder weg muss.

Nebenbefund zum Vergleich

proxy-body-size fällt im Gateway-Modus ebenfalls weg, und das ist bei diesem Chart laut Werte-Datei für qBittorrents Torrent-Datei-Hochladungen der Kandidat. Das ist aber eine andere Art Lücke — eine Grenze, die verschwindet, nicht ein Verhalten, das kippt. Die Werte benennen sie ausdrücklich; die Schrägstrich-Regel nicht.

In `freesailarr` 3.2.0+up1.0.0 fehlt im Gateway-Modus die Regel, die den fehlenden Schrägstrich hinter `/qbittorrent` ergänzt. Im Ingress-Modus leistet das ein `configuration-snippet`; in der gerenderten `HTTPRoute` hat es keine Entsprechung. Gefunden am 12.10.2026 beim Umschwenken von `freesailarr.jk-dev.de` auf die Gateway API (`jlab-cluster/cluster-config` #19). Der Host ist deshalb **nicht** migriert worden. ## Was gerendert wird ```yaml rules: - matches: - path: { type: PathPrefix, value: /qbittorrent } filters: - type: URLRewrite urlRewrite: path: { type: ReplacePrefixMatch, replacePrefixMatch: / } backendRefs: - name: freesailarr-freesailarr-qbittorrent-service port: 8080 # … vier weitere Apps … - matches: - path: { type: PathPrefix, value: / } backendRefs: - name: freesailarr-freesailarr-seerr-service port: 5055 ``` Das Praefix-Abschneiden ist damit sauber gelöst — und der Kommentar in den Werten beschreibt genau das zu Recht als besser als vorher, weil der Filter **je Regel** gilt und das zweite Ingress-Objekt entfällt. ## Was fehlt Eine Anfrage auf `/qbittorrent` **ohne** abschließenden Schrägstrich: 1. trifft `PathPrefix /qbittorrent` 2. wird auf `/` umgeschrieben, qBittorrent liefert seine Anmeldeseite 3. **die Dokument-URL im Browser bleibt `/qbittorrent`** — es gibt keine Umleitung 4. relative Verweise der Seite lösen damit gegen die Wurzel auf: `css/login.css` → `/css/login.css` 5. das trifft die `/`-Regel und landet bei **Seerr** Das ist genau das Fehlerbild, das die Werte-Datei im `ingress`-Block beschreibt — nur dass es dort durch das Snippet verhindert wird: ``` if ($uri = "/qbittorrent") { return 301 $scheme://$host/qbittorrent/; } ``` Im Gateway-Modus gibt es dafür keinen Ersatz, und die Werte erwähnen die Lücke nicht. Der `ingress`-Block warnt ausdrücklich, dass Annotationen im Gateway-Modus wegfallen — aber bei dieser einen ist der Wegfall nicht neutral, sondern eine Verhaltensänderung. ## Die Entsprechung gibt es, sie wird nur nicht gerendert Ein `RequestRedirect`-Filter an einer Regel mit **Exact**-Pfad leistet dasselbe und gewinnt nach den Vorrangregeln gegen beide `PathPrefix`-Regeln: ```yaml - matches: - path: { type: Exact, value: /qbittorrent } filters: - type: RequestRedirect requestRedirect: path: { type: ReplaceFullPath, replaceFullPath: /qbittorrent/ } statusCode: 301 ``` Dasselbe gilt für jede andere App, die über `<app>.ingress.host` auf einen Unterpfad gelegt wird und keinen Basispfad kennt — die Regel ist nicht qBittorrent-spezifisch, sondern gehört zu „App ohne Basispfad auf einem Unterpfad". Zu prüfen wäre dabei, ob `301` oder `308` richtig ist: `308` erhält die Methode. Für einen Aufruf der Oberfläche ist das gleichgültig, für eine API unter demselben Präfix nicht. ## Warum nicht im Cluster umgangen Eine handgeschriebene `HTTPRoute` mit dieser einen Regel neben dem Chart wäre möglich (der Namensraum steht in der Allowlist des Listeners, ein Geschwister-Bundle wäre frei). Dagegen spricht, dass der Chart im Ingress-Modus die Sache selbst löst: Die Asymmetrie gehört in den Chart, nicht in das Cluster-Repository — sonst steht dort ein Objekt, das beim nächsten Chart-Upgrade wieder weg muss. ## Nebenbefund zum Vergleich `proxy-body-size` fällt im Gateway-Modus ebenfalls weg, und das ist bei diesem Chart laut Werte-Datei für qBittorrents Torrent-Datei-Hochladungen der Kandidat. Das ist aber eine andere Art Lücke — eine Grenze, die verschwindet, nicht ein Verhalten, das kippt. Die Werte benennen sie ausdrücklich; die Schrägstrich-Regel nicht.
jlab-bot added the inhouse-chart label 2026-10-12 21:30:29 +02:00
Author
Member

Nachgeprüft, genehmigter Plan — und eine Korrektur an der Einordnung

Der Befund ist echt und reproduzierbar beschrieben. Eine tragende Annahme hält aber nicht, und sie ändert den Auftrag, deshalb zuerst die Korrektur.

Korrektur: der Ingress-Modus löst es nicht, es gibt dort kein Snippet

Im Ingress-Modus leistet das ein configuration-snippet

Ein solches Snippet existiert in diesem Chart nicht. Ein repo-weites git grep über origin/main nach configuration-snippet, return 30[18] und $uri = findet genau einen Treffer im ganzen Repository, und das ist ein auskommentiertes Beispiel in bentopdf/values.yaml:223. Weder freesailarr/templates/freesailarr.ingress.yaml noch freesailarr/values.yaml enthalten etwas davon. Die im Issue zitierte Zeile

if ($uri = "/qbittorrent") { return 301 $scheme://$host/qbittorrent/; }

steht nirgends im Chart.

Folglich leitet auch der Ingress-Modus nicht um, und zwar Schritt für Schritt aus demselben Grund:

  1. Der Pfad des qBittorrent-Objekts ist {{ printf "%s(/|$)(.*)" $sharedQbt.path }}, also /qbittorrent(/|$)(.*), mit rewrite-target: /$2.
  2. Für eine Anfrage auf /qbittorrent greift der $-Zweig der Alternation. $1 und $2 sind leer.
  3. Die Anfrage nach oben lautet damit /. qBittorrent liefert seine Anmeldeseite mit 200 — es sieht / und hat keinen Grund umzuleiten.
  4. Die Dokument-URL im Browser bleibt /qbittorrent.
  5. css/login.css löst gegen die Wurzel auf, trifft den /-Pfad des Hauptobjekts und landet bei Seerr.

Das ist dasselbe Fehlerbild, dieselben fünf Schritte, derselbe falsche Zielort. Die Lücke ist also kein Verhaltenswechsel des Gateway-Modus, sondern ein Vorbestand in beiden Modi, den die Migration zum ersten Mal sichtbar gemacht hat.

Zwei Folgen, und die zweite ist der Grund, warum der Umfang wächst:

  • Der Satz „bei dieser einen ist der Wegfall nicht neutral, sondern eine Verhaltensänderung" trifft nicht zu. Es fällt nichts weg, was vorher gewirkt hätte.
  • Eine Behebung nur im Gateway-Zweig würde den Fehler im Standardmodus des Charts stehen lassen — ingress.enabled ist der Hauptschalter und steht auf true. Der Chart würde eine bekannte stille Falle weiter ausliefern, und zwar in genau dem Modus, den heute alle Releases fahren.

Davon unberührt: der Nebenbefund zu proxy-body-size ist richtig eingeordnet. Eine verschwindende Grenze ist eine andere Gattung als ein kippendes Verhalten, und sie ist in den Werten benannt.

Was am Issue bestätigt ist, gegen die Spezifikation geprüft

Geprüft gegen kubernetes-sigs/gateway-api v1.6.3, apis/v1/httproute_types.go.

Die Vorrangregel trägt. Die Rangfolge lautet dort wörtlich * "Exact" path match. vor * "Prefix" path match with largest number of characters., und sie gilt unabhängig von der Listenposition — die Position entscheidet erst, wenn alle vorherigen Kriterien gleichstehen („If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order)"). Die vorgeschlagene Exact-Regel gewinnt damit gegen PathPrefix /qbittorrent und gegen PathPrefix /, ohne dass die Reihenfolge im Template eine Zusage wäre, auf die sich jemand verlassen muss.

Die 301/308-Frage ist entscheidbar, und nicht nach Geschmack: es wird 301. Die Spezifikation sagt Support: statusCode 301 and 302 are Core; all others are Extended, und 308 trägt ein eigenes Merkmal HTTPRoute308RedirectStatusCode. Entscheidend ist, dass ein nicht unterstützter Wert nicht weich fällt:

Unknown values here must result in the implementation setting the Accepted Condition for the Route to status: False, with a Reason of UnsupportedValue.

Verworfen wird damit die ganze Route, nicht die eine Regel — auf einer Implementierung ohne 308 wäre also auch das Routing der vier Nachbar-Apps weg. Gegen Entscheidung 1 von #114 (implementierungs-neutral, keine Bindung an eine Implementierung) ist das ausgeschlossen. Der Methodenerhalt, den 308 bietet, wird nicht gebraucht: die umgeleitete Anfrage ist der Aufruf der Oberfläche, und qBittorrents API liegt nicht auf dem präfixlosen Pfad, sondern unter /qbittorrent/api/..., das von der PathPrefix-Regel bedient wird und die Exact-Regel nie trifft.

Nebenbei, weil es leicht übersehen wird: statusCode hat die Vorgabe 302 und muss ausdrücklich gesetzt werden.

Der Umfang ist genau qBittorrent. Die Regel gehört, wie das Issue richtig sagt, zur Gattung „App ohne Basispfad auf einem Unterpfad" — diese Gattung hat in diesem Chart aber nur ein Mitglied. seerr ist vom Chart auf / festgenagelt und wird dort ausdrücklich verweigert, bazarr hat eine Base-URL-Einstellung und erwartet das Präfix. Die Regel wird deshalb an qBittorrent gebunden und nicht spekulativ verallgemeinert; dass sie eine Gattung bedient, gehört in den Kommentar, nicht in eine Schleife über Apps, die es nicht gibt.

Plan

1. Gateway-Modus — die fehlende Regel. Eine zusätzliche Regel in der geteilten Route:

- matches:
    - path: { type: Exact, value: /qbittorrent }
  filters:
    - type: RequestRedirect
      requestRedirect:
        path: { type: ReplaceFullPath, replaceFullPath: /qbittorrent/ }
        statusCode: 301

Der Pfad kommt aus qbittorrent.ingress.path, nicht als Literal. Die Core/Extended-Begründung für 301 steht als Kommentar daneben, samt der Folge, die sie trägt (verworfen würde die ganze Route). Gilt auch im Fall qbittorrent.ingress.host, wo qBittorrent auf einem eigenen Host unter / liegt — dort ist weder Rewrite noch Umleitung nötig, und die Regel darf dann nicht rendern.

2. Ingress-Modus — dieselbe Umleitung, ohne Snippet. Ein drittes Objekt mit pathType: Exact auf /qbittorrent und nginx.ingress.kubernetes.io/permanent-redirect.

Ausdrücklich kein configuration-snippet: allow-snippet-annotations ist in ingress-nginx seit 1.9 standardmäßig false, ein Snippet bliebe dort also wirkungslos, und zwar still — womit die Behebung genau die Eigenschaft des Fehlers teilen würde, den sie behebt.

Das setzt voraus, dass nginx' location = (aus pathType: Exact) Vorrang vor der Regex-Location des bestehenden qBittorrent-Objekts hat. Diese Annahme ist zu messen, nicht zu glauben. Hält sie nicht, wird die Falle stattdessen in values.yaml dokumentiert (Aufruf mit abschließendem Schrägstrich) und der Ingress-Zweig bleibt unverändert — halb behoben ist hier schlechter als offen benannt, weil ein drittes Objekt, das nichts tut, die nächste Diagnose kostet.

3. Render-Zusicherung in ci/run-httproute-match-test.sh. Rendert ein Chart eine Regel mit URLRewrite/ReplacePrefixMatch auf einem Präfix ungleich /, muss zum selben Präfix eine Exact-Regel mit RequestRedirect existieren. Das ist der Teil, der verhindert, dass die Lücke beim nächsten Chart mit Präfix-Rewrite wieder entsteht — bulwark ist in Gruppe 3 der zweite rewrite-target-Chart und läuft damit gegen dieselbe Prüfung.

4. Wiki. Die freesailarr-Seite, und Lessons-Learned, weil die Lehre allgemein ist: ein Präfix-Rewrite ohne begleitende Umleitung erzeugt ein Fehlerbild, das wie ein Anwendungsfehler aussieht und auf den Nachbardienst zeigt. Dass der Fehler in beiden Modi steckte und erst bei der Migration auffiel, gehört dazu — die Ursache ist nicht die Gateway-API, sondern dass ein Rewrite ohne Umleitung unvollständig ist.

5. Version: Major, 4.0.0+up1.0.0. Eine neue 301 ist nach den Konventionen dieses Repositories eine Laufzeit-Verhaltensänderung. Kein bestehendes funktionierendes Setup bricht, aber die Konvention knüpft an „runtime behaviour changed", nicht an „bricht jemandem etwas". Entsprechend wird hier kein leerer helm template-Diff gegen origin/main behauptet: der Diff ist der Zweck der Änderung, und der PR zeigt ihn.

Entschieden

Punkt 2 geht über die Frage des Issues hinaus, das nur den Gateway-Modus betrifft. Beides — der erweiterte Umfang und die Major-Veröffentlichung — ist vom Maintainer genehmigt, mit der Begründung aus der Korrektur oben: der Standardmodus darf die Falle nicht behalten.

# Nachgeprüft, genehmigter Plan — und eine Korrektur an der Einordnung Der Befund ist echt und reproduzierbar beschrieben. Eine tragende Annahme hält aber nicht, und sie ändert den Auftrag, deshalb zuerst die Korrektur. ## Korrektur: der Ingress-Modus löst es nicht, es gibt dort kein Snippet > Im Ingress-Modus leistet das ein `configuration-snippet` **Ein solches Snippet existiert in diesem Chart nicht.** Ein repo-weites `git grep` über `origin/main` nach `configuration-snippet`, `return 30[18]` und `$uri =` findet **genau einen** Treffer im ganzen Repository, und das ist ein auskommentiertes Beispiel in `bentopdf/values.yaml:223`. Weder `freesailarr/templates/freesailarr.ingress.yaml` noch `freesailarr/values.yaml` enthalten etwas davon. Die im Issue zitierte Zeile ``` if ($uri = "/qbittorrent") { return 301 $scheme://$host/qbittorrent/; } ``` steht nirgends im Chart. **Folglich leitet auch der Ingress-Modus nicht um**, und zwar Schritt für Schritt aus demselben Grund: 1. Der Pfad des qBittorrent-Objekts ist `{{ printf "%s(/|$)(.*)" $sharedQbt.path }}`, also `/qbittorrent(/|$)(.*)`, mit `rewrite-target: /$2`. 2. Für eine Anfrage auf `/qbittorrent` greift der `$`-Zweig der Alternation. `$1` und `$2` sind leer. 3. Die Anfrage nach oben lautet damit `/`. qBittorrent liefert seine Anmeldeseite mit 200 — es sieht `/` und hat keinen Grund umzuleiten. 4. Die Dokument-URL im Browser bleibt `/qbittorrent`. 5. `css/login.css` löst gegen die Wurzel auf, trifft den `/`-Pfad des **Haupt**objekts und landet bei **Seerr**. Das ist dasselbe Fehlerbild, dieselben fünf Schritte, derselbe falsche Zielort. **Die Lücke ist also kein Verhaltenswechsel des Gateway-Modus, sondern ein Vorbestand in beiden Modi**, den die Migration zum ersten Mal sichtbar gemacht hat. Zwei Folgen, und die zweite ist der Grund, warum der Umfang wächst: - Der Satz „bei dieser einen ist der Wegfall nicht neutral, sondern eine Verhaltensänderung" trifft nicht zu. Es fällt nichts weg, was vorher gewirkt hätte. - Eine Behebung **nur** im Gateway-Zweig würde den Fehler im **Standardmodus** des Charts stehen lassen — `ingress.enabled` ist der Hauptschalter und steht auf `true`. Der Chart würde eine bekannte stille Falle weiter ausliefern, und zwar in genau dem Modus, den heute alle Releases fahren. Davon unberührt: der Nebenbefund zu `proxy-body-size` ist richtig eingeordnet. Eine verschwindende Grenze ist eine andere Gattung als ein kippendes Verhalten, und sie ist in den Werten benannt. ## Was am Issue bestätigt ist, gegen die Spezifikation geprüft Geprüft gegen `kubernetes-sigs/gateway-api` v1.6.3, `apis/v1/httproute_types.go`. **Die Vorrangregel trägt.** Die Rangfolge lautet dort wörtlich `* "Exact" path match.` vor `* "Prefix" path match with largest number of characters.`, und sie gilt **unabhängig von der Listenposition** — die Position entscheidet erst, wenn alle vorherigen Kriterien gleichstehen (*„If ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order)"*). Die vorgeschlagene `Exact`-Regel gewinnt damit gegen `PathPrefix /qbittorrent` **und** gegen `PathPrefix /`, ohne dass die Reihenfolge im Template eine Zusage wäre, auf die sich jemand verlassen muss. **Die 301/308-Frage ist entscheidbar, und nicht nach Geschmack: es wird 301.** Die Spezifikation sagt `Support: statusCode 301 and 302 are Core; all others are Extended`, und 308 trägt ein eigenes Merkmal `HTTPRoute308RedirectStatusCode`. Entscheidend ist, dass ein nicht unterstützter Wert **nicht weich fällt**: > Unknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`. Verworfen wird damit die **ganze Route**, nicht die eine Regel — auf einer Implementierung ohne 308 wäre also auch das Routing der vier Nachbar-Apps weg. Gegen Entscheidung 1 von #114 (implementierungs-neutral, keine Bindung an eine Implementierung) ist das ausgeschlossen. Der Methodenerhalt, den 308 bietet, wird nicht gebraucht: die umgeleitete Anfrage ist der Aufruf der Oberfläche, und qBittorrents API liegt nicht auf dem präfixlosen Pfad, sondern unter `/qbittorrent/api/...`, das von der `PathPrefix`-Regel bedient wird und die `Exact`-Regel nie trifft. Nebenbei, weil es leicht übersehen wird: `statusCode` hat die **Vorgabe 302** und muss ausdrücklich gesetzt werden. **Der Umfang ist genau qBittorrent.** Die Regel gehört, wie das Issue richtig sagt, zur Gattung „App ohne Basispfad auf einem Unterpfad" — diese Gattung hat in diesem Chart aber nur ein Mitglied. `seerr` ist vom Chart auf `/` festgenagelt und wird dort ausdrücklich verweigert, `bazarr` hat eine Base-URL-Einstellung und erwartet das Präfix. Die Regel wird deshalb an qBittorrent gebunden und **nicht** spekulativ verallgemeinert; dass sie eine Gattung bedient, gehört in den Kommentar, nicht in eine Schleife über Apps, die es nicht gibt. ## Plan **1. Gateway-Modus — die fehlende Regel.** Eine zusätzliche Regel in der geteilten Route: ```yaml - matches: - path: { type: Exact, value: /qbittorrent } filters: - type: RequestRedirect requestRedirect: path: { type: ReplaceFullPath, replaceFullPath: /qbittorrent/ } statusCode: 301 ``` Der Pfad kommt aus `qbittorrent.ingress.path`, nicht als Literal. Die Core/Extended-Begründung für 301 steht als Kommentar daneben, samt der Folge, die sie trägt (verworfen würde die ganze Route). Gilt auch im Fall `qbittorrent.ingress.host`, wo qBittorrent auf einem eigenen Host unter `/` liegt — dort ist weder Rewrite noch Umleitung nötig, und die Regel darf dann **nicht** rendern. **2. Ingress-Modus — dieselbe Umleitung, ohne Snippet.** Ein drittes Objekt mit `pathType: Exact` auf `/qbittorrent` und `nginx.ingress.kubernetes.io/permanent-redirect`. Ausdrücklich **kein** `configuration-snippet`: `allow-snippet-annotations` ist in ingress-nginx seit 1.9 standardmäßig `false`, ein Snippet bliebe dort also wirkungslos, und zwar still — womit die Behebung genau die Eigenschaft des Fehlers teilen würde, den sie behebt. Das setzt voraus, dass nginx' `location =` (aus `pathType: Exact`) Vorrang vor der Regex-Location des bestehenden qBittorrent-Objekts hat. **Diese Annahme ist zu messen, nicht zu glauben.** Hält sie nicht, wird die Falle stattdessen in `values.yaml` dokumentiert (Aufruf mit abschließendem Schrägstrich) und der Ingress-Zweig bleibt unverändert — halb behoben ist hier schlechter als offen benannt, weil ein drittes Objekt, das nichts tut, die nächste Diagnose kostet. **3. Render-Zusicherung in `ci/run-httproute-match-test.sh`.** Rendert ein Chart eine Regel mit `URLRewrite`/`ReplacePrefixMatch` auf einem Präfix ungleich `/`, muss zum selben Präfix eine `Exact`-Regel mit `RequestRedirect` existieren. Das ist der Teil, der verhindert, dass die Lücke beim nächsten Chart mit Präfix-Rewrite wieder entsteht — `bulwark` ist in Gruppe 3 der zweite `rewrite-target`-Chart und läuft damit gegen dieselbe Prüfung. **4. Wiki.** Die `freesailarr`-Seite, und `Lessons-Learned`, weil die Lehre allgemein ist: ein Präfix-Rewrite ohne begleitende Umleitung erzeugt ein Fehlerbild, das wie ein Anwendungsfehler aussieht und auf den **Nachbardienst** zeigt. Dass der Fehler in beiden Modi steckte und erst bei der Migration auffiel, gehört dazu — die Ursache ist nicht die Gateway-API, sondern dass ein Rewrite ohne Umleitung unvollständig ist. **5. Version: Major, `4.0.0+up1.0.0`.** Eine neue 301 ist nach den Konventionen dieses Repositories eine Laufzeit-Verhaltensänderung. Kein bestehendes funktionierendes Setup bricht, aber die Konvention knüpft an „runtime behaviour changed", nicht an „bricht jemandem etwas". Entsprechend wird hier **kein** leerer `helm template`-Diff gegen `origin/main` behauptet: der Diff ist der Zweck der Änderung, und der PR zeigt ihn. ## Entschieden Punkt 2 geht über die Frage des Issues hinaus, das nur den Gateway-Modus betrifft. Beides — der erweiterte Umfang und die Major-Veröffentlichung — ist vom Maintainer genehmigt, mit der Begründung aus der Korrektur oben: der Standardmodus darf die Falle nicht behalten.
Author
Member

Richtigstellung meiner „Korrektur" — der Issue-Rumpf hatte recht

Mein Kommentar davor behauptet, der Ingress-Modus leite nicht um und die Lücke sei ein Vorbestand in beiden Modi ohne Verhaltensänderung. Das ist falsch. Gemessen am laufenden System:

$ curl -sS -D - https://freesailarr.jk-dev.de/qbittorrent
HTTP/2 301
location: https://freesailarr.jk-dev.de/qbittorrent/

Der Ingress-Modus leitet heute um. Die im Issue-Rumpf beschriebene Verhaltensänderung bei der Migration ist echt.

Wo das Snippet herkommt

Beide Live-Ingress-Objekte im Namensraum jellyfin tragen die Annotation:

nginx.ingress.kubernetes.io/configuration-snippet:
  if ($uri = "/qbittorrent") {
    return 301 $scheme://$host/qbittorrent/$is_args$args;
  }

Sie steht auf beiden Objekten — die Signatur des chart-globalen ingress.annotations-Merges. Sie kommt also aus den Werten, die der Betreiber setzt, und die liegen in jk-cluster-config (objectset.rio.cattle.io/id = default-jk-cluster-config-fleet-cd-jellyfin-freesailarr-c-2c5a3, Fleet-verwaltet). Und sie wirkt: kube-system/rke2-ingress-nginx-controller hat allow-snippet-annotations: "true" bei annotations-risk-level: "Critical".

Mein zweites Argument war damit ebenfalls falsch: dass ein Snippet wegen allow-snippet-annotations: false seit ingress-nginx 1.9 still wirkungslos bliebe, gilt für die Upstream-Vorgabe, nicht für diesen Cluster.

Was an meinem Befund richtig war, und was daran der Fehler war

Richtig und verstärkt: der Chart hat dieses Snippet nie geliefert. Ein git grep über origin/main findet es nicht, und git log --all -S über die ganze Historie von freesailarr/ findet null Commits — es war nie drin. freesailarr/values.yaml beschreibt das Fehlerbild auch an keiner Stelle.

Der Fehler war der Schluss daraus: Ich habe den Chart durchsucht und daraus auf das Verhalten des Clusters geschlossen. Das Grep war korrekt, die Folgerung nicht. Was der Chart tut und was das laufende System tut, sind zwei Messungen; die zweite habe ich nicht gemacht, bevor ich die erste als Verhaltensaussage hingeschrieben habe.

Der Autor des Issue-Rumpfs hat das Verhalten richtig beschrieben und nur den Ort des Snippets verwechselt — Werte des Cluster-Repositories, nicht Chart. Die Zeile, die er zitiert, existiert wörtlich, inklusive des $is_args$args, das im Zitat fehlte.

Das vollständige Bild

Der Chart hat die Umleitung nie geleistet. Das laufende Release hat die Lücke mit einer von Hand gesetzten Annotation hoher Risikostufe in einem anderen Repository geschlossen. Dadurch sah der Chart korrekt aus und das System funktionierte — und die Gateway-Migration, die ingress.annotations fallen lässt, schien etwas zu brechen, das der Chart nie bereitgestellt hatte. Es ist beides: ein echter Regress bei der Migration und eine latente Chart-Lücke für jedes Release ohne dieses Snippet.

Was sich am Plan ändert

Punkt 2 (Ingress-Modus mitbeheben) fällt — aber nicht wegen dieser Richtigstellung, sondern weil seine technische Voraussetzung widerlegt ist. Der vorab festgelegte Rückfall greift, und das aus einem härteren Grund als ich zugelassen hatte.

Gemessen an der Quelle des eingesetzten Controllers (rancher/nginx-ingress-controller:v1.14.5-hardened2, upstream controller-v1.14.5): template.go:531 kehrt im enforceRegex-Zweig zurück, bevor Zeile 534 die Exact-Prüfung erreicht, und nginx.tmpl:987 berechnet $enforceRegex einmal je server über alle Locations des Hostnamens. qBittorrents eigenes Objekt trägt rewrite-target, also ist enforceRegex für den ganzen server wahr. Ein drittes Objekt mit pathType: Exact würde deshalb als location ~* "^/qbittorrent" rendern — als Regex, nie als location =. Es verliert den Exact-Vorrang vollständig, ist am Ende nicht verankert und matcht damit alles unter dem Präfix; sortiert es vor das bestehende, legt es die ganze App in eine 301-Schleife. Also nicht „ein Objekt, das nichts tut", sondern eines, das schaden kann.

Der Ingress-Zweig bleibt daher unverändert, und die Falle wird in values.yaml dokumentiert: samt dem Mittel (configuration-snippet über ingress.annotations), dessen Preis (wirkt nur auf einem Cluster, der allow-snippet-annotations einschaltet und Risikostufe „Critical" in Kauf nimmt) und der Begründung, warum ein Exact-Objekt kein Ersatz ist.

Version: Minor 3.3.0+up1.0.0 statt Major 4.0.0. Mit unverändertem Ingress-Zweig und httpRoute.enabled standardmäßig false ändert sich der Render keines bestehenden Releases um ein Byte, und kein Wert wird verpflichtend — additiv mit unveränderten Vorgaben. Das Major war mit einer Laufzeitänderung im Standardmodus begründet, die nun nicht stattfindet.

Das Snippet in jk-cluster-config wird dadurch nicht redundant. Punkt 1 wirkt nur im Gateway-Modus, und dieser Host läuft im Ingress-Modus — dort ist das Snippet weiter das Einzige, was die Umleitung leistet. Es wird wirkungslos, wenn der Host migriert, weil dann ingress.annotations ohnehin wegfallen. Es ist also nichts abzuräumen, und dieser PR fasst das zweite Repository nicht an.

Zusätzlich dokumentiert, weil die Spezifikation schweigt: HTTPRequestRedirectFilter hat in Gateway API v1.6.3 genau die Felder Scheme, Hostname, Path, Port, StatusCode — „query" kommt im ganzen Struct nicht vor, und ReplaceFullPath ersetzt ausdrücklich den Pfad. Ob der Query-String bei der Umleitung erhalten bleibt, ist damit implementierungsabhängig und vom Chart nicht zusagbar — eine benannte Asymmetrie zum Snippet, das ihn mit $is_args$args ausdrücklich erhält. Praktisch trifft es kaum: die Exact-Regel feuert nur auf dem präfixlosen Einstiegspfad, qBittorrents API liegt unter /qbittorrent/api/... und wird von der PathPrefix-Regel bedient.

Unverändert gültig bleiben die beiden gegen die Spezifikation geprüften Punkte des vorigen Kommentars: Exact schlägt PathPrefix unabhängig von der Listenposition, und es wird 301, weil 308 Extended ist und ein nicht unterstützter Statuscode die ganze Route auf Accepted: False setzt.

# Richtigstellung meiner „Korrektur" — der Issue-Rumpf hatte recht Mein Kommentar davor behauptet, der Ingress-Modus leite nicht um und die Lücke sei ein Vorbestand in beiden Modi ohne Verhaltensänderung. **Das ist falsch.** Gemessen am laufenden System: ``` $ curl -sS -D - https://freesailarr.jk-dev.de/qbittorrent HTTP/2 301 location: https://freesailarr.jk-dev.de/qbittorrent/ ``` Der Ingress-Modus **leitet heute um**. Die im Issue-Rumpf beschriebene Verhaltensänderung bei der Migration ist echt. ## Wo das Snippet herkommt Beide Live-Ingress-Objekte im Namensraum `jellyfin` tragen die Annotation: ``` nginx.ingress.kubernetes.io/configuration-snippet: if ($uri = "/qbittorrent") { return 301 $scheme://$host/qbittorrent/$is_args$args; } ``` Sie steht auf **beiden** Objekten — die Signatur des chart-globalen `ingress.annotations`-Merges. Sie kommt also aus den Werten, die der Betreiber setzt, und die liegen in `jk-cluster-config` (`objectset.rio.cattle.io/id = default-jk-cluster-config-fleet-cd-jellyfin-freesailarr-c-2c5a3`, Fleet-verwaltet). Und sie **wirkt**: `kube-system/rke2-ingress-nginx-controller` hat `allow-snippet-annotations: "true"` bei `annotations-risk-level: "Critical"`. Mein zweites Argument war damit ebenfalls falsch: dass ein Snippet wegen `allow-snippet-annotations: false` seit ingress-nginx 1.9 still wirkungslos bliebe, gilt für die Upstream-Vorgabe, **nicht für diesen Cluster**. ## Was an meinem Befund richtig war, und was daran der Fehler war Richtig und verstärkt: der **Chart** hat dieses Snippet nie geliefert. Ein `git grep` über `origin/main` findet es nicht, und `git log --all -S` über die ganze Historie von `freesailarr/` findet **null** Commits — es war nie drin. `freesailarr/values.yaml` beschreibt das Fehlerbild auch an keiner Stelle. Der Fehler war der **Schluss daraus**: Ich habe den Chart durchsucht und daraus auf das Verhalten des Clusters geschlossen. Das Grep war korrekt, die Folgerung nicht. Was der Chart tut und was das laufende System tut, sind zwei Messungen; die zweite habe ich nicht gemacht, bevor ich die erste als Verhaltensaussage hingeschrieben habe. **Der Autor des Issue-Rumpfs hat das Verhalten richtig beschrieben und nur den Ort des Snippets verwechselt** — Werte des Cluster-Repositories, nicht Chart. Die Zeile, die er zitiert, existiert wörtlich, inklusive des `$is_args$args`, das im Zitat fehlte. ## Das vollständige Bild Der Chart hat die Umleitung nie geleistet. Das laufende Release hat die Lücke mit einer von Hand gesetzten Annotation hoher Risikostufe in einem anderen Repository geschlossen. Dadurch sah der Chart korrekt aus und das System funktionierte — und die Gateway-Migration, die `ingress.annotations` fallen lässt, schien etwas zu brechen, das der Chart nie bereitgestellt hatte. Es ist **beides**: ein echter Regress bei der Migration **und** eine latente Chart-Lücke für jedes Release ohne dieses Snippet. ## Was sich am Plan ändert **Punkt 2 (Ingress-Modus mitbeheben) fällt** — aber nicht wegen dieser Richtigstellung, sondern weil seine technische Voraussetzung widerlegt ist. Der vorab festgelegte Rückfall greift, und das aus einem härteren Grund als ich zugelassen hatte. Gemessen an der Quelle des eingesetzten Controllers (`rancher/nginx-ingress-controller:v1.14.5-hardened2`, upstream `controller-v1.14.5`): `template.go:531` kehrt im `enforceRegex`-Zweig zurück, **bevor** Zeile 534 die `Exact`-Prüfung erreicht, und `nginx.tmpl:987` berechnet `$enforceRegex` **einmal je `server`** über alle Locations des Hostnamens. qBittorrents eigenes Objekt trägt `rewrite-target`, also ist `enforceRegex` für den ganzen `server` wahr. Ein drittes Objekt mit `pathType: Exact` würde deshalb als `location ~* "^/qbittorrent"` rendern — als Regex, nie als `location =`. Es verliert den Exact-Vorrang vollständig, ist am Ende nicht verankert und matcht damit alles unter dem Präfix; sortiert es vor das bestehende, legt es die ganze App in eine 301-Schleife. Also nicht „ein Objekt, das nichts tut", sondern eines, das schaden kann. Der Ingress-Zweig bleibt daher **unverändert**, und die Falle wird in `values.yaml` dokumentiert: samt dem Mittel (`configuration-snippet` über `ingress.annotations`), dessen Preis (wirkt nur auf einem Cluster, der `allow-snippet-annotations` einschaltet und Risikostufe „Critical" in Kauf nimmt) und der Begründung, warum ein `Exact`-Objekt kein Ersatz ist. **Version: Minor `3.3.0+up1.0.0` statt Major `4.0.0`.** Mit unverändertem Ingress-Zweig und `httpRoute.enabled` standardmäßig `false` ändert sich der Render keines bestehenden Releases um ein Byte, und kein Wert wird verpflichtend — additiv mit unveränderten Vorgaben. Das Major war mit einer Laufzeitänderung im Standardmodus begründet, die nun nicht stattfindet. **Das Snippet in `jk-cluster-config` wird dadurch nicht redundant.** Punkt 1 wirkt nur im Gateway-Modus, und dieser Host läuft im Ingress-Modus — dort ist das Snippet weiter das Einzige, was die Umleitung leistet. Es wird wirkungslos, wenn der Host migriert, weil dann `ingress.annotations` ohnehin wegfallen. Es ist also nichts abzuräumen, und dieser PR fasst das zweite Repository nicht an. **Zusätzlich dokumentiert, weil die Spezifikation schweigt:** `HTTPRequestRedirectFilter` hat in Gateway API v1.6.3 genau die Felder `Scheme`, `Hostname`, `Path`, `Port`, `StatusCode` — „query" kommt im ganzen Struct nicht vor, und `ReplaceFullPath` ersetzt ausdrücklich den *Pfad*. Ob der Query-String bei der Umleitung erhalten bleibt, ist damit implementierungsabhängig und vom Chart nicht zusagbar — eine benannte Asymmetrie zum Snippet, das ihn mit `$is_args$args` ausdrücklich erhält. Praktisch trifft es kaum: die `Exact`-Regel feuert nur auf dem präfixlosen Einstiegspfad, qBittorrents API liegt unter `/qbittorrent/api/...` und wird von der `PathPrefix`-Regel bedient. Unverändert gültig bleiben die beiden gegen die Spezifikation geprüften Punkte des vorigen Kommentars: `Exact` schlägt `PathPrefix` unabhängig von der Listenposition, und es wird **301**, weil 308 Extended ist und ein nicht unterstützter Statuscode die **ganze** Route auf `Accepted: False` setzt.
Author
Member

Implemented in #119 (CI green: all eight checks, including the required validate-charts context and the install-test with the real tunnel).

Your description of the behaviour is correct, including the cause — the bare prefix is stripped to /, qBittorrent answers 200, and the page’s relative links are then resolved against the host root, which is seerr. The only detail that moved on investigation is where the existing redirect lives: it is not in this chart and never was (the pickaxe searches for configuration-snippet and return 301 over freesailarr/ return zero commits, and values.yaml did not mention the trap at all). It is an operator-supplied nginx.ingress.kubernetes.io/configuration-snippet on the live Ingress objects, managed by Fleet from jk-cluster-config. That is why the chart looked correct and the running system worked at the same time.

What landed:

  • Gateway mode renders the redirect — the shared HTTPRoute gains a second rule for qBittorrent: Exact match on qbittorrent.ingress.path, RequestRedirect with ReplaceFullPath to the slashed form, statusCode: 301. Exact outranks every prefix match in Gateway-API precedence, so it wins over qBittorrent’s own PathPrefix rule and over seerr’s / wherever it sits. 301 and not 308 because only 301/302 are Core — an Extended value an implementation declines sets Accepted: False on the whole route and would take the four neighbouring apps down with it. statusCode is written out because it defaults to 302.
  • Ingress mode is deliberately unchanged, and no third Ingress object was added. nginx’s only mechanism here is the snippet annotation, which is a silent no-op unless the controller runs with allow-snippet-annotations=true (off by default since ingress-nginx 1.9, and switching it on means accepting annotation risk level Critical). So values.yaml now documents the trap, the working snippet, that price, and why a third object with pathType: Exact is not a substitute — buildLocation() in ingress-nginx controller-v1.14.5 returns a regex location in its enforceRegex branch before reaching the PathTypeExact branch, and enforceRegex is computed once per nginx server over every location of the hostname, one of which carries rewrite-target.
  • The operator’s snippet stays and stays necessary. freesailarr.jk-dev.de still runs in Ingress mode — it was deliberately not migrated — so that snippet remains the only thing performing the redirect there. Nothing in this change makes it redundant, there is no follow-up to remove it, and jk-cluster-config was not touched.
  • A render assertion so the gap cannot reappear: ci/run-httproute-match-test.sh now requires, per HTTPRoute, that a rule stripping a prefix with ReplacePrefixMatch is accompanied by a rule matching that path Exactly with a RequestRedirect filter. 37 assertions over 15 charts, up from 36; both fail paths exercised. bulwark meets the same check when group 3 of #114 lands.
  • Chart 3.2.0+up1.0.0 → 3.3.0+up1.0.0 (minor: the helm template diff against origin/main is empty for both Ingress shapes, so no existing release’s render changes).
  • Wiki updated in the same round: freesailarr, CI-CD and Lessons-Learned.

What is not proven, stated plainly: no Gateway controller runs in CI and no HTTPRoute ever reaches Accepted: True, so "it redirects" is not demonstrable here. What is demonstrable is a schema-valid object against the real Gateway-API CRDs plus the render assertions. The query-string behaviour is a further open point — HTTPRequestRedirectFilter has no field for it, where the nginx snippet carries it over with $is_args$args; it costs nothing on the UI entry path, which takes no query parameters.

Implemented in #119 (CI green: all eight checks, including the required `validate-charts` context and the install-test with the real tunnel). **Your description of the behaviour is correct**, including the cause — the bare prefix is stripped to `/`, qBittorrent answers 200, and the page’s relative links are then resolved against the host root, which is seerr. The only detail that moved on investigation is *where the existing redirect lives*: it is not in this chart and never was (the pickaxe searches for `configuration-snippet` and `return 301` over `freesailarr/` return zero commits, and `values.yaml` did not mention the trap at all). It is an operator-supplied `nginx.ingress.kubernetes.io/configuration-snippet` on the live Ingress objects, managed by Fleet from `jk-cluster-config`. That is why the chart looked correct and the running system worked at the same time. **What landed:** - **Gateway mode renders the redirect** — the shared `HTTPRoute` gains a second rule for qBittorrent: `Exact` match on `qbittorrent.ingress.path`, `RequestRedirect` with `ReplaceFullPath` to the slashed form, `statusCode: 301`. `Exact` outranks every prefix match in Gateway-API precedence, so it wins over qBittorrent’s own `PathPrefix` rule and over seerr’s `/` wherever it sits. 301 and not 308 because only 301/302 are Core — an Extended value an implementation declines sets `Accepted: False` on the **whole route** and would take the four neighbouring apps down with it. `statusCode` is written out because it defaults to 302. - **Ingress mode is deliberately unchanged**, and no third Ingress object was added. nginx’s only mechanism here is the snippet annotation, which is a silent no-op unless the controller runs with `allow-snippet-annotations=true` (off by default since ingress-nginx 1.9, and switching it on means accepting annotation risk level `Critical`). So `values.yaml` now documents the trap, the working snippet, that price, and why a third object with `pathType: Exact` is **not** a substitute — `buildLocation()` in ingress-nginx `controller-v1.14.5` returns a regex location in its `enforceRegex` branch before reaching the `PathTypeExact` branch, and `enforceRegex` is computed once per nginx `server` over every location of the hostname, one of which carries `rewrite-target`. - **The operator’s snippet stays and stays necessary.** `freesailarr.jk-dev.de` still runs in Ingress mode — it was deliberately not migrated — so that snippet remains the only thing performing the redirect there. Nothing in this change makes it redundant, there is no follow-up to remove it, and `jk-cluster-config` was not touched. - **A render assertion so the gap cannot reappear**: `ci/run-httproute-match-test.sh` now requires, per `HTTPRoute`, that a rule stripping a prefix with `ReplacePrefixMatch` is accompanied by a rule matching that path `Exact`ly with a `RequestRedirect` filter. 37 assertions over 15 charts, up from 36; both fail paths exercised. `bulwark` meets the same check when group 3 of #114 lands. - Chart `3.2.0+up1.0.0` → `3.3.0+up1.0.0` (**minor**: the `helm template` diff against `origin/main` is empty for both Ingress shapes, so no existing release’s render changes). - Wiki updated in the same round: `freesailarr`, `CI-CD` and `Lessons-Learned`. **What is not proven**, stated plainly: no Gateway controller runs in CI and no `HTTPRoute` ever reaches `Accepted: True`, so "it redirects" is not demonstrable here. What is demonstrable is a schema-valid object against the real Gateway-API CRDs plus the render assertions. The query-string behaviour is a further open point — `HTTPRequestRedirectFilter` has no field for it, where the nginx snippet carries it over with `$is_args$args`; it costs nothing on the UI entry path, which takes no query parameters.
Author
Member

Behoben in 3.3.0+up1.0.0 (veröffentlicht 12.10.2026, 22:27). Gerendert gegengeprüft gegen die CRD-Schemata des Clusters, mit den Werten von freesailarr.jk-dev.de:

0: PathPrefix /qbittorrent   URLRewrite(/)                          -> qbittorrent:8080
1: Exact      /qbittorrent   RequestRedirect(301 -> /qbittorrent/)
2: PathPrefix /prowlarr                                             -> prowlarr:9696
3: PathPrefix /bazarr                                               -> bazarr:6767
4: PathPrefix /radarr                                               -> radarr:7878
5: PathPrefix /sonarr                                               -> sonarr:8989
6: PathPrefix /                                                     -> seerr:5055

Die Regel ist da, als Exact-Pfad mit ausgeschriebenem statusCode, und backendRefs fehlt bei ihr richtigerweise. Ausgerollt wird es mit jlab-cluster/cluster-config #400; freesailarr.jk-dev.de war der Host, der diesen Befund ausgelöst hat.

Die offene Frage aus dem Ticketkopf ist beantwortet, und besser als ich sie gestellt hatte

Ich hatte 301 gegen 308 mit der Methodenerhaltung begründet und das für das entscheidende Kriterium gehalten. Der Kommentar an der Regel führt ein Argument, das schwerer wiegt und das ich nicht auf dem Schirm hatte:

301 und 302 sind in der Gateway API CORE, alles andere — 308 eingeschlossen — ist EXTENDED. Eine Implementierung, die den gegebenen Wert nicht unterstützt, muss Accepted=False mit reason: UnsupportedValue setzen — nicht an der Regel, sondern an der ganzen Route. Auf einem geteilten Host trägt eine Route alle sechs Apps, ein abgelehntes 308 nähme also prowlarr, radarr, sonarr, bazarr und seerr mit qBittorrent mit. Und zwar an einem Objekt, das schema-valide ist, in einem Statusfeld, das keine CI ausliest.

Das deckt sich mit Entscheidung 1 aus #114 — nur die Teilmenge rendern, die auf nginx-gateway-fabric, Cilium, Envoy Gateway und Istio gleich aussieht. Die Methodenerhaltung ist dagegen gar nicht betroffen: Die Regel trifft genau den präfixlosen Einstieg in die WebUI, den ein Browser mit GET erreicht; qBittorrents API liegt unter <path>/api/..., also beschrägstrichtet, länger und von der PathPrefix-Regel bedient, die diese Regel nie sieht.

Eine Beobachtung zur Reihenfolge, falls sie jemand später „korrigiert"

Der Kommentar sagt, Position trage keine Bedeutung, und das ist richtig: Exact schlägt jedes Präfix, unter Präfixen gewinnt das längste, und die Listenreihenfolge wird erst herangezogen, wenn alles davor gleich ist. Regel 1 gewinnt also gegen Regel 0 und gegen Regel 6, egal wo sie steht.

Der Zusatz im Chart, das sei „ALSO the order nginx would need … a coincidence of this chart's history", ist der Teil, der das vor einer späteren Umsortierung schützt — ohne ihn würde die absteigende Sortierung früher oder später als Korrektheitsbedingung gelesen.

Was aus diesem Fall als Verfahren bleibt

Der Mangel war nicht im Betrieb zu finden, sondern nur im Rendering: qBittorrent antwortet mit 200, und erst die relativen Verweise der Seite landen bei einer anderen Anwendung. Für die restlichen Charts in #114 heißt das — die gerenderte Route ansehen, nicht nur prüfen, dass eine entsteht. Dass ein Chart „Gateway-fähig" ist, sagt nichts darüber, ob er jede Verhaltensweise mitgenommen hat, die seine Ingress-Fassung hatte.

Aus meiner Sicht kann das Issue zu. Danke für die schnelle Fassung.

Behoben in **3.3.0+up1.0.0** (veröffentlicht 12.10.2026, 22:27). Gerendert gegengeprüft gegen die CRD-Schemata des Clusters, mit den Werten von `freesailarr.jk-dev.de`: ``` 0: PathPrefix /qbittorrent URLRewrite(/) -> qbittorrent:8080 1: Exact /qbittorrent RequestRedirect(301 -> /qbittorrent/) 2: PathPrefix /prowlarr -> prowlarr:9696 3: PathPrefix /bazarr -> bazarr:6767 4: PathPrefix /radarr -> radarr:7878 5: PathPrefix /sonarr -> sonarr:8989 6: PathPrefix / -> seerr:5055 ``` Die Regel ist da, als `Exact`-Pfad mit ausgeschriebenem `statusCode`, und `backendRefs` fehlt bei ihr richtigerweise. Ausgerollt wird es mit `jlab-cluster/cluster-config` #400; `freesailarr.jk-dev.de` war der Host, der diesen Befund ausgelöst hat. ## Die offene Frage aus dem Ticketkopf ist beantwortet, und besser als ich sie gestellt hatte Ich hatte 301 gegen 308 mit der **Methodenerhaltung** begründet und das für das entscheidende Kriterium gehalten. Der Kommentar an der Regel führt ein Argument, das schwerer wiegt und das ich nicht auf dem Schirm hatte: **301 und 302 sind in der Gateway API CORE, alles andere — 308 eingeschlossen — ist EXTENDED.** Eine Implementierung, die den gegebenen Wert nicht unterstützt, muss `Accepted=False` mit `reason: UnsupportedValue` setzen — **nicht an der Regel, sondern an der ganzen Route**. Auf einem geteilten Host trägt eine Route alle sechs Apps, ein abgelehntes 308 nähme also prowlarr, radarr, sonarr, bazarr und seerr mit qBittorrent mit. Und zwar an einem Objekt, das schema-valide ist, in einem Statusfeld, das keine CI ausliest. Das deckt sich mit Entscheidung 1 aus #114 — nur die Teilmenge rendern, die auf nginx-gateway-fabric, Cilium, Envoy Gateway und Istio gleich aussieht. **Die Methodenerhaltung ist dagegen gar nicht betroffen:** Die Regel trifft genau den präfixlosen Einstieg in die WebUI, den ein Browser mit GET erreicht; qBittorrents API liegt unter `<path>/api/...`, also beschrägstrichtet, länger und von der `PathPrefix`-Regel bedient, die diese Regel nie sieht. ## Eine Beobachtung zur Reihenfolge, falls sie jemand später „korrigiert" Der Kommentar sagt, Position trage keine Bedeutung, und das ist richtig: `Exact` schlägt jedes Präfix, unter Präfixen gewinnt das längste, und die Listenreihenfolge wird erst herangezogen, wenn alles davor gleich ist. Regel 1 gewinnt also gegen Regel 0 **und** gegen Regel 6, egal wo sie steht. Der Zusatz im Chart, das sei „ALSO the order nginx would need … a coincidence of this chart's history", ist der Teil, der das vor einer späteren Umsortierung schützt — ohne ihn würde die absteigende Sortierung früher oder später als Korrektheitsbedingung gelesen. ## Was aus diesem Fall als Verfahren bleibt Der Mangel war **nicht im Betrieb zu finden**, sondern nur im Rendering: qBittorrent antwortet mit 200, und erst die relativen Verweise der Seite landen bei einer anderen Anwendung. Für die restlichen Charts in #114 heißt das — **die gerenderte Route ansehen, nicht nur prüfen, dass eine entsteht.** Dass ein Chart „Gateway-fähig" ist, sagt nichts darüber, ob er jede Verhaltensweise mitgenommen hat, die seine Ingress-Fassung hatte. Aus meiner Sicht kann das Issue zu. Danke für die schnelle Fassung.
Author
Member

Nachtrag: die offene Query-String-Frage ist gemessen — er bleibt erhalten

Dieses Issue und PR #119 ließen eine Frage ausdrücklich offen: ob die Gateway-API-Umleitung den Query-String erhält. HTTPRequestRedirectFilter hat in v1.6.3 die Felder Scheme, Hostname, Path, Port und StatusCode — kein Query-Feld —, und ReplaceFullPath ist dokumentiert als Ersatz des Pfades. Die Spezifikation sagt also nichts, und der Chart durfte nichts zusagen.

Seit freesailarr.jk-dev.de im Gateway-Modus läuft (13.10.2026), ist es messbar:

GET https://freesailarr.jk-dev.de/qbittorrent?foo=bar
→ 301
  location: https://freesailarr.jk-dev.de/qbittorrent/?foo=bar

Der Query-String bleibt erhalten. Die Asymmetrie zum $is_args$args des Betreiber-Snippets, die dieses Issue als Rest benannt hat, ist in der Praxis keine.

Zwei Einschränkungen, damit der Befund nicht mehr trägt, als er kann:

  • Das ist eine Implementierung, gemessen. Die Spezifikation schweigt weiterhin, also bleibt die Formulierung in values.yaml und im Wiki richtig: nicht zusagbar. Sie bekommt einen gemessenen Datenpunkt, keine Zusage.
  • Welche Implementierung, kann ich nicht benennen: httproutes und gatewayclasses sind für die lesende Identität RBAC-gesperrt.

Nebenbei bestätigt dieselbe Messung, dass die Umleitung dieses Charts jetzt wirklich aus der Regel aus #119 kommt und nicht mehr aus dem Snippet: Es gibt keine freesailarr-Ingress-Objekte mehr, das Snippet hat also keinen Träger, und die Antwortsignatur hat gewechselt (vorher nginx: content-type: text/html, 162 Bytes, HSTS — jetzt 17 Bytes, keines davon).

Das Snippet in jk-cluster-config ist damit wirkungslos und kann entfernt werden. Solange der Host im Ingress-Modus lief, wäre das falsch gewesen — es war dort das Einzige, was die Umleitung leistete.

# Nachtrag: die offene Query-String-Frage ist gemessen — er bleibt erhalten Dieses Issue und PR #119 ließen eine Frage ausdrücklich offen: ob die Gateway-API-Umleitung den Query-String erhält. `HTTPRequestRedirectFilter` hat in v1.6.3 die Felder `Scheme`, `Hostname`, `Path`, `Port` und `StatusCode` — **kein Query-Feld** —, und `ReplaceFullPath` ist dokumentiert als Ersatz des *Pfades*. Die Spezifikation sagt also nichts, und der Chart durfte nichts zusagen. Seit `freesailarr.jk-dev.de` im Gateway-Modus läuft (13.10.2026), ist es messbar: ``` GET https://freesailarr.jk-dev.de/qbittorrent?foo=bar → 301 location: https://freesailarr.jk-dev.de/qbittorrent/?foo=bar ``` **Der Query-String bleibt erhalten.** Die Asymmetrie zum `$is_args$args` des Betreiber-Snippets, die dieses Issue als Rest benannt hat, ist in der Praxis keine. Zwei Einschränkungen, damit der Befund nicht mehr trägt, als er kann: - Das ist **eine** Implementierung, gemessen. Die Spezifikation schweigt weiterhin, also bleibt die Formulierung in `values.yaml` und im Wiki richtig: nicht zusagbar. Sie bekommt einen gemessenen Datenpunkt, keine Zusage. - **Welche** Implementierung, kann ich nicht benennen: `httproutes` und `gatewayclasses` sind für die lesende Identität RBAC-gesperrt. Nebenbei bestätigt dieselbe Messung, dass die Umleitung dieses Charts jetzt wirklich aus der Regel aus #119 kommt und nicht mehr aus dem Snippet: Es gibt keine `freesailarr`-Ingress-Objekte mehr, das Snippet hat also keinen Träger, und die Antwortsignatur hat gewechselt (vorher nginx: `content-type: text/html`, 162 Bytes, HSTS — jetzt 17 Bytes, keines davon). Das Snippet in `jk-cluster-config` ist damit wirkungslos und kann entfernt werden. Solange der Host im Ingress-Modus lief, wäre das falsch gewesen — es war dort das Einzige, was die Umleitung leistete.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jlab-cluster/helm-charts#118