feat: HoneyOS – Retro-Schreibtisch und Leiste für die eigenen Apps
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>
This commit is contained in:
commit
84cc5d619e
17 files changed
+3126
No files matched your search
@@ -0,0 +1,164 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user