Marcs BentoPDF-Image (ghcr.io/alam00000/bentopdf-simple) schickt eine strenge CSP — mit dem Image von Docker Hub, an dem ich zuerst geprueft hatte, war das nicht zu sehen. Statt fremde Kopfzeilen umzuschreiben, reicht der Einsetzer jetzt auch /honeybar/ und die zwei Abfragen der Leiste unter seiner eigenen Adresse durch. Damit ist fuer den Browser alles derselbe Ursprung: CSP unangetastet, kein CORS, kein COEP, und der Schluessel bleibt auf dem Server. Durchgereicht wird nur /api/apps und /api/weather, nur lesend. Ein pauschales /api/ haette die ganze Schnittstelle unter die Adresse der App gehaengt — beim Ausprobieren hat meine eigene Schreibprobe prompt die App-Liste geleert. Das Binden auf 127.0.0.1 ist raus: Es passt nur, wenn der Proxy auf demselben Wirt im Host-Netz laeuft, und nicht fuer Marcs Aufbau. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
384 lines
18 KiB
Markdown
384 lines
18 KiB
Markdown
# HoneyOS
|
|
|
|
Ein Retro-Schreibtisch vor den eigenen Apps. Amber-CRT, Boot-Log, zwei Betriebsarten,
|
|
Anmeldung über den eigenen OIDC-Anbieter — und dieselbe Leiste zum Einhängen in die Apps.
|
|
|
|
**Was es ist:** eine Oberfläche davor. Jede App bleibt ihre eigene PWA auf ihrer eigenen
|
|
Domain mit ihrer eigenen Anmeldung. HoneyOS ist der Startpunkt, der Umschalter und die
|
|
gemeinsame Leiste — **kein Rahmen, der die Apps einsperrt** (kein iframe).
|
|
|
|
**Wo der Zustand liegt:** im Server. Die App-Liste gilt für alle, die Einstellungen gehören
|
|
je Nutzer — deshalb sieht dein HoneyOS auf jedem Gerät gleich aus, an dem du angemeldet bist.
|
|
Der Browser hält dieselben Einstellungen nur als Vorschuss, damit die Röhre beim Laden nicht
|
|
in der falschen Farbe aufflackert.
|
|
|
|
## Betrieb
|
|
|
|
```bash
|
|
mkdir -p /srv/honeyos && chown 1000:1000 /srv/honeyos
|
|
cp .env.example .env # PUBLIC_URL, SESSION_SECRET, OIDC-Zugang
|
|
docker login gitea.honeyy.duckdns.org
|
|
docker compose pull && docker compose up -d
|
|
```
|
|
|
|
Zu sichern ist genau eine Datei: `${HONEYOS_DATA_DIR}/honeyos.json`. Darin stehen die Apps
|
|
und die Einstellungen je Nutzer. Sichere sie zusammen mit der `.env` — ohne
|
|
`HONEYOS_SESSION_SECRET` müssen sich alle neu anmelden, mehr passiert aber nicht.
|
|
|
|
`/healthz` antwortet mit `ok`. Hinter deinem Reverse Proxy braucht es nichts Besonderes,
|
|
außer dass `X-Forwarded-Proto: https` durchkommt — sonst setzt HoneyOS das Sitzungscookie
|
|
ohne `Secure`.
|
|
|
|
## Anmeldung — zwei Wege, einer davon reicht
|
|
|
|
**A · Ein Proxy davor macht die Tür** (Pangolin, Authelia, …). Dann in der `.env`:
|
|
|
|
```dotenv
|
|
HONEYOS_PROXY_AUTH=true
|
|
# HONEYOS_USER_HEADER=Remote-User # optional, trennt die Einstellungen je Nutzer
|
|
```
|
|
|
|
HoneyOS fragt dann nicht noch einmal — kein zweiter Anmeldeschirm. **Der
|
|
Container darf in diesem Betrieb nur über den Proxy erreichbar sein**, denn er
|
|
prüft selbst nichts mehr.
|
|
|
|
**B · HoneyOS meldet selbst an**, mit eigenem Anmeldeschirm über deinen
|
|
OIDC-Anbieter. In PocketID einen Client anlegen und als Rückrufadresse **exakt**
|
|
`<PUBLIC_URL>/api/auth/callback` eintragen. HoneyOS schickt PKCE mit, braucht
|
|
aber trotzdem ein Client-Geheimnis — also **keinen „public client"**.
|
|
|
|
```dotenv
|
|
HONEYOS_SESSION_SECRET=… # openssl rand -hex 32
|
|
HONEYOS_OIDC_ISSUER=https://id.example.com
|
|
HONEYOS_OIDC_CLIENT_ID=honeyos
|
|
HONEYOS_OIDC_CLIENT_SECRET=…
|
|
HONEYOS_BUTTON_LABEL=Anmelden mit PocketID
|
|
```
|
|
|
|
Wer nicht angemeldet ist, sieht nur den Anmeldeschirm — keine App-Liste, keine
|
|
Namen. Die Sitzung ist ein signiertes Cookie ohne Gegenstück im Server: ein
|
|
Neustart wirft niemanden hinaus. Sie hält 30 Tage.
|
|
|
|
**Weder A noch B** heißt: offen für jeden. Sagt beim Start deutlich, dass das
|
|
nur zum Entwickeln taugt.
|
|
|
|
## Wenn HoneyOS auf einer anderen Site liegt als die Apps
|
|
|
|
Der Browser behandelt `honeyos.duckdns.org` und `cloud.honeyy.duckdns.org` als
|
|
**fremde Sites** — `duckdns.org` steht auf der Public-Suffix-Liste, die
|
|
gemeinsame Endung zählt also nicht. Folge: Bei der Anfrage aus einer App heraus
|
|
schickt der Browser die HoneyOS-Sitzung **nicht** mit.
|
|
|
|
Damit die Leiste trotzdem eine App-Liste bekommt:
|
|
|
|
```dotenv
|
|
HONEYOS_APPS_KEY=… # openssl rand -hex 16
|
|
```
|
|
|
|
und derselbe Wert im Skript-Tag der App:
|
|
|
|
```html
|
|
<script defer src="https://honeyos.duckdns.org/honeybar/honeybar.js"
|
|
data-app="aether" data-key="…"></script>
|
|
```
|
|
|
|
Der Schlüssel steht damit in der App, die ohnehin nur Berechtigte sehen. Ohne
|
|
ihn antwortet `/api/apps` mit 401 — auch im Proxy-Betrieb, gerade weil dieser
|
|
Pfad im Proxy von der Anmeldung befreit sein muss.
|
|
|
|
Zweite Schranke, unabhängig davon: HoneyOS gibt die Liste nur an Ursprünge
|
|
heraus, die **selbst als App eingetragen** sind. Eine fremde Seite scheitert
|
|
also an CORS, selbst mit richtigem Schlüssel.
|
|
|
|
**Was auf zwei Sites nicht geht:** der Anmeldestand auf den Kacheln des
|
|
Schreibtischs (fragt über die Domaingrenze). Der Name *in der Leiste* geht
|
|
weiterhin — den holt sie bei der App selbst, gleicher Ursprung.
|
|
|
|
Liegen alle Adressen unter derselben Domain (`os.honeyy…`, `cloud.honeyy…`),
|
|
entfällt der Schlüssel und alles funktioniert ohne Einschränkung.
|
|
|
|
## Apps anlegen
|
|
|
|
**In der Oberfläche**, nicht in der `.env`: *Einstellungen → Programme* oder die Kachel
|
|
„+ app hinzufügen". Pro App:
|
|
|
|
| Feld | wofür |
|
|
|---|---|
|
|
| Kennung | klein, ohne Leerzeichen — muss zu `data-app` der Leiste passen |
|
|
| Name, Untertitel | Beschriftung auf Kachel und Symbol |
|
|
| Adresse | wohin der Start springt |
|
|
| Symbol | cloud · note · box · chip · folder · gear · globe · disk |
|
|
| Taste | Zifferntaste im Terminal-Modus |
|
|
| Wer-bin-ich | optional, siehe unten |
|
|
| Token-Schlüssel | nur für Apps mit Bearer-Token im localStorage statt Cookie |
|
|
|
|
Gespeichert wird die Liste als Ganzes; der Server prüft sie noch einmal und antwortet mit
|
|
dem, was er wirklich behalten hat. Eine Adresse ohne `https://` kommt als Fehlermeldung
|
|
zurück, nicht als kaputter Eintrag.
|
|
|
|
## Zwei Betriebsarten, ein Datenstand
|
|
|
|
| | Terminal-Modus | Desktop-Modus |
|
|
|---|---|---|
|
|
| Apps | Kacheln nebeneinander | Symbole auf der Fläche, Doppelklick öffnet |
|
|
| Fenster | Dialog in der Mitte | verschiebbar, minimierbar |
|
|
| unten | Statuszeile mit Tastenhinweisen | Taskleiste mit Startmenü |
|
|
|
|
Umschalten mit **`M`**, über *Einstellungen → Betriebsart* oder im Startmenü. Gleich bleiben
|
|
in beiden: Menüleiste, Palette, Einstellungen, App-Liste.
|
|
|
|
**Tasten:** `1` `2` App starten · `M` Betriebsart · `S` Einstellungen · `?` Hilfe ·
|
|
`Esc` schließt Startmenü, oberstes Fenster oder Dialog — in dieser Reihenfolge.
|
|
|
|
## Wetter
|
|
|
|
Ein Pixelsymbol mit der Temperatur — wahlweise auf dem Schreibtisch, in der
|
|
Leiste, in beidem oder nirgends. Zwei getrennte Schalter unter
|
|
*Einstellungen → Wetter*, weil das zwei verschiedene Orte sind: Wer es beim
|
|
Arbeiten in einer App sehen will, will es nicht zwangsläufig auch auf einer
|
|
Seite, die er selten aufruft.
|
|
|
|
Die Stadt wird dort einmal gesucht und **mit ihren Koordinaten** gespeichert;
|
|
sonst müsste jede Abfrage den Ort erneut suchen — zwei Anfragen nach draußen
|
|
statt einer. Ohne Stadt bleibt das Wetter aus.
|
|
|
|
**Der Server holt es, nicht der Browser.** Die Leiste steht auf jeder Seite
|
|
jeder App: Fragte sie selbst beim Wetterdienst an, ginge bei jedem Seitenaufruf
|
|
aus jeder App eine Anfrage samt IP dorthin, und die CSP jeder App müsste den
|
|
fremden Ursprung erlauben. So sieht der Dienst einen Server. Die Antwort gilt
|
|
zehn Minuten — das Wetter ändert sich langsamer, als Seiten neu geladen werden.
|
|
|
|
Verwendet wird [Open-Meteo](https://open-meteo.com): kein Zugangsschlüssel, kein
|
|
Konto, nichts einzurichten. Ist der Dienst nicht erreichbar oder das Gerät
|
|
offline, verschwindet das Symbol — lieber keins als eine Zahl von gestern.
|
|
|
|
Ändern lässt sich die Stadt nur auf dem Schreibtisch; ein Klick auf das Symbol
|
|
in der Leiste führt dorthin. Die Leiste liest über den Schlüssel und darf nichts
|
|
schreiben.
|
|
|
|
## Die Leiste in Aether / InkNote
|
|
|
|
Eine Zeile ins `frontend/index.html` der App:
|
|
|
|
```html
|
|
<script defer src="https://os.honeyy.duckdns.org/honeybar/honeybar.js"
|
|
data-app="aether"></script>
|
|
```
|
|
|
|
`defer` ist nicht optional: ohne wartet der Seitenaufbau der App auf das Skript.
|
|
Ist HoneyOS langsam oder aus, soll die Leiste den Start von Aether nicht bremsen.
|
|
|
|
`data-app` ist die Kennung aus der App-Liste. Die Liste holt sich die Leiste selbst von
|
|
`/api/apps` — mit deiner HoneyOS-Sitzung, weil alle Adressen unter derselben Domain liegen.
|
|
**Freigegeben sind nur die Ursprünge, die selbst als App eingetragen sind**: die App-Liste
|
|
ist zugleich ihre eigene CORS-Freigabe.
|
|
|
|
Sie rendert in einem **Shadow DOM**: Tailwind (Aether) und die InkNote-Palette kommen nicht
|
|
hinein, ihre Stile nicht hinaus. Sie schiebt den Seiteninhalt um 32 px nach unten
|
|
(`data-push="none"` legt sie stattdessen darüber). Rechnet die App mit voller Höhe:
|
|
|
|
```css
|
|
.app-shell { height: calc(100vh - var(--honeybar-h, 0px)); }
|
|
```
|
|
|
|
Vorschau ohne Einbau: `/honeybar/demo.html`.
|
|
|
|
## Die Leiste in fremde Apps einsetzen (Reverse Proxy)
|
|
|
|
Für Werkzeuge, die man **nicht selbst geschrieben hat**, gibt es keinen
|
|
`<script>`-Tag zum Ändern — den setzt der Reverse Proxy ein, während er die
|
|
Seite durchreicht. Getestet mit nginx gegen eine App mit gzip und eigener CSP:
|
|
|
|
```nginx
|
|
location / {
|
|
proxy_pass http://127.0.0.1:8181;
|
|
proxy_set_header Host $host;
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
|
|
|
# 1. Unkomprimiert anfordern — in einem gzip-Strom ist nichts zu ersetzen.
|
|
proxy_set_header Accept-Encoding "";
|
|
|
|
# 2. Die CSP der App ersetzen, nicht ergänzen. Zwei CSP-Kopfzeilen bedeuten
|
|
# nicht „mehr erlaubt", sondern „beide müssen zustimmen".
|
|
proxy_hide_header Content-Security-Policy;
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://honeyos.example.com; connect-src 'self' https://honeyos.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:" always;
|
|
|
|
# 3. Nur HTML anfassen; JSON, Bilder und Downloads bleiben unberührt.
|
|
sub_filter_types text/html;
|
|
sub_filter_once on;
|
|
sub_filter '</head>' '<script defer src="https://honeyos.example.com/honeybar/honeybar.js" data-app="werkzeug"></script></head>';
|
|
}
|
|
```
|
|
|
|
Dazu muss der Ursprung dieser App **in HoneyOS als App eingetragen** sein — die
|
|
Liste ist zugleich die CORS-Freigabe. **Auf den Ursprung genau**: `localhost` und
|
|
`127.0.0.1` sind für den Browser zwei verschiedene, ebenso `http` und `https`
|
|
oder ein abweichender Port. Passt er nicht, erscheint die Leiste mit leerer
|
|
App-Liste und in der Konsole steht ein CORS-Fehler.
|
|
|
|
**Warum `style-src 'unsafe-inline'`:** Die Leiste bringt ihre Stile mit, im
|
|
Shadow DOM. Eine strenge CSP verbietet die — gemessen: die Leiste erscheint,
|
|
aber schwarz auf schwarz, ohne Röhre. Das ist eine echte Abschwächung der
|
|
CSP dieser App; viele fertige Werkzeuge brauchen `unsafe-inline` ohnehin für
|
|
sich selbst, aber wissen sollte man es. (Auflösbar, indem die Leiste ihre Stile
|
|
als eigene Datei ausliefert — steht in [IDEAS.md](IDEAS.md).)
|
|
|
|
**Apps mit COEP** (alles mit WASM oder SharedArrayBuffer — BentoPDF etwa)
|
|
lehnen fremde Dateien ab, die sich nicht zur Einbettung bekennen. HoneyOS
|
|
liefert die Leiste deshalb mit `Cross-Origin-Resource-Policy: cross-origin`
|
|
aus. Ohne das erscheint dort keine Leiste, und in der Konsole steht nur
|
|
`ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep`.
|
|
Das COEP der App bleibt dabei unangetastet — ihre eigenen Fähigkeiten also auch.
|
|
|
|
**Zwei Bauarten.** Die zweite ist die bessere, sobald die App eine eigene CSP
|
|
schickt — und das tun die meisten:
|
|
|
|
| | Skript von HoneyOS | Skript über die eigene Adresse |
|
|
|---|---|---|
|
|
| CSP der App | muss ersetzt werden | **unangetastet** (`'self'` genügt) |
|
|
| CORS | Ursprung muss eingetragen sein | entfällt |
|
|
| COEP der App | braucht `Cross-Origin-Resource-Policy` | entfällt |
|
|
| `HONEYOS_APPS_KEY` | steht im Quelltext der Seite | **bleibt auf dem Server** |
|
|
| Aufwand | eine `sub_filter`-Zeile | drei `location`-Blöcke |
|
|
|
|
Bei der zweiten reicht der Einsetzer auch `/honeybar/` und die zwei Abfragen der
|
|
Leiste durch, unter *seiner* Adresse. Für den Browser ist damit alles derselbe
|
|
Ursprung, und die drei Hürden fallen auf einmal weg. Durchgereicht werden dabei
|
|
**nur** `/api/apps` und `/api/weather`, und nur lesend (`limit_except GET`) —
|
|
ein pauschales `/api/` hinge sonst die ganze HoneyOS-Schnittstelle unter die
|
|
Adresse der App.
|
|
|
|
**Fertige Beispiele:**
|
|
|
|
- [`beispiele/bentopdf/`](beispiele/bentopdf/) — ein Dienst, alles über die
|
|
eigene Adresse. Geprüft mit `ghcr.io/alam00000/bentopdf-simple`, das eine
|
|
strenge CSP und COEP mitbringt: Leiste, Apps und Wetter erscheinen, ohne dass
|
|
an dessen Kopfzeilen etwas geändert wird.
|
|
- [`beispiele/mehrere-apps/`](beispiele/mehrere-apps/) — **HoneyOS und ein
|
|
Einsetzer für alle Dienste in einem Stapel.** Am Host erkennt nginx, welcher
|
|
Dienst gemeint ist; eine neue App sind zwei Zeilen in den Tabellen oben in
|
|
`leiste.conf`. Das ist der Aufbau, den man haben will, sobald es mehr als ein
|
|
Werkzeug ist — nicht ein Behälter je App, und nicht HoneyOS getrennt davon
|
|
gepflegt.
|
|
|
|
Das Netz `honeybar` legt dieser Stapel selbst an, unter festem Namen. Die
|
|
fremden Dienste treten ihm bei (`networks: [default, honeybar]` und
|
|
`honeybar: {external: true}`); ihre eigenen `ports:` können dann ganz weg,
|
|
erreichbar ist nur noch der Einsetzer. Deshalb diesen Stapel zuerst starten. Ein Host, der in der Tabelle fehlt, bekommt **404** statt einer
|
|
geratenen Weiterleitung — sonst wird aus einem Tippfehler in Pangolin eine
|
|
stille Umleitung irgendwohin. WebSocket-Verbindungen (Uptime Kuma, Dashboards,
|
|
alles mit Live-Anzeige) gehen durch, dafür sorgt `upgrade.conf`.
|
|
|
|
**Nur Pangolin davor?** Traefik kann Antwortkörper nicht von sich aus ändern;
|
|
dafür bräuchte es ein Plugin. Der kürzere Weg ist ein kleiner nginx *zwischen*
|
|
Pangolin und der App, der nichts tut außer einsetzen:
|
|
|
|
```yaml
|
|
leiste:
|
|
image: nginx:alpine
|
|
restart: unless-stopped
|
|
volumes: [./leiste.conf:/etc/nginx/conf.d/default.conf:ro]
|
|
# Pangolin zeigt jetzt hierher statt direkt auf die App.
|
|
```
|
|
|
|
Geprüft mit BentoPDF: Es schickt keine CSP, also genügen `proxy_pass`,
|
|
`Accept-Encoding ""` und die `sub_filter`-Zeile — die CSP-Zeilen oben braucht
|
|
nur, wessen App selbst eine schickt.
|
|
|
|
**Was dabei zu erwarten ist:**
|
|
|
|
- **Das Layout der fremden App kennt die Leiste nicht.** Rechnet sie mit
|
|
`100vh`, wird die Seite um 32 px zu hoch (Bildlaufleiste) — ihr CSS lässt
|
|
sich nicht anpassen wie das eigene. Dann hilft `data-push="none"`: Die Leiste
|
|
legt sich darüber, statt zu schieben. Verdeckt dafür die oberste Zeile.
|
|
- **Apps mit eigener Kopfleiste** sehen doppelt belegt aus. Geschmackssache.
|
|
- **Anmeldeseiten, iframes, Downloads** bekommen die Leiste ebenfalls, soweit
|
|
sie HTML sind. `sub_filter` unterscheidet das nicht.
|
|
- **Andere Proxys:** Caddy braucht ein Zusatzmodul (`replace-response`),
|
|
Traefik ein Plugin für den Antwortkörper. nginx kann es ohne Erweiterung.
|
|
|
|
## Wer ist wo angemeldet
|
|
|
|
Das Feld **Wer-bin-ich** ist der Pfad, unter dem eine App „wer bin ich" beantwortet
|
|
(relativ zur Adresse oder absolut). Aus der Antwort wird der Anzeigename gefischt —
|
|
`display_name`, `displayName`, `name`, `username`, auch verschachtelt unter `user`.
|
|
|
|
Gelesen wird das an zwei Stellen, und die verhalten sich **unterschiedlich**:
|
|
|
|
1. **Die Leiste in der App** fragt ihre eigene App. Gleicher Ursprung → funktioniert
|
|
sofort, ohne Änderung an Aether oder InkNote.
|
|
2. **Der Desktop auf `os.…`** fragt über die Domaingrenze. Dafür muss die App diesen
|
|
Ursprung erlauben:
|
|
- InkNote: `ALLOWED_ORIGINS=https://os.honeyy.duckdns.org`
|
|
- Aether: CORS mit `Access-Control-Allow-Credentials` — noch nicht vorhanden
|
|
|
|
Fehlt das, bleibt die Anzeige leer und sonst passiert nichts.
|
|
|
|
**Voraussetzung:** alle Adressen unter derselben Domain (`*.honeyy.duckdns.org`). Sonst
|
|
behandelt der Browser sie als fremde Site und schickt die Sitzung gar nicht erst mit.
|
|
|
|
## Entwickeln
|
|
|
|
```bash
|
|
docker compose -f docker-compose.dev.yml up -d --build # → http://localhost:8088
|
|
go test ./... # Anmeldeablauf, CSRF, Prüfungen
|
|
```
|
|
|
|
Der Dev-Betrieb läuft **ohne Anmeldung**. Die Oberfläche steckt per `go:embed` im Binary —
|
|
nach Änderungen an `web/` also neu bauen, ein Reload allein genügt nicht.
|
|
|
|
```
|
|
main.go Konfiguration, Routen, statische Auslieferung
|
|
auth.go OIDC mit PKCE, Sitzungscookie, CSRF
|
|
store.go die JSON-Datei: Apps, Nutzer, Einstellungen
|
|
auth_test.go falscher OIDC-Anbieter — der ganze Anmeldeweg im Test
|
|
web/ index.html · style.css · os.js · honeybar/ · looks/
|
|
```
|
|
|
|
Die Tests fahren einen echten OIDC-Anbieter mit eigenem Schlüssel hoch und gehen den Weg
|
|
einmal ganz durch. Wenn die grün sind, liegt ein Anmeldeproblem am Anbieter oder an der
|
|
Konfiguration, nicht am Code.
|
|
|
|
## Bauen und schieben
|
|
|
|
```bash
|
|
./build.sh # Tag kommt aus `const Version` in main.go, dazu :latest
|
|
./build.sh 1.0-test # abweichender Tag, falls nötig
|
|
```
|
|
|
|
**Die Version steht an genau einer Stelle:** `const Version` in `main.go`.
|
|
`build.sh` liest sie von dort, die Oberfläche holt sie über `/api/public`. Vorher
|
|
stand sie zusätzlich in `os.js` — dann hiess das Image 0.4 und der Schreibtisch
|
|
zeigte 0.2.
|
|
|
|
**Wann sie steigt (Marcs Regel, 25.08.2026): nicht bei jeder Kleinigkeit.**
|
|
Sonst steht in zwei Wochen 0.30 da, und die Zahl sagt nichts mehr — eine
|
|
Versionsnummer soll unterscheidbare Stände benennen, nicht Arbeitsschritte
|
|
zählen.
|
|
|
|
- **Steigt:** neue Fähigkeit, geänderte Bedienung, neuer Pfad oder neue
|
|
Einstellung — alles, wofür man wissen will, ob der Server es schon kann.
|
|
- **Steigt nicht:** Fehlerbehebung an frisch Gebautem, Feinschliff am Aussehen,
|
|
Text, Dokumentation, Aufräumen.
|
|
|
|
Das Abbild wird trotzdem gebaut und geschoben — nur unter demselben Tag und
|
|
`:latest`. Wer wissen will, was genau läuft, schaut in `git log`; dafür ist die
|
|
Versionsnummer zu grob.
|
|
|
|
## Grenzen, die man kennen sollte
|
|
|
|
- **Die Lampe an der Kachel ist kein Health-Check.** Bei einer fremden Domain darf der
|
|
Browser nur sehen, *dass* geantwortet wurde. Ein 500er sähe aus wie „online".
|
|
- **Die App-Liste ist für alle gleich.** Wer angemeldet ist, darf sie ändern — das ist für
|
|
eine Handvoll Konten gewollt und wäre für mehr Leute zu wenig.
|
|
- **Ein Angemeldeter kann jede Adresse eintragen.** Der Server prüft nur, dass es eine
|
|
http(s)-Adresse ist. Das ist zugleich die CORS-Freigabe der Leiste, also nicht
|
|
gedankenlos etwas Fremdes eintragen.
|
|
|
|
## Was als Nächstes ansteht
|
|
|
|
1. **CORS in Aether** für `os.…`, damit der Desktop den Anmeldestand sehen kann.
|
|
2. **Überall abmelden** in der Leiste — braucht pro App den CSRF-Weg zum Logout.
|
|
3. **Fenster mit Inhalt** statt nur Verweisen: „letzte Dateien", „letzte Notizen".
|