Im Homelab sammeln sich Weboberflächen an: Proxmox, Monitoring, Home Assistant, Router, Drucker, Steckdosen, Mediaserver. Ich selbst habe die Adressen im Kopf oder in der Doku. Alle anderen im Haus nicht. Die Frage „Wie komme ich noch mal auf den Drucker?“ war der Anstoß für ein kleines Intranet. Es ist die Einstiegsseite für alle im Heimnetz: Dienste öffnen, Geräte einrichten, kurz die Verbindung messen, eine Datei an jemand anderen im Haus weitergeben oder eine Störung melden. Hier steht, was die Seite kann, warum sie bewusst ohne Anmeldung auskommt und wie ich sie baue und veröffentliche.

Was hineingehört und was nicht
Bevor ich eine Zeile geschrieben habe, habe ich festgelegt, was das Intranet nicht sein soll. Es ist kein zweites Monitoring und keine zweite Dokumentation. Kennzahlen, Backup-Status und die Proxmox-Übersicht bleiben dort, wo sie schon sind. Das Intranet ist der Einstieg zum Loslegen.
| Gehört ins Intranet | Bleibt woanders |
|---|---|
| Kacheln für alle Dienste, Suche, Statuspunkt | Kennzahlen und Backup-Status (Monitoring) |
| Geräte einrichten: CA-Zertifikat, Netzwerk-Konfiguration | Checklisten zur Einrichtung (Doku) |
| Werkzeuge: Erreichbarkeit, Speedtest, Übergabefach | Update-Übersicht (Doku) |
| Störung melden (landet in GitLab) | Fristenkalender (Doku) |
Jedes Mal, wenn mir eine neue Idee kam, etwa ein Aufweck-Knopf für Rechner oder eine Druckerkarte, war die Frage schnell beantwortet: eigene Aufgabe, später, wenn überhaupt.
Die Startseite: Kacheln mit Statuspunkt
Die Startseite zeigt heute 45 Kacheln in sechs Gruppen: Infrastruktur, Entwicklung und KI, Smart Home, Geräte, Medien und Dateien, Netzwerk. Jede Kachel hat einen Statuspunkt. Er sagt nur, ob der Dienst antwortet. Für alles andere gibt es das Monitoring.
Der Statuspunkt kommt nicht aus dem Browser. Auf dem Webserver läuft ein kleiner Dienst per systemd-Timer jede Minute, prüft jede Kachel per TCP und schreibt das Ergebnis in eine status.json. Der Browser liest nur diese eine Datei. So erzeugen zehn offene Tabs keine zehnfache Last, und der Browser braucht keinen Zugriff auf die Zielsysteme. Es gibt drei Zustände: antwortet, langsam (über 1,5 Sekunden oder nur ein Teil der Adressen erreichbar) und keine Antwort.
Dazu kommen eine Suche (mit / oder Strg+K, Enter öffnet den ersten Treffer), eine Liste „Zuletzt benutzt“, Hell und Dunkel nach Vorgabe des Geräts und eine Ansicht, die am Handy funktioniert. Die Suche findet nicht nur Kacheln, sondern auch die Seiten des Intranets und rund hundert Seiten meiner Dokumentation, mit Textauszug.
Offen im LAN, deshalb ohne Geheimnisse
Die wichtigste Entscheidung: Das Intranet ist im Heimnetz offen, ohne Anmeldung. Niemand im Haus soll sich ein weiteres Kennwort merken müssen, um herauszufinden, wie der Drucker heißt. Die Konsequenz ist genauso klar: Auf der Seite stehen nie Kennwörter, Schlüssel oder Tokens. Jeder im Heimnetz kann alles sehen, also darf dort nichts stehen, was nicht jeder sehen darf.
Trotzdem gibt es je Kachel einen „Spickzettel“. Er nennt den Anmeldeweg und den Ort, an dem das Kennwort liegt, aber nie das Kennwort selbst. Der Spickzettel ist noch lückenhaft: Eingetragen habe ich bisher nur, was belegt ist. Bei den übrigen Kacheln steht „noch nicht erfasst“, und das Nachtragen steht auf meiner Liste.
Eine Ausnahme gibt es beim Zertifikat. Die Seite läuft per HTTPS mit einem Zertifikat meiner eigenen CA, und HTTP leitet auf HTTPS um. Ein neues Gerät vertraut der CA aber noch nicht und sieht nur eine Warnung. Deshalb ist der Pfad /ca/ auch per HTTP erreichbar. Dort liegen CA-Zertifikat, Installer und eine Hilfeseite, die per Anfrage an die HTTPS-Adresse prüft, ob das Gerät der CA schon vertraut.
Werkzeuge und Störungsmeldung
Für die Werkzeuge braucht die Seite ein kleines Backend. Es ist ein Python-Dienst, der nur auf 127.0.0.1 lauscht und hinter dem Webserver unter /api/ erreichbar ist, mit eigenem Benutzer und eigener systemd-Unit.

