Web interface for the RAUC firmware updater
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 |
.. image:: ui/src/assets/logo_border.svg SEBRAUC ist eine einfach zu bedienende Weboberfläche für den `RAUC <https://github.com/rauc/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 <https://github.com/rauc/rauc-hawkbit-updater>`__ 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 ========== .. image:: _screenshots/update0.png :width: 500 Auf der Startseite findet der Nutzer einen großen Upload-Button, mit dem er ein ``*.raucb``-Image auswählen und hochladen kann. Update hochladen ================ .. image:: _screenshots/update1.png :width: 500 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 =================== .. image:: _screenshots/update2.png :width: 500 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: .. code-block:: 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 ========================== .. image:: _screenshots/update3.png :width: 500 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 :guilabel:`Restart`-Button, startet das System mit einer Verzögerung von 3 Sekunden neu. Systeminformationen =================== .. image:: _screenshots/sysinfo.png :width: 500 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 <https://github.com/gin-gonic/gin>`_ 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 <https://preactjs.com/>`_ 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 <https://dev.to/this-is-learning/javascript-framework-todomvc-size-comparison-504f>`_ 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 <https://code.thetadev.de/TSGRain/SEBRAUC/src/branch/main/sebrauc.example.toml>`__. 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. .. list-table:: :widths: 35 50 35 :header-rows: 1 * - Option - Beschreibung - Standard * - ``Tmpdir`` - Temporäres Verzeichnis - ``"/tmp/sebrauc"`` * - ``Server.Address`` - IP-Adresse, von der der Server Verbindungen akzeptiert. Leer lassen, um Verbindungen von allen Adressen zu akzeptieren. - ``""`` * - ``Server.Port`` - Server-Port - ``80`` * - ``Server.Websocket.Ping`` - Zeitabstand, in denen ein Ping vom Websocket-Server an den Client erfolgt. - ``45`` * - ``Server.Websocket.Timeout`` - Timeout in Sekunden, nachdem der Server eine Websocket-Verbindung ohne Antwort trennt. - ``45`` * - ``Server.Compression.Gzip`` - Webseiteninhalte mit Gzip komprimieren, wenn vom Browser unterstützt. Optionen: ``"false"``, ``"default"``, ``"min"``, ``"max"``, ``"1"``-``"9"`` - ``"default"`` * - ``Server.Compression.Brotli`` - Webseiteninhalte mit Brotli komprimieren, wenn vom Browser unterstützt. Optionen: ``"false"``, ``"default"``, ``"min"``, ``"max"``, ``"1"``-``"11"`` - ``"default"`` * - ``Authentication.Enable`` - HTTP Basic-Authentifizierung (Passwortabfrage im Browser) aktivieren. - ``false`` * - ``Authentication.PasswdFile`` - 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"`` * - ``Sysinfo.NameKey`` - Schlüssel für den Namen des Betriebssystems aus der ReleaseFile. - ``"NAME"`` * - ``Sysinfo.VersionKey`` - Schlüssel für die Version des Betriebssystems aus der ReleaseFile. - ``"VERSION"`` * - ``Sysinfo.HostnameFile`` - Datei, aus der der Hostname gelesen wird. - ``"/etc/hostname"`` * - ``Sysinfo.UptimeFile`` - Datei, aus der die Betriebszeit des Systems gelesen wird. - ``"/proc/uptime"`` * - ``Commands.RaucStatus`` - RAUC-Befehl, um den Status im JSON-Format auszulesen. - ``"rauc status`` ``--output-format=json"`` * - ``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. .. code-block:: # 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 <https://golangci-lint.run/>`_ (Überprüft Go-Code auf Formatierung, Code Style und bestimmete Fehler) - `air <https://github.com/cosmtrek/air>`_ (Kompiliert und startet Go-Anwendungen bei Codeänderungen neu) - `go-swagger <https://github.com/go-swagger/go-swagger>`_ (Generierung einer OpenAPI-Dokumentation aus Go-Servercode, der nach einem bestimmten Schema kommentiert wurde) - `openapi-generator <https://openapi-generator.tech/docs/installation>`_ (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: .. code-block:: sh # 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. .. code-block:: toml # 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. .. code-block:: text 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.