Marcs Frage: gilt die Einstellung vom Rechner auch für die Leiste am Handy? Sie hätte es nicht getan, sobald HONEYOS_USER_HEADER gesetzt ist: die Einstellungen liegen dann unter "proxy:marc", die Leiste fragt aber ohne Sitzung und landet beim Standardnutzer — dessen Satz ist leer, die Leiste wäre bernsteinfarben geblieben, während der Schreibtisch grün leuchtet. SettingsFor() hilft nach: gibt es genau einen Nutzer mit Einstellungen, gelten dessen. Bei mehreren bleibt es beim Standard, dann ist nicht zu erraten, wer da schaut. Nachgestellt mit einem Pixel-7-Profil ohne jedes Cookie: die Leiste kommt grün an. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
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 Leiste 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.
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.
Die Leiste in Aether / InkNote
Eine Zeile ins frontend/index.html der App:
<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:
.app-shell { height: calc(100vh - var(--honeybar-h, 0px)); }
Vorschau ohne Einbau: /honeybar/demo.html.
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:
- Die Leiste in der App fragt ihre eigene App. Gleicher Ursprung → funktioniert sofort, ohne Änderung an Aether oder InkNote.
- 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
- InkNote:
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
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.
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
- CORS in Aether für
os.…, damit der Desktop den Anmeldestand sehen kann. - Überall abmelden in der Leiste — braucht pro App den CSRF-Weg zum Logout.
- Fenster mit Inhalt statt nur Verweisen: „letzte Dateien", „letzte Notizen".