generated from Dicken/dickendock
288 lines
12 KiB
Markdown
288 lines
12 KiB
Markdown
# LaunchPad
|
||
|
||
Ein moderner, minimalistischer Homelab-Launcher – inspiriert von Raycast, Spotlight, Arc
|
||
und Linear. Tippe wenige Buchstaben, finde sofort den gewünschten Dienst, drücke Enter.
|
||
|
||
Kein überladenes Dashboard. Keine Kacheln. Im Mittelpunkt steht eine extrem schnelle Suche.
|
||
|
||
## Quickstart (Docker)
|
||
|
||
```bash
|
||
git clone <repo-url> LaunchPad
|
||
cd LaunchPad
|
||
cp .env.example .env # optional anpassen, siehe unten
|
||
docker compose up -d --build
|
||
```
|
||
|
||
- Frontend: http://localhost:8080 (HTTPS: https://localhost:8443, siehe unten)
|
||
- Backend / Health-API: http://localhost:3001/api/health
|
||
|
||
## HTTPS im lokalen Netz
|
||
|
||
LaunchPad ist per HTTPS auf Port `8443` erreichbar (`https://<deine-ip>:8443`),
|
||
zusätzlich zu HTTP auf `8080` – kein erzwungener Redirect, kein externer
|
||
Dienst beteiligt.
|
||
|
||
Der Frontend-Container erzeugt beim ersten Start automatisch eine **eigene
|
||
lokale Root-CA** (`LaunchPad Local CA`) und signiert damit ein Server-
|
||
Zertifikat für die in `.env` hinterlegte `LAUNCHPAD_HOST` (IP oder Hostname).
|
||
Beides liegt in einem Docker-Volume (`launchpad-certs`) und bleibt über
|
||
Neustarts und `--build` hinweg stabil – die CA wird nur einmal erzeugt, nicht
|
||
bei jedem Deploy neu.
|
||
|
||
**Ohne weiteren Schritt** funktioniert HTTPS sofort, der Browser zeigt aber
|
||
eine Warnung (unbekannte CA). Für eine echte, warnungsfreie Verbindung – und
|
||
damit Chrome die PWA auch wirklich "installiert" statt nur eine Verknüpfung
|
||
anzulegen – reicht **ein einziger, pro Gerät einmaliger Schritt**: die
|
||
Root-CA importieren. Es gibt dafür keinen Weg drumherum, der nicht doch einen
|
||
externen Dienst braucht (siehe Kasten unten) – aber so ist es so einfach wie
|
||
möglich gehalten.
|
||
|
||
### Root-CA einmal pro Gerät importieren
|
||
|
||
```bash
|
||
# .env: IP/Hostname eintragen, unter der du zugreifst
|
||
LAUNCHPAD_HOST=192.168.1.49
|
||
|
||
docker compose up -d --force-recreate frontend
|
||
```
|
||
|
||
**Am Handy (Android):** `http://192.168.1.49:8080/ca.crt` im Browser öffnen
|
||
(bewusst HTTP, dafür braucht's noch kein Vertrauen) → Android bietet direkt
|
||
"Zertifikat installieren" an → als "VPN und App" oder "WLAN"-Zertifikat
|
||
speichern (je nach Android-Version heißt die Kategorie leicht anders,
|
||
Hauptsache es landet unter "Von Nutzer installierte Zertifikate"). Danach ggf.
|
||
kurz Chrome neu starten.
|
||
|
||
**Am PC (Windows):** dieselbe URL im Browser öffnen, `ca.crt` herunterladen,
|
||
Datei doppelklicken → "Zertifikat installieren" → "Lokaler Computer" →
|
||
"Alle Zertifikate in folgendem Speicher speichern" → "Vertrauenswürdige
|
||
Stammzertifizierungsstellen" → Fertigstellen. Kein zusätzliches Tool nötig.
|
||
|
||
**macOS:** `ca.crt` herunterladen, doppelklicken (öffnet Schlüsselbundverwaltung),
|
||
dort öffnen, Vertrauenseinstellung auf "Immer vertrauen" setzen.
|
||
|
||
Danach zeigt `https://192.168.1.49:8443` keine Warnung mehr, und die
|
||
PWA-Installation sollte funktionieren.
|
||
|
||
> **Warum es keinen Weg ganz ohne diesen Schritt gibt:** Damit ein Browser
|
||
> einem Zertifikat *automatisch*, ohne jeden manuellen Trust-Schritt, vertraut,
|
||
> muss es entweder von einer öffentlich vorinstallierten CA stammen (z. B.
|
||
> Let's Encrypt) – das erfordert zwangsläufig einen externen Dienst und meist
|
||
> einen öffentlich auflösbaren Domainnamen – oder eben eine lokale CA, die du
|
||
> einmal manuell für vertrauenswürdig erklärst. Es gibt keine dritte Option.
|
||
> Diese Lösung minimiert den Aufwand auf "eine Datei öffnen, einmal pro Gerät",
|
||
> mehr geht ohne externe Dienste nicht.
|
||
|
||
### Zertifikat wechseln / eigenes einbinden
|
||
|
||
Falls du lieber ein eigenes Zertifikat verwendest (z. B. von einer internen
|
||
CA, die du schon hast): `fullchain.pem` + `privkey.pem` ins Volume
|
||
`launchpad-certs` legen (z. B. `docker cp fullchain.pem launchpad-frontend:/etc/nginx/certs/` bei laufendem Container, oder das Volume direkt mounten),
|
||
danach `docker compose restart frontend`. Liegt dabei keine `ca-cert.pem` im
|
||
selben Verzeichnis, generiert der Entrypoint nichts automatisch nach.
|
||
|
||
## Quickstart (lokale Entwicklung)
|
||
|
||
Voraussetzungen: Node.js ≥ 20, pnpm ≥ 9.
|
||
|
||
```bash
|
||
pnpm install
|
||
pnpm dev:backend # startet Fastify auf :3001
|
||
pnpm dev:frontend # startet Vite auf :5173 (proxyt /api zum Backend)
|
||
```
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
LaunchPad
|
||
│
|
||
├── apps
|
||
│ ├── frontend React + Vite + TypeScript + TailwindCSS
|
||
│ └── backend Fastify + TypeScript + Drizzle ORM + SQLite
|
||
│
|
||
├── packages
|
||
│ ├── shared gemeinsame Typen (Device, Service) + Such-Ranking-Logik
|
||
│ └── ui gemeinsame UI-Komponenten (SearchInput, StatusBadge)
|
||
│
|
||
├── docker zusätzliche Docker-Hilfsdateien
|
||
├── docs Projektdokumentation
|
||
├── pnpm-workspace.yaml
|
||
├── docker-compose.yml
|
||
└── tsconfig.base.json
|
||
```
|
||
|
||
## Stand dieses Commits
|
||
|
||
Dieser erste Commit liefert ein lauffähiges Grundgerüst:
|
||
|
||
- ✅ pnpm-Monorepo mit `apps/*` und `packages/*`
|
||
- ✅ Fastify-Backend mit `/api/health`-Endpunkt
|
||
- ✅ SQLite-Datenbank (better-sqlite3) inkl. Schema für `devices`, `services`, `categories`
|
||
(Drizzle ORM), automatisch angelegt beim Start
|
||
- ✅ React-Startseite mit Suchfeld, Dark-/Light-Mode und Live-Statusanzeige des Backends
|
||
- ✅ Tastaturkürzel `/` und `Strg+K` zum Fokussieren der Suche
|
||
- ✅ Docker-Compose-Setup: `docker compose up -d --build` startet Frontend + Backend
|
||
- ✅ Persistentes Docker-Volume für die SQLite-Datenbank
|
||
- ✅ REST-API für Geräte & Dienste (`/api/devices`, `/api/services`), Zod-validiert,
|
||
mit Repository-Layer über Drizzle (siehe `apps/backend/src/db/repositories`)
|
||
- ✅ Suche im Frontend gegen echte Backend-Daten (TanStack Query), inkl. Ranking-Logik
|
||
aus `packages/shared` und Tastatur-Navigation (Pfeiltasten, Enter, Escape)
|
||
- ✅ Kategorien-API (`/api/categories`), inkl. Umbenennen, Löschen (Dienste behalten
|
||
ihre Zuordnung nicht, werden aber nicht gelöscht) und Bulk-Reorder für Drag & Drop
|
||
- ✅ Favoriten-Toggle direkt in der Trefferliste (Stern anklicken)
|
||
- ✅ Scanner-Engine + API (`POST /api/scan/devices/:id`, `POST /api/scan/fritzbox`):
|
||
DNS-Kandidaten (hostname/.home/.local), Portscan (80, 443 + typische Ports),
|
||
Titel-/Favicon-Auslesen, Softwareerkennung, FritzBox-Geräteliste per TR-064
|
||
(HTTP-Digest-Auth). Läuft ausschließlich manuell per API-Aufruf – nie automatisch.
|
||
Benutzeränderungen an Diensten (Name, Kategorie, Favorit, Alias, Icon, Reihenfolge)
|
||
bleiben bei erneuten Scans garantiert erhalten.
|
||
- ✅ Adminbereich unter `/admin` (TanStack Router): Dashboard, Geräte (inkl.
|
||
„Jetzt scannen"-Button), Dienste (Inline-Bearbeitung), Kategorien (natives
|
||
Drag & Drop), Scanner (FritzBox-Trigger + Sammel-Scan), Logs (Scan-Historie),
|
||
Einstellungen (Live-Systeminfo + Theme), Plugins (ehrlicher Hinweis auf
|
||
zukünftigen Commit)
|
||
- ✅ PWA: installierbar (Manifest + Icons für Android/iOS/Desktop), Service
|
||
Worker mit App-Shell-Precaching, `/api/*` läuft offline über den letzten
|
||
Cache-Stand (NetworkFirst, 3s-Timeout)
|
||
- ✅ Plugin-System: echte, ladbare Plugins unter `apps/backend/plugins/*`
|
||
(Bind-Mount, kein Rebuild nötig). Plugins können die Softwareerkennung des
|
||
Scanners erweitern (inkl. eigenem Icon) und eigene Geräte-Importquellen
|
||
bereitstellen. Zwei funktionierende Beispiel-Plugins liegen bei.
|
||
|
||
Noch **nicht** enthalten:
|
||
|
||
- shadcn/ui, React Hook Form (aktuell einfache kontrollierte Formulare)
|
||
- Eigene Admin-Routen/Menüpunkte pro Plugin (aktuell gesammelt auf einer Seite)
|
||
|
||
### Umgebungsvariablen (.env)
|
||
|
||
Alle Umgebungsvariablen sind in `.env.example` dokumentiert. Für die lokale
|
||
Entwicklung und für Docker Compose:
|
||
|
||
```bash
|
||
cp .env.example .env
|
||
# .env anpassen (v. a. FRITZBOX_* für den FritzBox-Scan)
|
||
```
|
||
|
||
`.env` ist in `.gitignore` und wird nie committet. **Auf dem Server
|
||
(`/opt/LaunchPad`) muss sie nach dem ersten `git pull` einmalig manuell
|
||
angelegt werden**, da git-ignorierte Dateien nicht mitgepullt werden:
|
||
|
||
```bash
|
||
cd /opt/LaunchPad
|
||
cp .env.example .env
|
||
nano .env # FRITZBOX_* eintragen
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Fehlt `.env` komplett, startet alles trotzdem mit den in `docker-compose.yml`
|
||
hinterlegten Defaults – der FritzBox-Scan liefert dann kontrolliert `HTTP 400`
|
||
statt abzustürzen. Für `pnpm dev:backend` (ohne Docker) wird `.env` automatisch
|
||
über `dotenv` geladen (`apps/backend/src/env.ts`, wird als allererstes importiert).
|
||
|
||
Siehe [`docs/ROADMAP.md`](./docs/ROADMAP.md) für die geplante Reihenfolge.
|
||
|
||
## API-Endpunkte (Stand Commit 2)
|
||
|
||
```
|
||
GET /api/health
|
||
GET /api/devices Liste aller Geräte inkl. ihrer Dienste
|
||
GET /api/devices/:id
|
||
POST /api/devices
|
||
PATCH /api/devices/:id
|
||
DELETE /api/devices/:id (löscht zugehörige Dienste per Cascade)
|
||
|
||
GET /api/services optional ?deviceId=&category=&favorite=true
|
||
GET /api/services/:id
|
||
POST /api/services erfordert existierende deviceId
|
||
PATCH /api/services/reorder Body: [{ id, order }, ...]
|
||
PATCH /api/services/:id
|
||
DELETE /api/services/:id
|
||
|
||
GET /api/bookmarks
|
||
POST /api/bookmarks Titel/Favicon/Beschreibung werden automatisch
|
||
geladen, falls nicht angegeben
|
||
PATCH /api/bookmarks/reorder Body: [{ id, order }, ...]
|
||
PATCH /api/bookmarks/:id
|
||
DELETE /api/bookmarks/:id
|
||
|
||
GET /api/categories
|
||
POST /api/categories optional color (Hex, z. B. "#3b82f6")
|
||
PATCH /api/categories/reorder Body: [{ id, order }, ...]
|
||
PATCH /api/categories/:id Umbenennen (aktualisiert automatisch alle Dienste mit altem Namen)
|
||
DELETE /api/categories/:id Dienste behalten ihre category nicht mehr (null),
|
||
werden aber nicht gelöscht
|
||
|
||
POST /api/scan/devices/:id Netzwerk-Scan für ein Gerät; Antwort enthält
|
||
zusätzlich staleServices (nicht mehr gefundene
|
||
Dienste), nameChanges (abweichender Name/
|
||
Kategorie ggü. gespeichertem Wert) und
|
||
deviceNameSuggestion (per Reverse-DNS) -
|
||
nichts davon wird automatisch übernommen,
|
||
nur zur Bestätigung im Adminbereich zurückgegeben
|
||
POST /api/scan/fritzbox Liest Geräteliste der FritzBox per TR-064;
|
||
Antwort enthält found (rohe Einträge) und
|
||
uniqueDevices (nach MAC/IP zusammengeführt,
|
||
kann kleiner als found sein - kein Datenverlust),
|
||
zusätzlich staleDevices und deviceChanges
|
||
(abweichender Name/IP ggü. gespeichertem Wert,
|
||
nichts davon wird automatisch übernommen)
|
||
|
||
GET /api/logs optional ?limit= (Default 100, Max 500)
|
||
|
||
GET /api/transfer/export ?format=csv|xlsx|json (Default csv). Geräte
|
||
UND Dienste in einer Datei, Spalte "type"
|
||
unterscheidet die Zeilen.
|
||
POST /api/transfer/import Body: { format, content: base64 }. Legt nur
|
||
neue Geräte/Dienste an, überschreibt/
|
||
dupliziert nie Bestehendes.
|
||
|
||
GET /api/recent-visits optional ?limit=
|
||
POST /api/recent-visits Body: { itemType: "service"|"bookmark", itemId }
|
||
DELETE /api/recent-visits
|
||
|
||
GET /api/settings
|
||
PATCH /api/settings Body: { recentVisitsLimit }
|
||
|
||
GET /api/read-later
|
||
POST /api/read-later Titel/Favicon werden automatisch geladen
|
||
PATCH /api/read-later/:id
|
||
DELETE /api/read-later/:id
|
||
POST /api/read-later/:id/promote verschiebt zu Lesezeichen (als Favorit)
|
||
|
||
POST /api/reset Löscht ALLE Geräte + Dienste (Cascade). Erfordert
|
||
Body { "confirm": true }, sonst 400.
|
||
|
||
GET /api/plugins geladene Plugins mit Capabilities
|
||
POST /api/plugins/:name/import löst importDevices() eines Plugins aus
|
||
```
|
||
|
||
## Frontend-Routen
|
||
|
||
```
|
||
/ Startseite: minimalistische Suche
|
||
/admin -> redirect zu /admin/dashboard
|
||
/admin/dashboard
|
||
/admin/devices
|
||
/admin/services
|
||
/admin/bookmarks
|
||
/admin/categories
|
||
/admin/scanner
|
||
/admin/plugins Hinweis: Plugin-System noch nicht gebaut
|
||
/admin/settings
|
||
/admin/logs
|
||
```
|
||
|
||
## Deployment auf dem Server (xlc-launchpad)
|
||
|
||
```bash
|
||
cd /opt/LaunchPad
|
||
git pull
|
||
docker compose up -d --build
|
||
```
|
||
|
||
## Branching
|
||
|
||
Entwicklung erfolgt ausschließlich auf `dev`. `main` bleibt der stabile Branch.
|