marc cc7a50c621 Erst die Sitzung, dann der Schluessel
Der Schluessel kennt keinen Nutzer und landete beim Standardnutzer — auch
dann, wenn die App unter derselben Site liegt und der Browser das
HoneyOS-Cookie laengst mitschickt (cloud.honeyos.… und honeyos.… sind
dieselbe Site, duckdns.org steht auf der Public-Suffix-Liste). Damit trug
die HoneyBar in Aether die Farben eines fremden Nutzers.

Jetzt sieht /api/apps erst nach der Sitzung, und nur bei eigener
Anmeldung: Im offenen Betrieb und hinter einem Anmelde-Proxy gilt jede
Anfrage als angemeldet, dort bleibt der Schluessel das letzte Wort.
Test dazu, mit zwei Nutzern, damit der Rueckfall auf den einzigen Nutzer
das Ergebnis nicht zufaellig richtig aussehen laesst.
2026-08-29 23:29:53 +02:00
2026-08-29 22:57:01 +02:00
2026-08-29 23:29:53 +02:00
2026-08-29 23:29:53 +02:00

HoneyOS

Ein Retro-Schreibtisch vor den eigenen Apps. Amber-CRT, Boot-Log, zwei Betriebsarten, 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 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. Der Browser hält dieselben Einstellungen nur als Vorschuss, damit die Röhre beim Laden nicht in der falschen Farbe aufflackert.

Betrieb

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:

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".

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 HoneyBar trotzdem eine App-Liste bekommt:

HONEYOS_APPS_KEY=…                  # openssl rand -hex 16

und derselbe Wert im Skript-Tag der App:

<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.

Erst die Sitzung, dann der Schlüssel. Bringt die Anfrage ein gültiges HoneyOS-Cookie mit, antwortet der Server mit den Einstellungen dieses Nutzers; der Schlüssel wird dann gar nicht erst angesehen. Er ist die Krücke für den Fall, dass das Cookie nicht ankommen kann — und darf eine echte Anmeldung nicht überstimmen, denn er kennt keinen Nutzer und landet beim Standardnutzer.

Ob das Cookie ankommt, entscheidet die Site, nicht die Adresse: Weil duckdns.org auf der Public-Suffix-Liste steht, sind honeyos.duckdns.org und cloud.honeyos.duckdns.org dieselbe Site (Cookie fließt), honeyy.duckdns.org dagegen eine fremde (Cookie fließt nicht).

Cookie Folge
App unter derselben Site kommt an eigene Röhre, eigenes Wetter, data-key überflüssig
App auf fremder Site bleibt weg Schlüssel nötig, HoneyBar trägt die Farben des Standardnutzers

Zweiteres gilt auch für den Anmeldestand auf den Kacheln des Schreibtischs: Der fragt über die Domaingrenze und bleibt dort leer.

Apps anlegen

In der Oberfläche, nicht in der .env: Einstellungen → Programme oder die Kachel „+ app hinzufügen". Pro App:

Feld wofür
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
Taste Zifferntaste im Terminal-Modus

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.

Die Schublade

Der Pfeil rechts in beiden Leisten klappt alle Apps als Raster auf: Symbol, Name, Untertitel. Drei Spalten, am Handy dieselben drei.

Warum nicht nur Symbole? Weil acht geteilte Pixelglyphen nicht identifizieren — zwei Apps können dieselbe Wolke tragen, und bei Homelab-Diensten ist das der Normalfall. Dazu der Unterschied der beiden Orte: Den Streifen oben sieht man im Vorbeigehen, da reicht ein Blick und die Symbole sind abschaltbar. Die Schublade macht man auf, weil man sucht — Suchen heißt Lesen.

Der Untertitel aus der App-Liste steht damit endlich dort, wo er nützt: Er beantwortet die Frage, die ein generisches Symbol offenlässt. Ist er leer, fällt die Zeile weg.

Ein Satz Symbole für alles

Die Pixelraster stehen an genau einer Stelle: web/glyphs.json, 12×12 Zeichen je Symbol, ein # ist ein Pixel. Der Schreibtisch holt die Datei selbst; die HoneyBar in fremden Apps bekommt die Raster mit der App-Liste mitgeliefert — sie soll eine einzelne Datei ohne zweite Anfrage bleiben, und /api/apps fragt sie ohnehin. Wer dort ein Symbol ergänzt, kann es sofort im Programm-Editor auswählen; abgetippt wird nichts mehr.

Dass das nötig war, hatte einen Anlass: Die Wettersymbole standen einmal in os.js und in honeybar.js. Sie liefen auseinander, und niemand merkte es, bis das Wetter in der einen Leiste anders aussah als in der anderen.

Wo die Symbole stehen:

Symbol
App-Streifen in beiden Leisten nach Schalter Einstellungen → HoneyBar
Aufklappmenü beider Leisten immer
Kacheln, Schreibtischsymbole, Startmenü immer