Die Erreichbarkeitsprüfung funktioniert so: Name eingeben, das Backend löst ihn auf, pingt und prüft bis zu fünf TCP-Ports. Erlaubt sind nur Ziele in privaten Adressbereichen, nie Loopback und nie das Internet. Pro Gerät gehen 20 Prüfungen je Minute.
Für den LAN-Speedtest lädt der Browser eine 128 MB große Datei mit Zufallsdaten herunter, damit keine Kompression das Ergebnis schönt. Der Upload geht an das Backend, das die Daten verwirft. Gemessen wird mit drei parallelen Verbindungen, sechs Sekunden je Richtung, dazu der Ping als Median aus zwölf Anfragen. Die letzten sechs Messungen merkt sich der Browser, praktisch für einen Vorher-nachher-Vergleich nach einem Umbau. Gemessen wird bis zum Server im Haus, nicht ins Internet.
Das Übergabefach nimmt Text oder eine Datei bis 25 MB und löscht sie nach 24 Stunden automatisch. Insgesamt passen höchstens 60 Einträge mit 600 MB hinein. So kommt ein Link schnell vom Handy auf den Rechner. Weil jeder im Haus das Fach sieht, steht direkt darüber der Hinweis „keine Kennwörter“.
Das Übergabefach ist der Teil, bei dem ich am vorsichtigsten war. Downloads gehen immer als Anhang hinaus, mit nosniff und einer eigenen, sehr strengen Content-Security-Policy:
Content-Security-Policy: default-src 'none'; sandbox
X-Content-Type-Options: nosniff
Content-Disposition: attachment
Damit kann eine hochgeladene HTML-Datei im Browser nichts ausführen, sondern wird nur heruntergeladen.
Dazu kommt die Störungsmeldung. Wer im Haus merkt, dass etwas nicht geht, soll es sagen können, ohne mir hinterherzulaufen und ohne GitLab-Konto. Das Formular „Melden“ legt ein Issue in einem eigenen, privaten GitLab-Projekt an. Dafür nutzt das Backend ein Projekt-Zugriffstoken, das nur für dieses eine Projekt gilt, mit der Rolle Reporter. Es liegt in einer Datei auf dem Server, die nur root und der Dienst lesen dürfen, nie im Git. Das Token läuft nach einem Jahr ab, und das Datum steht in meiner Fristenliste.
Gegen Missbrauch und Versehen helfen einfache Grenzen: fünf Meldungen je Stunde und Gerät, 40 am Tag insgesamt, eine Längenprüfung und ein Honigtopf-Feld gegen Formularroboter. Die Meldung enthält Art, einen freiwilligen Kontakt, Adresse und Name des Geräts und die Uhrzeit.
Eine Falle steckt im Text selbst: Eine Zeile wie /close im Text eines Issues führt GitLab als Befehl aus. Ein Formular, das fremden Text ungeprüft in ein Issue schreibt, kann damit Issues schließen oder zuweisen. Ich setze den Text deshalb als Zitat mit > davor und habe mit einem Test-Issue geprüft, dass der Befehl dann wirkungslos ist.
Bauen, veröffentlichen, überwachen
Die Seite ist statisch: HTML, CSS und etwas JavaScript, ohne Framework, ohne Build-Werkzeug und ohne Fremdserver. Schrift und Icons liegen lokal. Farben und Hell/Dunkel teilt sie sich mit meiner Dokumentation, die Wahl gilt für beide Seiten.
Die Kacheln pflege ich nicht im Intranet. Sie stammen aus meinem Asset-Register, einer YAML-Datei, aus der auch Doku-Seiten und IP-Tabellen entstehen. Daraus erzeugt ein Skript die Kachelliste. Ein zweites Skript prüft die Liste, baut die Seite in ein Verzeichnis und überträgt sie per tar über SSH auf den Webserver. Der Ordner mit den CA-Dateien bleibt dabei unberührt.
# neue Kachel: Eintrag im Asset-Register, dann der tägliche Lauf
# erzeugt die Kachelliste aus dem Register
# prüft und baut die Seite
# veröffentlicht per tar über ssh auf den Webserver
Eine neue Kachel ist also ein Eintrag im Register. Die erzeugte Kachelliste ändere ich nie von Hand, sonst ist die Änderung beim nächsten Lauf weg. Der Quelltext liegt im selben Repository wie die Doku und läuft durch denselben Ablauf. Ein eigenes Repository habe ich bewusst nicht angelegt, solange nur ich daran arbeite.
Ein Local-Check im Monitoring liefert fünf Services: Alter der status.json (Warnung ab drei, kritisch ab zehn Minuten), Antwort der API, die Webseite samt Zertifikatskette, die Meldung für die Netzlaufwerke und den Füllstand des Übergabefachs. Die VM mit dem Webserver steht im nächtlichen Backup-Job. Nach einer Wiederherstellung müssen nur der API-Dienst und der Status-Timer wieder laufen. Die 128-MB-Datei für den Speedtest ist nicht im Git, ich kann sie jederzeit neu erzeugen.
Noch im Aufbau ist eine Seite für Netzlaufwerke: Ein Windows-Server meldet täglich, welche Freigaben welche Person lesen darf, und das Intranet erzeugt daraus je Person eine fertige .bat. Die Meldung enthält nur Fakten und keine Befehle, die Datei entsteht aus einer festen Vorlage. Dabei hing die Meldung per HTTPS-POST zunächst, bis ich das Backend auf HTTP/1.1 umgestellt hatte: PowerShell schickt den Header Expect: 100-continue und wartet auf eine Antwort, die ein einfacher HTTP/1.0-Server nie sendet.
Backend, Grenzen und Fehlerfälle habe ich bisher nur mit curl getestet. Offen sind die Darstellung im Browser, Drag-and-Drop im Übergabefach, die Handyansicht und der Probelauf der Netzlaufwerk-Seite an einem echten PC.
Schreibe einen Kommentar