Die Leiste soll zuerst die serverseitige Röhre übernehmen — kleinster Eingriff, größte Wirkung fürs "ein System"-Gefühl. Danach Systemmenü im Logo, dann die App-Schublade mit Symbolen. Neu als offene Entscheidung: wie sichtbar die Leiste sein darf. Sie ist bei jedem Aufruf der App da, auch wenn man mit HoneyOS gerade nichts zu tun hat. Vorschlag steht dabei, ebenso der Hinweis, dass es keine Einbahnstraße ist. Ausserdem defer an der Script-Zeile in README und Demo. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
184 lines
7.8 KiB
Markdown
184 lines
7.8 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
|
|
|
|
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`:
|
|
|
|
```dotenv
|
|
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:
|
|
|
|
```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`.
|
|
|
|
## 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 # → 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".
|