Selbst bauen · Schritt 5

Server installieren

Voraussetzung ist Docker mit Compose. Zum Ausprobieren reicht ein Rechner, für den Betrieb ein kleiner Server im Heimnetz.

Auf dieser Seite

Zum Ausprobieren

Die Entwicklungsumgebung bringt den Simulator und einen Mail-Fänger mit. Nichts verlässt den Rechner.

  1. Unter Windows dev.bat doppelklicken und „1 Starten“ wählen. Sonst:

    pnpm install
    pnpm dev:up
    
  2. Warten. Der erste Start dauert, weil beim Bauen die Firmware für Hub und Module mitgebaut wird. Spätere Builds nutzen den Zwischenspeicher.

  3. Die Oberfläche öffnen: http://localhost:8080, von einem anderen Gerät im Netz https://<rechner>:8443.

Auch in der Entwicklung geht nichts im Klartext über ein Netz. Ohne TLS gibt es nur Ports auf 127.0.0.1. Was vom Handy oder einem zweiten Rechner aus erreichbar ist, spricht HTTPS mit dem Zertifikat der eigenen CA. Damit der Browser nicht warnt, das CA-Zertifikat einmal importieren (System → Status → „CA-Zertifikat herunterladen“).

dev.bat hat ein Menü: starten, stoppen, neu starten, aktualisieren, Status, Protokoll, Sicherung anlegen und zurückspielen, alles zurücksetzen, Prüfungen. Jeder Punkt geht auch direkt, zum Beispiel dev.bat logs.

Im Homelab

Zwei Wege führen zum laufenden Server. Option A ist die einfache Installation auf einem Rechner, auf dem Docker schon läuft. Option B ist die vollständige Anleitung für einen eigenen Server, etwa einen LXC-Container unter Debian 13: SmartBeet läuft unter einem eigenen Benutzer deploy ohne root-Rechte, und auf Wunsch spielt ein GitHub-Runner jeden Push auf main von selbst ein.

Option A: einfache Installation

  1. Repository auf den Server kopieren.

  2. .env.example nach .env kopieren und anpassen.

  3. Starten, ohne die Ergänzung für die Entwicklung:

    docker compose up --build -d
    
  4. Die Oberfläche öffnen. Der Einrichtungsassistent erscheint von selbst.

Fehlt Docker, steht die Installation unter Option B im ersten Schritt.

Option B: Debian 13 mit Benutzer deploy

Das System richtest du als root ein (Pakete, Docker). Alles, was SmartBeet selbst betrifft (klonen, einstellen, bauen, starten), läuft unter dem Benutzer deploy, der später auch das automatische Aktualisieren ausführt. Die Schritte sind mit [root] und [deploy] markiert. Wechsel mit su - deploy, zurück mit exit. deploy hat kein Passwort, keinen SSH-Zugang und ruft nie sudo auf.

In einem LXC-Container (etwa unter Proxmox) braucht Docker die Optionen nesting=1 und keyctl=1. Richtwert für die Größe: 2 Kerne, 4 GB RAM (oder 2 GB mit Swap), 40 GB Platte. Das erste Bauen ist der anspruchsvollste Teil.

  1. Voraussetzungen — [root]. System aktualisieren und Docker aus dem offiziellen Repository installieren:

    apt update && apt upgrade -y
    apt install -y git curl ca-certificates gnupg
    curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
    echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
    apt update
    apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
    
  2. Benutzer deploy anlegen — [root]. Mit Docker-Rechten und dem Verzeichnis unter /opt:

    useradd -m -s /bin/bash deploy
    usermod -aG docker deploy
    mkdir -p /opt/smartbeet
    chown deploy:deploy /opt/smartbeet
    

    Wer in der Gruppe docker ist, kann den Docker-Dienst steuern und damit faktisch alles auf dem Server. Deshalb hat deploy kein Passwort und keinen SSH-Zugang.

  3. Repository klonen — [deploy]. Solange das Repository privat ist, fragt git clone nach dem GitHub-Benutzernamen und einem Personal Access Token als Passwort (fine-grained mit „Contents: Read“).

    su - deploy
    git clone https://github.com/smartbeet/smartbeet.git /opt/smartbeet
    cd /opt/smartbeet
    

    Der Server braucht nur Docker, kein Node und kein pnpm: Jedes Image baut seine Abhängigkeiten selbst, auch die Firmware.

  4. Einstellungen — [deploy].

    cp .env.example .env
    nano .env
    chmod 600 .env
    

    Mindestens APP_URL und NTFY_PUBLIC_URL anpassen, SERVER_HOST nur bei Hubs an einem anderen Ort, siehe „Die drei Adressen“ unten. Passwörter, Schlüssel, die eigene CA und das Serverzertifikat stehen nicht in der .env, sie entstehen beim ersten Start von selbst.

  5. Erster Start — [deploy]. Der erste Lauf dauert, weil die Firmware mitgebaut wird.

    docker compose up -d --build
    docker compose ps
    docker compose logs -f backend
    

    Danach die Oberfläche unter APP_URL öffnen. Der Einrichtungsassistent legt Admin-Konto, Standort und Mailserver an.

