marcandClaude Opus 5 283f9a1850 feat: Backend mit OIDC-Anmeldung, Apps und Einstellungen im Server
Aus der statischen Seite wird ein Go-Binary mit eingebetteter Oberfläche.
Grund: Apps über .env zu pflegen wurde unübersichtlich, und kosmetische
Einstellungen sollen auf jedem Gerät gleich sein.

- Anmeldung über den eigenen OIDC-Anbieter (PocketID), mit PKCE. Ohne
  Anmeldung gibt es nichts zu sehen – auch keine App-Liste.
- Sitzung als signiertes Cookie ohne Gegenstück im Server; Schreibzugriffe
  zusätzlich über ein lesbares CSRF-Cookie abgesichert
- Apps werden in der Oberfläche angelegt und bearbeitet, nicht mehr in der
  .env. Der Server prüft jeden Eintrag und meldet zurück, was er behält.
- Einstellungen liegen je Nutzer im Server; localStorage ist nur noch der
  Vorschuss, damit die Röhre beim Laden nicht in der falschen Farbe blitzt
- die Leiste holt die App-Liste aus /api/apps – freigegeben sind allein die
  Ursprünge, die selbst als App eingetragen sind
- Tests fahren einen echten OIDC-Anbieter hoch und gehen den Anmeldeweg
  einmal ganz durch

Ohne HONEYOS_OIDC_ISSUER läuft alles offen – für die Entwicklung, mit
deutlicher Warnung beim Start.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:20:55 +02:00

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

In PocketID einen Client anlegen und dort als Rückrufadresse exakt eintragen:

https://os.honeyy.duckdns.org/api/auth/callback

HoneyOS schickt PKCE mit, braucht aber trotzdem ein Client-Geheimnis — also keinen „public client" anlegen. Danach in die .env:

HONEYOS_PUBLIC_URL=https://os.honeyy.duckdns.org
HONEYOS_SESSION_SECRET=…             # openssl rand -hex 32
HONEYOS_OIDC_ISSUER=https://id.honeyy.duckdns.org
HONEYOS_OIDC_CLIENT_ID=honeyos
HONEYOS_OIDC_CLIENT_SECRET=…
HONEYOS_BUTTON_LABEL=Anmelden mit PocketID
# HONEYOS_ALLOWED_USERS=marc          # zweiter Riegel neben den PocketID-Gruppen

Wer nicht angemeldet ist, sieht nur den Anmeldeschirm — keine App-Liste, keine Namen. Die Sitzung ist ein signiertes Cookie ohne Gegenstück auf dem Server: ein Neustart wirft niemanden hinaus, und es gibt nichts aufzuräumen. Sie hält 30 Tage.

Ohne HONEYOS_OIDC_ISSUER läuft HoneyOS ganz ohne Anmeldung. Das ist zum Entwickeln gedacht und sagt beim Start deutlich, dass es so nicht ins offene Netz gehört.

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 src="https://os.honeyy.duckdns.org/honeybar/honeybar.js"
        data-app="aether"></script>

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:

  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

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            # → gitea.honeyy.duckdns.org/docker/honeyos:latest
./build.sh 0.2        # mit Tag

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