docs: durchgaengig HoneyBar statt Leiste

Der Dienst im Beispiel heisst jetzt einsetzer — er ist nicht die HoneyBar,
er setzt sie ein. Zusammensetzungen wie Menueleiste, Taskleiste und
Statusleiste bleiben, die meinen etwas anderes.

Dazu in IDEAS notiert, dass HoneyBar und Menueleiste zwei getrennte Stilsaetze
sind und schon einmal auseinandergelaufen ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
marcandClaude Opus 5 committed 2026-08-29 16:57:01 +02:00
1 parent 3b7367944b
commit 9da6503837
6 files changed
+156 -131

No files matched your search

+38 -38
View File
@@ -1,11 +1,11 @@
# 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.
Anmeldung über den eigenen OIDC-Anbieter — und dieselbe HoneyBar 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).
gemeinsame HoneyBar — **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.
@@ -69,7 +69,7 @@ Der Browser behandelt `honeyos.duckdns.org` und `cloud.honeyy.duckdns.org` als
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:
Damit die HoneyBar trotzdem eine App-Liste bekommt:
```dotenv
HONEYOS_APPS_KEY=… # openssl rand -hex 16
@@ -91,7 +91,7 @@ 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
Schreibtischs (fragt über die Domaingrenze). Der Name *in der HoneyBar* geht
weiterhin — den holt sie bei der App selbst, gleicher Ursprung.
Liegen alle Adressen unter derselben Domain (`os.honeyy…`, `cloud.honeyy…`),
@@ -104,7 +104,7 @@ entfällt der Schlüssel und alles funktioniert ohne Einschränkung.
| Feld | wofür |
|---|---|
| ID | klein, ohne Leerzeichen — muss zu `data-app` der Leiste passen |
| ID | klein, ohne Leerzeichen — muss zu `data-app` der HoneyBar passen |
| Name, Untertitel | Beschriftung auf Kachel und Symbol |
| Adresse | wohin der Start springt |
| Symbol | cloud · note · box · chip · folder · gear · globe · disk |
@@ -133,7 +133,7 @@ in beiden: Menüleiste, Palette, Einstellungen, App-Liste.
## Wetter
Ein Pixelsymbol mit der Temperatur — wahlweise auf dem Schreibtisch, in der
Leiste, in beidem oder nirgends. Zwei getrennte Schalter unter
HoneyBar, 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.
@@ -142,7 +142,7 @@ 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
**Der Server holt es, nicht der Browser.** Die HoneyBar 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
@@ -153,10 +153,10 @@ 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
in der HoneyBar führt dorthin. Die HoneyBar liest über den Schlüssel und darf nichts
schreiben.
## Die Leiste in deine Apps bringen
## Die HoneyBar in deine Apps bringen
Es gibt zwei Wege, und welcher gilt, hängt an einer einzigen Frage: **Kannst du
den Quelltext der App ändern?**
@@ -164,11 +164,11 @@ den Quelltext der App ändern?**
| | Weg 1 — selbst geschrieben | Weg 2 — fertig bezogen |
|---|---|---|
| Beispiele | Aether, InkNote | BentoPDF, Uptime Kuma, alles aus einem Image |
| Einbau | die App lädt die Leiste selbst | ein nginx davor setzt sie ein |
| Einbau | die App lädt die HoneyBar selbst | ein nginx davor setzt sie ein |
| Umstellen | eine Zeile in der `.env` | zwei Zeilen in der Proxy-Tabelle |
| Layout | passt sich an (`--honeybar-h`) | bleibt, wie die App es macht |
Beide Wege ergeben dieselbe Leiste; die App-Liste, die Farben und das Wetter
Beide Wege ergeben dieselbe HoneyBar; die App-Liste, die Farben und das Wetter
kommen ohnehin aus HoneyOS.
### Weg 1 — Apps, die du selbst schreibst
@@ -181,17 +181,17 @@ Eine Zeile im HTML der App:
```
`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.
Ist HoneyOS langsam oder aus, soll die HoneyBar den Start von Aether nicht bremsen.
**Aether und InkNote machen es einen Schritt anders — und das ist die bessere
Art:** Sie laden die Leiste zur *Laufzeit*, die Adresse kommt aus ihrer eigenen
Art:** Sie laden die HoneyBar zur *Laufzeit*, die Adresse kommt aus ihrer eigenen
`.env` (`HONEYBAR_ORIGIN`) über einen kleinen Endpunkt der App. Der Grund ist
Erfahrung: Ein fertiges Abbild wird gezogen, nicht gebaut — was beim Bauen ins
HTML geschrieben wird, lässt sich hinterher nicht mehr ändern. Und der Service
Worker einer PWA hebt ganze Antworten auf, samt Kopfzeilen; eine ins HTML
eingefrorene Adresse überlebt dort jede Konfigurationsänderung.
`data-app` ist die ID aus der App-Liste. Die Liste holt sich die Leiste selbst von
`data-app` ist die ID aus der App-Liste. Die Liste holt sich die HoneyBar 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.
@@ -213,7 +213,7 @@ Für Werkzeuge, die man **nicht selbst geschrieben hat**, gibt es keinen
Seite durchreicht:
```
außen ──► Pangolin ──► leiste (nginx) ──► app:8080
außen ──► Pangolin ──► honeybar (nginx) ──► app:8080
TLS, Auth setzt eine Zeile ein, reicht sonst nur durch
```
@@ -244,20 +244,20 @@ location / {
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
oder ein abweichender Port. Passt er nicht, erscheint die HoneyBar 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,
**Warum `style-src 'unsafe-inline'`:** Die HoneyBar bringt ihre Stile mit, im
Shadow DOM. Eine strenge CSP verbietet die — gemessen: die HoneyBar 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
sich selbst, aber wissen sollte man es. (Auflösbar, indem die HoneyBar 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
liefert die HoneyBar deshalb mit `Cross-Origin-Resource-Policy: cross-origin`
aus. Ohne das erscheint dort keine HoneyBar, und in der Konsole steht nur
`ERR_BLOCKED_BY_RESPONSE.NotSameOriginAfterDefaultedToSameOriginByCoep`.
Das COEP der App bleibt dabei unangetastet — ihre eigenen Fähigkeiten also auch.
@@ -273,7 +273,7 @@ schickt — und das tun die meisten:
| 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
HoneyBar 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
@@ -314,42 +314,42 @@ Zwei Änderungen, mehr nicht:
Einsetzer spricht sie im Docker-Netz unter ihrem Namen an (`bentopdf:8080`;
die `8080` ist der Port *im* Behälter und bleibt, die `8888` war nur die
Klinke an der Außenseite). Ginge Pangolin weiter direkt auf die App, wäre der
Einsetzer umgangen und die Leiste fehlte. Zum Nachsehen darf ein `ports:`
ruhig stehen bleiben — dann hast du zwei Wege: direkt ohne Leiste, über
Einsetzer umgangen und die HoneyBar fehlte. Zum Nachsehen darf ein `ports:`
ruhig stehen bleiben — dann hast du zwei Wege: direkt ohne HoneyBar, über
Pangolin mit.
2. **Das Netz `honeybar` dazu.** Es entsteht im Einsetzer-Stapel; hier wird es
nur als `external` mitbenutzt. Deshalb jenen Stapel zuerst starten.
Dazu noch zwei Zeilen im Einsetzer (`$ziel` und `$appid`) und in Pangolin die
Ressource von der App auf `leiste:80` umstellen. Fertig.
Ressource von der App auf `honeybar:80` umstellen. Fertig.
#### Was es kostet
Nicht viel, aber nicht nichts:
- **Ein Ausfallpunkt mehr.** Ist der Einsetzer weg, sind die Apps dahinter weg —
nicht nur ihre Leiste. `restart: unless-stopped` ist deshalb Pflicht; ein
nicht nur ihre HoneyBar. `restart: unless-stopped` ist deshalb Pflicht; ein
nginx, der nur durchreicht, fällt praktisch nie aus.
- **Eine Stelle mehr zum Pflegen.** Je App zwei Zeilen, und beim Umbenennen
einer Adresse muss man daran denken.
- **Ein Sprung mehr im Netz.** Im selben Docker-Netz nicht messbar.
- **Nicht jede App muss dahinter.** Was ohne Leiste laufen soll, zeigt in
- **Nicht jede App muss dahinter.** Was ohne HoneyBar laufen soll, zeigt in
Pangolin weiter direkt auf die App. Das entscheidest du je 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
strenge CSP und COEP mitbringt: HoneyBar, 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 beliebig viele Dienste in einem Stapel.** Zwei Tabellen oben in
der Datei sagen, welcher Name zu welchem Behälter gehört und welche App das
für HoneyOS ist; eine neue App sind zwei Zeilen. Die Leiste hebt die aktuelle
für HoneyOS ist; eine neue App sind zwei Zeilen. Die HoneyBar hebt die aktuelle
App hervor, sobald `$appid` der `ID` in der App-Liste entspricht — genau wie
in Aether und InkNote. 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
`honeybar.conf`. Das ist der Aufbau, den man haben will, sobald es mehr als ein
Nicht ein Behälter je App, und nicht HoneyOS getrennt davon gepflegt.
Das Netz `honeybar` legt dieser Stapel selbst an, unter festem Namen. Die
@@ -365,10 +365,10 @@ 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:
honeybar:
image: nginx:alpine
restart: unless-stopped
volumes: [./leiste.conf:/etc/nginx/conf.d/default.conf:ro]
volumes: [./honeybar.conf:/etc/nginx/conf.d/default.conf:ro]
# Pangolin zeigt jetzt hierher statt direkt auf die App.
```
@@ -378,12 +378,12 @@ nur, wessen App selbst eine schickt.
**Was dabei zu erwarten ist:**
- **Das Layout der fremden App kennt die Leiste nicht.** Rechnet sie mit
- **Das Layout der fremden App kennt die HoneyBar 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
sich nicht anpassen wie das eigene. Dann hilft `data-push="none"`: Die HoneyBar
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
- **Anmeldeseiten, iframes, Downloads** bekommen die HoneyBar 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.
@@ -396,7 +396,7 @@ Das Feld **Wer-bin-ich** ist der Pfad, unter dem eine App „wer bin ich" beantw
Gelesen wird das an zwei Stellen, und die verhalten sich **unterschiedlich**:
1. **Die Leiste in der App** fragt ihre eigene App. Gleicher Ursprung → funktioniert
1. **Die HoneyBar 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:
@@ -463,11 +463,11 @@ Versionsnummer zu grob.
- **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
http(s)-Adresse ist. Das ist zugleich die CORS-Freigabe der HoneyBar, 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.
2. **Überall abmelden** in der HoneyBar — braucht pro App den CSRF-Weg zum Logout.
3. **Fenster mit Inhalt** statt nur Verweisen: „letzte Dateien", „letzte Notizen".