HTTPS und Reverse-Proxy

SmartBeet ist immer verschlüsselt erreichbar, auch ohne eigenen Proxy. Der Container frontend ist zugleich der TLS-Endpunkt.

Port (Standard) Was Für wen
8443 HTTPS: Oberfläche, API, Live, Firmware für die Hubs, CA Browser, Hubs, Proxy auf einem anderen Rechner
8883 MQTT über TLS Hubs
8091 Push (ntfy) über HTTPS ntfy-App, Proxy auf einem anderen Rechner
127.0.0.1:8080, 127.0.0.1:8090 Oberfläche und Push ohne TLS nur ein Proxy auf demselben Rechner

Das Zertifikat stammt von der eigenen CA der Installation. Der Dienst certs legt sie beim ersten Start an und erneuert das Serverzertifikat 30 Tage vor dem Ablauf selbst. Browser warnen, bis das CA-Zertifikat importiert ist (System → Status → „CA-Zertifikat herunterladen“). Hubs brauchen das nicht: Sie erkennen die CA am Server-Schlüssel im Einrichtungscode.

Die Hubs gehen nie über einen eigenen Proxy, sondern immer direkt an Port 8443 und 8883 des Stacks. Der Proxy ist nur dafür da, dass Menschen die Oberfläche von außen erreichen.

Mit eigenem Proxy. Er nimmt HTTPS entgegen und leitet an SmartBeet weiter.

Deine Adresse (Beispiel) Proxy auf anderem Rechner Proxy auf demselben Rechner Wofür
https://beet.example.org https://<server>:8443 http://127.0.0.1:8080 Oberfläche, API, Live
https://push.beet.example.org https://<server>:8091 http://127.0.0.1:8090 Push-Nachrichten (ntfy)

Dabei beachten:

  • Auch zwischen Proxy und Stack liegt ein Netz. Steht der Proxy auf einem anderen Rechner, leitet er an die HTTPS-Ports weiter und prüft das Zertifikat mit dem CA-Zertifikat des Stacks. Bei nginx:

    proxy_pass https://192.168.1.10:8443;
    proxy_ssl_trusted_certificate /etc/nginx/smartbeet-ca.crt;
    proxy_ssl_verify on;
    proxy_ssl_name smartbeet-server;
    

    http:// zum Stack nur, wenn Proxy und SmartBeet auf demselben Rechner laufen.

  • WebSocket durchreichen (Upgrade, Connection) und lange Verbindungen erlauben, sonst aktualisiert sich die Übersicht nicht live. Für Push ebenso.

  • Uploads bis 32 MB erlauben (Firmware), bei nginx client_max_body_size 32m.

  • Die Kopfzeilen Host, X-Forwarded-For und X-Forwarded-Proto setzen.

  • In der .env die Adressen so eintragen, wie man sie von außen sieht, und SmartBeet neu starten (./deploy.sh → „Neu starten“):

    APP_URL=https://beet.example.org
    NTFY_PUBLIC_URL=https://push.beet.example.org
    

    Mit https:// in APP_URL markiert SmartBeet seine Cookies als „secure“.

Ohne eigenen Proxy, mit eigener Domain. Der mitgelieferte HTTPS-Vorbau, Caddy mit Let’s Encrypt. In der .env:

COMPOSE_PROFILES=caddy
PUBLIC_DOMAIN=beet.example.org
PUSH_DOMAIN=push.beet.example.org
APP_URL=https://beet.example.org
NTFY_PUBLIC_URL=https://push.beet.example.org

