diff --git a/.air.toml b/.air.toml
index 91be4cb..9520ff3 100644
--- a/.air.toml
+++ b/.air.toml
@@ -1,11 +1,11 @@
-root = "."
+root = "./src"
tmp_dir = "tmp"
[build]
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./src/."
delay = 1000
- exclude_dir = ["tmp", "vendor", "ui/dist", "ui/node_modules", "ui/src"]
+ exclude_dir = ["assets", "tmp", "vendor"]
exclude_file = []
exclude_regex = []
exclude_unchanged = false
diff --git a/.gitignore b/.gitignore
index 56dfd25..d59a62b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,2 @@
build
tmp
-/sebrauc.toml
-/htpasswd
diff --git a/.golangci.yaml b/.golangci.yaml
index 2e06378..3898f1c 100644
--- a/.golangci.yaml
+++ b/.golangci.yaml
@@ -1,6 +1,7 @@
linters:
presets:
- bugs
+ - unused
- import
- module
@@ -13,17 +14,11 @@ linters:
disable:
- scopelint
- - noctx
linters-settings:
lll:
line-length: 88
- tab-width: 4
gocognit:
min-complexity: 10
nestif:
min-complexity: 3
- errcheck:
- exclude-functions:
- - "(*github.com/gin-gonic/gin.Context).Error"
- - "(*github.com/gin-gonic/gin.Context).AbortWithError"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index cc4ae18..bf556ef 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -6,7 +6,10 @@ repos:
name: GolangCI Lint
- id: go-test-repo-mod
name: Backend tests
-
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: v2.4.1
+ hooks:
+ - id: prettier
- repo: local
hooks:
- id: tsc
@@ -17,8 +20,3 @@ repos:
args: ["-p", "./ui/tsconfig.json"]
additional_dependencies: ["typescript@4.5.2"]
pass_filenames: false
-
- - repo: https://github.com/pre-commit/mirrors-prettier
- rev: v2.4.1
- hooks:
- - id: prettier
diff --git a/ui/.prettierignore b/.prettierignore
similarity index 66%
rename from ui/.prettierignore
rename to .prettierignore
index d638edf..f06235c 100644
--- a/ui/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1,2 @@
node_modules
dist
-tmp
-.tmp
diff --git a/.woodpecker.yml b/.woodpecker.yml
deleted file mode 100644
index 69668a7..0000000
--- a/.woodpecker.yml
+++ /dev/null
@@ -1,14 +0,0 @@
-pipeline:
- frontend:
- image: node:16-alpine
- commands:
- - cd ui
- - npm install -g pnpm
- - pnpm install
- - pnpm run build
- backend:
- image: golangci/golangci-lint:latest
- commands:
- - go get -t ./src/...
- - golangci-lint run --timeout 5m
- - go test -v ./src/...
diff --git a/Makefile b/Makefile
index 8da4406..ed465a0 100644
--- a/Makefile
+++ b/Makefile
@@ -1,35 +1,19 @@
SRC_DIR=./src
UI_DIR=./ui
-APIDOC_FILE=./src/server/swagger/swagger.yaml
-
-VER=$(or ${VERSION},$(shell git tag --sort=-version:refname | head -n 1))
+VERSION=$(shell git tag --sort=-version:refname | head -n 1)
setup:
+ go get -t ./src/...
cd ${UI_DIR} && pnpm install
test:
go test -v ./src/...
-lint:
- golangci-lint run
- cd ${UI_DIR} && npm run format && npm run lint
-
build-ui:
- cd ${UI_DIR} && pnpm run build
+ cd ${UI_DIR} && VITE_VERSION=${VERSION} pnpm run build
build-server:
- go build -tags prod -ldflags "-s -w -X code.thetadev.de/TSGRain/SEBRAUC/src/util.version=${VER}" -o build/sebrauc ./src/.
+ go build -tags prod -ldflags "-s -w -X code.thetadev.de/TSGRain/SEBRAUC/src/util.version=${VERSION}" -o build/sebrauc ./src/.
build: build-ui build-server
-
-generate-apidoc:
- SWAGGER_GENERATE_EXTENSION=false swagger generate spec --scan-models -o ${APIDOC_FILE}
-
-generate-apiclient:
- openapi-generator generate -i ${APIDOC_FILE} -g typescript-axios -o ${UI_DIR}/src/sebrauc-client -p "supportsES6=true"
- cd ${UI_DIR} && npm run format
-
-clean:
- rm -f build/*
- rm -rf ${UI_DIR}/dist/**
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c074425
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+
diff --git a/README.rst b/README.rst
deleted file mode 100644
index ecb1dfc..0000000
--- a/README.rst
+++ /dev/null
@@ -1,460 +0,0 @@
-.. image:: ui/src/assets/logo_border.svg
-
-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:: _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 `_ 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.
-
-.. 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 `_ (Ü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:
-
-.. 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.
diff --git a/_screenshots/sysinfo.png b/_screenshots/sysinfo.png
deleted file mode 100644
index a6ca4e2..0000000
Binary files a/_screenshots/sysinfo.png and /dev/null differ
diff --git a/_screenshots/update0.png b/_screenshots/update0.png
deleted file mode 100644
index faa7ebe..0000000
Binary files a/_screenshots/update0.png and /dev/null differ
diff --git a/_screenshots/update1.png b/_screenshots/update1.png
deleted file mode 100644
index 2196373..0000000
Binary files a/_screenshots/update1.png and /dev/null differ
diff --git a/_screenshots/update2.png b/_screenshots/update2.png
deleted file mode 100644
index 7dfcfed..0000000
Binary files a/_screenshots/update2.png and /dev/null differ
diff --git a/_screenshots/update3.png b/_screenshots/update3.png
deleted file mode 100644
index 399edef..0000000
Binary files a/_screenshots/update3.png and /dev/null differ
diff --git a/go.mod b/go.mod
index 92ad7c6..5b0a27c 100644
--- a/go.mod
+++ b/go.mod
@@ -3,29 +3,10 @@ module code.thetadev.de/TSGRain/SEBRAUC
go 1.16
require (
- code.thetadev.de/TSGRain/ginzip v0.1.1
- github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db
- github.com/fortytw2/leaktest v1.3.0
- github.com/gin-contrib/cors v1.3.1
- github.com/gin-gonic/gin v1.7.7
- github.com/go-errors/errors v1.4.1 // indirect
- github.com/golang/protobuf v1.5.2 // indirect
- github.com/google/go-cmp v0.5.6 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/gofiber/fiber/v2 v2.21.0
+ github.com/gofiber/websocket/v2 v2.0.12
github.com/google/uuid v1.3.0
- github.com/gorilla/websocket v1.4.1
- github.com/jinzhu/configor v1.2.1
- github.com/json-iterator/go v1.1.12 // indirect
- github.com/kr/text v0.2.0 // indirect
- github.com/mattn/go-isatty v0.0.14 // indirect
- github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
- github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/stretchr/testify v1.7.0
- github.com/tg123/go-htpasswd v1.2.0
- golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
- golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect
- golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
- google.golang.org/protobuf v1.27.1 // indirect
- gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
- gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)
diff --git a/go.sum b/go.sum
index c082427..6bcb5c4 100644
--- a/go.sum
+++ b/go.sum
@@ -1,128 +1,48 @@
-code.thetadev.de/TSGRain/ginzip v0.1.1 h1:+X0L6qumEZiKYSLmM+Q0LqKVHsKvdcg4CVzsEpvM7fk=
-code.thetadev.de/TSGRain/ginzip v0.1.1/go.mod h1:BH7VkvpP83vPRyMQ8rLIjKycQwGzF+/mFV0BKzg+BuA=
-github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
-github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
-github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
-github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
-github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
+github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db h1:oZ4U9IqO8NS+61OmGTBi8vopzqTRxwQeogyBHdrhjbc=
-github.com/ekyoung/gin-nice-recovery v0.0.0-20160510022553-1654dca486db/go.mod h1:Pk7/9x6tyChFTkahDvLBQMlvdsWvfC+yU8HTT5VD314=
-github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
-github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
-github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
-github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
-github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
-github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
-github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
-github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
-github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
-github.com/go-errors/errors v1.4.1 h1:IvVlgbzSsaUNudsw5dcXSzF3EWyXTi5XrAdngnuhRyg=
-github.com/go-errors/errors v1.4.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
-github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
-github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
-github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
-github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
-github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
-github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
-github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
-github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
-github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
-github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
-github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
-github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
-github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
-github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
-github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/fasthttp/websocket v1.4.3-rc.9 h1:CWJH0vONrOatdKXZgkgbFKWllijD9aY50C5KfbSDcWk=
+github.com/fasthttp/websocket v1.4.3-rc.9/go.mod h1:eXL2zqDbexYJxaCw8/PQlm7VcMK6uoGvwbYbTdt4dFo=
+github.com/gofiber/fiber/v2 v2.20.1/go.mod h1:/LdZHMUXZvTTo7gU4+b1hclqCAdoQphNQ9bi9gutPyI=
+github.com/gofiber/fiber/v2 v2.21.0 h1:tdRNrgqWqcHWBwE3o51oAleEVsil4Ro02zd2vMEuP4Q=
+github.com/gofiber/fiber/v2 v2.21.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ=
+github.com/gofiber/websocket/v2 v2.0.12 h1:jKwTrXiOut9UGOGEzFTAD6gq+/78mM3NcrI05VbxjAU=
+github.com/gofiber/websocket/v2 v2.0.12/go.mod h1:lQRy0u5ACJfiez/e/bhGeYvM0/M940Y3NFw14U3/otI=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
-github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
-github.com/jinzhu/configor v1.2.1 h1:OKk9dsR8i6HPOCZR8BcMtcEImAFjIhbJFZNyn5GCZko=
-github.com/jinzhu/configor v1.2.1/go.mod h1:nX89/MOmDba7ZX7GCyU/VIaQ2Ar2aizBl2d3JLF/rDc=
-github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
-github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
-github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
-github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
-github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
-github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
-github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
-github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
-github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
-github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
-github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
-github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
-github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
-github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
-github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
-github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
-github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
-github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
-github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s=
+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4 h1:ocK/D6lCgLji37Z2so4xhMl46se1ntReQQCUIU4BWI8=
+github.com/savsgio/gotils v0.0.0-20210921075833-21a6215cb0e4/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/tg123/go-htpasswd v1.2.0 h1:UKp34m9H467/xklxUxU15wKRru7fwXoTojtxg25ITF0=
-github.com/tg123/go-htpasswd v1.2.0/go.mod h1:h7IzlfpvIWnVJhNZ0nQ9HaFxHb7pn5uFJYLlEUJa2sM=
-github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
-github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
-github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
-github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
-golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.29.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
+github.com/valyala/fasthttp v1.30.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
+github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE=
+github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
+github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
-golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
+golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
-google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
-google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
-gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
-gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
-gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
-gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/openapi.yml b/openapi.yml
new file mode 100644
index 0000000..a9332ce
--- /dev/null
+++ b/openapi.yml
@@ -0,0 +1,107 @@
+openapi: "3.0.3"
+info:
+ title: SEBRAUC
+ version: "0.0.1"
+servers:
+ - url: http://localhost:8080/api
+paths:
+ /status:
+ get:
+ responses:
+ "200":
+ description: OK
+ content:
+ "application/json":
+ schema:
+ $ref: "#/components/schemas/RaucStatus"
+ default:
+ description: "Server error"
+ content:
+ "application/json":
+ schema:
+ $ref: "#/components/schemas/StatusMessage"
+ /update:
+ post:
+ requestBody:
+ content:
+ multipart/form-data:
+ schema:
+ type: object
+ properties:
+ updateFile:
+ type: string
+ format: binary
+ responses:
+ "200":
+ description: "OK"
+ content:
+ "application/json":
+ schema:
+ $ref: "#/components/schemas/StatusMessage"
+ default:
+ description: "Server error"
+ content:
+ "application/json":
+ schema:
+ $ref: "#/components/schemas/StatusMessage"
+
+ /reboot:
+ post:
+ responses:
+ "200":
+ description: "OK"
+ content:
+ "application/json":
+ schema:
+ $ref: "#/components/schemas/StatusMessage"
+ default:
+ description: "Server error"
+ content:
+ "application/json":
+ schema:
+ $ref: "#/components/schemas/StatusMessage"
+
+components:
+ schemas:
+ RaucStatus:
+ type: object
+ properties:
+ installing:
+ description: "True if the installer is running"
+ type: boolean
+ percent:
+ description: "Installation progress"
+ type: integer
+ minimum: 0
+ maximum: 100
+ message:
+ description: "Current installation step"
+ type: string
+ example: "Copying image to rootfs.0"
+ last_error:
+ description: "Installation error message"
+ type: string
+ example: "Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?"
+ log:
+ description: "Full command line output of the current installation"
+ type: string
+ example: "0% Installing\n0% Determining slot states\n20% Determining slot states done.\n"
+ required:
+ - installing
+ - percent
+ - message
+ - last_error
+ - log
+
+ StatusMessage:
+ type: object
+ properties:
+ success:
+ description: "Is operation successful"
+ type: boolean
+ msg:
+ description: "Success message"
+ type: string
+ example: "Update started"
+ required:
+ - msg
diff --git a/sebrauc.example.toml b/sebrauc.example.toml
deleted file mode 100644
index 5f95ce0..0000000
--- a/sebrauc.example.toml
+++ /dev/null
@@ -1,44 +0,0 @@
-# SEBRAUC config file example
-
-# Temporary directory
-# Update packages are stored temporarily under Tmpdir/update-.raucb
-Tmpdir = "/tmp/sebrauc"
-
-# Webserver options
-[Server]
-# IP address to accept connections from
-Address = ""
-# Port to listen at
-Port = 8080
-
-# Websocket connection settings: Ping interval/Timeout in seconds
-[Server.Websocket]
-Ping = 45
-Timeout = 15
-
-# Compression settings. Refer to code.thetadev.de/TSGRain/ginzip for details.
-[Server.Compression]
-Gzip = "default"
-Brotli = "default"
-
-[Authentication]
-Enable = true
-PasswdFile = "htpasswd"
-
-# Where to obtain system info from
-[Sysinfo]
-ReleaseFile = "/etc/os-release"
-# Keys to look for in the OS release file
-NameKey = "NAME"
-VersionKey = "VERSION"
-HostnameFile = "/etc/hostname"
-UptimeFile = "/proc/uptime"
-
-# Commands to be run by SEBRAUC
-[Commands]
-# RAUC status command (outputs updater status in json format)
-RaucStatus = "rauc status --output-format=json"
-# RAUC install command (installs FW image passed as argument)
-RaucInstall = "rauc install"
-# System reboot command
-Reboot = "shutdown -r 0"
diff --git a/src/assets/assets.go b/src/assets/assets.go
new file mode 100644
index 0000000..8f2c9de
--- /dev/null
+++ b/src/assets/assets.go
@@ -0,0 +1,8 @@
+package assets
+
+import (
+ "embed"
+)
+
+//go:embed files/**
+var Assets embed.FS
diff --git a/src/assets/files/index.html b/src/assets/files/index.html
new file mode 100644
index 0000000..0554e60
--- /dev/null
+++ b/src/assets/files/index.html
@@ -0,0 +1,88 @@
+
+
+
+
+ Chat Example
+
+
+
+
+
+
+
+
+
diff --git a/src/config/config.go b/src/config/config.go
deleted file mode 100644
index a46106d..0000000
--- a/src/config/config.go
+++ /dev/null
@@ -1,130 +0,0 @@
-package config
-
-import (
- "strings"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode"
- "github.com/jinzhu/configor"
-)
-
-var (
- cfgFilePaths = []string{"sebrauc", "/etc/sebrauc/sebrauc"}
- cfgFileTypes = []string{"toml", "yaml", "yml", "json"}
-)
-
-// SEBRAUC config object
-type Config struct {
- // Temporary directory
- // Update packages are stored temporarily under Tmpdir/update-.raucb
- Tmpdir string
-
- // Webserver options
- Server struct {
- // IP address to accept connections from
- Address string
- // Port to listen at
- Port int `default:"80"`
-
- // Websocket connection settings: Ping interval/Timeout in seconds
- Websocket struct {
- Ping int `default:"45"`
- Timeout int `default:"15"`
- }
-
- // Compression settings. Refer to code.thetadev.de/TSGRain/ginzip for details.
- Compression struct {
- Gzip string
- Brotli string
- }
- }
-
- Authentication struct {
- Enable bool `default:"false"`
- PasswdFile string
- }
-
- // Where to obtain system info from
- Sysinfo struct {
- ReleaseFile string `default:"/etc/os-release"`
- // Keys to look for in the OS release file
- NameKey string `default:"NAME"`
- VersionKey string `default:"VERSION"`
- HostnameFile string `default:"/etc/hostname"`
- UptimeFile string `default:"/proc/uptime"`
- }
-
- // Commands to be run by SEBRAUC
- // Note that these are overriden when running in development mode
- Commands struct {
- // RAUC status command (outputs updater status in json format)
- RaucStatus string `default:"rauc status --output-format=json"`
- // RAUC install command (installs FW image passed as argument)
- RaucInstall string `default:"rauc install"`
- // System reboot command
- Reboot string `default:"shutdown -r 0"`
- }
-}
-
-func findConfigFile(pathIn string) string {
- fpath, err := util.FindFile(pathIn, cfgFilePaths, cfgFileTypes)
- if err != nil {
- if pathIn != "" {
- panic("cfg file not found: " + err.Error())
- }
- return ""
- }
-
- return fpath
-}
-
-func stripTrailingSlashes(pathIn string) string {
- return strings.TrimRight(pathIn, "/")
-}
-
-func loadConfig(cfgFile string) *Config {
- cfg := new(Config)
- cfgor := configor.New(&configor.Config{
- // Debug: true,
- ENVPrefix: "SEBRAUC",
- })
-
- err := cfgor.Load(cfg, cfgFile)
- if err != nil {
- panic(err)
- }
-
- // Override commands with testing options
- if mode.IsDev() {
- cfg.Commands.RaucStatus = testcmd.RaucStatus
- cfg.Commands.RaucInstall = testcmd.RaucInstall
- cfg.Commands.Reboot = testcmd.Reboot
- }
-
- cfg.Tmpdir = stripTrailingSlashes(cfg.Tmpdir)
-
- return cfg
-}
-
-// GetWithFlags returns the configuration extracted from cmdline args,
-// env variables or config file.
-func GetWithFlags(pathIn string, portIn int) *Config {
- cfg := loadConfig(findConfigFile(pathIn))
-
- // Override port with cmdline flag if set
- if portIn > 0 {
- cfg.Server.Port = portIn
- }
-
- return cfg
-}
-
-// Get returns the configuration extracted from env variables or config file.
-func Get() *Config {
- return loadConfig(findConfigFile(""))
-}
-
-func GetDefault() *Config {
- return loadConfig("")
-}
diff --git a/src/config/config_test.go b/src/config/config_test.go
deleted file mode 100644
index 332437a..0000000
--- a/src/config/config_test.go
+++ /dev/null
@@ -1,161 +0,0 @@
-package config
-
-import (
- "fmt"
- "os"
- "path/filepath"
- "testing"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode"
- "github.com/stretchr/testify/assert"
-)
-
-func TestDefault(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- cfg := Get()
-
- assert.Equal(t, "", cfg.Server.Address)
- assert.Equal(t, 80, cfg.Server.Port)
-
- assert.Equal(t, 45, cfg.Server.Websocket.Ping)
- assert.Equal(t, 15, cfg.Server.Websocket.Timeout)
-
- assert.Equal(t, "", cfg.Server.Compression.Gzip)
- assert.Equal(t, "", cfg.Server.Compression.Brotli)
-
- assert.Equal(t, "", cfg.Tmpdir)
-
- assert.Equal(t, false, cfg.Authentication.Enable)
- assert.Equal(t, "", cfg.Authentication.PasswdFile)
-
- assert.Equal(t, "/etc/os-release", cfg.Sysinfo.ReleaseFile)
- assert.Equal(t, "NAME", cfg.Sysinfo.NameKey)
- assert.Equal(t, "VERSION", cfg.Sysinfo.VersionKey)
- assert.Equal(t, "/etc/hostname", cfg.Sysinfo.HostnameFile)
- assert.Equal(t, "/proc/uptime", cfg.Sysinfo.UptimeFile)
-
- assert.Equal(t, "rauc status --output-format=json", cfg.Commands.RaucStatus)
- assert.Equal(t, "rauc install", cfg.Commands.RaucInstall)
- assert.Equal(t, "shutdown -r 0", cfg.Commands.Reboot)
-}
-
-func TestConfigFile(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- cfile := filepath.Join(fixtures.GetTestfilesDir(), "sebrauc.toml")
- cfg := GetWithFlags(cfile, 0)
-
- assert.Equal(t, "127.0.0.1", cfg.Server.Address)
- assert.Equal(t, 8001, cfg.Server.Port)
-
- assert.Equal(t, 30, cfg.Server.Websocket.Ping)
- assert.Equal(t, 10, cfg.Server.Websocket.Timeout)
-
- assert.Equal(t, "max", cfg.Server.Compression.Gzip)
- assert.Equal(t, "false", cfg.Server.Compression.Brotli)
-
- assert.Equal(t, "/var/tmp", cfg.Tmpdir)
-
- assert.Equal(t, true, cfg.Authentication.Enable)
- assert.Equal(t, "/etc/htpasswd", cfg.Authentication.PasswdFile)
-
- assert.Equal(t, "/etc/release", cfg.Sysinfo.ReleaseFile)
- assert.Equal(t, "PRETTY_NAME", cfg.Sysinfo.NameKey)
- assert.Equal(t, "VER", cfg.Sysinfo.VersionKey)
- assert.Equal(t, "/etc/hn", cfg.Sysinfo.HostnameFile)
- assert.Equal(t, "/proc/up", cfg.Sysinfo.UptimeFile)
-
- assert.Equal(t, "myrauc status --output-format=json", cfg.Commands.RaucStatus)
- assert.Equal(t, "myrauc install", cfg.Commands.RaucInstall)
- assert.Equal(t, "reboot", cfg.Commands.Reboot)
-}
-
-func TestEnvvar(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- fixtures.ResetEnv()
- defer fixtures.ResetEnv()
-
- os.Setenv("SEBRAUC_TMPDIR", "/var/tmp")
- os.Setenv("SEBRAUC_SERVER_ADDRESS", "127.0.0.1")
- os.Setenv("SEBRAUC_SERVER_PORT", "8001")
- os.Setenv("SEBRAUC_SERVER_WEBSOCKET_PING", "30")
- os.Setenv("SEBRAUC_SERVER_WEBSOCKET_TIMEOUT", "10")
- os.Setenv("SEBRAUC_SERVER_COMPRESSION_GZIP", "max")
- os.Setenv("SEBRAUC_SERVER_COMPRESSION_BROTLI", "false")
- os.Setenv("SEBRAUC_AUTHENTICATION_ENABLE", "true")
- os.Setenv("SEBRAUC_AUTHENTICATION_PASSWDFILE", "/etc/htpasswd")
- os.Setenv("SEBRAUC_SYSINFO_RELEASEFILE", "/etc/release")
- os.Setenv("SEBRAUC_SYSINFO_NAMEKEY", "PRETTY_NAME")
- os.Setenv("SEBRAUC_SYSINFO_VERSIONKEY", "VER")
- os.Setenv("SEBRAUC_SYSINFO_HOSTNAMEFILE", "/etc/hn")
- os.Setenv("SEBRAUC_SYSINFO_UPTIMEFILE", "/proc/up")
- os.Setenv("SEBRAUC_COMMANDS_RAUCSTATUS", "myrauc status --output-format=json")
- os.Setenv("SEBRAUC_COMMANDS_RAUCINSTALL", "myrauc install")
- os.Setenv("SEBRAUC_COMMANDS_REBOOT", "reboot")
-
- cfg := Get()
-
- assert.Equal(t, "127.0.0.1", cfg.Server.Address)
- assert.Equal(t, 8001, cfg.Server.Port)
-
- assert.Equal(t, 30, cfg.Server.Websocket.Ping)
- assert.Equal(t, 10, cfg.Server.Websocket.Timeout)
-
- assert.Equal(t, "max", cfg.Server.Compression.Gzip)
- assert.Equal(t, "false", cfg.Server.Compression.Brotli)
-
- assert.Equal(t, "/var/tmp", cfg.Tmpdir)
-
- assert.Equal(t, true, cfg.Authentication.Enable)
- assert.Equal(t, "/etc/htpasswd", cfg.Authentication.PasswdFile)
-
- assert.Equal(t, "/etc/release", cfg.Sysinfo.ReleaseFile)
- assert.Equal(t, "PRETTY_NAME", cfg.Sysinfo.NameKey)
- assert.Equal(t, "VER", cfg.Sysinfo.VersionKey)
- assert.Equal(t, "/etc/hn", cfg.Sysinfo.HostnameFile)
- assert.Equal(t, "/proc/up", cfg.Sysinfo.UptimeFile)
-
- assert.Equal(t, "myrauc status --output-format=json", cfg.Commands.RaucStatus)
- assert.Equal(t, "myrauc install", cfg.Commands.RaucInstall)
- assert.Equal(t, "reboot", cfg.Commands.Reboot)
-}
-
-func TestDevMode(t *testing.T) {
- cfg := Get()
-
- //nolint:lll
- assert.Equal(t, "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json", cfg.Commands.RaucStatus)
- //nolint:lll
- assert.Equal(t, "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install", cfg.Commands.RaucInstall)
- assert.Equal(t, "touch /tmp/sebrauc_reboot_test", cfg.Commands.Reboot)
-}
-
-func TestFlags(t *testing.T) {
- cfg := GetWithFlags("", 8001)
-
- assert.Equal(t, 8001, cfg.Server.Port)
-}
-
-func TestStripTrailingSlashes(t *testing.T) {
- tests := []struct {
- in string
- out string
- }{
- {in: "/tmp", out: "/tmp"},
- {in: "/tmp/", out: "/tmp"},
- {in: "/tmp///", out: "/tmp"},
- }
-
- for i, tt := range tests {
- t.Run(fmt.Sprint(i), func(t *testing.T) {
- res := stripTrailingSlashes(tt.in)
- assert.Equal(t, tt.out, res)
- })
- }
-}
diff --git a/src/fixtures/rauc_mock/main.go b/src/fixtures/rauc_mock/main.go
index 228aeda..4c9b884 100644
--- a/src/fixtures/rauc_mock/main.go
+++ b/src/fixtures/rauc_mock/main.go
@@ -41,54 +41,22 @@ LastError: Failed to check bundle identifier: Invalid identifier. ` +
idle
Installing ` + "/app/demo` failed"
-const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` +
- `"boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs",` +
- `"device":"/dev/mmcblk0p3","type":"ext4","bootname":"B","state":"inactive",` +
- `"parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":` +
- `{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` +
- `"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}`
+func main() {
+ arg := ""
+ if len(os.Args) > 1 {
+ arg = os.Args[1]
+ }
+
+ var lines string
+ switch arg {
+ case "fail":
+ lines = outputFailure
+ default:
+ lines = outputSuccess
+ }
-func printLinesWithDelay(lines string, delay time.Duration) {
for _, line := range strings.Split(lines, "\n") {
fmt.Println(line)
- time.Sleep(delay)
- }
-}
-
-func getBoolEnvvar(name string) bool {
- val := strings.ToLower(os.Getenv(name))
- return val != "" && val != "false" && val != "0"
-}
-
-func main() {
- method := ""
- if len(os.Args) > 1 {
- method = os.Args[1]
- }
-
- test := getBoolEnvvar("RAUC_MOCK_TEST")
- failure := getBoolEnvvar("RAUC_MOCK_FAIL")
-
- delay := 500 * time.Millisecond
- if test {
- delay = 10 * time.Millisecond
- }
-
- switch method {
- case "install":
- if failure {
- printLinesWithDelay(outputFailure, delay)
- } else {
- printLinesWithDelay(outputSuccess, delay)
- }
- case "status":
- if os.Args[2] != "--output-format=json" {
- fmt.Println("output format must be json")
- os.Exit(1)
- }
- fmt.Println(statusJson)
- default:
- fmt.Println("invalid method")
- os.Exit(1)
+ time.Sleep(500 * time.Millisecond)
}
}
diff --git a/src/fixtures/testcmd/testcmd.go b/src/fixtures/testcmd/testcmd.go
deleted file mode 100644
index b157bea..0000000
--- a/src/fixtures/testcmd/testcmd.go
+++ /dev/null
@@ -1,8 +0,0 @@
-package testcmd
-
-//nolint:lll
-const (
- RaucStatus = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock status --output-format=json"
- RaucInstall = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock install"
- Reboot = "touch /tmp/sebrauc_reboot_test"
-)
diff --git a/src/fixtures/testfiles/htpasswd b/src/fixtures/testfiles/htpasswd
deleted file mode 100644
index c6382c7..0000000
--- a/src/fixtures/testfiles/htpasswd
+++ /dev/null
@@ -1,3 +0,0 @@
-plain:1234
-md5:$apr1$V2wxHBfb$gBU2yIYjTIeciKapglql6/
-bcrypt:$2y$05$f9rV6uTQEEnNR1saPksExOR31LauUZzpLDhpCrodAvxX3zZ6nLy12
diff --git a/src/fixtures/testfiles/os-release b/src/fixtures/testfiles/os-release
deleted file mode 100644
index 84a41a1..0000000
--- a/src/fixtures/testfiles/os-release
+++ /dev/null
@@ -1,5 +0,0 @@
-ID=tsgrain
-NAME="TSGRain distro"
-VERSION="0.0.1"
-VERSION_ID=0.0.1
-PRETTY_NAME="TSGRain distro 0.0.1"
diff --git a/src/fixtures/testfiles/sebrauc.toml b/src/fixtures/testfiles/sebrauc.toml
deleted file mode 100644
index 2700a3f..0000000
--- a/src/fixtures/testfiles/sebrauc.toml
+++ /dev/null
@@ -1,32 +0,0 @@
-# SEBRAUC config file for testing
-# Dont use for real, the commands and paths are not correct
-
-Tmpdir = "/var/tmp/"
-
-[Server]
-Address = "127.0.0.1"
-Port = 8001
-
-[Server.Websocket]
-Ping = 30
-Timeout = 10
-
-[Server.Compression]
-Gzip = "max"
-Brotli = "false"
-
-[Authentication]
-Enable = true
-PasswdFile = "/etc/htpasswd"
-
-[Sysinfo]
-ReleaseFile = "/etc/release"
-NameKey = "PRETTY_NAME"
-VersionKey = "VER"
-HostnameFile = "/etc/hn"
-UptimeFile = "/proc/up"
-
-[Commands]
-RaucStatus = "myrauc status --output-format=json"
-RaucInstall = "myrauc install"
-Reboot = "reboot"
diff --git a/src/fixtures/testutil.go b/src/fixtures/testutil.go
index 624eb29..ee2f352 100644
--- a/src/fixtures/testutil.go
+++ b/src/fixtures/testutil.go
@@ -3,11 +3,8 @@ package fixtures
import (
"os"
"path/filepath"
- "strings"
)
-var envPrefixes = []string{"SEBRAUC", "RAUC_MOCK"}
-
func doesFileExist(filepath string) bool {
_, err := os.Stat(filepath)
return !os.IsNotExist(err)
@@ -41,20 +38,3 @@ func GetTestfilesDir() string {
CdProjectRoot()
return filepath.Join("src", "fixtures", "testfiles")
}
-
-func ResetEnv() {
- for _, envvar := range os.Environ() {
- split := strings.SplitN(envvar, "=", 2)
- if len(split) != 2 {
- continue
- }
-
- key := split[0]
-
- for _, prefix := range envPrefixes {
- if strings.HasPrefix(key, prefix) {
- _ = os.Unsetenv(key)
- }
- }
- }
-}
diff --git a/src/fixtures/testutil_test.go b/src/fixtures/testutil_test.go
index 79941f3..44b32ef 100644
--- a/src/fixtures/testutil_test.go
+++ b/src/fixtures/testutil_test.go
@@ -35,15 +35,3 @@ func TestCdProjectRoot(t *testing.T) {
CdProjectRoot()
assert.True(t, doesFileExist("go.sum"))
}
-
-func TestResetEnv(t *testing.T) {
- os.Setenv("RAUC_MOCK_TEST", "1")
- os.Setenv("SEBRAUC_PORT", "8001")
-
- ResetEnv()
-
- _, exists := os.LookupEnv("RAUC_MOCK_TEST")
- assert.False(t, exists)
- _, exists = os.LookupEnv("SEBRAUC_PORT")
- assert.False(t, exists)
-}
diff --git a/src/main.go b/src/main.go
index 3fcd902..2e83122 100644
--- a/src/main.go
+++ b/src/main.go
@@ -1,45 +1,22 @@
package main
import (
- "flag"
"fmt"
"log"
- "os"
- "code.thetadev.de/TSGRain/SEBRAUC/src/config"
"code.thetadev.de/TSGRain/SEBRAUC/src/server"
"code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode"
)
-const titleArt = ` _____ __________ ____ ___ __ ________
- / ___// ____/ __ )/ __ \/ | / / / / ____/
- \__ \/ __/ / __ / /_/ / /| |/ / / / /
- ___/ / /___/ /_/ / _, _/ ___ / /_/ / /___
-/____/_____/_____/_/ |_/_/ |_\____/\____/ `
-
func main() {
- run(os.Args[1:])
-}
+ fmt.Println("SEBRAUC " + util.Version())
-func run(args []string) {
- fmt.Println(titleArt + util.Version() + "\n")
-
- cmdFlags := flag.NewFlagSet("sebrauc", flag.ExitOnError)
- port := cmdFlags.Int("p", 0, "HTTP port")
- cfgPath := cmdFlags.String("c", "", "Config file path")
- _ = cmdFlags.Parse(args)
-
- if mode.IsDev() {
+ if util.TestMode {
fmt.Println("Test mode active - no update operations are executed.")
fmt.Println("Build with -tags prod to enable live mode.")
}
- cfg := config.GetWithFlags(*cfgPath, *port)
-
- fmt.Printf("Starting server at %s:%d\n", cfg.Server.Address, cfg.Server.Port)
-
- srv := server.NewServer(cfg)
+ srv := server.NewServer(":8080")
err := srv.Run()
if err != nil {
log.Fatalln(err)
diff --git a/src/model/error.go b/src/model/error.go
deleted file mode 100644
index b2f1396..0000000
--- a/src/model/error.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package model
-
-// Error model
-//
-// The Error contains error relevant information.
-//
-//swagger:model Error
-type Error struct {
- // The general error message according to HTTP specification.
- //
- // required: true
- // example: Unauthorized
- Error string `json:"error"`
- // The http error code.
- //
- // required: true
- // example: 500
- StatusCode int `json:"status_code"`
- // Concrete error message.
- //
- // required: true
- // example: already running
- Message string `json:"msg"`
-}
diff --git a/src/model/rauc_status.go b/src/model/rauc_status.go
deleted file mode 100644
index eb0d92d..0000000
--- a/src/model/rauc_status.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package model
-
-// RaucStatus model
-//
-// RaucStatus contains information about the current RAUC updater status.
-//
-//swagger:model RaucStatus
-//nolint:lll
-type RaucStatus struct {
- // True if the installer is running
- // required: true
- Installing bool `json:"installing"`
-
- // Installation progress
- // required: true
- // minimum: 0
- // maximum: 100
- Percent int `json:"percent"`
-
- // Current installation step
- // required: true
- // example: Copying image to rootfs.0
- Message string `json:"message"`
-
- // Installation error message
- // required: true
- // example: Failed to check bundle identifier: Invalid identifier.
- LastError string `json:"last_error"`
-
- // Full command line output of the current installation
- // required: true
- // example: 0% Installing 0% Determining slot states 20% Determining slot states done
- Log string `json:"log"`
-}
diff --git a/src/model/status_message.go b/src/model/status_message.go
deleted file mode 100644
index d01fe03..0000000
--- a/src/model/status_message.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package model
-
-// StatusMessage model
-//
-// StatusMessage contains the status of an operation.
-//
-//swagger:model StatusMessage
-type StatusMessage struct {
- // Is operation successful?
- // required: true
- Success bool `json:"success"`
-
- // Status message text
- // required: true
- // example: Update started
- Msg string `json:"msg"`
-}
diff --git a/src/model/system_info.go b/src/model/system_info.go
deleted file mode 100644
index 6c923d7..0000000
--- a/src/model/system_info.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package model
-
-// SystemInfo model
-//
-// SystemInfo contains information about the running system.
-//
-//swagger:model SystemInfo
-type SystemInfo struct {
- // Hostname of the system
- // required: true
- // example: raspberrypi3
- Hostname string `json:"hostname"`
-
- // Name of the os distribution
- // required: true
- // example: Poky
- OsName string `json:"os_name"`
-
- // Operating system version
- // required: true
- // example: 1.0.2
- OsVersion string `json:"os_version"`
-
- // System uptime in seconds
- // required: true
- // example: 5832
- Uptime int `json:"uptime"`
-
- // Compatible firmware name
- // required: true
- // example: Poky
- RaucCompatible string `json:"rauc_compatible"`
-
- // Compatible firmware variant
- // required: true
- // example: rpi-prod
- RaucVariant string `json:"rauc_variant"`
-
- // List of RAUC root filesystems
- // required: true
- RaucRootfs map[string]Rootfs `json:"rauc_rootfs"`
-}
-
-//swagger:model Rootfs
-type Rootfs struct {
- // Block device
- // required: true
- // example: /dev/mmcblk0p2
- Device string `json:"device"`
-
- // Filesystem
- // required: true
- // example: ext4
- Type string `json:"type"`
-
- // Mount path (null when not mounted)
- // required: true
- // nullable: true
- // example: /
- Mountpoint *string `json:"mountpoint"`
-
- // Is the filesystem bootable?
- // required: true
- Bootable bool `json:"bootable"`
-
- // Is the filesystem booted?
- // required: true
- Booted bool `json:"booted"`
-
- // Is the filesystem the next boot target?
- // required: true
- Primary bool `json:"primary"`
-
- Bootname string `json:"-"`
-}
diff --git a/src/rauc/rauc.go b/src/rauc/rauc.go
index d4b81dc..7585285 100644
--- a/src/rauc/rauc.go
+++ b/src/rauc/rauc.go
@@ -9,7 +9,6 @@ import (
"strconv"
"sync"
- "code.thetadev.de/TSGRain/SEBRAUC/src/model"
"code.thetadev.de/TSGRain/SEBRAUC/src/util"
)
@@ -19,26 +18,32 @@ var (
)
type Rauc struct {
- cmdRaucInstall string
- bc util.Broadcaster
- status model.RaucStatus
- runningMtx sync.Mutex
+ broadcast chan string
+ status RaucStatus
+ runningMtx sync.Mutex
}
-func New(cmdRaucInstall string) *Rauc {
- return &Rauc{
- cmdRaucInstall: cmdRaucInstall,
+type RaucStatus struct {
+ Installing bool `json:"installing"`
+ Percent int `json:"percent"`
+ Message string `json:"message"`
+ LastError string `json:"last_error"`
+ Log string `json:"log"`
+}
+
+func NewRauc(broadcast chan string) *Rauc {
+ r := &Rauc{
+ broadcast: broadcast,
}
-}
-func (r *Rauc) SetBroadcaster(bc util.Broadcaster) {
- r.bc = bc
- r.bcStatus()
+ r.broadcast <- r.GetStatusJson()
+
+ return r
}
func (r *Rauc) completed(updateFile string) {
r.status.Installing = false
- r.bcStatus()
+ r.broadcast <- r.GetStatusJson()
_ = os.Remove(updateFile)
}
@@ -60,12 +65,12 @@ func (r *Rauc) RunRauc(updateFile string) error {
}
// Reset installer
- r.status = model.RaucStatus{
+ r.status = RaucStatus{
Installing: true,
}
- r.bcStatus()
+ r.broadcast <- r.GetStatusJson()
- cmd := util.CommandFromString(r.cmdRaucInstall + " " + updateFile)
+ cmd := util.CommandFromString(fmt.Sprintf("%s %s", util.UpdateCmd, updateFile))
readPipe, _ := cmd.StdoutPipe()
cmd.Stderr = cmd.Stdout
@@ -95,7 +100,7 @@ func (r *Rauc) RunRauc(updateFile string) error {
}
if hasUpdate {
- r.bcStatus()
+ r.broadcast <- r.GetStatusJson()
}
}
}()
@@ -117,19 +122,11 @@ func (r *Rauc) RunRauc(updateFile string) error {
return nil
}
-func (r *Rauc) GetStatus() model.RaucStatus {
+func (r *Rauc) GetStatus() RaucStatus {
return r.status
}
-func (r *Rauc) GetStatusJson() []byte {
- statusJson, err := json.Marshal(r.status)
- if err != nil {
- return []byte{}
- }
-
- return statusJson
-}
-
-func (r *Rauc) bcStatus() {
- r.bc.Broadcast(r.GetStatusJson())
+func (r *Rauc) GetStatusJson() string {
+ statusJson, _ := json.Marshal(r.status)
+ return string(statusJson)
}
diff --git a/src/rauc/rauc_test.go b/src/rauc/rauc_test.go
deleted file mode 100644
index 19595cd..0000000
--- a/src/rauc/rauc_test.go
+++ /dev/null
@@ -1,118 +0,0 @@
-package rauc
-
-import (
- "os"
- "path/filepath"
- "testing"
- "time"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures"
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "github.com/stretchr/testify/assert"
-)
-
-type broadcasterMock struct {
- messages []string
-}
-
-func (b *broadcasterMock) Broadcast(msg []byte) {
- b.messages = append(b.messages, string(msg))
-}
-
-func TestRauc(t *testing.T) {
- //nolint:lll
- tests := []struct {
- name string
- fail string
- messages []string
- }{
- {
- name: "ok",
- fail: "",
- messages: []string{
- "{\"installing\":false,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}",
- "{\"installing\":true,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}",
- "{\"installing\":true,\"percent\":0,\"message\":\"Installing\",\"last_error\":\"\",\"log\":\"0% Installing\\n\"}",
- "{\"installing\":true,\"percent\":0,\"message\":\"Determining slot states\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n\"}",
- "{\"installing\":true,\"percent\":20,\"message\":\"Determining slot states done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n\"}",
- "{\"installing\":true,\"percent\":20,\"message\":\"Checking bundle\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n\"}",
- "{\"installing\":true,\"percent\":20,\"message\":\"Verifying signature\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n\"}",
- "{\"installing\":true,\"percent\":40,\"message\":\"Verifying signature done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n\"}",
- "{\"installing\":true,\"percent\":40,\"message\":\"Checking bundle done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n\"}",
- "{\"installing\":true,\"percent\":40,\"message\":\"Checking manifest contents\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n\"}",
- "{\"installing\":true,\"percent\":60,\"message\":\"Checking manifest contents done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n\"}",
- "{\"installing\":true,\"percent\":60,\"message\":\"Determining target install group\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n\"}",
- "{\"installing\":true,\"percent\":80,\"message\":\"Determining target install group done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n\"}",
- "{\"installing\":true,\"percent\":80,\"message\":\"Updating slots\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n\"}",
- "{\"installing\":true,\"percent\":80,\"message\":\"Checking slot rootfs.0\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n\"}",
- "{\"installing\":true,\"percent\":90,\"message\":\"Checking slot rootfs.0 done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n\"}",
- "{\"installing\":true,\"percent\":90,\"message\":\"Copying image to rootfs.0\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n\"}",
- "{\"installing\":true,\"percent\":100,\"message\":\"Copying image to rootfs.0 done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n\"}",
- "{\"installing\":true,\"percent\":100,\"message\":\"Updating slots done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n\"}",
- "{\"installing\":true,\"percent\":100,\"message\":\"Installing done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n100% Installing done.\\n\"}",
- "{\"installing\":false,\"percent\":100,\"message\":\"Installing done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n20% Verifying signature\\n40% Verifying signature done.\\n40% Checking bundle done.\\n40% Checking manifest contents\\n60% Checking manifest contents done.\\n60% Determining target install group\\n80% Determining target install group done.\\n80% Updating slots\\n80% Checking slot rootfs.0\\n90% Checking slot rootfs.0 done.\\n90% Copying image to rootfs.0\\n100% Copying image to rootfs.0 done.\\n100% Updating slots done.\\n100% Installing done.\\nInstalling `/app/tsgrain-update-raspberrypi3.raucb` succeeded\\n\"}",
- },
- },
- {
- name: "fail",
- fail: "1",
- messages: []string{
- "{\"installing\":false,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}",
- "{\"installing\":true,\"percent\":0,\"message\":\"\",\"last_error\":\"\",\"log\":\"\"}",
- "{\"installing\":true,\"percent\":0,\"message\":\"Installing\",\"last_error\":\"\",\"log\":\"0% Installing\\n\"}",
- "{\"installing\":true,\"percent\":0,\"message\":\"Determining slot states\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n\"}",
- "{\"installing\":true,\"percent\":20,\"message\":\"Determining slot states done.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n\"}",
- "{\"installing\":true,\"percent\":20,\"message\":\"Checking bundle\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n\"}",
- "{\"installing\":true,\"percent\":40,\"message\":\"Checking bundle failed.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n\"}",
- "{\"installing\":true,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\n\"}",
- "{\"installing\":true,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\nLastError: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\\n\"}",
- "{\"installing\":false,\"percent\":100,\"message\":\"Installing failed.\",\"last_error\":\"Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\",\"log\":\"0% Installing\\n0% Determining slot states\\n20% Determining slot states done.\\n20% Checking bundle\\n40% Checking bundle failed.\\n100% Installing failed.\\nLastError: Failed to check bundle identifier: Invalid identifier. Did you pass a valid RAUC bundle?\\nInstalling /app/demo` failed\\n\"}",
- },
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- fixtures.ResetEnv()
- defer fixtures.ResetEnv()
-
- os.Setenv("RAUC_MOCK_TEST", "1")
- os.Setenv("RAUC_MOCK_FAIL", tt.fail)
-
- updater := New(testcmd.RaucInstall)
- bc := &broadcasterMock{}
- updater.SetBroadcaster(bc)
-
- testfile := createTmpfile()
-
- err := updater.RunRauc(testfile)
- assert.NoError(t, err)
-
- // Dont run multiple updates concurrently
- err = updater.RunRauc(testfile)
- assert.ErrorIs(t, err, util.ErrAlreadyRunning)
-
- // Wait for updater to finish
- for updater.GetStatus().Installing {
- time.Sleep(50 * time.Millisecond)
- }
-
- assert.False(t, util.DoesFileExist(testfile), "update file was not deleted")
-
- assert.Equal(t, tt.messages, bc.messages)
- })
- }
-}
-
-func createTmpfile() string {
- tmpdir := util.GetTmpdir("")
-
- tmpfile := filepath.Join(tmpdir, "test.raucb")
- _, err := os.Create(tmpfile)
- if err != nil {
- panic(err)
- }
-
- return tmpfile
-}
diff --git a/src/server/hub.go b/src/server/hub.go
new file mode 100644
index 0000000..77e1dfd
--- /dev/null
+++ b/src/server/hub.go
@@ -0,0 +1,98 @@
+package server
+
+import (
+ "log"
+ "sync"
+
+ "github.com/gofiber/websocket/v2"
+)
+
+type hubClient struct{}
+
+type MessageHub struct {
+ Broadcast chan string
+
+ clients map[*websocket.Conn]hubClient
+ register chan *websocket.Conn
+ unregister chan *websocket.Conn
+ lastMessage string
+
+ running bool
+ runningMtx sync.Mutex
+}
+
+func NewHub() *MessageHub {
+ return &MessageHub{
+ clients: make(map[*websocket.Conn]hubClient),
+ register: make(chan *websocket.Conn),
+ Broadcast: make(chan string, 5),
+ unregister: make(chan *websocket.Conn),
+ }
+}
+
+func (hub *MessageHub) sendMessage(conn *websocket.Conn, message string) {
+ if err := conn.WriteMessage(
+ websocket.TextMessage, []byte(message)); err != nil {
+ log.Println("write error:", err)
+
+ _ = conn.WriteMessage(websocket.CloseMessage, []byte{})
+ _ = conn.Close()
+ delete(hub.clients, conn)
+ }
+}
+
+func (hub *MessageHub) Run() {
+ hub.runningMtx.Lock()
+ isRunning := hub.running
+ hub.running = true
+ hub.runningMtx.Unlock()
+
+ if isRunning {
+ return
+ }
+
+ for {
+ select {
+ case conn := <-hub.register:
+ hub.clients[conn] = hubClient{}
+ log.Println("connection registered")
+
+ case message := <-hub.Broadcast:
+ log.Println("message received:", message)
+ hub.lastMessage = message
+
+ // Send the message to all clients
+ for conn := range hub.clients {
+ hub.sendMessage(conn, message)
+ }
+
+ case conn := <-hub.unregister:
+ // Remove the client from the hub
+ delete(hub.clients, conn)
+
+ log.Println("connection unregistered")
+ }
+ }
+}
+
+func (hub *MessageHub) Handler(conn *websocket.Conn) {
+ // When the function returns, unregister the client and close the connection
+ defer func() {
+ hub.unregister <- conn
+ conn.Close()
+ }()
+
+ // Register the client
+ hub.register <- conn
+
+ if hub.lastMessage != "" {
+ hub.sendMessage(conn, hub.lastMessage)
+ }
+
+ for {
+ _, _, err := conn.ReadMessage()
+ if err != nil {
+ return // Calls the deferred function, i.e. closes the connection on error
+ }
+ }
+}
diff --git a/src/server/middleware/authentication.go b/src/server/middleware/authentication.go
deleted file mode 100644
index c2f33f2..0000000
--- a/src/server/middleware/authentication.go
+++ /dev/null
@@ -1,36 +0,0 @@
-package middleware
-
-import (
- "net/http"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "github.com/gin-gonic/gin"
- "github.com/tg123/go-htpasswd"
-)
-
-var pwdFilePaths = []string{"htpasswd", "/etc/sebrauc/htpasswd"}
-
-// Authentication requires HTTP basic auth or an active session
-func Authentication(pwdFile string) gin.HandlerFunc {
- fpath, err := util.FindFile(pwdFile, pwdFilePaths, nil)
- if err != nil {
- panic("passwd file not found: " + err.Error())
- }
-
- myauth, err := htpasswd.New(fpath, htpasswd.DefaultSystems, nil)
- if err != nil {
- panic(err)
- }
-
- return func(c *gin.Context) {
- if user, pass, ok := c.Request.BasicAuth(); ok {
- if myauth.Match(user, pass) {
- c.Set(gin.AuthUserKey, user)
- return
- }
- }
-
- c.Header("WWW-Authenticate", "Basic realm=\"Authorization Required\"")
- c.AbortWithStatus(http.StatusUnauthorized)
- }
-}
diff --git a/src/server/middleware/authentication_test.go b/src/server/middleware/authentication_test.go
deleted file mode 100644
index f430032..0000000
--- a/src/server/middleware/authentication_test.go
+++ /dev/null
@@ -1,54 +0,0 @@
-package middleware
-
-import (
- "net/http"
- "net/http/httptest"
- "path/filepath"
- "testing"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
-)
-
-func TestAuthentication(t *testing.T) {
- testfiles := fixtures.GetTestfilesDir()
- pwdfile := filepath.Join(testfiles, "htpasswd")
-
- router := gin.New()
- router.Use(Authentication(pwdfile))
- router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "HelloWorld") })
-
- tests := []struct {
- name string
- }{
- {name: "plain"},
- {name: "md5"},
- {name: "bcrypt"},
- }
-
- for _, tt := range tests {
- t.Run(tt.name+"_ok", func(t *testing.T) {
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/", nil)
- req.SetBasicAuth(tt.name, "1234")
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
- assert.Equal(t, "HelloWorld", w.Body.String())
- assert.Empty(t, w.Header().Get("WWW-Authenticate"))
- })
- }
-
- t.Run("fail", func(t *testing.T) {
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/", nil)
- req.SetBasicAuth("plain", "asdf")
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusUnauthorized, w.Code)
- assert.Empty(t, w.Body.String())
- assert.Equal(t, "Basic realm=\"Authorization Required\"",
- w.Header().Get("WWW-Authenticate"))
- })
-}
diff --git a/src/server/middleware/cache.go b/src/server/middleware/cache.go
deleted file mode 100644
index 8c97b26..0000000
--- a/src/server/middleware/cache.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package middleware
-
-import "github.com/gin-gonic/gin"
-
-func Cache(c *gin.Context) {
- c.Writer.Header().Set("Cache-Control", "public, max-age=604800, immutable")
-}
diff --git a/src/server/middleware/cache_test.go b/src/server/middleware/cache_test.go
deleted file mode 100644
index eaf7f84..0000000
--- a/src/server/middleware/cache_test.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package middleware
-
-import (
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
-)
-
-func TestCache(t *testing.T) {
- router := gin.New()
- router.Use(Cache)
- router.GET("/", func(c *gin.Context) { c.String(http.StatusOK, "HelloWorld") })
-
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/", nil)
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
- assert.Equal(t, "HelloWorld", w.Body.String())
- assert.Equal(t, "public, max-age=604800, immutable",
- w.Header().Get("Cache-Control"))
-}
diff --git a/src/server/middleware/compression.go b/src/server/middleware/compression.go
deleted file mode 100644
index 0fb0100..0000000
--- a/src/server/middleware/compression.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package middleware
-
-import (
- "code.thetadev.de/TSGRain/ginzip"
- "github.com/gin-gonic/gin"
-)
-
-func Compression(gzip, brotli string) gin.HandlerFunc {
- opts := ginzip.DefaultOptions()
- opts.GzipLevel = gzip
- opts.BrotliLevel = brotli
-
- return ginzip.New(opts)
-}
diff --git a/src/server/middleware/error_handler.go b/src/server/middleware/error_handler.go
deleted file mode 100644
index 94aac0f..0000000
--- a/src/server/middleware/error_handler.go
+++ /dev/null
@@ -1,58 +0,0 @@
-package middleware
-
-import (
- "errors"
- "fmt"
- "net/http"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/model"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
- nice "github.com/ekyoung/gin-nice-recovery"
- "github.com/gin-gonic/gin"
-)
-
-// ErrorHandler creates a gin middleware for handling errors.
-func ErrorHandler(isApi bool) gin.HandlerFunc {
- return func(c *gin.Context) {
- c.Next()
-
- if len(c.Errors) > 0 {
- for _, e := range c.Errors {
- writeError(c, e.Err, isApi)
- }
- }
- }
-}
-
-func PanicHandler(isApi bool) gin.HandlerFunc {
- return nice.Recovery(func(c *gin.Context, err interface{}) {
- writeError(c, fmt.Errorf("[PANIC] %s", err), isApi)
- })
-}
-
-func writeError(c *gin.Context, err error, isApi bool) {
- status := http.StatusInternalServerError
-
- var httpErr util.HttpError
- if errors.As(err, &httpErr) {
- status = httpErr.StatusCode()
- }
-
- // only write error message if there is no content
- if c.Writer.Size() != -1 {
- c.Status(status)
- return
- }
-
- if isApi {
- // Machine-readable JSON error message
- c.JSON(status, &model.Error{
- Error: http.StatusText(status),
- StatusCode: status,
- Message: err.Error(),
- })
- } else {
- // Human-readable error message
- c.String(status, "%d %s: %s", status, http.StatusText(status), err.Error())
- }
-}
diff --git a/src/server/middleware/error_handler_test.go b/src/server/middleware/error_handler_test.go
deleted file mode 100644
index 4efedb1..0000000
--- a/src/server/middleware/error_handler_test.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package middleware
-
-import (
- "errors"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
-)
-
-func TestErrorHandler(t *testing.T) {
- tests := []struct {
- name string
- controller gin.HandlerFunc
- isApi bool
- expectResponse string
- expectStatus int
- }{
- {
- name: "error",
- controller: controllerError,
- isApi: false,
- expectResponse: "400 Bad Request: error test",
- expectStatus: http.StatusBadRequest,
- },
- {
- name: "error_api",
- controller: controllerError,
- isApi: true,
- //nolint:lll
- expectResponse: `{"error":"Bad Request","status_code":400,"msg":"error test"}`,
- expectStatus: http.StatusBadRequest,
- },
- {
- name: "generic_error",
- controller: controllerErrorGeneric,
- isApi: false,
- expectResponse: "500 Internal Server Error: generic error",
- expectStatus: http.StatusInternalServerError,
- },
- {
- name: "generic_error_api",
- controller: controllerErrorGeneric,
- isApi: true,
- //nolint:lll
- expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"generic error"}`,
- expectStatus: http.StatusInternalServerError,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- router := gin.New()
- router.Use(ErrorHandler(tt.isApi))
- router.GET("/", tt.controller)
-
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/", nil)
- router.ServeHTTP(w, req)
-
- assert.Equal(t, tt.expectStatus, w.Code)
- assert.Equal(t, tt.expectResponse, w.Body.String())
- })
- }
-}
-
-func TestPanicHandler(t *testing.T) {
- tests := []struct {
- name string
- controller gin.HandlerFunc
- isApi bool
- expectResponse string
- expectStatus int
- }{
- {
- name: "panic",
- controller: controllerPanic,
- isApi: false,
- expectResponse: "500 Internal Server Error: [PANIC] panic message",
- expectStatus: http.StatusInternalServerError,
- },
- {
- name: "panic_api",
- controller: controllerPanic,
- isApi: true,
- //nolint:lll
- expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"[PANIC] panic message"}`,
- expectStatus: http.StatusInternalServerError,
- },
- {
- name: "panic_w_error",
- controller: controllerPanicErr,
- isApi: false,
- expectResponse: "500 Internal Server Error: [PANIC] panic message in error",
- expectStatus: http.StatusInternalServerError,
- },
- {
- name: "panic_w_error_api",
- controller: controllerPanicErr,
- isApi: true,
- //nolint:lll
- expectResponse: `{"error":"Internal Server Error","status_code":500,"msg":"[PANIC] panic message in error"}`,
- expectStatus: http.StatusInternalServerError,
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- router := gin.New()
- router.Use(PanicHandler(tt.isApi))
- router.GET("/", tt.controller)
-
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/", nil)
- router.ServeHTTP(w, req)
-
- assert.Equal(t, tt.expectStatus, w.Code)
- assert.Equal(t, tt.expectResponse, w.Body.String())
- })
- }
-}
-
-func controllerError(c *gin.Context) {
- c.Error(util.HttpErrNew("error test", http.StatusBadRequest))
-}
-
-func controllerErrorGeneric(c *gin.Context) {
- c.Error(errors.New("generic error"))
-}
-
-func controllerPanic(c *gin.Context) {
- panic("panic message")
-}
-
-func controllerPanicErr(c *gin.Context) {
- panic(errors.New("panic message in error"))
-}
diff --git a/src/server/server.go b/src/server/server.go
index fe41c45..b9ca070 100644
--- a/src/server/server.go
+++ b/src/server/server.go
@@ -1,266 +1,158 @@
-// SEBRAUC
-//
-// # REST API for the SEBRAUC firmware updater
-//
-// ---
-// Schemes: http, https
-// Version: 0.2.0
-// License: MIT
-//
-// swagger:meta
package server
import (
"errors"
"fmt"
"net/http"
+ "strings"
"time"
- "code.thetadev.de/TSGRain/SEBRAUC/src/config"
- "code.thetadev.de/TSGRain/SEBRAUC/src/model"
"code.thetadev.de/TSGRain/SEBRAUC/src/rauc"
- "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware"
- "code.thetadev.de/TSGRain/SEBRAUC/src/server/stream"
- "code.thetadev.de/TSGRain/SEBRAUC/src/server/swagger"
- "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo"
"code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode"
"code.thetadev.de/TSGRain/SEBRAUC/ui"
- "github.com/gin-contrib/cors"
- "github.com/gin-gonic/gin"
+ "github.com/gofiber/fiber/v2"
+ "github.com/gofiber/fiber/v2/middleware/compress"
+ "github.com/gofiber/fiber/v2/middleware/cors"
+ "github.com/gofiber/fiber/v2/middleware/filesystem"
+ "github.com/gofiber/fiber/v2/middleware/logger"
+ "github.com/gofiber/websocket/v2"
"github.com/google/uuid"
)
type SEBRAUCServer struct {
- config *config.Config
- streamer *stream.API
- updater *rauc.Rauc
- sysinfo *sysinfo.Sysinfo
- tmpdir string
+ address string
+ raucUpdater *rauc.Rauc
+ hub *MessageHub
+ tmpdir string
}
-func NewServer(config *config.Config) *SEBRAUCServer {
- updater := rauc.New(config.Commands.RaucInstall)
- streamer := stream.New(
- time.Duration(config.Server.Websocket.Ping)*time.Second,
- time.Duration(config.Server.Websocket.Timeout)*time.Second,
- []string{},
- )
- sysinfo := sysinfo.New(
- config.Commands.RaucStatus,
- config.Sysinfo.ReleaseFile,
- config.Sysinfo.NameKey,
- config.Sysinfo.VersionKey,
- config.Sysinfo.HostnameFile,
- config.Sysinfo.UptimeFile,
- )
+type statusMessage struct {
+ Success bool `json:"success"`
+ Msg string `json:"msg"`
+}
- updater.SetBroadcaster(streamer)
+func NewServer(address string) *SEBRAUCServer {
+ hub := NewHub()
- tmpdir := util.GetTmpdir(config.Tmpdir)
+ raucUpdater := rauc.NewRauc(hub.Broadcast)
+
+ tmpdir, err := util.GetTmpdir()
+ if err != nil {
+ panic(err)
+ }
return &SEBRAUCServer{
- config: config,
- updater: updater,
- streamer: streamer,
- sysinfo: sysinfo,
- tmpdir: tmpdir,
+ address: address,
+ raucUpdater: raucUpdater,
+ hub: hub,
+ tmpdir: tmpdir,
}
}
-func (srv *SEBRAUCServer) getRouter() *gin.Engine {
- router := gin.New()
- router.Use(gin.Logger())
- _ = router.SetTrustedProxies(nil)
-
- if mode.IsDev() {
- router.Use(cors.Default())
- }
-
- router.Use(middleware.ErrorHandler(false), middleware.PanicHandler(false))
- router.NoRoute(func(c *gin.Context) { c.Error(util.ErrPageNotFound) })
-
- if srv.config.Authentication.Enable {
- router.Use(middleware.Authentication(srv.config.Authentication.PasswdFile))
- }
-
- api := router.Group("/api",
- middleware.ErrorHandler(true), middleware.PanicHandler(true))
-
- // API ROUTES
- api.GET("/ws", srv.streamer.Handle)
- api.GET("/status", srv.controllerStatus)
- api.GET("/info", srv.controllerInfo)
-
- api.POST("/update", srv.controllerUpdate)
- api.POST("/reboot", srv.controllerReboot)
-
- // Error routes for testing
- if mode.IsDev() {
- router.GET("/error", srv.controllerError)
- router.GET("/panic", srv.controllerPanic)
-
- api.GET("/error", srv.controllerError)
- api.GET("/panic", srv.controllerPanic)
- }
-
- // UI
- uiGroup := router.Group("/", middleware.Compression(
- srv.config.Server.Compression.Gzip,
- srv.config.Server.Compression.Brotli),
- )
- ui.Register(uiGroup)
- swagger.Register(uiGroup)
-
- return router
-}
-
func (srv *SEBRAUCServer) Run() error {
- router := srv.getRouter()
+ app := fiber.New(fiber.Config{
+ AppName: "SEBRAUC",
+ BodyLimit: 1024 * 1024 * 1024,
+ ErrorHandler: errorHandler,
+ DisableStartupMessage: true,
+ })
- return router.Run(fmt.Sprintf("%s:%d",
- srv.config.Server.Address, srv.config.Server.Port))
+ app.Use(logger.New())
+
+ app.Use(compress.New(compress.Config{
+ Next: func(c *fiber.Ctx) bool {
+ return strings.HasPrefix(c.Path(), "/api")
+ },
+ }))
+
+ // just for testing
+ app.Use("/api", cors.New())
+
+ app.Use("/api/ws", func(c *fiber.Ctx) error {
+ // IsWebSocketUpgrade returns true if the client
+ // requested upgrade to the WebSocket protocol.
+ if websocket.IsWebSocketUpgrade(c) {
+ c.Locals("allowed", true)
+ return c.Next()
+ }
+ return fiber.ErrUpgradeRequired
+ })
+
+ app.Use("/", filesystem.New(filesystem.Config{
+ Root: http.FS(ui.Assets),
+ PathPrefix: ui.AssetsDir,
+ MaxAge: 7200,
+ }))
+
+ // ROUTES
+ app.Get("/api/ws", websocket.New(srv.hub.Handler))
+ app.Post("/api/update", srv.controllerUpdate)
+ app.Get("/api/status", srv.controllerStatus)
+ app.Post("/api/reboot", srv.controllerReboot)
+
+ // Start messaging hub
+ go srv.hub.Run()
+
+ return app.Listen(srv.address)
}
-// swagger:operation POST /update startUpdate
-//
-// # Start the update process
-//
-// ---
-// consumes:
-// - multipart/form-data
-// produces: [application/json]
-// parameters:
-// - name: updateFile
-// in: formData
-// description: RAUC firmware image file (*.raucb)
-// required: true
-// type: file
-//
-// responses:
-//
-// 200:
-// description: Ok
-// schema:
-// $ref: "#/definitions/StatusMessage"
-// 409:
-// description: already running
-// schema:
-// $ref: "#/definitions/Error"
-// 500:
-// description: Server Error
-// schema:
-// $ref: "#/definitions/Error"
-func (srv *SEBRAUCServer) controllerUpdate(c *gin.Context) {
+func (srv *SEBRAUCServer) controllerUpdate(c *fiber.Ctx) error {
file, err := c.FormFile("updateFile")
if err != nil {
- c.Error(err)
- return
+ return err
}
uid, err := uuid.NewRandom()
if err != nil {
- c.Error(err)
- return
+ return err
}
updateFile := fmt.Sprintf("%s/update_%s.raucb", srv.tmpdir, uid.String())
- err = c.SaveUploadedFile(file, updateFile)
+ err = c.SaveFile(file, updateFile)
if err != nil {
- c.Error(err)
- return
+ return err
}
- err = srv.updater.RunRauc(updateFile)
+ err = srv.raucUpdater.RunRauc(updateFile)
if err == nil {
writeStatus(c, true, "Update started")
+ } else if errors.Is(err, util.ErrAlreadyRunning) {
+ return fiber.NewError(fiber.StatusConflict, "already running")
} else {
- c.Error(err)
- return
+ return err
}
+ return nil
}
-// swagger:operation GET /status getStatus
-//
-// # Get the current status of the RAUC updater
-//
-// ---
-// produces: [application/json]
-// responses:
-//
-// 200:
-// description: Ok
-// schema:
-// $ref: "#/definitions/RaucStatus"
-// 500:
-// description: Server Error
-// schema:
-// $ref: "#/definitions/Error"
-func (srv *SEBRAUCServer) controllerStatus(c *gin.Context) {
- c.JSON(http.StatusOK, srv.updater.GetStatus())
+func (srv *SEBRAUCServer) controllerStatus(c *fiber.Ctx) error {
+ c.Context().SetStatusCode(200)
+ _ = c.JSON(srv.raucUpdater.GetStatus())
+ return nil
}
-// swagger:operation GET /info getInfo
-//
-// # Get the current system info
-//
-// ---
-// produces: [application/json]
-// responses:
-//
-// 200:
-// description: Ok
-// schema:
-// $ref: "#/definitions/SystemInfo"
-// 500:
-// description: Server Error
-// schema:
-// $ref: "#/definitions/Error"
-func (srv *SEBRAUCServer) controllerInfo(c *gin.Context) {
- info, err := srv.sysinfo.GetSysinfo()
- if err != nil {
- c.Error(err)
- } else {
- c.JSON(http.StatusOK, info)
- }
-}
-
-// swagger:operation POST /reboot startReboot
-//
-// # Reboot the system
-//
-// ---
-// produces: [application/json]
-// responses:
-//
-// 200:
-// description: Ok
-// schema:
-// $ref: "#/definitions/StatusMessage"
-// 500:
-// description: Server Error
-// schema:
-// $ref: "#/definitions/Error"
-func (srv *SEBRAUCServer) controllerReboot(c *gin.Context) {
- go util.Reboot(srv.config.Commands.Reboot, 5*time.Second)
+func (srv *SEBRAUCServer) controllerReboot(c *fiber.Ctx) error {
+ go util.Reboot(5 * time.Second)
writeStatus(c, true, "System is rebooting")
+ return nil
}
-// controllerError throws an error for testing
-func (srv *SEBRAUCServer) controllerError(c *gin.Context) {
- c.Error(util.HttpErrNew("error test", http.StatusBadRequest))
+func errorHandler(c *fiber.Ctx, err error) error {
+ // API error handling
+ if strings.HasPrefix(c.Path(), "/api") {
+ writeStatus(c, false, err.Error())
+ }
+ return err
}
-// controllerPanic panics for testing
-func (srv *SEBRAUCServer) controllerPanic(c *gin.Context) {
- panic(errors.New("panic message"))
-}
-
-func writeStatus(c *gin.Context, success bool, msg string) {
- c.JSON(http.StatusOK, model.StatusMessage{
+func writeStatus(c *fiber.Ctx, success bool, msg string) {
+ _ = c.JSON(statusMessage{
Success: success,
Msg: msg,
})
+
+ if success {
+ c.Context().SetStatusCode(200)
+ }
}
diff --git a/src/server/server_test.go b/src/server/server_test.go
deleted file mode 100644
index 30b25b1..0000000
--- a/src/server/server_test.go
+++ /dev/null
@@ -1,206 +0,0 @@
-package server
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "mime/multipart"
- "net/http"
- "net/http/httptest"
- "os"
- "path/filepath"
- "regexp"
- "testing"
- "time"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/config"
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures"
- "code.thetadev.de/TSGRain/SEBRAUC/src/model"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
-)
-
-type testServer struct {
- srv *SEBRAUCServer
- router *gin.Engine
-}
-
-func newTestServer() *testServer {
- return newTestServerCfg(config.GetDefault())
-}
-
-func newTestServerCfg(cfg *config.Config) *testServer {
- sebraucServer := NewServer(cfg)
- router := sebraucServer.getRouter()
-
- return &testServer{
- srv: sebraucServer,
- router: router,
- }
-}
-
-func (srv *testServer) testRequest(t assert.TestingT, method string, url string,
- body io.Reader, contentType string,
-) *httptest.ResponseRecorder {
- req, err := http.NewRequest(method, url, body)
- req.Header.Set("Content-Type", contentType)
- assert.Nil(t, err)
-
- w := httptest.NewRecorder()
- srv.router.ServeHTTP(w, req)
-
- return w
-}
-
-func TestUpdate(t *testing.T) {
- fixtures.ResetEnv()
- defer fixtures.ResetEnv()
- util.RemoveTmpdir("")
-
- os.Setenv("RAUC_MOCK_TEST", "1")
-
- srv := newTestServer()
-
- updateContent := []byte("mock update file")
-
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("updateFile", "update.raucb")
- assert.Nil(t, err)
- _, err = part.Write(updateContent)
- assert.Nil(t, err)
- err = writer.Close()
- assert.Nil(t, err)
-
- w := srv.testRequest(t, "POST", "/api/update", body, writer.FormDataContentType())
-
- assert.Equal(t, 200, w.Code)
- assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
- assert.Equal(t,
- `{"success":true,"msg":"Update started"}`,
- w.Body.String(),
- )
-
- // Find update file
- tmpdir := util.GetTmpdir("")
- //nolint:lll
- updateFileExp := regexp.MustCompile(
- `update_[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\.raucb`)
- updateFile := ""
-
- tmpdirObj, err := os.Open(tmpdir)
- assert.Nil(t, err)
- list, err := tmpdirObj.ReadDir(-1)
- assert.Nil(t, err)
-
- for _, f := range list {
- if updateFileExp.MatchString(f.Name()) {
- updateFile = filepath.Join(tmpdir, f.Name())
- break
- }
- }
- assert.NotEmpty(t, updateFile, "update file not found")
-
- // Check update file
- content, err := os.ReadFile(updateFile)
- assert.Nil(t, err)
- assert.Equal(t, updateContent, content)
-
- // Wait for update to complete
- time.Sleep(1000 * time.Millisecond)
-
- // Update file should be removed when update is completed
- assert.NoFileExists(t, updateFile)
-
- // Get final status
- w = srv.testRequest(t, "GET", "/api/status", nil, "")
-
- assert.Equal(t, 200, w.Code)
- assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
- //nolint:lll
- assert.Equal(t,
- `{"installing":false,"percent":100,"message":"Installing done.","last_error":"","log":"0% Installing\n0% Determining slot states\n20% Determining slot states done.\n20% Checking bundle\n20% Verifying signature\n40% Verifying signature done.\n40% Checking bundle done.\n40% Checking manifest contents\n60% Checking manifest contents done.\n60% Determining target install group\n80% Determining target install group done.\n80% Updating slots\n80% Checking slot rootfs.0\n90% Checking slot rootfs.0 done.\n90% Copying image to rootfs.0\n100% Copying image to rootfs.0 done.\n100% Updating slots done.\n100% Installing done.\nInstalling `+"`/app/tsgrain-update-raspberrypi3.raucb`"+` succeeded\n"}`,
- w.Body.String(),
- )
-}
-
-func TestStatus(t *testing.T) {
- srv := newTestServer()
- w := srv.testRequest(t, "GET", "/api/status", nil, "")
-
- assert.Equal(t, 200, w.Code)
- assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
- assert.Equal(t,
- `{"installing":false,"percent":0,"message":"","last_error":"","log":""}`,
- w.Body.String(),
- )
-}
-
-func TestInfo(t *testing.T) {
- srv := newTestServer()
- w := srv.testRequest(t, "GET", "/api/info", nil, "")
-
- assert.Equal(t, 200, w.Code)
- assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
-
- var info model.SystemInfo
- err := json.Unmarshal(w.Body.Bytes(), &info)
- assert.Nil(t, err)
-
- assert.Equal(t, "TSGRain", info.RaucCompatible)
- assert.Equal(t, "dev", info.RaucVariant)
- assert.Len(t, info.RaucRootfs, 2)
-}
-
-func TestReboot(t *testing.T) {
- srv := newTestServer()
- testfile := "/tmp/sebrauc_reboot_test"
- _ = os.Remove(testfile)
-
- w := srv.testRequest(t, "POST", "/api/reboot", nil, "")
-
- assert.Equal(t, 200, w.Code)
- assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
- assert.Equal(t,
- `{"success":true,"msg":"System is rebooting"}`,
- w.Body.String(),
- )
-
- time.Sleep(5100 * time.Millisecond)
-
- assert.FileExists(t, testfile)
-}
-
-func TestAuth(t *testing.T) {
- testfiles := fixtures.GetTestfilesDir()
-
- cfg := config.GetDefault()
- cfg.Authentication.Enable = true
- cfg.Authentication.PasswdFile = filepath.Join(testfiles, "htpasswd")
-
- srv := newTestServerCfg(cfg)
-
- t.Run("fail", func(t *testing.T) {
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/", nil)
- req.SetBasicAuth("plain", "asdf")
- srv.router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusUnauthorized, w.Code)
- assert.Empty(t, w.Body.String())
- assert.Equal(t, "Basic realm=\"Authorization Required\"",
- w.Header().Get("WWW-Authenticate"))
- })
-
- t.Run("ok", func(t *testing.T) {
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/", nil)
- req.SetBasicAuth("plain", "1234")
- srv.router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
- assert.NotEmpty(t, w.Body.String())
- assert.Empty(t, w.Header().Get("WWW-Authenticate"))
- })
-}
diff --git a/src/server/stream/client.go b/src/server/stream/client.go
deleted file mode 100644
index c00249f..0000000
--- a/src/server/stream/client.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package stream
-
-import (
- "errors"
- "fmt"
- "time"
-
- "github.com/gorilla/websocket"
-)
-
-const (
- writeWait = 2 * time.Second
-)
-
-var ping = func(conn *websocket.Conn) error {
- return conn.WriteMessage(websocket.PingMessage, nil)
-}
-
-var writeBytes = func(conn *websocket.Conn, data []byte) error {
- return conn.WriteMessage(websocket.TextMessage, data)
-}
-
-type client struct {
- conn *websocket.Conn
- onClose func(*client)
- write chan []byte
- id uint
- once once
-}
-
-func newClient(conn *websocket.Conn, id uint, onClose func(*client)) *client {
- return &client{
- conn: conn,
- write: make(chan []byte, 1),
- id: id,
- onClose: onClose,
- }
-}
-
-// Close closes the connection.
-func (c *client) Close() {
- c.once.Do(func() {
- c.conn.Close()
- close(c.write)
- })
-}
-
-// NotifyClose closes the connection and notifies that the connection was closed.
-func (c *client) NotifyClose() {
- c.once.Do(func() {
- c.conn.Close()
- close(c.write)
- c.onClose(c)
- })
-}
-
-// startWriteHandler starts listening on the client connection.
-// As we do not need anything from the client,
-// we ignore incoming messages. Leaves the loop on errors.
-func (c *client) startReading(pongWait time.Duration) {
- defer c.NotifyClose()
- c.conn.SetReadLimit(64)
- _ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
- c.conn.SetPongHandler(func(appData string) error {
- _ = c.conn.SetReadDeadline(time.Now().Add(pongWait))
- return nil
- })
- for {
- if _, _, err := c.conn.NextReader(); err != nil {
- printWebSocketError("ReadError", err)
- return
- }
- }
-}
-
-// startWriteHandler starts the write loop. The method has the following tasks:
-// * ping the client in the interval provided as parameter
-// * write messages send by the channel to the client
-// * on errors exit the loop.
-func (c *client) startWriteHandler(pingPeriod time.Duration) {
- pingTicker := time.NewTicker(pingPeriod)
- defer func() {
- c.NotifyClose()
- pingTicker.Stop()
- }()
-
- for {
- select {
- case message, ok := <-c.write:
- if !ok {
- return
- }
-
- _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
- if err := writeBytes(c.conn, message); err != nil {
- printWebSocketError("WriteError", err)
- return
- }
- case <-pingTicker.C:
- _ = c.conn.SetWriteDeadline(time.Now().Add(writeWait))
- if err := ping(c.conn); err != nil {
- printWebSocketError("PingError", err)
- return
- }
- }
- }
-}
-
-func printWebSocketError(prefix string, err error) {
- var closeError *websocket.CloseError
- ok := errors.As(err, &closeError)
-
- if ok && closeError != nil && (closeError.Code == 1000 || closeError.Code == 1001) {
- // normal closure
- return
- }
-
- fmt.Println("WebSocket:", prefix, err)
-}
diff --git a/src/server/stream/once.go b/src/server/stream/once.go
deleted file mode 100644
index 2df2523..0000000
--- a/src/server/stream/once.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package stream
-
-import (
- "sync"
- "sync/atomic"
-)
-
-// Modified version of sync.Once
-// (https://github.com/golang/go/blob/master/src/sync/once.go)
-// This version unlocks the mutex early and therefore doesn't
-// hold the lock while executing func f().
-type once struct {
- m sync.Mutex
- done uint32
-}
-
-func (o *once) Do(f func()) {
- if atomic.LoadUint32(&o.done) == 1 {
- return
- }
- if o.mayExecute() {
- f()
- }
-}
-
-func (o *once) mayExecute() bool {
- o.m.Lock()
- defer o.m.Unlock()
- if o.done == 0 {
- atomic.StoreUint32(&o.done, 1)
- return true
- }
- return false
-}
diff --git a/src/server/stream/once_test.go b/src/server/stream/once_test.go
deleted file mode 100644
index 720f65b..0000000
--- a/src/server/stream/once_test.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package stream
-
-import (
- "testing"
- "time"
-
- "github.com/stretchr/testify/assert"
-)
-
-func Test_Execute(t *testing.T) {
- executeOnce := once{}
- execution := make(chan struct{})
- fExecute := func() {
- execution <- struct{}{}
- }
- go executeOnce.Do(fExecute)
- go executeOnce.Do(fExecute)
-
- select {
- case <-execution:
- // expected
- case <-time.After(100 * time.Millisecond):
- t.Fatal("Execute should be executed once")
- }
-
- select {
- case <-execution:
- t.Fatal("should only execute once")
- case <-time.After(100 * time.Millisecond):
- // expected
- }
-
- assert.False(t, executeOnce.mayExecute())
-
- go executeOnce.Do(fExecute)
-
- select {
- case <-execution:
- t.Fatal("should only execute once")
- case <-time.After(100 * time.Millisecond):
- // expected
- }
-}
diff --git a/src/server/stream/stream.go b/src/server/stream/stream.go
deleted file mode 100644
index 6a91fbc..0000000
--- a/src/server/stream/stream.go
+++ /dev/null
@@ -1,161 +0,0 @@
-package stream
-
-import (
- "net/http"
- "net/url"
- "regexp"
- "strings"
- "sync"
- "time"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode"
- "github.com/gin-gonic/gin"
- "github.com/gorilla/websocket"
-)
-
-// The API provides a handler for a WebSocket stream API.
-type API struct {
- clients map[uint]*client
- lock sync.RWMutex
- pingPeriod time.Duration
- pongTimeout time.Duration
- upgrader *websocket.Upgrader
- counter *util.Counter
- lastBroadcast []byte
-}
-
-// New creates a new instance of API.
-// pingPeriod: is the interval, in which is server sends the a ping to the client.
-// pongTimeout: is the duration after the connection will be terminated,
-// when the client does not respond with the pong command.
-func New(pingPeriod, pongTimeout time.Duration, allowedWebSocketOrigins []string) *API {
- return &API{
- clients: make(map[uint]*client),
- pingPeriod: pingPeriod,
- pongTimeout: pingPeriod + pongTimeout,
- upgrader: newUpgrader(allowedWebSocketOrigins),
- counter: &util.Counter{},
- }
-}
-
-// NotifyDeletedUser closes existing connections for the given user.
-func (a *API) NotifyDeletedClient(userID uint) error {
- a.lock.Lock()
- defer a.lock.Unlock()
- if client, ok := a.clients[userID]; ok {
- client.Close()
- delete(a.clients, userID)
- }
- return nil
-}
-
-// Notify notifies the clients with the given userID that a new messages was created.
-func (a *API) Notify(userID uint, msg []byte) {
- a.lock.RLock()
- defer a.lock.RUnlock()
- if client, ok := a.clients[userID]; ok {
- client.write <- msg
- }
-}
-
-func (a *API) Broadcast(msg []byte) {
- a.lock.RLock()
- defer a.lock.RUnlock()
- for _, client := range a.clients {
- client.write <- msg
- }
- a.lastBroadcast = msg
-}
-
-func (a *API) remove(remove *client) {
- a.lock.Lock()
- defer a.lock.Unlock()
- delete(a.clients, remove.id)
-}
-
-func (a *API) register(client *client) {
- a.lock.Lock()
- defer a.lock.Unlock()
- a.clients[client.id] = client
-
- // Send new clients the last broadcast so they get the current state
- if a.lastBroadcast != nil {
- client.write <- a.lastBroadcast
- }
-}
-
-func (a *API) Handle(ctx *gin.Context) {
- conn, err := a.upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
- if err != nil {
- ctx.Error(err)
- return
- }
-
- client := newClient(conn, a.counter.Increment(), a.remove)
- a.register(client)
- go client.startReading(a.pongTimeout)
- go client.startWriteHandler(a.pingPeriod)
-}
-
-// Close closes all client connections and stops answering new connections.
-func (a *API) Close() {
- a.lock.Lock()
- defer a.lock.Unlock()
-
- for _, client := range a.clients {
- client.Close()
- }
- for k := range a.clients {
- delete(a.clients, k)
- }
-}
-
-func isAllowedOrigin(r *http.Request, allowedOrigins []*regexp.Regexp) bool {
- origin := r.Header.Get("origin")
- if origin == "" {
- return true
- }
-
- u, err := url.Parse(origin)
- if err != nil {
- return false
- }
-
- if strings.EqualFold(u.Host, r.Host) {
- return true
- }
-
- for _, allowedOrigin := range allowedOrigins {
- if allowedOrigin.Match([]byte(strings.ToLower(u.Hostname()))) {
- return true
- }
- }
-
- return false
-}
-
-func newUpgrader(allowedWebSocketOrigins []string) *websocket.Upgrader {
- compiledAllowedOrigins := compileAllowedWebSocketOrigins(allowedWebSocketOrigins)
-
- return &websocket.Upgrader{
- ReadBufferSize: 1024,
- WriteBufferSize: 1024,
- CheckOrigin: func(r *http.Request) bool {
- if mode.IsDev() {
- return true
- }
- return isAllowedOrigin(r, compiledAllowedOrigins)
- },
- }
-}
-
-func compileAllowedWebSocketOrigins(allowedOrigins []string) []*regexp.Regexp {
- var compiledAllowedOrigins []*regexp.Regexp
- for _, origin := range allowedOrigins {
- compiledAllowedOrigins = append(compiledAllowedOrigins,
- regexp.MustCompile(origin))
- }
-
- return compiledAllowedOrigins
-}
diff --git a/src/server/stream/stream_test.go b/src/server/stream/stream_test.go
deleted file mode 100644
index 771e6b2..0000000
--- a/src/server/stream/stream_test.go
+++ /dev/null
@@ -1,472 +0,0 @@
-package stream
-
-import (
- "errors"
- "fmt"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/util/mode"
- "github.com/fortytw2/leaktest"
- "github.com/gin-gonic/gin"
- "github.com/gorilla/websocket"
- "github.com/stretchr/testify/assert"
-)
-
-func TestFailureOnNormalHttpRequest(t *testing.T) {
- mode.Set(mode.TestDev)
- defer mode.Set(mode.Dev)
-
- defer leaktest.Check(t)()
-
- server, api := bootTestServer()
- defer server.Close()
- defer api.Close()
-
- resp, err := http.Get(server.URL)
- assert.Nil(t, err)
- assert.Equal(t, 400, resp.StatusCode)
- resp.Body.Close()
-}
-
-func TestWriteMessageFails(t *testing.T) {
- mode.Set(mode.TestDev)
- defer mode.Set(mode.Dev)
-
- oldWrite := writeBytes
- // try emulate an write error, mostly this should kill the ReadMessage
- // goroutine first but you'll never know.
- writeBytes = func(conn *websocket.Conn, data []byte) error {
- return errors.New("asd")
- }
- defer func() {
- writeBytes = oldWrite
- }()
- defer leaktest.Check(t)()
-
- server, api := bootTestServer()
- defer server.Close()
- defer api.Close()
-
- wsURL := wsURL(server.URL)
- user := testClient(t, wsURL)
-
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
- client := getClient(api, 1)
- assert.NotNil(t, client)
-
- api.Notify(1, []byte("HI"))
- user.expectNoMessage()
-}
-
-func TestWritePingFails(t *testing.T) {
- mode.Set(mode.TestDev)
- defer mode.Set(mode.Dev)
-
- oldPing := ping
- // try emulate an write error, mostly this should kill the ReadMessage
- // gorouting first but you'll never know.
- ping = func(conn *websocket.Conn) error {
- return errors.New("asd")
- }
- defer func() {
- ping = oldPing
- }()
-
- defer leaktest.CheckTimeout(t, 10*time.Second)()
-
- server, api := bootTestServer()
- defer api.Close()
- defer server.Close()
-
- wsURL := wsURL(server.URL)
- user := testClient(t, wsURL)
- defer user.conn.Close()
-
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
- client := getClient(api, 1)
-
- assert.NotNil(t, client)
-
- time.Sleep(api.pingPeriod) // waiting for ping
-
- api.Notify(1, []byte("HI"))
- user.expectNoMessage()
-}
-
-func TestPing(t *testing.T) {
- mode.Set(mode.TestDev)
- defer mode.Set(mode.Dev)
-
- server, api := bootTestServer()
- defer server.Close()
- defer api.Close()
-
- wsURL := wsURL(server.URL)
-
- user := createClient(t, wsURL)
- defer user.conn.Close()
-
- ping := make(chan bool)
- oldPingHandler := user.conn.PingHandler()
- user.conn.SetPingHandler(func(appData string) error {
- err := oldPingHandler(appData)
- ping <- true
- return err
- })
-
- startReading(user)
-
- expectNoMessage(user)
-
- select {
- case <-time.After(2 * time.Second):
- assert.Fail(t, "Expected ping but there was one :(")
- case <-ping:
- // expected
- }
-
- expectNoMessage(user)
- api.Notify(1, []byte("HI"))
- user.expectMessage([]byte("HI"))
-}
-
-func TestCloseClientOnNotReading(t *testing.T) {
- mode.Set(mode.TestDev)
- defer mode.Set(mode.Dev)
-
- server, api := bootTestServer()
- defer server.Close()
- defer api.Close()
-
- wsURL := wsURL(server.URL)
-
- ws, resp, err := websocket.DefaultDialer.Dial(wsURL, nil)
- assert.Nil(t, err)
- resp.Body.Close()
- defer ws.Close()
-
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
- assert.NotNil(t, getClient(api, 1))
-
- time.Sleep(api.pingPeriod + api.pongTimeout)
-
- assert.Nil(t, getClient(api, 1))
-}
-
-func TestMessageDirectlyAfterConnect(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- defer leaktest.Check(t)()
- server, api := bootTestServer()
- defer server.Close()
- defer api.Close()
-
- wsURL := wsURL(server.URL)
-
- user := testClient(t, wsURL)
- defer user.conn.Close()
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
- api.Notify(1, []byte("msg"))
- user.expectMessage([]byte("msg"))
-}
-
-func TestDeleteClientShouldCloseConnection(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- defer leaktest.Check(t)()
- server, api := bootTestServer()
- defer server.Close()
- defer api.Close()
-
- wsURL := wsURL(server.URL)
-
- user := testClient(t, wsURL)
- defer user.conn.Close()
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
- api.Notify(1, []byte("HI"))
- user.expectMessage([]byte("HI"))
-
- assert.Nil(t, api.NotifyDeletedClient(1))
-
- api.Notify(1, []byte("HI"))
- user.expectNoMessage()
-}
-
-func TestNotify(t *testing.T) {
- mode.Set(mode.TestDev)
- defer mode.Set(mode.Dev)
-
- defer leaktest.Check(t)()
- server, api := bootTestServer()
- defer server.Close()
-
- wsURL := wsURL(server.URL)
-
- client1 := testClient(t, wsURL)
- defer client1.conn.Close()
-
- client2 := testClient(t, wsURL)
- defer client2.conn.Close()
-
- client3 := testClient(t, wsURL)
- defer client3.conn.Close()
-
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
-
- api.Notify(1, []byte("msg"))
- expectMessage([]byte("msg"), client1)
- expectNoMessage(client2)
- expectNoMessage(client3)
-
- assert.Nil(t, api.NotifyDeletedClient(1))
-
- api.Notify(1, []byte("msg"))
- expectNoMessage(client1)
- expectNoMessage(client2)
- expectNoMessage(client3)
-
- api.Notify(2, []byte("msg"))
- expectNoMessage(client1)
- expectMessage([]byte("msg"), client2)
- expectNoMessage(client3)
-
- api.Notify(3, []byte("msg"))
- expectNoMessage(client1)
- expectNoMessage(client2)
- expectMessage([]byte("msg"), client3)
-
- api.Close()
-}
-
-func TestBroadcast(t *testing.T) {
- defer leaktest.Check(t)()
- server, api := bootTestServer()
- defer server.Close()
-
- wsURL := wsURL(server.URL)
-
- client1 := testClient(t, wsURL)
- defer client1.conn.Close()
-
- client2 := testClient(t, wsURL)
- defer client2.conn.Close()
-
- client3 := testClient(t, wsURL)
- defer client3.conn.Close()
-
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
-
- testMsg1 := []byte("hello1")
- api.Broadcast(testMsg1)
- expectMessage(testMsg1, client1, client2, client3)
-
- assert.Nil(t, api.NotifyDeletedClient(1))
-
- testMsg2 := []byte("hello2")
- api.Broadcast(testMsg2)
- expectNoMessage(client1)
- expectMessage(testMsg2, client2, client3)
-}
-
-func TestLastBroadcast(t *testing.T) {
- defer leaktest.Check(t)()
- server, api := bootTestServer()
- defer server.Close()
-
- wsURL := wsURL(server.URL)
-
- testMsg1 := []byte("hello1")
- api.Broadcast(testMsg1)
-
- client1 := testClient(t, wsURL)
- defer client1.conn.Close()
-
- client2 := testClient(t, wsURL)
- defer client2.conn.Close()
-
- // the server may take some time to register the client
- time.Sleep(100 * time.Millisecond)
-
- expectMessage(testMsg1, client1, client2)
-}
-
-func Test_sameOrigin_returnsTrue(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- req := httptest.NewRequest("GET", "http://example.com/stream", nil)
- req.Header.Set("Origin", "http://example.com")
- actual := isAllowedOrigin(req, nil)
- assert.True(t, actual)
-}
-
-func Test_sameOrigin_returnsTrue_withCustomPort(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
- req := httptest.NewRequest("GET", "http://example.com:8080/stream", nil)
- req.Header.Set("Origin", "http://example.com:8080")
- actual := isAllowedOrigin(req, nil)
- assert.True(t, actual)
-}
-
-func Test_isAllowedOrigin_withoutAllowedOrigins_failsWhenNotSameOrigin(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- req := httptest.NewRequest("GET", "http://example.com/stream", nil)
- req.Header.Set("Origin", "http://gorify.example.com")
- actual := isAllowedOrigin(req, nil)
- assert.False(t, actual)
-}
-
-func Test_isAllowedOriginMatching(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- compiledAllowedOrigins := compileAllowedWebSocketOrigins(
- []string{"go.{4}\\.example\\.com", "go\\.example\\.com"},
- )
-
- req := httptest.NewRequest("GET", "http://example.me/stream", nil)
- req.Header.Set("Origin", "http://gorify.example.com")
- assert.True(t, isAllowedOrigin(req, compiledAllowedOrigins))
-
- req.Header.Set("Origin", "http://go.example.com")
- assert.True(t, isAllowedOrigin(req, compiledAllowedOrigins))
-
- req.Header.Set("Origin", "http://hello.example.com")
- assert.False(t, isAllowedOrigin(req, compiledAllowedOrigins))
-}
-
-func Test_emptyOrigin_returnsTrue(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- req := httptest.NewRequest("GET", "http://example.com/stream", nil)
- actual := isAllowedOrigin(req, nil)
- assert.True(t, actual)
-}
-
-func Test_otherOrigin_returnsFalse(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- req := httptest.NewRequest("GET", "http://example.com/stream", nil)
- req.Header.Set("Origin", "http://otherexample.de")
- actual := isAllowedOrigin(req, nil)
- assert.False(t, actual)
-}
-
-func Test_invalidOrigin_returnsFalse(t *testing.T) {
- mode.Set(mode.Prod)
- defer mode.Set(mode.Dev)
-
- req := httptest.NewRequest("GET", "http://example.com/stream", nil)
- req.Header.Set("Origin", "http\\://otherexample.de")
- actual := isAllowedOrigin(req, nil)
- assert.False(t, actual)
-}
-
-func Test_compileAllowedWebSocketOrigins(t *testing.T) {
- assert.Equal(t, 0, len(compileAllowedWebSocketOrigins([]string{})))
- assert.Equal(t, 3, len(compileAllowedWebSocketOrigins([]string{"^.*$", "", "abc"})))
-}
-
-func getClient(api *API, user uint) *client {
- api.lock.RLock()
- defer api.lock.RUnlock()
-
- return api.clients[user]
-}
-
-func testClient(t *testing.T, url string) *testingClient {
- client := createClient(t, url)
- startReading(client)
- return client
-}
-
-func startReading(client *testingClient) {
- go func() {
- for {
- _, payload, err := client.conn.ReadMessage()
- if err != nil {
- return
- }
-
- client.readMessage <- payload
- }
- }()
-}
-
-func createClient(t *testing.T, url string) *testingClient {
- ws, resp, err := websocket.DefaultDialer.Dial(url, nil)
- assert.Nil(t, err)
- resp.Body.Close()
-
- readMessages := make(chan []byte)
-
- return &testingClient{conn: ws, readMessage: readMessages, t: t}
-}
-
-type testingClient struct {
- conn *websocket.Conn
- readMessage chan []byte
- t *testing.T
-}
-
-func (c *testingClient) expectMessage(expected []byte) {
- select {
- case <-time.After(50 * time.Millisecond):
- assert.Fail(c.t, "Expected message but none was send :(")
- case actual := <-c.readMessage:
- assert.Equal(c.t, expected, actual)
- }
-}
-
-func expectMessage(expected []byte, clients ...*testingClient) {
- for _, client := range clients {
- client.expectMessage(expected)
- }
-}
-
-func expectNoMessage(clients ...*testingClient) {
- for _, client := range clients {
- client.expectNoMessage()
- }
-}
-
-func (c *testingClient) expectNoMessage() {
- select {
- case <-time.After(50 * time.Millisecond):
- // no message == as expected
- case msg := <-c.readMessage:
- assert.Fail(c.t, "Expected NO message but there was one :(", fmt.Sprint(msg))
- }
-}
-
-func bootTestServer() (*httptest.Server, *API) {
- r := gin.New()
- // ping every 500 ms, and the client has 500 ms to respond
- api := New(500*time.Millisecond, 500*time.Millisecond, []string{})
-
- r.GET("/", api.Handle)
- server := httptest.NewServer(r)
- return server, api
-}
-
-func wsURL(httpURL string) string {
- return "ws" + strings.TrimPrefix(httpURL, "http")
-}
diff --git a/src/server/swagger/swagger.go b/src/server/swagger/swagger.go
deleted file mode 100644
index 733b372..0000000
--- a/src/server/swagger/swagger.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package swagger
-
-import (
- _ "embed"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/server/middleware"
- "github.com/gin-gonic/gin"
-)
-
-//go:embed swagger.html
-var swaggerHtml []byte
-
-//go:embed swagger.yaml
-var swaggerYaml []byte
-
-func Register(r gin.IRouter) {
- swg := r.Group("/api/swagger", middleware.Cache)
-
- swg.GET("/", func(c *gin.Context) {
- c.Data(200, "text/html", swaggerHtml)
- })
- swg.GET("/swagger.yaml", func(c *gin.Context) {
- c.Data(200, "text/yaml", swaggerYaml)
- })
-}
diff --git a/src/server/swagger/swagger.html b/src/server/swagger/swagger.html
deleted file mode 100644
index cc6855f..0000000
--- a/src/server/swagger/swagger.html
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
- SEBRAUC API documentation
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/server/swagger/swagger.yaml b/src/server/swagger/swagger.yaml
deleted file mode 100644
index e21f243..0000000
--- a/src/server/swagger/swagger.yaml
+++ /dev/null
@@ -1,228 +0,0 @@
-definitions:
- Error:
- description: The Error contains error relevant information.
- properties:
- error:
- description: The general error message according to HTTP specification.
- example: Unauthorized
- type: string
- msg:
- description: Concrete error message.
- example: already running
- type: string
- status_code:
- description: The http error code.
- example: 500
- format: int64
- type: integer
- required:
- - error
- - status_code
- - msg
- title: Error model
- type: object
- RaucStatus:
- description: RaucStatus contains information about the current RAUC updater status.
- properties:
- installing:
- description: True if the installer is running
- type: boolean
- last_error:
- description: Installation error message
- example: "Failed to check bundle identifier: Invalid identifier."
- type: string
- log:
- description: Full command line output of the current installation
- example: 0% Installing 0% Determining slot states 20% Determining slot states
- done
- type: string
- message:
- description: Current installation step
- example: Copying image to rootfs.0
- type: string
- percent:
- description: Installation progress
- format: int64
- maximum: 100
- minimum: 0
- type: integer
- required:
- - installing
- - percent
- - message
- - last_error
- - log
- title: RaucStatus model
- type: object
- Rootfs:
- properties:
- bootable:
- description: Is the filesystem bootable?
- type: boolean
- booted:
- description: Is the filesystem booted?
- type: boolean
- device:
- description: Block device
- example: /dev/mmcblk0p2
- type: string
- mountpoint:
- description: Mount path (null when not mounted)
- example: /
- type: string
- primary:
- description: Is the filesystem the next boot target?
- type: boolean
- type:
- description: Filesystem
- example: ext4
- type: string
- required:
- - device
- - type
- - mountpoint
- - bootable
- - booted
- - primary
- type: object
- StatusMessage:
- description: StatusMessage contains the status of an operation.
- properties:
- msg:
- description: Status message text
- example: Update started
- type: string
- success:
- description: Is operation successful?
- type: boolean
- required:
- - success
- - msg
- title: StatusMessage model
- type: object
- SystemInfo:
- description: SystemInfo contains information about the running system.
- properties:
- hostname:
- description: Hostname of the system
- example: raspberrypi3
- type: string
- os_name:
- description: Name of the os distribution
- example: Poky
- type: string
- os_version:
- description: Operating system version
- example: 1.0.2
- type: string
- rauc_compatible:
- description: Compatible firmware name
- example: Poky
- type: string
- rauc_rootfs:
- additionalProperties:
- $ref: "#/definitions/Rootfs"
- description: List of RAUC root filesystems
- type: object
- rauc_variant:
- description: Compatible firmware variant
- example: rpi-prod
- type: string
- uptime:
- description: System uptime in seconds
- example: 5832
- format: int64
- type: integer
- required:
- - hostname
- - os_name
- - os_version
- - uptime
- - rauc_compatible
- - rauc_variant
- - rauc_rootfs
- title: SystemInfo model
- type: object
-info:
- description: REST API for the SEBRAUC firmware updater
- license:
- name: MIT
- title: SEBRAUC
- version: 0.2.0
-paths:
- /info:
- get:
- description: Get the current system info
- operationId: getInfo
- produces:
- - application/json
- responses:
- "200":
- description: Ok
- schema:
- $ref: "#/definitions/SystemInfo"
- "500":
- description: Server Error
- schema:
- $ref: "#/definitions/Error"
- /reboot:
- post:
- description: Reboot the system
- operationId: startReboot
- produces:
- - application/json
- responses:
- "200":
- description: Ok
- schema:
- $ref: "#/definitions/StatusMessage"
- "500":
- description: Server Error
- schema:
- $ref: "#/definitions/Error"
- /status:
- get:
- description: Get the current status of the RAUC updater
- operationId: getStatus
- produces:
- - application/json
- responses:
- "200":
- description: Ok
- schema:
- $ref: "#/definitions/RaucStatus"
- "500":
- description: Server Error
- schema:
- $ref: "#/definitions/Error"
- /update:
- post:
- consumes:
- - multipart/form-data
- description: Start the update process
- operationId: startUpdate
- parameters:
- - description: RAUC firmware image file (*.raucb)
- in: formData
- name: updateFile
- required: true
- type: file
- produces:
- - application/json
- responses:
- "200":
- description: Ok
- schema:
- $ref: "#/definitions/StatusMessage"
- "409":
- description: already running
- schema:
- $ref: "#/definitions/Error"
- "500":
- description: Server Error
- schema:
- $ref: "#/definitions/Error"
-schemes:
- - http
- - https
-swagger: "2.0"
diff --git a/src/server/swagger/swagger_test.go b/src/server/swagger/swagger_test.go
deleted file mode 100644
index 9fd3c03..0000000
--- a/src/server/swagger/swagger_test.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package swagger
-
-import (
- "bytes"
- "net/http"
- "net/http/httptest"
- "testing"
-
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
-)
-
-func TestSwagger(t *testing.T) {
- router := gin.New()
- Register(router)
-
- w := httptest.NewRecorder()
- req, _ := http.NewRequest("GET", "/api/swagger/", nil)
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
- assert.Equal(t, swaggerHtml, w.Body.Bytes())
- assert.NotEmpty(t, w.Header().Get("Cache-Control"))
-
- w = httptest.NewRecorder()
- req, _ = http.NewRequest("GET", "/api/swagger/swagger.yaml", nil)
- router.ServeHTTP(w, req)
-
- assert.Equal(t, http.StatusOK, w.Code)
- assert.Equal(t, swaggerYaml, w.Body.Bytes())
- assert.NotEmpty(t, w.Header().Get("Cache-Control"))
-}
-
-func TestSwaggerData(t *testing.T) {
- assert.True(t, bytes.Contains(swaggerHtml,
- []byte("https://cdn.jsdelivr.net/npm/redoc/bundles/redoc.standalone.js")),
- "HTML data missing",
- )
-
- assert.True(t, bytes.Contains(swaggerYaml,
- []byte("REST API for the SEBRAUC firmware updater")),
- "YAML data missing",
- )
-}
diff --git a/src/sysinfo/sysinfo.go b/src/sysinfo/sysinfo.go
deleted file mode 100644
index f2aabc0..0000000
--- a/src/sysinfo/sysinfo.go
+++ /dev/null
@@ -1,188 +0,0 @@
-package sysinfo
-
-import (
- "encoding/json"
- "os"
- "regexp"
- "strconv"
- "strings"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/model"
- "code.thetadev.de/TSGRain/SEBRAUC/src/util"
-)
-
-type Sysinfo struct {
- cmdRaucStatus string
- releaseFile string
- hostnameFile string
- uptimeFile string
- rexpName *regexp.Regexp
- rexpVersion *regexp.Regexp
-}
-
-type raucInfo struct {
- Compatible string `json:"compatible"`
- Variant string `json:"variant"`
- Booted string `json:"booted"`
- BootPrimary string `json:"boot_primary"`
- Slots []map[string]raucFS `json:"slots"`
-}
-
-type raucFS struct {
- Class string `json:"class"`
- Device string `json:"device"`
- Type string `json:"type"`
- Bootname string `json:"bootname"`
- State string `json:"state"`
- Mountpoint *string `json:"mountpoint"`
- BootStatus string `json:"boot_status"`
-}
-
-type osRelease struct {
- OsName string `json:"os_name"`
- OsVersion string `json:"os_version"`
-}
-
-var rexpUptime = regexp.MustCompile(`^\d+`)
-
-func New(cmdRaucStatus string, releaseFile string, nameKey string, versionKey string,
- hostnameFile string, uptimeFile string,
-) *Sysinfo {
- return &Sysinfo{
- cmdRaucStatus: cmdRaucStatus,
- releaseFile: releaseFile,
- hostnameFile: hostnameFile,
- uptimeFile: uptimeFile,
- rexpName: regexp.MustCompile(
- `(?m)^` + regexp.QuoteMeta(nameKey) + `="(.+)"`),
- rexpVersion: regexp.MustCompile(
- `(?m)^` + regexp.QuoteMeta(versionKey) + `="(.+)"`),
- }
-}
-
-func Default(cmdRaucStatus string) *Sysinfo {
- return New(cmdRaucStatus, "/etc/os-release", "NAME", "VERSION",
- "/etc/hostname", "/proc/uptime")
-}
-
-func parseRaucInfo(raucInfoJson []byte) (raucInfo, error) {
- res := raucInfo{}
- err := json.Unmarshal(raucInfoJson, &res)
- return res, err
-}
-
-func (s *Sysinfo) parseOsRelease() (osRelease, error) {
- osReleaseTxt, err := os.ReadFile(s.releaseFile)
- if err != nil {
- return osRelease{}, err
- }
-
- nameMatch := s.rexpName.FindSubmatch(osReleaseTxt)
- versionMatch := s.rexpVersion.FindSubmatch(osReleaseTxt)
-
- name := ""
- if nameMatch != nil {
- name = string(nameMatch[1])
- }
-
- version := ""
- if versionMatch != nil {
- version = string(versionMatch[1])
- }
-
- return osRelease{
- OsName: name,
- OsVersion: version,
- }, nil
-}
-
-func mapRootfs(rinf raucInfo) map[string]model.Rootfs {
- res := make(map[string]model.Rootfs)
-
- for _, slot := range rinf.Slots {
- for name, fs := range slot {
- if fs.Class == "rootfs" {
- res[name] = model.Rootfs{
- Device: fs.Device,
- Type: fs.Type,
- Bootname: fs.Bootname,
- Mountpoint: fs.Mountpoint,
- Bootable: fs.BootStatus == "good",
- Booted: fs.State == "booted",
- Primary: rinf.BootPrimary == name,
- }
- }
- }
- }
- return res
-}
-
-func getFSNameFromBootname(rfslist map[string]model.Rootfs, bootname string) string {
- for name, rfs := range rfslist {
- if rfs.Bootname == bootname {
- return name
- }
- }
- return "n/a"
-}
-
-func mapSysinfo(rinf raucInfo, osr osRelease, uptime int,
- hostname string,
-) model.SystemInfo {
- rfslist := mapRootfs(rinf)
-
- return model.SystemInfo{
- Hostname: hostname,
- OsName: osr.OsName,
- OsVersion: osr.OsVersion,
- Uptime: uptime,
- RaucCompatible: rinf.Compatible,
- RaucVariant: rinf.Variant,
- RaucRootfs: rfslist,
- }
-}
-
-func (s *Sysinfo) getUptime() (int, error) {
- uptimeRaw, err := os.ReadFile(s.uptimeFile)
- if err != nil {
- return 0, err
- }
-
- uptimeChars := rexpUptime.Find(uptimeRaw)
- return strconv.Atoi(string(uptimeChars))
-}
-
-func (s *Sysinfo) getHostname() string {
- hostname, err := os.ReadFile(s.hostnameFile)
- if err != nil {
- return ""
- }
- return strings.TrimSpace(string(hostname))
-}
-
-func (s *Sysinfo) GetSysinfo() (model.SystemInfo, error) {
- cmd := util.CommandFromString(s.cmdRaucStatus)
- rinfJson, err := cmd.Output()
- if err != nil {
- return model.SystemInfo{}, err
- }
-
- rinf, err := parseRaucInfo(rinfJson)
- if err != nil {
- return model.SystemInfo{}, err
- }
-
- osinf, err := s.parseOsRelease()
- if err != nil {
- return model.SystemInfo{}, err
- }
-
- uptime, err := s.getUptime()
- if err != nil {
- return model.SystemInfo{}, err
- }
-
- hostname := s.getHostname()
-
- return mapSysinfo(rinf, osinf, uptime, hostname), nil
-}
diff --git a/src/sysinfo/sysinfo_test.go b/src/sysinfo/sysinfo_test.go
deleted file mode 100644
index 575502e..0000000
--- a/src/sysinfo/sysinfo_test.go
+++ /dev/null
@@ -1,129 +0,0 @@
-package sysinfo
-
-import (
- "path/filepath"
- "testing"
-
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures"
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd"
- "code.thetadev.de/TSGRain/SEBRAUC/src/model"
- "github.com/stretchr/testify/assert"
-)
-
-const statusJson = `{"compatible":"TSGRain","variant":"dev","booted":"A",` +
- `"boot_primary":"rootfs.0","slots":[{"rootfs.1":{"class":"rootfs",` +
- `"device":"/dev/mmcblk0p3","type":"ext4","bootname":"B","state":"inactive",` +
- `"parent":null,"mountpoint":null,"boot_status":"good"}},{"rootfs.0":` +
- `{"class":"rootfs","device":"/dev/mmcblk0p2","type":"ext4","bootname":"A",` +
- `"state":"booted","parent":null,"mountpoint":"/","boot_status":"good"}}]}`
-
-var mountRoot = "/"
-
-var expectedRaucInfo = raucInfo{
- Compatible: "TSGRain",
- Variant: "dev",
- Booted: "A",
- BootPrimary: "rootfs.0",
- Slots: []map[string]raucFS{
- {
- "rootfs.1": {
- Class: "rootfs",
- Device: "/dev/mmcblk0p3",
- Type: "ext4",
- Bootname: "B",
- State: "inactive",
- Mountpoint: nil,
- BootStatus: "good",
- },
- },
- {
- "rootfs.0": {
- Class: "rootfs",
- Device: "/dev/mmcblk0p2",
- Type: "ext4",
- Bootname: "A",
- State: "booted",
- Mountpoint: &mountRoot,
- BootStatus: "good",
- },
- },
- },
-}
-
-var expectedRootfsList = map[string]model.Rootfs{
- "rootfs.0": {
- Device: "/dev/mmcblk0p2",
- Type: "ext4",
- Bootname: "A",
- Mountpoint: &mountRoot,
- Bootable: true,
- Booted: true,
- Primary: true,
- },
- "rootfs.1": {
- Device: "/dev/mmcblk0p3",
- Type: "ext4",
- Bootname: "B",
- Mountpoint: nil,
- Bootable: true,
- Booted: false,
- Primary: false,
- },
-}
-
-func TestParseRaucInfo(t *testing.T) {
- info, err := parseRaucInfo([]byte(statusJson))
- if err != nil {
- panic(err)
- }
-
- assert.Equal(t, expectedRaucInfo, info)
-}
-
-func TestParseOsRelease(t *testing.T) {
- testfiles := fixtures.GetTestfilesDir()
- osReleaseFile := filepath.Join(testfiles, "os-release")
-
- si := New(testcmd.RaucStatus, osReleaseFile, "NAME", "VERSION",
- "/etc/hostname", "/proc/uptime")
-
- osRel, err := si.parseOsRelease()
- if err != nil {
- panic(err)
- }
-
- expected := osRelease{
- OsName: "TSGRain distro",
- OsVersion: "0.0.1",
- }
-
- assert.Equal(t, expected, osRel)
-}
-
-func TestMapRootfsList(t *testing.T) {
- rootfsList := mapRootfs(expectedRaucInfo)
-
- assert.Equal(t, expectedRootfsList, rootfsList)
-}
-
-func TestGetFSNameFromBootname(t *testing.T) {
- rootfsList := mapRootfs(expectedRaucInfo)
-
- assert.Equal(t, "rootfs.0", getFSNameFromBootname(rootfsList, "A"))
- assert.Equal(t, "rootfs.1", getFSNameFromBootname(rootfsList, "B"))
- assert.Equal(t, "n/a", getFSNameFromBootname(rootfsList, "C"))
-}
-
-func TestGetSysinfo(t *testing.T) {
- si := Default(testcmd.RaucStatus)
-
- sysinfo, err := si.GetSysinfo()
- if err != nil {
- panic(err)
- }
-
- assert.Greater(t, sysinfo.Uptime, 0)
- assert.Equal(t, "TSGRain", sysinfo.RaucCompatible)
- assert.Equal(t, "dev", sysinfo.RaucVariant)
- assert.Equal(t, expectedRootfsList, sysinfo.RaucRootfs)
-}
diff --git a/src/util/commands.go b/src/util/commands.go
new file mode 100644
index 0000000..5bb0604
--- /dev/null
+++ b/src/util/commands.go
@@ -0,0 +1,10 @@
+//go:build prod
+
+package util
+
+const (
+ RebootCmd = "shutdown -r 0"
+ UpdateCmd = "rauc install"
+
+ TestMode = false
+)
diff --git a/src/util/commands_mock.go b/src/util/commands_mock.go
new file mode 100644
index 0000000..b2a18f3
--- /dev/null
+++ b/src/util/commands_mock.go
@@ -0,0 +1,10 @@
+//go:build !prod
+
+package util
+
+const (
+ RebootCmd = "touch /tmp/sebrauc_reboot_test"
+ UpdateCmd = "go run code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/rauc_mock"
+
+ TestMode = true
+)
diff --git a/src/util/counter.go b/src/util/counter.go
deleted file mode 100644
index 9e264ee..0000000
--- a/src/util/counter.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package util
-
-import "sync"
-
-type Counter struct {
- count uint
- mutex sync.RWMutex
-}
-
-func (c *Counter) Get() uint {
- c.mutex.RLock()
- defer c.mutex.RUnlock()
-
- return c.count
-}
-
-func (c *Counter) Reset() {
- c.mutex.Lock()
- defer c.mutex.Unlock()
-
- c.count = 0
-}
-
-func (c *Counter) Increment() uint {
- c.mutex.Lock()
- defer c.mutex.Unlock()
-
- c.count++
- return c.count
-}
diff --git a/src/util/counter_test.go b/src/util/counter_test.go
deleted file mode 100644
index bfc3e9d..0000000
--- a/src/util/counter_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package util
-
-import (
- "sync"
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestCounter(t *testing.T) {
- counter := Counter{}
-
- var wg sync.WaitGroup
-
- incrementer := func() {
- for i := 0; i < 50; i++ {
- counter.Increment()
- }
- wg.Done()
- }
-
- for i := 0; i < 100; i++ {
- wg.Add(1)
- go incrementer()
- }
-
- wg.Wait()
-
- assert.EqualValues(t, 5000, counter.Get())
-}
diff --git a/src/util/errors.go b/src/util/errors.go
index a229d66..20ee9e4 100644
--- a/src/util/errors.go
+++ b/src/util/errors.go
@@ -1,13 +1,8 @@
package util
-import (
- "errors"
- "net/http"
-)
+import "errors"
var (
- ErrAlreadyRunning = HttpErrNew("rauc already running", http.StatusConflict)
+ ErrAlreadyRunning = errors.New("rauc already running")
ErrFileDoesNotExist = errors.New("file does not exist")
- ErrPageNotFound = HttpErrNew("page not found", http.StatusNotFound)
- ErrUnauthorized = HttpErrNew("unauthorized", http.StatusUnauthorized)
)
diff --git a/src/util/http_error.go b/src/util/http_error.go
deleted file mode 100644
index c5d7cf8..0000000
--- a/src/util/http_error.go
+++ /dev/null
@@ -1,39 +0,0 @@
-package util
-
-import "errors"
-
-type HttpError interface {
- error
- StatusCode() int
-}
-
-type httpErr struct {
- err error
- statusCode int
-}
-
-func HttpErrWrap(e error, statusCode int) HttpError {
- return &httpErr{
- err: e,
- statusCode: statusCode,
- }
-}
-
-func HttpErrNew(msg string, statusCode int) HttpError {
- return HttpErrWrap(errors.New(msg), statusCode)
-}
-
-func (e *httpErr) Error() string {
- if e.err == nil {
- return ""
- }
- return e.err.Error()
-}
-
-func (e *httpErr) Unwrap() error {
- return e.err
-}
-
-func (e *httpErr) StatusCode() int {
- return e.statusCode
-}
diff --git a/src/util/mode/mode.go b/src/util/mode/mode.go
deleted file mode 100644
index 6b0a3c8..0000000
--- a/src/util/mode/mode.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package mode
-
-import "github.com/gin-gonic/gin"
-
-const (
- // Dev for development mode.
- Dev = "dev"
- // Prod for production mode.
- Prod = "prod"
- // TestDev used for tests.
- TestDev = "testdev"
-)
-
-var currentMode = Dev
-
-func init() {
- Set(appmode)
-}
-
-func Set(newMode string) {
- currentMode = newMode
- updateGinMode()
-}
-
-// Get returns the current mode.
-func Get() string {
- return currentMode
-}
-
-// IsDev returns true if the current mode is dev mode.
-func IsDev() bool {
- return Get() == Dev || Get() == TestDev
-}
-
-func updateGinMode() {
- switch Get() {
- case Dev:
- gin.SetMode(gin.DebugMode)
- case TestDev:
- gin.SetMode(gin.TestMode)
- case Prod:
- gin.SetMode(gin.ReleaseMode)
- default:
- panic("unknown mode")
- }
-}
diff --git a/src/util/mode/mode_dev.go b/src/util/mode/mode_dev.go
deleted file mode 100644
index 71cb6cd..0000000
--- a/src/util/mode/mode_dev.go
+++ /dev/null
@@ -1,6 +0,0 @@
-//go:build !prod
-// +build !prod
-
-package mode
-
-const appmode = Dev
diff --git a/src/util/mode/mode_prod.go b/src/util/mode/mode_prod.go
deleted file mode 100644
index 571398d..0000000
--- a/src/util/mode/mode_prod.go
+++ /dev/null
@@ -1,6 +0,0 @@
-//go:build prod
-// +build prod
-
-package mode
-
-const appmode = Prod
diff --git a/src/util/mode/mode_test.go b/src/util/mode/mode_test.go
deleted file mode 100644
index 8b06166..0000000
--- a/src/util/mode/mode_test.go
+++ /dev/null
@@ -1,35 +0,0 @@
-package mode
-
-import (
- "testing"
-
- "github.com/gin-gonic/gin"
- "github.com/stretchr/testify/assert"
-)
-
-func TestDevMode(t *testing.T) {
- Set(Dev)
- assert.Equal(t, Get(), Dev)
- assert.True(t, IsDev())
- assert.Equal(t, gin.Mode(), gin.DebugMode)
-}
-
-func TestTestDevMode(t *testing.T) {
- Set(TestDev)
- assert.Equal(t, Get(), TestDev)
- assert.True(t, IsDev())
- assert.Equal(t, gin.Mode(), gin.TestMode)
-}
-
-func TestProdMode(t *testing.T) {
- Set(Prod)
- assert.Equal(t, Get(), Prod)
- assert.False(t, IsDev())
- assert.Equal(t, gin.Mode(), gin.ReleaseMode)
-}
-
-func TestInvalidMode(t *testing.T) {
- assert.Panics(t, func() {
- Set("asdasda")
- })
-}
diff --git a/src/util/types.go b/src/util/types.go
deleted file mode 100644
index afd106b..0000000
--- a/src/util/types.go
+++ /dev/null
@@ -1,5 +0,0 @@
-package util
-
-type Broadcaster interface {
- Broadcast(msg []byte)
-}
diff --git a/src/util/util.go b/src/util/util.go
index e826e45..a64d409 100644
--- a/src/util/util.go
+++ b/src/util/util.go
@@ -1,7 +1,6 @@
package util
import (
- "fmt"
"os"
"os/exec"
"path/filepath"
@@ -26,24 +25,10 @@ func CreateDirIfNotExists(dirpath string) error {
return nil
}
-func GetTmpdir(tmpdirPath string) string {
- tmpdir := tmpdirPath
- // Default temporary directory
- if tmpdirPath == "" {
- tmpdir = filepath.Join(os.TempDir(), tmpdirName)
- }
-
+func GetTmpdir() (string, error) {
+ tmpdir := filepath.Join(os.TempDir(), tmpdirName)
err := CreateDirIfNotExists(tmpdir)
- if err != nil {
- panic(fmt.Sprintf("could not create tmpdir %s: %s", tmpdir, err))
- }
-
- return tmpdir
-}
-
-func RemoveTmpdir(tmpdirPath string) {
- tmpdir := GetTmpdir(tmpdirPath)
- _ = os.RemoveAll(tmpdir)
+ return tmpdir, err
}
func CommandFromString(cmdString string) *exec.Cmd {
@@ -56,41 +41,8 @@ func CommandFromString(cmdString string) *exec.Cmd {
return exec.Command(parts[0], parts[1:]...)
}
-func Reboot(rebootCmd string, t time.Duration) {
+func Reboot(t time.Duration) {
time.Sleep(t)
- cmd := CommandFromString(rebootCmd)
+ cmd := CommandFromString(RebootCmd)
_ = cmd.Run()
}
-
-func FindFile(explicitPath string, locations, endings []string) (string, error) {
- if explicitPath != "" {
- if !DoesFileExist(explicitPath) {
- return "", fmt.Errorf("file %s not found", explicitPath)
- }
- return explicitPath, nil
- }
-
- notFound := []string{}
-
- for _, f := range locations {
- if endings != nil {
- for _, t := range endings {
- fpath := f + "." + t
-
- if DoesFileExist(fpath) {
- return fpath, nil
- } else {
- notFound = append(notFound, fpath)
- }
- }
- } else {
- if DoesFileExist(f) {
- return f, nil
- } else {
- notFound = append(notFound, f)
- }
- }
- }
- return "", fmt.Errorf("none of the following files found: %s",
- strings.Join(notFound, "; "))
-}
diff --git a/src/util/util_test.go b/src/util/util_test.go
index 0ef6a32..eb28203 100644
--- a/src/util/util_test.go
+++ b/src/util/util_test.go
@@ -1,13 +1,11 @@
package util
import (
- "errors"
"os"
"path/filepath"
"testing"
"code.thetadev.de/TSGRain/SEBRAUC/src/fixtures"
- "code.thetadev.de/TSGRain/SEBRAUC/src/fixtures/testcmd"
"github.com/stretchr/testify/assert"
)
@@ -43,46 +41,27 @@ func TestDoesFileExist(t *testing.T) {
}
func TestTmpdir(t *testing.T) {
- tests := []struct {
- name string
- path string
- }{
- {
- name: "default",
- path: "",
- },
- {
- name: "custom",
- path: filepath.Join(os.TempDir(), "customTmpdir"),
- },
+ td, err := GetTmpdir()
+ if err != nil {
+ panic(err)
}
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- td := GetTmpdir(tt.path)
- assert.DirExists(t, td)
-
- tfile := filepath.Join(td, "test.txt")
- f, err := os.Create(tfile)
- if err != nil {
- panic(err)
- }
-
- _, err = f.WriteString("Hello")
- if err != nil {
- panic(err)
- }
- err = f.Close()
- if err != nil {
- panic(err)
- }
-
- assert.FileExists(t, tfile)
-
- RemoveTmpdir(tt.path)
- assert.NoDirExists(t, td)
- })
+ tfile := filepath.Join(td, "test.txt")
+ f, err := os.Create(tfile)
+ if err != nil {
+ panic(err)
}
+
+ _, err = f.WriteString("Hello")
+ if err != nil {
+ panic(err)
+ }
+ err = f.Close()
+ if err != nil {
+ panic(err)
+ }
+
+ assert.FileExists(t, tfile)
}
func TestCommandFromString(t *testing.T) {
@@ -124,78 +103,7 @@ func TestReboot(t *testing.T) {
testfile := "/tmp/sebrauc_reboot_test"
_ = os.Remove(testfile)
- Reboot(testcmd.Reboot, 0)
+ Reboot(0)
assert.FileExists(t, testfile)
}
-
-func TestFindFile(t *testing.T) {
- testfiles := fixtures.GetTestfilesDir()
-
- //nolint:lll
- tests := []struct {
- name string
- explicitPath string
- locations []string
- endings []string
- expect string
- expectErr error
- }{
- {
- name: "locations",
- explicitPath: "",
- locations: []string{filepath.Join(testfiles, "sebrauc")},
- endings: []string{"yml", "toml"},
- expect: filepath.Join(testfiles, "sebrauc.toml"),
- expectErr: nil,
- },
- {
- name: "locations_nf",
- explicitPath: "",
- locations: []string{filepath.Join(testfiles, "banana")},
- endings: []string{"yml", "toml"},
- expect: "",
- expectErr: errors.New("none of the following files found: src/fixtures/testfiles/banana.yml; src/fixtures/testfiles/banana.toml"),
- },
- {
- name: "no_endings",
- explicitPath: "",
- locations: []string{filepath.Join(testfiles, "banana"), filepath.Join(testfiles, "os-release")},
- endings: nil,
- expect: filepath.Join(testfiles, "os-release"),
- expectErr: nil,
- },
- {
- name: "no_endings_nf",
- explicitPath: "",
- locations: []string{filepath.Join(testfiles, "banana"), filepath.Join(testfiles, "apple")},
- endings: nil,
- expect: "",
- expectErr: errors.New("none of the following files found: src/fixtures/testfiles/banana; src/fixtures/testfiles/apple"),
- },
- {
- name: "explicit",
- explicitPath: filepath.Join(testfiles, "sebrauc.toml"),
- locations: []string{filepath.Join(testfiles, "banana")},
- endings: []string{"yml", "toml"},
- expect: filepath.Join(testfiles, "sebrauc.toml"),
- expectErr: nil,
- },
- {
- name: "explicit_nf",
- explicitPath: filepath.Join(testfiles, "banana.toml"),
- locations: []string{filepath.Join(testfiles, "banana")},
- endings: []string{"yml", "toml"},
- expect: "",
- expectErr: errors.New("file src/fixtures/testfiles/banana.toml not found"),
- },
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- fpath, err := FindFile(tt.explicitPath, tt.locations, tt.endings)
- assert.Equal(t, tt.expectErr, err)
- assert.Equal(t, tt.expect, fpath)
- })
- }
-}
diff --git a/ui/.env.development b/ui/.env.development
index 029df76..3eba399 100644
--- a/ui/.env.development
+++ b/ui/.env.development
@@ -1 +1,2 @@
+VITE_VERSION=dev
VITE_API_HOST=127.0.0.1:8080
diff --git a/ui/index.html b/ui/index.html
index 426ad0f..cf876c6 100644
--- a/ui/index.html
+++ b/ui/index.html
@@ -7,11 +7,7 @@
SEBRAUC
-
-