Der Schalter gilt für beide Leisten zugleich — es ist dieselbe Leiste, nur an zwei Orten. Er liegt wie alles andere im Server, wirkt also auf jedem Gerät und in jeder App, ohne dass eine App neu gebaut wird.

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 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.

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 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 zehn Minuten — das Wetter ändert sich langsamer, als Seiten neu geladen werden.

Verwendet wird Open-Meteo: 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 HoneyBar führt dorthin. Die HoneyBar liest über den Schlüssel und darf nichts schreiben.

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?

Weg 1 — selbst geschrieben Weg 2 — fertig bezogen
Beispiele Aether, InkNote BentoPDF, Uptime Kuma, alles aus einem Image
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 HoneyBar; die App-Liste, die Farben und das Wetter kommen ohnehin aus HoneyOS.

Weg 1 — Apps, die du selbst schreibst

Eine Zeile im HTML der App:

<script defer src="https://honeyos.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 HoneyBar den Start von Aether nicht bremsen.

Aether und InkNote machen es einen Schritt anders — und das ist die bessere 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 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.

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:

.app-shell { height: calc(100vh - var(--honeybar-h, 0px)); }

Vorschau ohne Einbau: /honeybar/demo.html.

Weg 2 — fertige Apps: ein HoneyBar-Proxy davor

Für Werkzeuge, die man nicht selbst geschrieben hat, gibt es keinen <script>-Tag zum Ändern — den setzt ein kleiner nginx ein, während er die Seite durchreicht:

außen ──► Pangolin ──► honeybar (nginx) ──► app:8080
          TLS, Auth      setzt eine Zeile ein, reicht sonst nur durch

Ein Reverse Proxy hinter dem Reverse Proxy also, mit klarer Arbeitsteilung. Getestet mit nginx gegen eine App mit gzip und eigener CSP:

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 HoneyBar mit leerer App-Liste und in der Konsole steht ein CORS-Fehler.

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 HoneyBar ihre Stile als eigene Datei ausliefert — steht in 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 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.

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 HoneyBar-Proxy auch /honeybar/ und die zwei Abfragen der 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 Adresse der App.

Was du an der Compose der App änderst

Vorher — BentoPDF, wie es aus der Anleitung des Herstellers kommt:

services:
  bentopdf:
    image: ghcr.io/alam00000/bentopdf-simple:latest
    container_name: bentopdf
    restart: unless-stopped
    ports:
      - '8888:8080'

Nachher:

services:
  bentopdf:
    image: ghcr.io/alam00000/bentopdf-simple:latest
    container_name: bentopdf
    restart: unless-stopped
    networks: [honeybar]        # statt ports:

networks:
  honeybar:
    external: true              # legt der HoneyBar-Proxy-Stapel an

Zwei Änderungen, mehr nicht:

  1. ports: fällt weg. Die App braucht kein Loch nach außen mehr — der HoneyBar-Proxy 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 HoneyBar-Proxy 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 HoneyBar-Proxy-Stapel; hier wird es nur als external mitbenutzt. Deshalb jenen Stapel zuerst starten.

Dazu noch zwei Zeilen im HoneyBar-Proxy ($ziel und $appid) und in Pangolin die Ressource von der App auf honeybar:80 umstellen. Fertig.

Was es kostet

Nicht viel, aber nicht nichts:

  • Ein Ausfallpunkt mehr. Ist der HoneyBar-Proxy weg, sind die Apps dahinter weg — 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 HoneyBar laufen soll, zeigt in Pangolin weiter direkt auf die App. Das entscheidest du je App.