Port 80 und 443 müssen aus dem Internet auf den Server zeigen. Caddy holt und erneuert das Zertifikat selbst. Danach docker compose up -d --build.

Ohne Proxy und ohne Domain. https://<server>:8443 öffnen (eigene CA, siehe oben) und APP_URL so eintragen.

Bedienen und aktualisieren

deploy.sh im Verzeichnis von SmartBeet hat ein Menü für alles, was auf dem Server anfällt:

cd /opt/smartbeet
./deploy.sh
Menü Direkt Was passiert
1 Aktualisieren ./deploy.sh update git pull, Images bauen, starten, warten bis es antwortet
2 Neu bauen ./deploy.sh rebuild dasselbe ohne git pull
3 Status ./deploy.sh status Dienste, Stand, Antwort des Backends
4 Protokoll ./deploy.sh logs läuft mit; ein Dienst: ./deploy.sh logs backend
5 bis 7 restart stop start neu starten, stoppen, starten; die Daten bleiben erhalten
8 Sicherung anlegen ./deploy.sh backup legt backups/smartbeet-<Zeit>.tar.gz an
9 Sicherung zurückspielen ./deploy.sh restore Auswahl aus backups/, fragt nach
X Alles zurücksetzen ./deploy.sh reset löscht alle Daten; zwei Rückfragen und das Wort RESET

Gebaut wird vor dem Neustart: Schlägt das Bauen fehl, läuft die bisherige Anlage weiter.

Automatisch aktualisieren

Push auf main, und der Server aktualisiert sich selbst. Dafür läuft auf dem Server ein selbst gehosteter GitHub-Actions-Runner unter dem Benutzer deploy. Bei jedem Push führt er git reset --hard origin/main und ./deploy.sh rebuild aus. Der Ablauf liegt im Repository als .github/workflows/deploy.yml und startet nur bei einem Push auf main, nie bei Pull Requests: Fremder Code läuft so nie auf deinem Server.

  1. Verzeichnis für den Runner — [root].

    mkdir -p /opt/actions-runner
    chown deploy:deploy /opt/actions-runner
    
  2. Runner einrichten. Token und Version stehen unter Repository → Settings → Actions → Runners → „New self-hosted runner“. Der Runner gehört zu diesem Repository, nicht zur Organisation.

    # [deploy] herunterladen
    cd /opt/actions-runner
    RUNNER_VER=2.323.0                        # die Version von der Runner-Seite
    curl -o runner.tar.gz -L https://github.com/actions/runner/releases/download/v${RUNNER_VER}/actions-runner-linux-x64-${RUNNER_VER}.tar.gz
    tar xzf runner.tar.gz
    
    # [root] Abhängigkeiten, einmal
    cd /opt/actions-runner
    ./bin/installdependencies.sh
    apt-get install -y libicu76                # Debian 13: ICU nachinstallieren
    
    # [deploy] anmelden
    cd /opt/actions-runner
    ./config.sh --url https://github.com/smartbeet/smartbeet \
      --token <TOKEN> --name "$(hostname)" --labels deploy --unattended
    
    # [root] als Dienst einrichten, läuft als deploy
    cd /opt/actions-runner
    ./svc.sh install deploy
    ./svc.sh start
    ./svc.sh status                            # active (running)
    

    RUNNER_ALLOW_RUNASROOT ist nicht nötig, der Runner läuft nicht als root.

  3. Einschalten. Der Ablauf ist aus, bis du ihn einschaltest. Dafür setzt du im Repository die Variable DEPLOY_ENABLED auf true (Settings → Secrets and variables → Actions → Variables). Sonst würde jeder Push auf main einen Lauf starten, der ohne Runner ewig wartet; bei Open Source träfe das auch jeden Fork. Liegt SmartBeet nicht in /opt/smartbeet, zusätzlich DEPLOY_DIR mit dem Pfad.

  4. Testen. dev nach main mergen, unter Repository → Actions erscheint der Lauf „Deploy“. Auf dem Server: ./deploy.sh status.

Alle Daten liegen in Docker-Volumes und überstehen jedes Aktualisieren. Die .env steht in .gitignore, git reset --hard lässt sie in Ruhe. Dateien aus dem Repository auf dem Server nicht von Hand ändern, sie werden beim nächsten Lauf überschrieben. Ein Push kann keine Daten löschen; Zurücksetzen geht nur von Hand mit ./deploy.sh reset.

