Statische Oberfläche vor Aether und InkNote: kein Backend, kein Build, nginx liefert web/ aus. Jede App bleibt ihre eigene PWA auf ihrer eigenen Domain – HoneyOS ist Startpunkt und Umschalter, kein iframe-Rahmen. - zwei Betriebsarten aus einem Datenstand: Terminal (Kacheln, Tastatur) und Desktop (Symbole, verschiebbare Fenster, Taskleiste) - honeybar.js als Drop-in für die Apps, rendert im Shadow DOM, damit Tailwind und die InkNote-Palette einander nicht in die Quere kommen - Konfiguration vollständig aus der .env: das Skript in /docker-entrypoint.d/ schreibt config.json beim Containerstart - optionaler _ME-Pfad pro App zeigt an, wer angemeldet ist; ohne CORS bleibt die Anzeige leer, ohne dass etwas kaputtgeht Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
165 lines
7.0 KiB
Markdown
165 lines
7.0 KiB
Markdown
# HoneyOS
|
||
|
||
Ein Retro-Schreibtisch vor den eigenen Apps. Amber-CRT, Boot-Log, zwei Betriebsarten —
|
||
und dieselbe Leiste zum Einhängen in Aether und InkNote.
|
||
|
||
**Was es ist:** eine Oberfläche davor. Jede App bleibt ihre eigene PWA auf ihrer eigenen
|
||
Domain mit ihrer eigenen Arbeitsumgebung und ihrer eigenen Anmeldung. HoneyOS ist der
|
||
Startpunkt, der Umschalter und die gemeinsame Leiste — **kein Rahmen, der die Apps
|
||
einsperrt** (kein iframe).
|
||
|
||
**Was es nicht ist:** eine Anmeldung. HoneyOS ist eine statische Seite ohne Backend; ein
|
||
OIDC-Client bräuchte ein Client-Geheimnis und eine Sitzung, und beides gehört nicht in
|
||
etwas, das der Browser vollständig lesen kann. Angemeldet wird bei Aether und InkNote,
|
||
die beide an PocketID hängen. HoneyOS kann den Stand nur **anzeigen** — siehe unten.
|
||
|
||
## Betrieb
|
||
|
||
```bash
|
||
cp .env.example .env # Adressen und Namen eintragen
|
||
docker login gitea.honeyy.duckdns.org
|
||
docker compose pull && docker compose up -d
|
||
```
|
||
|
||
Es gibt nichts zu sichern: keine Datenbank, kein Volume. Der ganze Zustand ist die `.env` —
|
||
und was sich der Browser pro Gerät merkt (Betriebsart, Röhrenfarbe).
|
||
|
||
Hinter deinem Reverse Proxy erwartet HoneyOS nichts Besonderes: `proxy_pass` auf den Port,
|
||
fertig. `/healthz` antwortet mit `ok`.
|
||
|
||
## Konfiguration
|
||
|
||
Alles kommt aus der `.env`. Beim Containerstart schreibt
|
||
[`docker-entrypoint.d/20-honeyos-config.sh`](docker-entrypoint.d/20-honeyos-config.sh)
|
||
daraus `config.json`, die der Browser liest. Nach jeder Änderung `docker compose up -d`.
|
||
|
||
**Alles darin ist öffentlich.** Adressen und Namen gehören hinein, Geheimnisse niemals.
|
||
|
||
Eine App entsteht durch ihre `_URL`; fehlt die, wird die Nummer übersprungen (bis 12):
|
||
|
||
```dotenv
|
||
HONEYOS_APP_1_ID=aether
|
||
HONEYOS_APP_1_NAME=AETHER
|
||
HONEYOS_APP_1_SUBTITLE=Cloud & Dateien
|
||
HONEYOS_APP_1_URL=https://cloud.honeyy.duckdns.org
|
||
HONEYOS_APP_1_GLYPH=cloud # cloud | note | box | chip | folder | gear
|
||
HONEYOS_APP_1_KEY=1 # Zifferntaste im Terminal-Modus
|
||
HONEYOS_APP_1_ME=/api/v1/auth/me # optional, siehe „Wer ist angemeldet"
|
||
```
|
||
|
||
Ohne `HONEYOS_APP_1_URL` bleibt die eingebackene `web/config.json` gültig — ein
|
||
`docker run` ohne `.env` zeigt also die Standard-Apps. Adressen lassen sich zusätzlich im
|
||
Browser unter *Einstellungen* überschreiben; das gilt dann nur für dieses Gerät.
|
||
|
||
## 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: die Menüleiste oben, die Palette, die Einstellungen, `config.json`.
|
||
`renderTiles()` und `renderDeskIcons()` in `os.js` zeichnen aus denselben Daten — eine neue
|
||
App erscheint ohne Zutun in beiden.
|
||
|
||
**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 src="https://os.honeyy.duckdns.org/honeybar/honeybar.js"
|
||
data-app="aether"></script>
|
||
```
|
||
|
||
`data-app` ist die `id` aus der Konfiguration — danach markiert sich die Leiste selbst als
|
||
aktiv. Die App-Liste holt sie von allein neben ihrem eigenen Skript.
|
||
|
||
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 angemeldet
|
||
|
||
`_ME` ist der Pfad, unter dem eine App „wer bin ich" beantwortet (relativ zur `_URL` 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. Die Lampe „online"
|
||
funktioniert unabhängig davon weiter.
|
||
|
||
`_TOKEN_KEY` brauchen nur Apps, die einen Bearer-Token im localStorage halten statt eines
|
||
Cookies (InkNote: `inknote_token`). Der ist pro Domain sichtbar — von `os.…` aus also nicht
|
||
lesbar, für die Leiste in der App dagegen schon.
|
||
|
||
**Voraussetzung für all das:** alle Adressen liegen 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
|
||
```
|
||
|
||
`web/` ist gemountet: Datei ändern, Seite neu laden. Weil der Mount schreibgeschützt ist,
|
||
greift dort die `.env`-Konfiguration **nicht** — es gilt die eingebackene `web/config.json`.
|
||
Zum Prüfen der `.env` das echte Image nehmen (`docker compose up -d --build`).
|
||
|
||
```
|
||
web/
|
||
index.html Desktop
|
||
style.css alle Farben in den --p*-Variablen ganz oben
|
||
os.js Boot, beide Ansichten, Fenster, Einstellungen
|
||
config.json Standardkonfiguration (wird zur Laufzeit aus der .env ersetzt)
|
||
honeybar/ die einbettbare Leiste + Vorschauseite
|
||
looks/ zwei verworfene Entwürfe (System 7, Win95) — nur Ansicht
|
||
```
|
||
|
||
Neue Symbole sind 12×12-Pixelraster in `os.js` (`GLYPHS`).
|
||
|
||
## Bauen und schieben
|
||
|
||
```bash
|
||
./build.sh # → gitea.honeyy.duckdns.org/docker/honeyos:latest
|
||
./build.sh 0.2 # mit Tag
|
||
```
|
||
|
||
## Grenzen, die man kennen sollte
|
||
|
||
- **localStorage ist pro Domain.** Betriebsart und Röhrenfarbe auf `os.…` kennt die Leiste
|
||
auf `notes.…` nicht. Der Umschalter ist ein Schalter, kein geteilter Zustand.
|
||
- **Die Lampe ist kein Health-Check.** Bei einer fremden Domain darf der Browser nur sehen,
|
||
*dass* geantwortet wurde, nicht *was*. Ein 500er sähe aus wie „online".
|
||
- **`config.json` und `honeybar.js` gehen mit `Access-Control-Allow-Origin: *` raus**, weil
|
||
die anderen Domains sie holen. Öffentliche Konfiguration — nichts Vertrauliches hinein.
|
||
|
||
## Was als Nächstes ansteht
|
||
|
||
1. **CORS in Aether** für `os.…`, damit der Desktop den Anmeldestand sehen kann. InkNote
|
||
hat mit `ALLOWED_ORIGINS` schon den Haken dafür.
|
||
2. **Überall abmelden** in der Leiste. Braucht pro App den CSRF-Weg zum Logout — bei Aether
|
||
ein `POST` mit Token, das geht nicht nebenbei über die Domaingrenze.
|
||
3. **Fenster mit Inhalt** statt nur Verweisen: „letzte Dateien", „letzte Notizen". Fenster
|
||
zeigen dann Daten aus den APIs — die Apps selbst bleiben in ihrem eigenen Tab.
|