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)

git clone <repo-url> LaunchPad
cd LaunchPad
cp .env.example .env   # optional anpassen, siehe unten
docker compose up -d --build

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

# .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.

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:

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:

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 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/categories
POST   /api/categories
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 (DNS, Ports, Titel,
                                   Favicon, Softwareerkennung); legt/aktualisiert Dienste
POST   /api/scan/fritzbox         Liest Geräteliste der FritzBox per TR-064
                                   (erfordert FRITZBOX_HOST/USERNAME/PASSWORD)

GET    /api/logs                  optional ?limit= (Default 100, Max 500)

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/categories
/admin/scanner
/admin/plugins                    Hinweis: Plugin-System noch nicht gebaut
/admin/settings
/admin/logs

Deployment auf dem Server (xlc-launchpad)

cd /opt/LaunchPad
git pull
docker compose up -d --build

Branching

Entwicklung erfolgt ausschließlich auf dev. main bleibt der stabile Branch.

Description
No description provided
Readme 3.7 MiB