Die drei Adressen

Einstellung Bedeutung Beispiel
APP_URL Adresse, unter der du die Oberfläche öffnest. Sie steht in allen Mails. https://server.lan:8443
NTFY_PUBLIC_URL Adresse des Push-Servers, wie sie die ntfy-App auf dem Handy sieht https://server.lan:8091
SERVER_HOST Nur für Hubs an einem anderen Ort: Name oder IP, unter der sie den Server erreichen. Steht im Zertifikat und beim Einrichtungscode. beet.example.org

Die Einträge unter „Nur Entwicklung“ braucht der Betrieb nicht. INTERNAL_URL und MQTT_PORT sind entfallen: Hubs laden ihre Firmware über HTTPS, und einen Broker ohne TLS gibt es nach außen nicht mehr. Stehen sie noch in einer älteren .env, nennt das Backend sie beim Start und ignoriert sie.

Geheimnisse

Passwörter für Datenbank und Broker und der Sitzungsschlüssel entstehen beim ersten Start von selbst und liegen in einem eigenen Volume, die eigene CA und das Serverzertifikat in einem zweiten. Es gibt keine Standardpasswörter.

Einrichtungsassistent

  1. Admin-Konto anlegen. Der erste Benutzer ist Hauptadmin.
  2. Standort eintragen. Hier lässt sich auch das Wetter abschalten.
  3. Mailserver eintragen und testen: der eigene oder der des Anbieters, nur mit STARTTLS oder TLS. In der Entwicklungsumgebung ist der Mail-Fänger voreingestellt.

Offene Ports

Port Dienst Hinweis
8443 HTTPS Oberfläche, API, Firmware, CA
8883 MQTT über TLS für die Hubs
8091 Push über HTTPS (ntfy) für die App auf dem Handy

Mit Caddy kommen 80 und 443 dazu. Oberfläche (8080) und ntfy (8090) ohne TLS hören nur auf 127.0.0.1. Datenbank, Broker (1883) und Backend sind nur innerhalb von Docker erreichbar.

Jeder Hub hat eigene Zugangsdaten und darf nur seine eigenen Topics lesen und schreiben. Sonst geht nichts im Klartext über ein Netz, auch nicht im Heimnetz. Die Ausnahmen nennt die README des Projekts: Container untereinander im Docker-Netz, Ports auf 127.0.0.1, der CAN-Bus und die offenen Einrichtungs-WLANs von Hub und Anzeige (WLAN).

Server an einem anderen Ort. Im Router dort Port 8443 und 8883 (TCP) auf den Server weiterleiten, SERVER_HOST auf den öffentlichen Namen oder die IP setzen und beim Hub diese Server-Adresse eintragen.

Damit der Hub den Server findet

Unter Linux kündigt ein kleiner Dienst den Server per mDNS im Heimnetz an, mit dem HTTPS-Port. Dafür braucht er Host-Netzwerk.

Unter Docker Desktop auf Windows und macOS erreicht mDNS das Heimnetz nicht. Der Hub sucht dann selbst das eigene Netz nach einem offenen Port 8443 oder 443 ab. Jeden Kandidaten prüft er über HTTPS: Er nimmt das CA-Zertifikat nur, wenn es zum Server-Schlüssel im Einrichtungscode passt. Das klappt überall, wo der Hub den Server erreicht, dauert aber bis etwa drei Minuten.

Liegt der Server in einem anderen Netz, wird seine Adresse im Einrichtungs-WLAN des Hubs eingetragen. Hat der Server mehrere Netze, legt ANNOUNCE_IP fest, welche Adresse angekündigt wird.

Ohne root

Kein Dienst läuft als root. Jeder Container hat einen festen, unprivilegierten Benutzer.

Wer von einem älteren Stand kommt, setzt einmal die Besitzer der Volumes, bei gestoppter Anlage:

docker compose stop
sh infra/scripts/fix-permissions.sh
docker compose up --build -d

Aktualisieren und sichern

./deploy.sh update           # oder: git pull && docker compose up --build -d
./deploy.sh backup           # legt backups/smartbeet-<Zeit>.tar.gz an

Die Datenbank migriert beim Start von selbst. Sicherung und Wiederherstellung stehen unter Updates und Sicherung.

    Durchsucht alle Seiten. Die Suche läuft im Browser, nichts wird übertragen.