package server import ( "errors" "fmt" "net/http" "strings" "time" "code.thetadev.de/TSGRain/SEBRAUC/src/rauc" "code.thetadev.de/TSGRain/SEBRAUC/src/sysinfo" "code.thetadev.de/TSGRain/SEBRAUC/src/util" "code.thetadev.de/TSGRain/SEBRAUC/ui" "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 { address string raucUpdater *rauc.Rauc hub *MessageHub tmpdir string } type statusMessage struct { Success bool `json:"success"` Msg string `json:"msg"` } func NewServer(address string) *SEBRAUCServer { hub := NewHub() raucUpdater := rauc.NewRauc(hub.Broadcast) tmpdir, err := util.GetTmpdir() if err != nil { panic(err) } return &SEBRAUCServer{ address: address, raucUpdater: raucUpdater, hub: hub, tmpdir: tmpdir, } } func (srv *SEBRAUCServer) Run() error { app := fiber.New(fiber.Config{ AppName: "SEBRAUC", BodyLimit: 1024 * 1024 * 1024, ErrorHandler: errorHandler, DisableStartupMessage: true, }) 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.Get("/api/info", srv.controllerInfo) app.Post("/api/reboot", srv.controllerReboot) // Start messaging hub go srv.hub.Run() return app.Listen(srv.address) } func (srv *SEBRAUCServer) controllerUpdate(c *fiber.Ctx) error { file, err := c.FormFile("updateFile") if err != nil { return err } uid, err := uuid.NewRandom() if err != nil { return err } updateFile := fmt.Sprintf("%s/update_%s.raucb", srv.tmpdir, uid.String()) err = c.SaveFile(file, updateFile) if err != nil { return err } 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 { return err } return nil } func (srv *SEBRAUCServer) controllerStatus(c *fiber.Ctx) error { c.Context().SetStatusCode(200) _ = c.JSON(srv.raucUpdater.GetStatus()) return nil } func (srv *SEBRAUCServer) controllerInfo(c *fiber.Ctx) error { info, err := sysinfo.GetSysinfo() if err != nil { return err } c.Context().SetStatusCode(200) _ = c.JSON(info) return nil } func (srv *SEBRAUCServer) controllerReboot(c *fiber.Ctx) error { go util.Reboot(5 * time.Second) writeStatus(c, true, "System is rebooting") return nil } func errorHandler(c *fiber.Ctx, err error) error { // API error handling if strings.HasPrefix(c.Path(), "/api") { writeStatus(c, false, err.Error()) } return err } func writeStatus(c *fiber.Ctx, success bool, msg string) { _ = c.JSON(statusMessage{ Success: success, Msg: msg, }) if success { c.Context().SetStatusCode(200) } }