Files
LaunchPad/README.md

365 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```
## Als Browser-Suchmaschine einrichten
LaunchPad lässt sich als eigene Suchmaschine mit Kürzel in Chrome, Edge und
Firefox hinterlegen danach reicht z. B. `lp proxmox` in der Adresszeile,
um direkt beim passenden Dienst zu landen (ohne LaunchPad überhaupt erst zu
öffnen). Das ist ein **einmaliger, manueller Schritt pro Browser/Gerät**
kein Browser lässt eine Website das automatisch für sich einrichten,
aus gutem Grund (Sicherheit).
**Chrome / Edge:**
1. Einstellungen → „Suchmaschine" → „Suchmaschinen verwalten"
2. „Website-Suchmaschine hinzufügen"
3. Suchmaschine: `LaunchPad`, Kürzel: `lp`, URL: `https://<deine-domain-oder-ip>:8443/search?q=%s`
**Firefox:**
1. Einmal `https://<deine-domain-oder-ip>:8443/` öffnen (Firefox erkennt den
hinterlegten OpenSearch-Eintrag automatisch)
2. Einstellungen → „Suche" → bei „LaunchPad" ein Schlagwort (z. B. `lp`)
eintragen
Danach: `lp <suchbegriff>` in die Adresszeile, Enter landet bei eindeutigem
Treffer direkt beim Dienst, sonst auf der LaunchPad-Startseite mit bereits
eingetragenem Suchbegriff.
**Android:** Firefox für Android unterstützt Schlagwort-Suchen (auch keine
synchronisierten Desktop-Kürzel) grundsätzlich nicht eine bekannte
Firefox-Einschränkung, kein LaunchPad-Problem. Auf Android funktioniert die
Chrome-Anleitung oben unverändert. Alternative für jeden Browser: die
LaunchPad-Seite über „Zum Startbildschirm hinzufügen" als App-Symbol
ablegen (LaunchPad ist als PWA installierbar) ein Fingertipp genügt dann
bis zum Suchfeld.
## Deployment auf unterschiedlichen Geräten
Läuft überall, wo Docker Compose verfügbar ist. Ein paar konkrete Wege:
### Generischer Linux-Server / LXC-Container (Proxmox, etc.)
```bash
git clone <repo-url> LaunchPad
cd LaunchPad
cp .env.example .env
docker compose up -d --build
```
### Synology NAS mit Container Manager (Portainer-ähnliche Oberfläche, DSM 7.2+)
1. Repo per `git clone` auf die NAS holen (z. B. per SSH, oder mit File
Station hochladen) Zielordner z. B. `/volume1/docker/LaunchPad`.
2. In Container Manager → „Projekt" → „Erstellen" → als Pfad den geklonten
Ordner wählen. Container Manager erkennt die `docker-compose.yml`
automatisch und bietet an, sie als Projekt zu importieren.
3. `.env` vorher (per File Station oder SSH) aus `.env.example` anlegen und
anpassen Container Manager selbst bietet dafür keine Oberfläche.
4. Projekt starten. Ports `8080`/`8443` müssen frei sein (in DSM ggf. mit
bereits belegten NAS-eigenen Ports abgleichen, notfalls in
`docker-compose.yml` die linke Seite der Port-Zuordnung ändern, z. B.
`18080:80`).
### Synology NAS mit Portainer (falls separat installiert)
1. Repo wie oben auf die NAS holen.
2. Portainer → „Stacks" → „Add stack" → „Repository" (Git-URL direkt
eintragen) oder „Upload" (`docker-compose.yml` hochladen).
3. Unter „Environment variables" die Inhalte aus `.env.example` eintragen
(Portainer braucht dafür keine separate `.env`-Datei, nimmt die Stack-Vars).
4. „Deploy the stack".
### DockHand / andere Docker-Compose-Oberflächen
Jede Oberfläche, die eine vorhandene `docker-compose.yml` importieren oder
auf einen Git-Ordner zeigen kann, funktioniert grundsätzlich gleich: Repo
bereitstellen (lokal geklont oder per Git-Integration der jeweiligen
Oberfläche), `.env` aus `.env.example` befüllen, Compose-Datei
starten/deployen. Es gibt keine LaunchPad-spezifischen Besonderheiten
außer den Standard-Docker-Compose-Grundlagen (Ports, Volumes, `.env`).
## 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.