docs: Beispiel-Links laden aus dem eigenen Gitea (D96)

git.javagil.de sendete auf raw-Dateien kein Access-Control-Allow-Origin,
weshalb die ?sourceUrl=-Beispiel-Links nach dem Umzug (D95) auf dem
GitHub-Klon bleiben mussten. Der Header ist jetzt auf mih09 gesetzt:
Giteas [cors]-Abschnitt deckt nur /api/v1 ab, die Web-raw-Route bekommt
ihn im Apache davor. Die Links zeigen nun auf origin.

Nebenbei: "Werkbaum selbst" war schon vorher tot (example-werkbaum.werkbaum
existiert nicht, die Datei heisst werkbaum.werkbaum) und ist mit korrigiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-09-03 18:31:23 +02:00
co-authored by Claude Opus 5
parent 29a217f892
commit 1ec7aedf04
7 changed files with 226 additions and 31 deletions
+1
View File
@@ -21,6 +21,7 @@ reverse.
- The project moved to its own Gitea at git.javagil.de — the footer's repository and version links point there now, and GitHub stays a clone carrying the very same commits
- Every push is built by Werkator, the project's own CI, alongside the GitHub Pages build: backend and frontend as two named builds
- The example plan links now load from git.javagil.de instead of the GitHub clone, and the "Werkbaum itself" link works again — it pointed at a file name that does not exist
## 2026-09-02
+61 -2
View File
@@ -635,8 +635,8 @@ mögliche Erweiterung.)
**CORS ist die eigentliche Einschränkung.** Der Browser lädt fremde Hosts nur,
wenn die Zielseite `Access-Control-Allow-Origin` sendet. Das tun u. a.
`raw.githubusercontent.com` und GitLab-Raw-Links; ein beliebiger Webserver
oft **nicht**. Scheitert das Laden (CORS, 404, Netz), bleibt der bisherige Stand
`raw.githubusercontent.com`, GitLab-Raw-Links und — seit D96 — das eigene
`git.javagil.de`; ein beliebiger Webserver oft **nicht**. Scheitert das Laden (CORS, 404, Netz), bleibt der bisherige Stand
stehen und es erscheint eine **Warnung** im Warnbereich (Typ `sourceLoad`,
zeilenlos ⇒ zuoberst), die CORS ausdrücklich als wahrscheinliche Ursache nennt.
Bewusst kein Proxy-Dienst als Ausweg: das würde fremde Inhalte über einen
@@ -9144,3 +9144,62 @@ beobachtete Klon `~/werkbaum` auf mih09 bekommt Gitea als `origin` — sonst
sähe der Watcher neue Commits erst nach dem Spiegeln. **Status-Checks werden
weiterhin nicht gepostet**, solange auf der Instanz kein Gitea-Token liegt;
die Sektion ist bis dahin eine Beschriftung.
**Nachtrag zu D95 — der CORS-Zwang ist weg (2026-09-03).** Von den zwei
gemessenen Gründen für den Klon hält nur noch einer: `git.javagil.de` liefert
`raw`-Dateien inzwischen mit `Access-Control-Allow-Origin` (D96), die
Beispiel-Links zeigen deshalb auf Gitea. **GitHub Pages bleibt** — der
Actions-Workflow der „latest build“-Instanz (D16) lässt sich nicht mitnehmen,
und daran hängt das Spiegeln weiterhin.
## D96 — `git.javagil.de` liefert `raw`-Dateien mit `Access-Control-Allow-Origin` (2026-09-03)
D95 hielt fest, dass die Beispiel-Links auf `raw.githubusercontent.com` bleiben
müssen, weil Gitea den CORS-Header nicht sendet. Das war eine
Server-Konfiguration, keine Eigenschaft von Gitea — sie ist jetzt gesetzt, und
die Links zeigen auf `origin`.
**Gemessen, nicht vermutet.** Giteas eigener `[cors]`-Abschnitt allein reicht
nicht: mit `ENABLED = true` / `ALLOW_DOMAIN = *` / `METHODS = GET,HEAD` in
`app.ini` trägt `/api/v1/repos/mi/werkdock/raw/README.md` den Header, die
**Web**-Route `/mi/werkdock/raw/branch/main/README.md` aber weiterhin nicht —
Gitea 1.27 legt die CORS-Middleware nur auf `/api/v1`. Die Beispiel-Links
benutzen die Web-Route.
**Also beides, mit klarer Aufteilung.** Der `[cors]`-Abschnitt bleibt für die
API-Route; die Web-Route bekommt den Header im Apache davor, in der
`.htaccess` der Domain:
```apache
SetEnvIf Request_URI "^/[^/]+/[^/]+/(raw|media)/" GITEA_RAW_CORS
Header always set Access-Control-Allow-Origin "*" env=GITEA_RAW_CORS
```
Zwei Fallen, beide beim Umsetzen aufgelaufen: `<LocationMatch>` ist in einer
`.htaccess` **nicht erlaubt** (nur Server-Config/VHost) — auf einem Managed
Webspace gibt es aber nur `.htaccess`, daher `SetEnvIf`. Und die Regel darf
`/api/v1` **nicht** mitfassen: `Header always` schreibt in `err_headers_out`
und *ergänzt* dort, statt zu ersetzen — zusammen mit Giteas eigenem Header
standen zwei `Access-Control-Allow-Origin: *` in der Antwort, was Browser als
ungültig verwerfen. Gemessen: je genau ein Header auf beiden raw-Routen, keiner
auf gewöhnlichen Repo-Seiten.
**`*` ohne Credentials ist hier die harmlose Variante.** Unter einem
Wildcard-Ursprung sendet der Browser grundsätzlich keine Cookies; ein fremder
Ursprung liest also nur, was ohnehin anonym abrufbar ist. Private Repositories
brauchen die Sitzung und antworten weiter mit 404/403 (nachgemessen: 404 auf
einen nicht existierenden Pfad). Ausdrücklich **nicht** getan: kein
`Access-Control-Allow-Credentials: true` (damit könnten fremde Seiten private
Repositories im Namen des angemeldeten Benutzers lesen), kein Zurückspiegeln
des `Origin` (dasselbe Risiko, sobald jemand später Credentials ergänzt), und
der Header steht nicht site-weit, sondern nur auf den raw-Pfaden.
**Kein Preflight nötig.** `OPTIONS` auf die Web-raw-Route antwortet weiterhin
`405`. Das ist folgenlos: der Abruf aus D23 ist ein *simple request*
schlichtes `GET`, `credentials:'omit'`, keine eigenen Header —, und dafür
schickt der Browser keinen Preflight.
**Die Konfiguration liegt in keinem Repository.** Sie gehört dem Unix-Benutzer
`mih09-git` auf mih09 (`gitea/custom/conf/app.ini` und
`doms/git.javagil.de/htdocs-ssl/.htaccess`, beide mit Zeitstempel-Sicherung
daneben) — deshalb steht sie hier im Wortlaut, damit sie nach einem Neuaufsetzen
wiederherstellbar ist.
@@ -0,0 +1,98 @@
> **Hinweis:** Dieses Dokument beschreibt nur die Änderung dieses PR.
> Es kann veraltet sein, sobald der nächste PR gemergt ist.
> Historische PR-Dokumentation wird nicht nachgepflegt — sie ist eine Momentaufnahme, keine aktuelle Doku.
## Das Problem
Die Beispiel-Links beider READMEs laden ihren Plan per `?sourceUrl=` aus einer Textdatei im Netz (D23).
Der Browser holt eine fremde Herkunft nur, wenn die Antwort `Access-Control-Allow-Origin` trägt.
`raw.githubusercontent.com` sendet den Header, `git.javagil.de` sendete ihn nicht.
Deshalb mussten die Links nach dem Umzug nach Gitea (D95) weiterhin auf den GitHub-Klon zeigen, obwohl das Repository dort gar nicht mehr zuhause ist.
Gemessen am 2026-09-03, mit `Origin:`-Header: HTTP 200, `Access-Control-Expose-Headers: Content-Disposition`, `Content-Type: text/plain; charset=utf-8` — aber kein `Access-Control-Allow-Origin`.
Die Datei selbst war also einwandfrei erreichbar; es fehlte genau ein Header.
## Nicht das Ziel
- Der GitHub-Klon verschwindet nicht: die GitHub-Pages-Instanz (D16) hängt weiter daran, sie ist ein Actions-Workflow und lässt sich nicht mitnehmen.
- Kein Proxy-Dienst als Ausweg — D23 verwirft ihn ausdrücklich, weil er fremde Inhalte über einen Dritt-Host leiten würde.
- Keine Änderung am Frontend: `sourceUrl` bleibt, wie es ist; der Fehler lag ausschließlich auf der Serverseite.
## Die Szenarien
### Feature: Beispiel-Pläne laden aus dem eigenen Gitea
#### Hintergrund
- Die Konfiguration liegt beim Unix-Benutzer `mih09-git` auf mih09 und in **keinem** Repository; sie ist im Wortlaut in D96 festgehalten.
- Giteas eigener `[cors]`-Abschnitt und die `.htaccess` der Domain teilen sich die Arbeit: die API-Route deckt Gitea ab, die Web-Route der Apache davor.
#### Szenario#000.01: Ein Beispiel-Link öffnet den Plan statt eines CORS-Fehlers
Damit die Links auf `origin` zeigen können und nicht auf einen Klon.
- **Gegeben** ein Beispiel-Link mit `?sourceUrl=https://git.javagil.de/mi/werkbaum/raw/branch/main/…`
- **Wenn** der Browser die Datei aus der fremden Herkunft holt
- **Dann** trägt die Antwort `Access-Control-Allow-Origin: *` und der Plan wird geöffnet
##### Nachgewiesen durch
- Messung 2026-09-03 auf allen fünf Beispieldateien: je HTTP 200 mit genau einem `Access-Control-Allow-Origin: *`.
#### Szenario#000.02: Die API-Route trägt den Header genau einmal
Damit die Antwort gültig bleibt — zwei `Access-Control-Allow-Origin`-Header verwirft der Browser als ungültig.
- **Gegeben** Gitea setzt den Header auf `/api/v1/…` bereits selbst
- **Wenn** die `.htaccess` diese Route nicht mitfasst
- **Dann** steht in der Antwort genau ein `Access-Control-Allow-Origin: *`
##### Nachgewiesen durch
- Messung 2026-09-03: `/api/v1/repos/mi/werkdock/raw/README.md` → ein Header (zuvor, mit der ersten Fassung der `.htaccess`, waren es zwei).
#### Szenario#000.03: Gewöhnliche Repo-Seiten bleiben unberührt
Damit der Header nur dort steht, wo er gebraucht wird, und nicht site-weit.
- **Gegeben** eine gewöhnliche Seite wie `/mi/werkdock`
- **Wenn** sie abgerufen wird
- **Dann** trägt die Antwort keinen `Access-Control-Allow-Origin`
##### Nachgewiesen durch
- Messung 2026-09-03: `/mi/werkdock` → HTTP 200, null `Access-Control-Allow-Origin`-Header.
## Die Lösung
Auf mih09, beim Benutzer `mih09-git` (Sicherungskopien mit Zeitstempel liegen jeweils daneben):
- `gitea/custom/conf/app.ini` bekommt einen `[cors]`-Abschnitt (`ENABLED = true`, `ALLOW_DOMAIN = *`, `METHODS = GET,HEAD`); Gitea neu gestartet.
- `doms/git.javagil.de/htdocs-ssl/.htaccess` bekommt `SetEnvIf Request_URI "^/[^/]+/[^/]+/(raw|media)/"` plus `Header always set Access-Control-Allow-Origin "*" env=…`, vor den vorhandenen Proxy-Regeln.
Im Repository:
- Die Beispiel-Links beider READMEs zeigen auf `git.javagil.de/mi/werkbaum/raw/branch/main/…`.
- Die CORS-Einschränkung in beiden READMEs und in D23 nennt `git.javagil.de` jetzt unter den Hosts, die den Header senden.
- Der Absatz „GitHub bleibt ein Klon“ nennt nur noch GitHub Pages als Grund; D95 bekommt einen Nachtrag, D96 hält die Konfiguration im Wortlaut fest.
Warum die Aufteilung auf zwei Stellen: Gitea 1.27 legt die CORS-Middleware nur auf `/api/v1`, nicht auf die Web-raw-Route — und genau die benutzen die Links.
Warum `SetEnvIf` statt `<LocationMatch>`: Location-Direktiven sind in einer `.htaccess` nicht erlaubt, und auf einem Managed Webspace gibt es nur `.htaccess`.
Warum `*` ohne Credentials: unter einem Wildcard-Ursprung sendet der Browser keine Cookies, ein fremder Ursprung liest also nur, was ohnehin anonym abrufbar ist; private Repositories antworten weiter mit 404/403.
## Offene Fragen
- Keine. Beide Fallen (verbotenes `<LocationMatch>`, doppelter Header) sind beim Umsetzen aufgetreten und behoben; die Messungen decken Web-raw, API-raw und eine gewöhnliche Seite ab.
## Weitere Änderungen
- Der Link „Werkbaum selbst“ war schon vorher tot: er zeigte auf `example-werkbaum.werkbaum`, die Datei heißt `werkbaum.werkbaum` — auch auf GitHub lieferte er 404. Beim Umstellen mit korrigiert.
- `docs/prs/` samt Konvention neu eingeführt; `CLAUDE.md` verweist darauf.
## Vorausgesetzte PRs
- Keine; setzt den Umzug nach Gitea (D95) voraus, der bereits auf `main` liegt.
## Folge-PRs
- Keine geplant.
+34
View File
@@ -0,0 +1,34 @@
# PR-Dokumentation
Jeder Pull Request bekommt hier **eine** Datei.
## Dateiname
`JJJJ-MM-TT-PR#<nummer>-kurze-beschreibung.md`
Die Nummer ist die des Gitea-PR auf <https://git.javagil.de/mi/werkbaum>.
Steht sie noch nicht fest, gilt `PR#000` als Platzhalter — im Dateinamen wie in
den Szenario-IDs — und wird nachgezogen, sobald der PR offen ist.
## Aufbau
Die `##`-Abschnitte in genau dieser Reihenfolge, nicht Zutreffendes weglassen:
1. Das Problem
2. Nicht das Ziel
3. Die Szenarien
4. Die Lösung
5. Offene Fragen
6. Weitere Änderungen
7. Vorausgesetzte PRs
8. Folge-PRs
## Regeln
- Deutsch (CLAUDE.md), Markdown, ein Satz je Zeile, kurz halten.
- Das „Warum“ erklären, nicht nur das „Was“.
- Szenarien in Markdown-eigenem Pseudo-Gherkin mit IDs `Szenario#<pr-nummer>.<nn>`,
jedes mit einer Liste `##### Nachgewiesen durch`.
- Ein PR-doc beschreibt den Stand **dieses** PR. Ältere PR-docs werden nicht
nachgepflegt, wenn spätere PRs das Verhalten ändern — dauerhafte Begründungen
gehören nach `docs/DECISIONS.md`.