Web interface for the RAUC firmware updater
Find a file
ThetaDev 28e51df5b8
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
fix: lint errors
2023-01-03 21:47:22 +01:00
.vscode Add uploader component 2021-11-20 20:32:29 +01:00
_screenshots Add README 2022-02-21 20:14:31 +01:00
src fix: lint errors 2023-01-03 21:47:22 +01:00
ui ci: add woodpecker.yml 2023-01-03 21:22:19 +01:00
.air.toml add tests for rauc, ui and middleware 2021-12-19 22:29:12 +01:00
.drone.yml add dynamic versioning 2021-11-21 23:29:37 +01:00
.editorconfig Add preact framework 2021-11-19 20:31:33 +01:00
.gitignore add HTTP basic auth 2021-12-25 20:52:04 +01:00
.golangci.yaml add tests for rauc, ui and middleware 2021-12-19 22:29:12 +01:00
.pre-commit-config.yaml add swagger page 2021-12-18 00:39:08 +01:00
.prettierrc.json Add preact framework 2021-11-19 20:31:33 +01:00
.woodpecker.yml add woodpecker ci config 2022-05-08 02:21:13 +02:00
go.mod add HTTP basic auth 2021-12-25 20:52:04 +01:00
go.sum add HTTP basic auth 2021-12-25 20:52:04 +01:00
LICENSE Initial commit 2021-11-13 22:44:05 +01:00
Makefile Add README 2022-02-21 20:14:31 +01:00
README.rst Add README 2022-02-21 20:14:31 +01:00
sebrauc.example.toml Add README 2022-02-21 20:14:31 +01:00
woodpecker.yml ci: add woodpecker.yml 2023-01-03 21:22:19 +01:00

image

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

image

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

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

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

image

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

image

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"

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.

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