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