460 lines
15 KiB
ReStructuredText
460 lines
15 KiB
ReStructuredText
.. 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.
|