ThetaDev
28e51df5b8
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
|
||
---|---|---|
.vscode | ||
_screenshots | ||
src | ||
ui | ||
.air.toml | ||
.drone.yml | ||
.editorconfig | ||
.gitignore | ||
.golangci.yaml | ||
.pre-commit-config.yaml | ||
.prettierrc.json | ||
.woodpecker.yml | ||
go.mod | ||
go.sum | ||
LICENSE | ||
Makefile | ||
README.rst | ||
sebrauc.example.toml | ||
woodpecker.yml |
SEBRAUC ist eine einfach zu bedienende Weboberfläche für den RAUC Firmware-Updater, die es erlaubt, Softwareupdates auf Embedded-Systemen wie dem Raspberry Pi zu installieren.
Ich habe die Anwendung für die TSGRain-Bewässerungssteuerung entwickelt, da es noch keine einfache Möglichkeit gab, RAUC-Updates über eine Weboberfläche zu installieren.
Der Updatevorgang funktioniert ähnlich wie bei älteren WLAN-Routern. Das *.raucb
-Firmwarepaket kann über den Browser an das Gerät übertragen und installiert werden. Im Gegensatz zu OTA-basierten Lösungen wie hawkBit wird keine Internetverbindung, sondern nur ein lokaler WLAN-Access-Point für die Datenübertragung benötigt. Das macht SEBRAUC ideal für fest installierte Geräte an abgelegenen Orten wie unser TSGRain-Bewässerungssystem.
Funktion
Startseite
Auf der Startseite findet der Nutzer einen großen Upload-Button, mit dem er ein *.raucb
-Image auswählen und hochladen kann.
Update hochladen
Wurde eine Update-Datei ausgewählt, wird sie auf den Server hochgeladen und zunächst temporär im Ordner /tmp/sebrauc
gespeichert. Dabei wird der Uploadfortschritt mit einer kreisförmigen Skala angezeigt.
Update installieren
Ist der Uplodad der Datei abgeschlossen, beginnt die Installation. SEBRAUC startet den RAUC-Updater mit dem entsprechenden Kommandozeilenbefehl.
Während des Updatevorgangs liefert RAUC folgende Konsolenausgabe:
installing
0% Installing
0% Determining slot states
20% Determining slot states done.
20% Checking bundle
20% Verifying signature
40% Verifying signature done.
40% Checking bundle done.
40% Checking manifest contents
60% Checking manifest contents done.
60% Determining target install group
80% Determining target install group done.
80% Updating slots
80% Checking slot rootfs.0
90% Checking slot rootfs.0 done.
90% Copying image to rootfs.0
100% Copying image to rootfs.0 done.
100% Updating slots done.
100% Installing done.
idle
Installing `/app/tsgrain-update-raspberrypi3.raucb` succeeded
Aus dieser Konsolenausgabe lässt sich der aktuelle Fortschritt in Prozent und der Status ermitteln. SEBRAUC leitet diese Daten in Echtzeit an die Weboberfläche weiter. So kann der Nutzer live den Updatevorgang mitverfolgen.
Wenn RAUC eine Fehlermeldung ausgibt, wird diese mit einem roten Warndreieck am unteren Bildschirmrand angezeigt.
Installation abgeschlossen
Ist die Installation abgeschlossen, kehrt SEBRAUC zum Startbildschirm zurück. Am unteren Rand erhält der Nutzer den Hinweis, dass das System neu gestartet werden muss, um die neue Software in Betrieb zu nehmen. Drückt man auf den Restart
-Button, startet das System mit einer Verzögerung von 3 Sekunden neu.
Systeminformationen
Mit der Schaltfläche oben rechts lässt sich ein Informationsbildschirm einblenden, der einige Statusinformationen über das System und den RAUC-Updater anzeigt. So lässt sich in Erfahrung bringen, welches Betriebssystem in welcher Version gerade ausgeführt wird und welche der 2 Root-Partitionen gerade aktiv ist.
Der RAUC-Updater hat eine Konfigurationsoption, mit der festgelegt werden kann, welche Firmware und Firmware-Variante akzeptiert wird. Diese Werte werden ebenfalls auf dem Systeminformationsbildschirm angezeigt, damit der Nutzer sehen kann, welche Firmware-Images mit seinem Gerät kompatibel sind.
Technische Details
SEBRAUC besteht aus zwei Komponenten: dem in Go geschriebenen Backend und einer mit Preact und Typescript umgesetzten Single-Page-Weboberfläche.
Das Backend steuert den RAUC-Updater und stellt die Web-API bereit, die vom Frontend angesprochen wird. Ich habe für das Backend das Gin-Webframework verwendet. Hierbei handelt es sich um ein minimales und performantes Framework in der Programmiersprache Go, vergleichbar mit Flask für Python oder Express für Javascript. Es erlaubt die einfache Erstellung von Routen und Handlern für Webanfragen, bietet aber im Gegensatz zu größeren Frameworks wie Django keine eingebaute Datenbankintegration oder Administrationsoberfläche.
Eine asynchrone Kommunikation vom Server zum Client mittels Websockets kann mit dem Modul github.com/gorilla/websocket
leicht in eine Gin-Anwendung integriert werden.
SEBRAUC verwendet eine solche Websocket-Verbindung, um den aktuellen Zustand des Systems an das Frontend zu streamen. Dadurch erhält der Nutzer immer die aktuellen Informationen, ohne dass die Seite neu aufgerufen werden oder die Daten durch das Frontend in bestimmten Zeitabständen aktualisiert werden müssen.
Beim Frontend kam das Javascript-Framework Preact zum Einsatz, was eine kleinere und schnellere Alternative zu React darstellt.
Da Preact in komprimierter Form nur 3kB groß ist, eignet es sich perfekt für kleine Webanwendungen, die schnell laden sollen. So ist die fertige SEBRAUC-Webanwendung inklusive Styles und Icons nur 22kB groß. Hätte ich hierfür React oder Vue verwendet, würde die Webseite mindestens doppelt so groß sein.
Der Autor Ryan Carniato hat in diesem Blogpost verschiedene Webframeworks, darunter React, Vue und Preact miteinander verglichen. Dafür hat er eine simple Todo-Liste mit allen Frameworks gebaut und die Größe des generierten Javascript-Codes verglichen. Laut diesem Vergleich ist die React-Anwendung mit 37.5kB fast siebenmal so groß wie die nur 5.6kB große Preact-Anwendung.
Um auch das Stylesheet der Anwendung kompakt zu halten, wurde auf fertige CSS-Komponentenbibliotheken wie Bootstrap verzichtet und stattdessen selbstgeschriebenes scss verwendet.
REST-API
Die Weboberfläche kommuniziert über REST-API-Anfragen mit dem Server.
Die API ist im OpenAPI 2.0-Format dokumentiert (die yaml-Dokumentation befindet sich unter src/server/swagger/swagger.yaml
).
OpenAPI ist ein Standard für maschinenlesbare API-Dokumentationen, aus denen mit geeigneten Tools menschenlesbare Dokumentationsseiten sowie fertige API-Clients in verschiedenen Programmiersprachen erzeugen lassen.
In diesem Projekt verwende ich ein Tool (go-swagger
), das Kommentare und Structs aus dem Programmcode einlesen und daraus die OpenAPI-Dokumentation generieren kann. Daraus wird dann der Code für den Typescript-Client für die Weboberfläche generiert.
Konfiguration
Die Konfiguration erfolgt über eine Datei im *.json
, *.yaml
oder *.toml
-Format. Eine Beispielkonfiguration findet sich hier.
Alternativ kann die Konfiguration auch mittels Umgebungsvariablen angepasst werden, wobei verschachtelte Datenstrukturen mit Unterstrichen addressiert werden. Beispiel:
SERVER_ADDRESS=80 SERVER_COMPRESSION_BROTLI=false ./sebrauc
SEBRAUC sucht zuerst nach einer Konfigurationsdatei mit dem Namen sebrauc.{json,yml,yaml,toml}
im aktuellen Arbeitsverzeichnis und anschließend im Ordner /etc/sebrauc
. Der Pfad zur Konfigurationsdatei kann auch manuell mit dem Flag -c
angegeben werden.
Option | Beschreibung | Standard |
---|---|---|
Tmpdir |
Temporäres Verzeichnis | "/tmp/sebrauc" |
|
IP-Adresse, von der der Server Verbindungen akzeptiert. Leer lassen, um Verbindungen von allen Adressen zu akzeptieren. |
|
Server.Port |
Server-Port | 80 |
|
Zeitabstand, in denen ein Ping vom Websocket-Server an den Client erfolgt. |
|
|
Timeout in Sekunden, nachdem der Server eine Websocket-Verbindung ohne Antwort trennt. |
|
|
Webseiteninhalte mit Gzip komprimieren, wenn vom Browser unterstützt. Optionen:
|
|
|
Webseiteninhalte mit Brotli komprimieren, wenn vom Browser unterstützt. Optionen:
|
|
|
HTTP Basic-Authentifizierung (Passwortabfrage im Browser) aktivieren. |
|
|
Apache-kompatible Passwortdatei. Suche nach einer Passwortdatei unter ./htpasswd oder /etc/sebrauc/htpasswd, wenn leergelassen. |
|
Sysinfo.ReleaseFile |
Datei, aus der OS-Informationen gelesen werden. | "/etc/os-release" |
|
Schlüssel für den Namen des Betriebssystems aus der ReleaseFile. |
|
|
Schlüssel für die Version des Betriebssystems aus der ReleaseFile. |
|
Sysinfo.HostnameFile |
Datei, aus der der Hostname gelesen wird. | "/etc/hostname" |
|
Datei, aus der die Betriebszeit des Systems gelesen wird. |
|
|
RAUC-Befehl, um den Status im JSON-Format auszulesen. |
|
Commands.RaucInstall |
RAUC-Befehl, um ein Update-Paket zu installieren. | "rauc install" |
Commands.Reboot |
Befehl, um das System neu zu starten. | "shutdown -r 0" |
Passwortdatei
Ist die Authentifizierung aktiviert, liest SEBRAUC Nutzernamen und Passwörter aus Apache-kompatiblen Passwortdateien (standardmäßig unter ./htpasswd
oder /etc/sebrauc/htpasswd
).
Die Passwörter können im Klartext oder als Hashes vorliegen. Gehashte Passwörter lassen sich mit mit dem Befehl htpasswd -nB username
erzeugen. Hierfür wird unter Linux das Paket apache-tools
benötigt.
# Klartextpasswort (nur zum Testen verwenden)
username:password
# Gehashtes Passwort
test:$2y$05$EzzPOZprUhPE.1ru1gM8du0ZNpmsU40EFDZ1PmzZtBzkMHsJVK1au
Entwicklung und Build
Systemvoraussetzungen
Um SEBRAUC zu kompilieren werden folgende Programme benötigt:
- Golang (Version 1.16 oder neuer)
- NodeJS
- PNPM-Paketmanager (installieren mit
npm i -g pnpm
)
Zur Entwicklung werden zusätzlich folgende Tools verwendet:
- golangci-lint (Überprüft Go-Code auf Formatierung, Code Style und bestimmete Fehler)
- air (Kompiliert und startet Go-Anwendungen bei Codeänderungen neu)
- go-swagger (Generierung einer OpenAPI-Dokumentation aus Go-Servercode, der nach einem bestimmten Schema kommentiert wurde)
- openapi-generator (Erzeugt fertige API-Clients aus OpenAPI-Dokumentationen)
Befehle
Das Projekt enthät eine Makefile, mit der sich oft benötigte Befehle schnell ausführen lassen.
make setup
Dieser Befehl muss vor dem Arbeiten an dem Projekt ausgeführt werden. Hiermit werden sämtliche Abhängigkeiten für das Frontend
make lint
Überprüfe den Backend-Code mit
golangci-lint
auf Fehler und schlechte Programmierpraktiken. Zudem wird der Typescript-Code im Frontend mit dem Typescript-Compiler auf Typfehler geprüft.make build
Baue die Produktionsversion der Software. Die Softwareversion, die in die Software einkompiliert wird, wird aus dem aktuellsten Git-Tag ermittelt.
make test
Führt die Tests für das Backend aus.
make generate-apidoc
Liest die API-Dokumentation aus den Kommentaren im Quellcode ein und produziert daraus eine Swagger-Dokumentationsdatei im yaml-Format.
make generate-apiclient
Erzeugt den Typescript-API-Client für das Frontend aus der Swagger API-Dokumentation. Um die API-Dokumentation zu aktualisieren, muss vorher
make generate-apidoc
ausgeführt werden.
Um die SEBRAUC-Software zu bauen, müssen nach dem Herunterladen des Repositorys folgende Befehle ausgeführt werden:
# Abhängigkeiten installieren
make setup
# Build für die CPU-Architektur des laufenden Systems
make build
# Build für andere CPU-Architekturen (Cross-Compile)
GOARCH=arm make build
Die kompilierte Anwendung befindet wird im Ordner build
abgelegt.
RAUC-Mock
Um SEBRAUC testweise auf einem Entwicklerrechner ohne den RAUC-Updater laufen zu lassen, habe ich ein kleines Go-Programm geschrieben, das die Konsolenausgabe von RAUC simuliert.
Man kann dieses Programm anstelle der echten RAUC-Befehle in die Konfigurationsdatei eintragen.
# Commands to be run by SEBRAUC
[Commands]
# RAUC status command (outputs updater status in json format)
RaucStatus = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json"
# RAUC install command (installs FW image passed as argument)
RaucInstall = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install"
Wird SEBRAUC im Testmodus (d.h. ohne das Build-Flag -tags prod
) gebaut, führt das Programm stets den Mock-RAUC-Befehl aus und ignoriert die konfigurierten Befehle. Ob sich SEBRAUC im Testmodus befindet, lässt sich an der Hinweisnachricht beim Start erkennen.
Test mode active - no update operations are executed.
Build with -tags prod to enable live mode.
Das Verhalten von rauc_mock
kann mittels Umgebungsvariablen angepasst werden. Ist die Variable RAUC_MOCK_FAIL
gesetzt, wird eine fehlerhafte Installation simuliert. Mit dem Setzen der Variable RAUC_MOCK_TEST
reduziert man die Verzögerung, mit der die Nachrichten angezeigt werden, von 500 auf 10 Millisekunden. Diese Option dient dazu, automatische Tests zu beschleunigen, während die Wartezeit bei manuellen Tests lang genug ist, um sämtliche Statusnachrichten lesen zu können.
Auto-Reload des Backends
Wenn man am Backend-Code arbeitet und die Webseite oder API dabei gleichzeitig testen möchte, hat man mit kompilierten Sprachen wie Go das Problem, dass sich Codeänderungen nicht sofort auf das Verhalten der Anwendung auswirken. Erst nach einer Rekompilierung und einem Neustart des Servers ist eine Änderung wirksam.
Glücklicherweise gibt es Tools wie air
, die die Quellcodedateien überwachen und die Anwendung bei Änderung automatisch kompilieren und neu starten.
Um den SEBRAUC-Server mit Auto-Reload zu starten, muss man einfach den Befehl air
im Stammverzeichnis des Projekts ausführen
Frontend
Um den Entwicklungsserver für das Frontend zu starten, navigiert man in das ui
-Verzeichnis und startet den Server mit dem Befehl npm run dev
.
Im Entwicklungsmodus kommuniziert das Frontend mit dem Backend unter der Adresse localhost:8080
. Man kann also parallel SEBRAUC mit entsprechender Konfiguration (Port: 8080) starten, um das Frontend mit einem funktionsfähigen Server zu testen.