Fertige Beispiele:

  • beispiele/bentopdf/ — ein Dienst, alles über die eigene Adresse. Geprüft mit ghcr.io/alam00000/bentopdf-simple, das eine strenge CSP und COEP mitbringt: HoneyBar, Apps und Wetter erscheinen, ohne dass an dessen Kopfzeilen etwas geändert wird.
  • beispiele/mehrere-apps/ — HoneyOS und ein HoneyBar-Proxy 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 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 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 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 HoneyBar-Proxy. 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:

  honeybar:
    image: nginx:alpine
    restart: unless-stopped
    volumes: [./honeybar.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 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 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 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.

Das ■ HONEYOS links ist ein Menü — auf dem Schreibtisch wie in jeder App:

in der HoneyBar (fremde App) auf dem Schreibtisch
Schreibtisch Über HoneyOS
Einstellungen … Einstellungen
HoneyBar einklappen Terminal-/Desktop-Modus

Rechts daneben klappt ein Pfeil die App-Liste auf — in beiden Leisten gleich. Das Zahnrad, das dort früher stand, ist weg: Einstellungen stehen jetzt links im Systemmenü, wo sie in jeder App auch stehen.

In den Apps sind die Punkte Links auf den Schreibtisch, keine Handlungen — und das ist eine Grenze, keine Bequemlichkeit. Einstellungen schreiben, und dafür braucht der Server die HoneyOS-Sitzung: Aus einer fremden Site schickt der Browser sie nicht mit, und selbst unter einer Domain liegt das CSRF-Prüfwort in einem Cookie, das nur HoneyOS' eigene Seiten lesen dürfen. Also stellt die HoneyBar die Frage in der Adresse (?einstellungen=1), und der Schreibtisch beantwortet sie: Er macht dasselbe Feld auf, das auch [S] öffnet, und nimmt den Anhang danach wieder aus der Adresse.

Abmelden steht in den Einstellungen, ganz unten — und sonst nirgends. Nicht in der HoneyBar fremder Apps, nicht im Systemmenü, nicht im Startmenü. Es ist die einzige Schaltfläche, die eine Sitzung beendet statt etwas einzustellen, und sie gehört dorthin, wo man sich angemeldet hat. Wer es aus einer App heraus braucht, geht über „Einstellungen …" — ein Klick mehr, dafür kann kein eingebettetes Bild einer fremden Seite eine Abmeldung auslösen. Der Punkt erscheint nur, wenn HoneyOS selbst anmeldet; hinter einem Proxy oder im offenen Betrieb fehlt er.

Wenn die HoneyBar veraltet aussieht

Die HoneyBar wird bei Weg 2 über den eigenen Ursprung der App ausgeliefert (/honeybar/honeybar.js). Das ist der Kniff, der CSP, CORS und COEP auf einmal erledigt — es setzt sie aber zugleich dem Zwischenspeicher dieser App aus, und dem ihres Service Workers, falls sie einen hat. Eine alte HoneyBar dort sieht aus wie ein Fehler in HoneyOS: Ein neuer Menüpunkt fehlt, obwohl der Schreibtisch ihn längst hat.

HoneyOS liefert seine Dateien deshalb mit Cache-Control: no-store aus. Bleibt trotzdem etwas hängen, hilft die Probe von Hand:

curl -s https://<app-adresse>/honeybar/honeybar.js | grep -c sysmenu

Kommt dort 0, ist die ausgelieferte Datei alt — dann läuft HoneyOS noch auf einem älteren Abbild (docker compose pull && up -d). Kommt 2, liegt es am Browser: einmal hart neu laden (Strg+Umschalt+R), und bei einer installierten PWA deren Daten löschen.

Am schnellsten zu sehen ohne Werkzeug: Stehen im App-Streifen der HoneyBar Symbole neben den Namen? Dann ist sie aktuell.

Wer angemeldet ist

Steht auf dem Schreibtisch, als eigenes Widget: Bild, Name und Begrüßung, aus der HoneyOS-Anmeldung. In den Apps steht es nicht — die HoneyBar dort trägt genau dasselbe wie die Menüleiste hier, sonst merkt man beim Wechsel, dass es zwei verschiedene HoneyBars sind.

Früher konnte jede App einen „Wer-bin-ich"-Pfad und einen Token-Schlüssel mitbringen; die HoneyBar fragte damit die App nach dem Anzeigenamen. Beides ist seit dem 29.08.2026 entfernt. Es brachte allein einen Namen, wirkte je nach Domain unterschiedlich, und der Token-Schlüssel hatte eine scharfe Kante: Zeigte das Wer-bin-ich-Feld auf eine fremde Adresse, ging der Ausweis der App dorthin. Wer die Felder in seiner honeyos.json noch stehen hat, braucht nichts zu tun — sie werden beim nächsten Speichern still verworfen.

Wie das Projekt liegt

main.go              Start, Routen, Handler
internal/
  config/            was aus der Umgebung kommt
  store/             die eine JSON-Datei: Apps und Nutzer
  auth/              wer fragt da? (OIDC, Proxy, offen)
  weather/           Wetter holen und zehn Minuten behalten
web/
  index.html
  css/style.css
  glyphs.json        die Pixelraster — die einzige Quelle, beide Leisten
  js/core.js         Zustand, Server, Einstellungen, Kleinkram
  js/glyphs.js       lädt die Raster und baut SVGs daraus
  js/os.js           die beiden Ansichten, Fenster, Dialoge
  js/widgets/        ein Widget je Datei
  honeybar/          die HoneyBar für fremde Seiten — bewusst eine Datei

Die HoneyBar bleibt absichtlich eine Datei ohne Importe: Sie wird von fremden Seiten mit einer Zeile eingebunden, da darf nichts nachladen.

Entwickeln

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

./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 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 HoneyBar — braucht pro App den CSRF-Weg zum Logout.
  3. Fenster mit Inhalt statt nur Verweisen: „letzte Dateien", „letzte Notizen".
S
Description
No description provided
Readme
13 MiB
0 Stars 1 Watchers 0 Forks
Languages
JavaScript 43.4%
Go 30.3%
CSS 14%
HTML 8.2%
Python 3.3%
Other 0.8%