From 9eced855595fe337ba98f237b904857752e6e1aa Mon Sep 17 00:00:00 2001 From: Jordan Jones Date: Mon, 6 Jan 2025 12:48:45 -0800 Subject: [PATCH] chore(reorganization): part one --- cmd/main.go | 87 ++++++++++- go.mod | 2 +- config.go => internal/config/config.go | 21 +-- .../hardware/block_device.go | 7 +- display.go => internal/hardware/display.go | 6 +- hw.go => internal/hardware/hw.go | 4 +- native.go => internal/hardware/native.go | 6 +- usb.go => internal/hardware/usb.go | 35 ++--- .../hardware/usb_mass_storage.go | 83 +++++------ jiggler.go => internal/jiggler/jiggler.go | 19 +-- internal/logging/logging.go | 8 ++ network.go => internal/network/network.go | 10 +- ntp.go => internal/network/ntp.go | 2 +- ota.go => internal/network/ota.go | 40 +++--- cloud.go => internal/server/cloud.go | 76 +++++----- fuse.go => internal/server/fuse.go | 2 +- jsonrpc.go => internal/server/jsonrpc.go | 136 ++++++++++-------- .../server/remote_mount.go | 6 +- terminal.go => internal/server/terminal.go | 15 +- video.go => internal/server/video.go | 16 ++- web.go => internal/server/web.go | 120 ++++++++-------- webrtc.go => internal/server/webrtc.go | 47 +++--- wol.go => internal/wol/wol.go | 10 +- log.go | 8 -- main.go | 85 ----------- pkg/.gitkeep | 0 26 files changed, 444 insertions(+), 407 deletions(-) rename config.go => internal/config/config.go (80%) rename block_device.go => internal/hardware/block_device.go (94%) rename display.go => internal/hardware/display.go (97%) rename hw.go => internal/hardware/hw.go (97%) rename native.go => internal/hardware/native.go (98%) rename usb.go => internal/hardware/usb.go (92%) rename usb_mass_storage.go => internal/hardware/usb_mass_storage.go (83%) rename jiggler.go => internal/jiggler/jiggler.go (50%) create mode 100644 internal/logging/logging.go rename network.go => internal/network/network.go (96%) rename ntp.go => internal/network/ntp.go (99%) rename ota.go => internal/network/ota.go (96%) rename cloud.go => internal/server/cloud.go (77%) rename fuse.go => internal/server/fuse.go (99%) rename jsonrpc.go => internal/server/jsonrpc.go (76%) rename remote_mount.go => internal/server/remote_mount.go (90%) rename terminal.go => internal/server/terminal.go (72%) rename video.go => internal/server/video.go (74%) rename web.go => internal/server/web.go (76%) rename webrtc.go => internal/server/webrtc.go (75%) rename wol.go => internal/wol/wol.go (80%) delete mode 100644 log.go delete mode 100644 main.go create mode 100644 pkg/.gitkeep diff --git a/cmd/main.go b/cmd/main.go index 6080aff..1ed4ce3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,9 +1,92 @@ package main import ( - "kvm" + "context" + "log" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/jetkvm/kvm/internal/config" + "github.com/jetkvm/kvm/internal/hardware" + "github.com/jetkvm/kvm/internal/logging" + "github.com/jetkvm/kvm/internal/network" + "github.com/jetkvm/kvm/internal/server" + + "github.com/gwatts/rootcerts" ) +var appCtx context.Context + func main() { - kvm.Main() + var cancel context.CancelFunc + appCtx, cancel = context.WithCancel(context.Background()) + defer cancel() + + logging.Logger.Info("Starting JetKvm") + go hardware.RunWatchdog() + go network.ConfirmCurrentSystem() + + http.DefaultClient.Timeout = 1 * time.Minute + cfg := config.LoadConfig() + logging.Logger.Debug("config loaded") + + err := rootcerts.UpdateDefaultTransport() + if err != nil { + logging.Logger.Errorf("failed to load CA certs: %v", err) + } + + go network.TimeSyncLoop() + + hardware.StartNativeCtrlSocketServer() + hardware.StartNativeVideoSocketServer() + + go func() { + err = hardware.ExtractAndRunNativeBin() + if err != nil { + logging.Logger.Errorf("failed to extract and run native bin: %v", err) + //TODO: prepare an error message screen buffer to show on kvm screen + } + }() + + go func() { + time.Sleep(15 * time.Minute) + for { + logging.Logger.Debugf("UPDATING - Auto update enabled: %v", cfg.AutoUpdateEnabled) + if cfg.AutoUpdateEnabled == false { + return + } + if server.CurrentSession != nil { + logging.Logger.Debugf("skipping update since a session is active") + time.Sleep(1 * time.Minute) + continue + } + includePreRelease := cfg.IncludePreRelease + err = network.TryUpdate(context.Background(), hardware.GetDeviceID(), includePreRelease) + if err != nil { + logging.Logger.Errorf("failed to auto update: %v", err) + } + time.Sleep(1 * time.Hour) + } + }() + //go RunFuseServer() + go server.RunWebServer() + go server.RunWebsocketClient() + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) + <-sigs + log.Println("JetKVM Shutting Down") + //if fuseServer != nil { + // err := setMassStorageImage(" ") + // if err != nil { + // log.Printf("Failed to unmount mass storage image: %v", err) + // } + // err = fuseServer.Unmount() + // if err != nil { + // log.Printf("Failed to unmount fuse: %v", err) + // } + + // os.Exit(0) } diff --git a/go.mod b/go.mod index 5ddcfb6..04c8e3b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module kvm +module github.com/jetkvm/kvm go 1.21.0 diff --git a/config.go b/internal/config/config.go similarity index 80% rename from config.go rename to internal/config/config.go index 1636434..8738410 100644 --- a/config.go +++ b/internal/config/config.go @@ -1,9 +1,11 @@ -package kvm +package config import ( "encoding/json" "fmt" "os" + + "github.com/jetkvm/kvm/internal/logging" ) type WakeOnLanDevice struct { @@ -33,30 +35,31 @@ var defaultConfig = &Config{ var config *Config -func LoadConfig() { +func LoadConfig() *Config { if config != nil { - return + return config } file, err := os.Open(configPath) if err != nil { - logger.Debug("default config file doesn't exist, using default") + logging.Logger.Debug("default config file doesn't exist, using default") config = defaultConfig - return + return config } defer file.Close() var loadedConfig Config if err := json.NewDecoder(file).Decode(&loadedConfig); err != nil { - logger.Errorf("config file JSON parsing failed, %v", err) + logging.Logger.Errorf("config file JSON parsing failed, %v", err) config = defaultConfig - return + return config } config = &loadedConfig + return config } -func SaveConfig() error { +func SaveConfig(cfg *Config) error { file, err := os.Create(configPath) if err != nil { return fmt.Errorf("failed to create config file: %w", err) @@ -65,7 +68,7 @@ func SaveConfig() error { encoder := json.NewEncoder(file) encoder.SetIndent("", " ") - if err := encoder.Encode(config); err != nil { + if err := encoder.Encode(cfg); err != nil { return fmt.Errorf("failed to encode config: %w", err) } diff --git a/block_device.go b/internal/hardware/block_device.go similarity index 94% rename from block_device.go rename to internal/hardware/block_device.go index 1e34884..0f56e33 100644 --- a/block_device.go +++ b/internal/hardware/block_device.go @@ -1,4 +1,4 @@ -package kvm +package hardware import ( "context" @@ -8,6 +8,7 @@ import ( "os" "time" + "github.com/jetkvm/kvm/internal/logging" "github.com/pojntfx/go-nbd/pkg/client" "github.com/pojntfx/go-nbd/pkg/server" ) @@ -17,8 +18,8 @@ type remoteImageBackend struct { func (r remoteImageBackend) ReadAt(p []byte, off int64) (n int, err error) { virtualMediaStateMutex.RLock() - logger.Debugf("currentVirtualMediaState is %v", currentVirtualMediaState) - logger.Debugf("read size: %d, off: %d", len(p), off) + logging.Logger.Debugf("currentVirtualMediaState is %v", currentVirtualMediaState) + logging.Logger.Debugf("read size: %d, off: %d", len(p), off) if currentVirtualMediaState == nil { return 0, errors.New("image not mounted") } diff --git a/display.go b/internal/hardware/display.go similarity index 97% rename from display.go rename to internal/hardware/display.go index f312eb6..dfec72c 100644 --- a/display.go +++ b/internal/hardware/display.go @@ -1,4 +1,4 @@ -package kvm +package hardware import ( "fmt" @@ -59,7 +59,7 @@ func updateDisplay() { var displayInited = false -func requestDisplayUpdate() { +func RequestDisplayUpdate() { if !displayInited { fmt.Println("display not inited, skipping updates") return @@ -91,6 +91,6 @@ func init() { updateStaticContents() displayInited = true fmt.Println("display inited") - requestDisplayUpdate() + RequestDisplayUpdate() }() } diff --git a/hw.go b/internal/hardware/hw.go similarity index 97% rename from hw.go rename to internal/hardware/hw.go index efe8f5c..37e007f 100644 --- a/hw.go +++ b/internal/hardware/hw.go @@ -1,4 +1,4 @@ -package kvm +package hardware import ( "fmt" @@ -51,7 +51,7 @@ func GetDeviceID() string { return deviceID } -func runWatchdog() { +func RunWatchdog() { file, err := os.OpenFile("/dev/watchdog", os.O_WRONLY, 0) if err != nil { logger.Warnf("unable to open /dev/watchdog: %v, skipping watchdog reset", err) diff --git a/native.go b/internal/hardware/native.go similarity index 98% rename from native.go rename to internal/hardware/native.go index 89e6803..c1af162 100644 --- a/native.go +++ b/internal/hardware/native.go @@ -1,4 +1,4 @@ -package kvm +package hardware import ( "bytes" @@ -198,8 +198,8 @@ func handleVideoClient(conn net.Conn) { sinceLastFrame := now.Sub(lastFrame) lastFrame = now //fmt.Println("Video packet received", n, sinceLastFrame) - if currentSession != nil { - err := currentSession.VideoTrack.WriteSample(media.Sample{Data: inboundPacket[:n], Duration: sinceLastFrame}) + if CurrentSession != nil { + err := CurrentSession.VideoTrack.WriteSample(media.Sample{Data: inboundPacket[:n], Duration: sinceLastFrame}) if err != nil { log.Println("Error writing sample", err) } diff --git a/usb.go b/internal/hardware/usb.go similarity index 92% rename from usb.go rename to internal/hardware/usb.go index 075409a..a6291e4 100644 --- a/usb.go +++ b/internal/hardware/usb.go @@ -1,4 +1,4 @@ -package kvm +package hardware import ( "errors" @@ -12,6 +12,7 @@ import ( "sync" "time" + "github.com/jetkvm/kvm/internal/logging" gadget "github.com/openstadia/go-usb-gadget" ) @@ -37,22 +38,22 @@ func init() { _ = os.MkdirAll(imagesFolder, 0755) udcs := gadget.GetUdcs() if len(udcs) < 1 { - usbLogger.Error("no udc found, skipping USB stack init") + logging.UsbLogger.Error("no udc found, skipping USB stack init") return } udc = udcs[0] _, err := os.Stat(kvmGadgetPath) if err == nil { - logger.Info("usb gadget already exists, skipping usb gadget initialization") + logging.Logger.Info("usb gadget already exists, skipping usb gadget initialization") return } err = mountConfigFS() if err != nil { - logger.Errorf("failed to mount configfs: %v, usb stack might not function properly", err) + logging.Logger.Errorf("failed to mount configfs: %v, usb stack might not function properly", err) } err = writeGadgetConfig() if err != nil { - logger.Errorf("failed to start gadget: %v", err) + logging.Logger.Errorf("failed to start gadget: %v", err) } //TODO: read hid reports(capslock, numlock, etc) from keyboardHidFile @@ -232,7 +233,7 @@ var keyboardLock = sync.Mutex{} var mouseHidFile *os.File var mouseLock = sync.Mutex{} -func rpcKeyboardReport(modifier uint8, keys []uint8) error { +func RPCKeyboardReport(modifier uint8, keys []uint8) error { keyboardLock.Lock() defer keyboardLock.Unlock() if keyboardHidFile == nil { @@ -254,11 +255,11 @@ func rpcKeyboardReport(modifier uint8, keys []uint8) error { keyboardHidFile = nil return err } - resetUserInputTime() + kvm.ResetUserInputTime() return err } -func rpcAbsMouseReport(x, y int, buttons uint8) error { +func RPCAbsMouseReport(x, y int, buttons uint8) error { mouseLock.Lock() defer mouseLock.Unlock() if mouseHidFile == nil { @@ -268,7 +269,7 @@ func rpcAbsMouseReport(x, y int, buttons uint8) error { return fmt.Errorf("failed to open hidg1: %w", err) } } - resetUserInputTime() + kvm.ResetUserInputTime() _, err := mouseHidFile.Write([]byte{ 1, // Report ID 1 buttons, // Buttons @@ -287,7 +288,7 @@ func rpcAbsMouseReport(x, y int, buttons uint8) error { var accumulatedWheelY float64 = 0 -func rpcWheelReport(wheelY int8) error { +func RPCWheelReport(wheelY int8) error { if mouseHidFile == nil { return errors.New("hid not initialized") } @@ -307,7 +308,7 @@ func rpcWheelReport(wheelY int8) error { // Reset the accumulator, keeping any remainder accumulatedWheelY -= float64(scaledWheelY) - resetUserInputTime() + kvm.ResetUserInputTime() return err } @@ -324,7 +325,7 @@ func abs(x float64) float64 { var usbState = "unknown" -func rpcGetUSBState() (state string) { +func RPCGetUSBState() (state string) { stateBytes, err := os.ReadFile("/sys/class/udc/ffb00000.usb/state") if err != nil { return "unknown" @@ -332,13 +333,13 @@ func rpcGetUSBState() (state string) { return strings.TrimSpace(string(stateBytes)) } -func triggerUSBStateUpdate() { +func TriggerUSBStateUpdate() { go func() { - if currentSession == nil { + if kvm.CurrentSession == nil { log.Println("No active RPC session, skipping update state update") return } - writeJSONRPCEvent("usbState", usbState, currentSession) + WriteJSONRPCEvent("usbState", usbState, kvm.CurrentSession) }() } @@ -347,12 +348,12 @@ var udc string func init() { go func() { for { - newState := rpcGetUSBState() + newState := RPCGetUSBState() if newState != usbState { log.Printf("USB state changed from %s to %s", usbState, newState) usbState = newState requestDisplayUpdate() - triggerUSBStateUpdate() + TriggerUSBStateUpdate() } time.Sleep(500 * time.Millisecond) } diff --git a/usb_mass_storage.go b/internal/hardware/usb_mass_storage.go similarity index 83% rename from usb_mass_storage.go rename to internal/hardware/usb_mass_storage.go index b897c20..35119dc 100644 --- a/usb_mass_storage.go +++ b/internal/hardware/usb_mass_storage.go @@ -1,4 +1,4 @@ -package kvm +package hardware import ( "encoding/json" @@ -17,6 +17,7 @@ import ( "time" "github.com/gin-gonic/gin" + "github.com/jetkvm/kvm/internal/logging" "github.com/psanford/httpreadat" @@ -40,7 +41,7 @@ func setMassStorageImage(imagePath string) error { return nil } -func setMassStorageMode(cdrom bool) error { +func SetMassStorageMode(cdrom bool) error { mode := "0" if cdrom { mode = "1" @@ -52,7 +53,7 @@ func setMassStorageMode(cdrom bool) error { return nil } -func onDiskMessage(msg webrtc.DataChannelMessage) { +func OnDiskMessage(msg webrtc.DataChannelMessage) { fmt.Println("Disk Message, len:", len(msg.Data)) diskReadChan <- msg.Data } @@ -73,7 +74,7 @@ var nbdDevice *NBDDevice const imagesFolder = "/userdata/jetkvm/images" -func rpcMountBuiltInImage(filename string) error { +func RPCMountBuiltInImage(filename string) error { log.Println("Mount Built-In Image", filename) _ = os.MkdirAll(imagesFolder, 0755) imagePath := filepath.Join(imagesFolder, filename) @@ -107,7 +108,7 @@ func rpcMountBuiltInImage(filename string) error { return mountImage(imagePath) } -func getMassStorageMode() (bool, error) { +func GetMassStorageMode() (bool, error) { data, err := os.ReadFile(path.Join(massStorageFunctionPath, "lun.0", "cdrom")) if err != nil { return false, fmt.Errorf("failed to read cdrom mode: %w", err) @@ -125,7 +126,7 @@ type VirtualMediaUrlInfo struct { Size int64 } -func rpcCheckMountUrl(url string) (*VirtualMediaUrlInfo, error) { +func RPCCheckMountUrl(url string) (*VirtualMediaUrlInfo, error) { return nil, errors.New("not implemented") } @@ -155,13 +156,13 @@ type VirtualMediaState struct { var currentVirtualMediaState *VirtualMediaState var virtualMediaStateMutex sync.RWMutex -func rpcGetVirtualMediaState() (*VirtualMediaState, error) { +func RPCGetVirtualMediaState() (*VirtualMediaState, error) { virtualMediaStateMutex.RLock() defer virtualMediaStateMutex.RUnlock() return currentVirtualMediaState, nil } -func rpcUnmountImage() error { +func RPCUnmountImage() error { virtualMediaStateMutex.Lock() defer virtualMediaStateMutex.Unlock() err := setMassStorageImage("\n") @@ -180,7 +181,7 @@ func rpcUnmountImage() error { var httpRangeReader *httpreadat.RangeReader -func rpcMountWithHTTP(url string, mode VirtualMediaMode) error { +func RPCMountWithHTTP(url string, mode VirtualMediaMode) error { virtualMediaStateMutex.Lock() if currentVirtualMediaState != nil { virtualMediaStateMutex.Unlock() @@ -192,7 +193,7 @@ func rpcMountWithHTTP(url string, mode VirtualMediaMode) error { virtualMediaStateMutex.Unlock() return fmt.Errorf("failed to use http url: %w", err) } - logger.Infof("using remote url %s with size %d", url, n) + logging.Logger.Infof("using remote url %s with size %d", url, n) currentVirtualMediaState = &VirtualMediaState{ Source: HTTP, Mode: mode, @@ -201,25 +202,25 @@ func rpcMountWithHTTP(url string, mode VirtualMediaMode) error { } virtualMediaStateMutex.Unlock() - logger.Debug("Starting nbd device") + logging.Logger.Debug("Starting nbd device") nbdDevice = NewNBDDevice() err = nbdDevice.Start() if err != nil { - logger.Errorf("failed to start nbd device: %v", err) + logging.Logger.Errorf("failed to start nbd device: %v", err) return err } - logger.Debug("nbd device started") + logging.Logger.Debug("nbd device started") //TODO: replace by polling on block device having right size time.Sleep(1 * time.Second) err = setMassStorageImage("/dev/nbd0") if err != nil { return err } - logger.Info("usb mass storage mounted") + logging.Logger.Info("usb mass storage mounted") return nil } -func rpcMountWithWebRTC(filename string, size int64, mode VirtualMediaMode) error { +func RPCMountWithWebRTC(filename string, size int64, mode VirtualMediaMode) error { virtualMediaStateMutex.Lock() if currentVirtualMediaState != nil { virtualMediaStateMutex.Unlock() @@ -232,26 +233,26 @@ func rpcMountWithWebRTC(filename string, size int64, mode VirtualMediaMode) erro Size: size, } virtualMediaStateMutex.Unlock() - logger.Debugf("currentVirtualMediaState is %v", currentVirtualMediaState) - logger.Debug("Starting nbd device") + logging.Logger.Debugf("currentVirtualMediaState is %v", currentVirtualMediaState) + logging.Logger.Debug("Starting nbd device") nbdDevice = NewNBDDevice() err := nbdDevice.Start() if err != nil { - logger.Errorf("failed to start nbd device: %v", err) + logging.Logger.Errorf("failed to start nbd device: %v", err) return err } - logger.Debug("nbd device started") + logging.Logger.Debug("nbd device started") //TODO: replace by polling on block device having right size time.Sleep(1 * time.Second) err = setMassStorageImage("/dev/nbd0") if err != nil { return err } - logger.Info("usb mass storage mounted") + logging.Logger.Info("usb mass storage mounted") return nil } -func rpcMountWithStorage(filename string, mode VirtualMediaMode) error { +func RPCMountWithStorage(filename string, mode VirtualMediaMode) error { filename, err := sanitizeFilename(filename) if err != nil { return err @@ -287,7 +288,7 @@ type StorageSpace struct { BytesFree int64 `json:"bytesFree"` } -func rpcGetStorageSpace() (*StorageSpace, error) { +func RPCGetStorageSpace() (*StorageSpace, error) { var stat syscall.Statfs_t err := syscall.Statfs(imagesFolder, &stat) if err != nil { @@ -314,7 +315,7 @@ type StorageFiles struct { Files []StorageFile `json:"files"` } -func rpcListStorageFiles() (*StorageFiles, error) { +func RPCListStorageFiles() (*StorageFiles, error) { files, err := os.ReadDir(imagesFolder) if err != nil { return nil, fmt.Errorf("failed to read directory: %v", err) @@ -353,7 +354,7 @@ func sanitizeFilename(filename string) (string, error) { return sanitized, nil } -func rpcDeleteStorageFile(filename string) error { +func RPCDeleteStorageFile(filename string) error { sanitizedFilename, err := sanitizeFilename(filename) if err != nil { return err @@ -378,9 +379,9 @@ type StorageFileUpload struct { DataChannel string `json:"dataChannel"` } -const uploadIdPrefix = "upload_" +const UploadIdPrefix = "upload_" -func rpcStartStorageFileUpload(filename string, size int64) (*StorageFileUpload, error) { +func RPCStartStorageFileUpload(filename string, size int64) (*StorageFileUpload, error) { sanitizedFilename, err := sanitizeFilename(filename) if err != nil { return nil, err @@ -398,7 +399,7 @@ func rpcStartStorageFileUpload(filename string, size int64) (*StorageFileUpload, alreadyUploadedBytes = stat.Size() } - uploadId := uploadIdPrefix + uuid.New().String() + uploadId := UploadIdPrefix + uuid.New().String() file, err := os.OpenFile(uploadPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return nil, fmt.Errorf("failed to open file for upload: %v", err) @@ -430,14 +431,14 @@ type UploadProgress struct { AlreadyUploadedBytes int64 } -func handleUploadChannel(d *webrtc.DataChannel) { +func HandleUploadChannel(d *webrtc.DataChannel) { defer d.Close() uploadId := d.Label() pendingUploadsMutex.Lock() pendingUpload, ok := pendingUploads[uploadId] pendingUploadsMutex.Unlock() if !ok { - logger.Warnf("upload channel opened for unknown upload: %s", uploadId) + logging.Logger.Warnf("upload channel opened for unknown upload: %s", uploadId) return } totalBytesWritten := pendingUpload.AlreadyUploadedBytes @@ -447,12 +448,12 @@ func handleUploadChannel(d *webrtc.DataChannel) { newName := strings.TrimSuffix(pendingUpload.File.Name(), ".incomplete") err := os.Rename(pendingUpload.File.Name(), newName) if err != nil { - logger.Errorf("failed to rename uploaded file: %v", err) + logging.Logger.Errorf("failed to rename uploaded file: %v", err) } else { - logger.Debugf("successfully renamed uploaded file to: %s", newName) + logging.Logger.Debugf("successfully renamed uploaded file to: %s", newName) } } else { - logger.Warnf("uploaded ended before the complete file received") + logging.Logger.Warnf("uploaded ended before the complete file received") } pendingUploadsMutex.Lock() delete(pendingUploads, uploadId) @@ -463,7 +464,7 @@ func handleUploadChannel(d *webrtc.DataChannel) { d.OnMessage(func(msg webrtc.DataChannelMessage) { bytesWritten, err := pendingUpload.File.Write(msg.Data) if err != nil { - logger.Errorf("failed to write to file: %v", err) + logging.Logger.Errorf("failed to write to file: %v", err) close(uploadComplete) return } @@ -485,11 +486,11 @@ func handleUploadChannel(d *webrtc.DataChannel) { } progressJSON, err := json.Marshal(progress) if err != nil { - logger.Errorf("failed to marshal upload progress: %v", err) + logging.Logger.Errorf("failed to marshal upload progress: %v", err) } else { err = d.SendText(string(progressJSON)) if err != nil { - logger.Errorf("failed to send upload progress: %v", err) + logging.Logger.Errorf("failed to send upload progress: %v", err) } } lastProgressTime = time.Now() @@ -500,7 +501,7 @@ func handleUploadChannel(d *webrtc.DataChannel) { <-uploadComplete } -func handleUploadHttp(c *gin.Context) { +func HandleUploadHttp(c *gin.Context) { uploadId := c.Query("uploadId") pendingUploadsMutex.Lock() pendingUpload, ok := pendingUploads[uploadId] @@ -517,12 +518,12 @@ func handleUploadHttp(c *gin.Context) { newName := strings.TrimSuffix(pendingUpload.File.Name(), ".incomplete") err := os.Rename(pendingUpload.File.Name(), newName) if err != nil { - logger.Errorf("failed to rename uploaded file: %v", err) + logging.Logger.Errorf("failed to rename uploaded file: %v", err) } else { - logger.Debugf("successfully renamed uploaded file to: %s", newName) + logging.Logger.Debugf("successfully renamed uploaded file to: %s", newName) } } else { - logger.Warnf("uploaded ended before the complete file received") + logging.Logger.Warnf("uploaded ended before the complete file received") } pendingUploadsMutex.Lock() delete(pendingUploads, uploadId) @@ -534,7 +535,7 @@ func handleUploadHttp(c *gin.Context) { for { n, err := reader.Read(buffer) if err != nil && err != io.EOF { - logger.Errorf("failed to read from request body: %v", err) + logging.Logger.Errorf("failed to read from request body: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read upload data"}) return } @@ -542,7 +543,7 @@ func handleUploadHttp(c *gin.Context) { if n > 0 { bytesWritten, err := pendingUpload.File.Write(buffer[:n]) if err != nil { - logger.Errorf("failed to write to file: %v", err) + logging.Logger.Errorf("failed to write to file: %v", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to write upload data"}) return } diff --git a/jiggler.go b/internal/jiggler/jiggler.go similarity index 50% rename from jiggler.go rename to internal/jiggler/jiggler.go index 06f2b6c..b017825 100644 --- a/jiggler.go +++ b/internal/jiggler/jiggler.go @@ -1,21 +1,24 @@ -package kvm +package jiggler import ( "time" + + "github.com/jetkvm/kvm/internal/hardware" + "github.com/jetkvm/kvm/internal/logging" ) var lastUserInput = time.Now() -func resetUserInputTime() { +func ResetUserInputTime() { lastUserInput = time.Now() } var jigglerEnabled = false -func rpcSetJigglerState(enabled bool) { +func RPCSetJigglerState(enabled bool) { jigglerEnabled = enabled } -func rpcGetJigglerState() bool { +func RPCGetJigglerState() bool { return jigglerEnabled } @@ -28,13 +31,13 @@ func runJiggler() { if jigglerEnabled { if time.Since(lastUserInput) > 20*time.Second { //TODO: change to rel mouse - err := rpcAbsMouseReport(1, 1, 0) + err := hardware.RPCAbsMouseReport(1, 1, 0) if err != nil { - logger.Warnf("Failed to jiggle mouse: %v", err) + logging.Logger.Warnf("Failed to jiggle mouse: %v", err) } - err = rpcAbsMouseReport(0, 0, 0) + err = hardware.RPCAbsMouseReport(0, 0, 0) if err != nil { - logger.Warnf("Failed to reset mouse position: %v", err) + logging.Logger.Warnf("Failed to reset mouse position: %v", err) } } } diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 0000000..0db5164 --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,8 @@ +package logging + +import "github.com/pion/logging" + +// we use logging framework from pion +// ref: https://github.com/pion/webrtc/wiki/Debugging-WebRTC +var Logger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm") +var UsbLogger = logging.NewDefaultLoggerFactory().NewLogger("usb") diff --git a/network.go b/internal/network/network.go similarity index 96% rename from network.go rename to internal/network/network.go index f461e45..8bdfa97 100644 --- a/network.go +++ b/internal/network/network.go @@ -1,12 +1,14 @@ -package kvm +package network import ( "fmt" + "net" + "time" + + "github.com/jetkvm/kvm/internal/hardware" "github.com/pion/mdns/v2" "golang.org/x/net/ipv4" "golang.org/x/net/ipv6" - "net" - "time" "github.com/vishvananda/netlink" "github.com/vishvananda/netlink/nl" @@ -58,7 +60,7 @@ func checkNetworkState() { if newState != networkState { networkState = newState fmt.Println("network state changed") - requestDisplayUpdate() + hardware.RequestDisplayUpdate() } } diff --git a/ntp.go b/internal/network/ntp.go similarity index 99% rename from ntp.go rename to internal/network/ntp.go index f785d96..feecf12 100644 --- a/ntp.go +++ b/internal/network/ntp.go @@ -1,4 +1,4 @@ -package kvm +package network import ( "errors" diff --git a/ota.go b/internal/network/ota.go similarity index 96% rename from ota.go rename to internal/network/ota.go index 9f9cb6f..06b725f 100644 --- a/ota.go +++ b/internal/network/ota.go @@ -1,4 +1,4 @@ -package kvm +package network import ( "bytes" @@ -158,7 +158,7 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress progress := float32(written) / float32(totalSize) if progress-*downloadProgress >= 0.01 { *downloadProgress = progress - triggerOTAStateUpdate() + TriggerOTAStateUpdate() } } if er != nil { @@ -218,7 +218,7 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32) error progress := float32(verified) / float32(totalSize) if progress-*verifyProgress >= 0.01 { *verifyProgress = progress - triggerOTAStateUpdate() + TriggerOTAStateUpdate() } } if er != nil { @@ -269,13 +269,13 @@ type OTAState struct { var otaState = OTAState{} -func triggerOTAStateUpdate() { +func TriggerOTAStateUpdate() { go func() { - if currentSession == nil { + if CurrentSession == nil { log.Println("No active RPC session, skipping update state update") return } - writeJSONRPCEvent("otaState", otaState, currentSession) + WriteJSONRPCEvent("otaState", otaState, CurrentSession) }() } @@ -288,11 +288,11 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err otaState = OTAState{ Updating: true, } - triggerOTAStateUpdate() + TriggerOTAStateUpdate() defer func() { otaState.Updating = false - triggerOTAStateUpdate() + TriggerOTAStateUpdate() }() updateStatus, err := GetUpdateStatus(ctx, deviceId, includePreRelease) @@ -305,7 +305,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err otaState.MetadataFetchedAt = &now otaState.AppUpdatePending = updateStatus.AppUpdateAvailable otaState.SystemUpdatePending = updateStatus.SystemUpdateAvailable - triggerOTAStateUpdate() + TriggerOTAStateUpdate() local := updateStatus.Local remote := updateStatus.Remote @@ -320,18 +320,18 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err err := downloadFile(ctx, "/userdata/jetkvm/jetkvm_app.update", remote.AppUrl, &otaState.AppDownloadProgress) if err != nil { otaState.Error = fmt.Sprintf("Error downloading app update: %v", err) - triggerOTAStateUpdate() + TriggerOTAStateUpdate() return err } downloadFinished := time.Now() otaState.AppDownloadFinishedAt = &downloadFinished otaState.AppDownloadProgress = 1 - triggerOTAStateUpdate() + TriggerOTAStateUpdate() err = verifyFile("/userdata/jetkvm/jetkvm_app.update", remote.AppHash, &otaState.AppVerificationProgress) if err != nil { otaState.Error = fmt.Sprintf("Error verifying app update hash: %v", err) - triggerOTAStateUpdate() + TriggerOTAStateUpdate() return err } verifyFinished := time.Now() @@ -339,7 +339,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err otaState.AppVerificationProgress = 1 otaState.AppUpdatedAt = &verifyFinished otaState.AppUpdateProgress = 1 - triggerOTAStateUpdate() + TriggerOTAStateUpdate() fmt.Println("App update downloaded") rebootNeeded = true @@ -352,25 +352,25 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err err := downloadFile(ctx, "/userdata/jetkvm/update_system.tar", remote.SystemUrl, &otaState.SystemDownloadProgress) if err != nil { otaState.Error = fmt.Sprintf("Error downloading system update: %v", err) - triggerOTAStateUpdate() + TriggerOTAStateUpdate() return err } downloadFinished := time.Now() otaState.SystemDownloadFinishedAt = &downloadFinished otaState.SystemDownloadProgress = 1 - triggerOTAStateUpdate() + TriggerOTAStateUpdate() err = verifyFile("/userdata/jetkvm/update_system.tar", remote.SystemHash, &otaState.SystemVerificationProgress) if err != nil { otaState.Error = fmt.Sprintf("Error verifying system update hash: %v", err) - triggerOTAStateUpdate() + TriggerOTAStateUpdate() return err } fmt.Println("System update downloaded") verifyFinished := time.Now() otaState.SystemVerifiedAt = &verifyFinished otaState.SystemVerificationProgress = 1 - triggerOTAStateUpdate() + TriggerOTAStateUpdate() cmd := exec.Command("rk_ota", "--misc=update", "--tar_path=/userdata/jetkvm/update_system.tar", "--save_dir=/userdata/jetkvm/ota_save", "--partition=all") var b bytes.Buffer @@ -398,7 +398,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err if otaState.SystemUpdateProgress > 0.99 { otaState.SystemUpdateProgress = 0.99 } - triggerOTAStateUpdate() + TriggerOTAStateUpdate() case <-ctx.Done(): return } @@ -416,7 +416,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err fmt.Printf("rk_ota success, output: %s\n", output) otaState.SystemUpdateProgress = 1 otaState.SystemUpdatedAt = &verifyFinished - triggerOTAStateUpdate() + TriggerOTAStateUpdate() rebootNeeded = true } else { fmt.Println("System is up to date") @@ -495,7 +495,7 @@ func IsUpdatePending() bool { } // make sure our current a/b partition is set as default -func confirmCurrentSystem() { +func ConfirmCurrentSystem() { output, err := exec.Command("rk_ota", "--misc=now").CombinedOutput() if err != nil { logger.Warnf("failed to set current partition in A/B setup: %s", string(output)) diff --git a/cloud.go b/internal/server/cloud.go similarity index 77% rename from cloud.go rename to internal/server/cloud.go index db47727..2382eb2 100644 --- a/cloud.go +++ b/internal/server/cloud.go @@ -1,4 +1,4 @@ -package kvm +package server import ( "bytes" @@ -7,13 +7,17 @@ import ( "fmt" "net/http" "net/url" - "github.com/coder/websocket/wsjson" "time" + "github.com/coder/websocket/wsjson" + "github.com/jetkvm/kvm/internal/config" + "github.com/jetkvm/kvm/internal/hardware" + "github.com/jetkvm/kvm/internal/logging" + "github.com/coreos/go-oidc/v3/oidc" - "github.com/gin-gonic/gin" "github.com/coder/websocket" + "github.com/gin-gonic/gin" ) type CloudRegisterRequest struct { @@ -23,7 +27,7 @@ type CloudRegisterRequest struct { ClientId string `json:"clientId"` } -func handleCloudRegister(c *gin.Context) { +func HandleCloudRegister(c *gin.Context) { var req CloudRegisterRequest if err := c.ShouldBindJSON(&req); err != nil { @@ -68,8 +72,10 @@ func handleCloudRegister(c *gin.Context) { return } - config.CloudToken = tokenResp.SecretToken - config.CloudURL = req.CloudAPI + cfg := config.LoadConfig() + + cfg.CloudToken = tokenResp.SecretToken + cfg.CloudURL = req.CloudAPI provider, err := oidc.NewProvider(c, "https://accounts.google.com") if err != nil { @@ -88,10 +94,10 @@ func handleCloudRegister(c *gin.Context) { return } - config.GoogleIdentity = idToken.Audience[0] + ":" + idToken.Subject + cfg.GoogleIdentity = idToken.Audience[0] + ":" + idToken.Subject // Save the updated configuration - if err := SaveConfig(); err != nil { + if err := config.SaveConfig(cfg); err != nil { c.JSON(500, gin.H{"error": "Failed to save configuration"}) return } @@ -100,11 +106,12 @@ func handleCloudRegister(c *gin.Context) { } func runWebsocketClient() error { - if config.CloudToken == "" { + cfg := config.LoadConfig() + if cfg.CloudToken == "" { time.Sleep(5 * time.Second) return fmt.Errorf("cloud token is not set") } - wsURL, err := url.Parse(config.CloudURL) + wsURL, err := url.Parse(cfg.CloudURL) if err != nil { return fmt.Errorf("failed to parse config.CloudURL: %w", err) } @@ -114,8 +121,8 @@ func runWebsocketClient() error { wsURL.Scheme = "wss" } header := http.Header{} - header.Set("X-Device-ID", GetDeviceID()) - header.Set("Authorization", "Bearer "+config.CloudToken) + header.Set("X-Device-ID", hardware.GetDeviceID()) + header.Set("Authorization", "Bearer "+cfg.CloudToken) dialCtx, cancelDial := context.WithTimeout(context.Background(), time.Minute) defer cancelDial() c, _, err := websocket.Dial(dialCtx, wsURL.String(), &websocket.DialOptions{ @@ -125,7 +132,7 @@ func runWebsocketClient() error { return err } defer c.CloseNow() - logger.Infof("WS connected to %v", wsURL.String()) + logging.Logger.Infof("WS connected to %v", wsURL.String()) runCtx, cancelRun := context.WithCancel(context.Background()) defer cancelRun() go func() { @@ -133,7 +140,7 @@ func runWebsocketClient() error { time.Sleep(15 * time.Second) err := c.Ping(runCtx) if err != nil { - logger.Warnf("websocket ping error: %v", err) + logging.Logger.Warnf("websocket ping error: %v", err) cancelRun() return } @@ -151,19 +158,20 @@ func runWebsocketClient() error { var req WebRTCSessionRequest err = json.Unmarshal(msg, &req) if err != nil { - logger.Warnf("unable to parse ws message: %v", string(msg)) + logging.Logger.Warnf("unable to parse ws message: %v", string(msg)) continue } err = handleSessionRequest(runCtx, c, req) if err != nil { - logger.Infof("error starting new session: %v", err) + logging.Logger.Infof("error starting new session: %v", err) continue } } } func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSessionRequest) error { + cfg := config.LoadConfig() oidcCtx, cancelOIDC := context.WithTimeout(ctx, time.Minute) defer cancelOIDC() provider, err := oidc.NewProvider(oidcCtx, "https://accounts.google.com") @@ -183,11 +191,11 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess } googleIdentity := idToken.Audience[0] + ":" + idToken.Subject - if config.GoogleIdentity != googleIdentity { + if cfg.GoogleIdentity != googleIdentity { return fmt.Errorf("google identity mismatch") } - session, err := newSession() + session, err := NewSession() if err != nil { _ = wsjson.Write(context.Background(), c, gin.H{"error": err}) return err @@ -198,15 +206,15 @@ func handleSessionRequest(ctx context.Context, c *websocket.Conn, req WebRTCSess _ = wsjson.Write(context.Background(), c, gin.H{"error": err}) return err } - if currentSession != nil { - writeJSONRPCEvent("otherSessionConnected", nil, currentSession) - peerConn := currentSession.peerConnection + if CurrentSession != nil { + WriteJSONRPCEvent("otherSessionConnected", nil, CurrentSession) + peerConn := CurrentSession.PeerConnection go func() { time.Sleep(1 * time.Second) _ = peerConn.Close() }() } - currentSession = session + CurrentSession = session _ = wsjson.Write(context.Background(), c, gin.H{"sd": sd}) return nil } @@ -226,24 +234,26 @@ type CloudState struct { URL string `json:"url,omitempty"` } -func rpcGetCloudState() CloudState { +func RPCGetCloudState() CloudState { + cfg := config.LoadConfig() return CloudState{ - Connected: config.CloudToken != "" && config.CloudURL != "", - URL: config.CloudURL, + Connected: cfg.CloudToken != "" && cfg.CloudURL != "", + URL: cfg.CloudURL, } } -func rpcDeregisterDevice() error { - if config.CloudToken == "" || config.CloudURL == "" { +func RPCDeregisterDevice() error { + cfg := config.LoadConfig() + if cfg.CloudToken == "" || cfg.CloudURL == "" { return fmt.Errorf("cloud token or URL is not set") } - req, err := http.NewRequest(http.MethodDelete, config.CloudURL+"/devices/"+GetDeviceID(), nil) + req, err := http.NewRequest(http.MethodDelete, cfg.CloudURL+"/devices/"+hardware.GetDeviceID(), nil) if err != nil { return fmt.Errorf("failed to create deregister request: %w", err) } - req.Header.Set("Authorization", "Bearer "+config.CloudToken) + req.Header.Set("Authorization", "Bearer "+cfg.CloudToken) client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Do(req) if err != nil { @@ -256,10 +266,10 @@ func rpcDeregisterDevice() error { // 404 Not Found means the device is not in the database, which could be due to various reasons // (e.g., wrong cloud token, already deregistered). Regardless of the reason, we can safely remove it. if resp.StatusCode == http.StatusNotFound || (resp.StatusCode >= 200 && resp.StatusCode < 300) { - config.CloudToken = "" - config.CloudURL = "" - config.GoogleIdentity = "" - if err := SaveConfig(); err != nil { + cfg.CloudToken = "" + cfg.CloudURL = "" + cfg.GoogleIdentity = "" + if err := config.SaveConfig(cfg); err != nil { return fmt.Errorf("failed to save configuration after deregistering: %w", err) } diff --git a/fuse.go b/internal/server/fuse.go similarity index 99% rename from fuse.go rename to internal/server/fuse.go index 6ecc49c..1fe5af8 100644 --- a/fuse.go +++ b/internal/server/fuse.go @@ -1,4 +1,4 @@ -package kvm +package server import ( "context" diff --git a/jsonrpc.go b/internal/server/jsonrpc.go similarity index 76% rename from jsonrpc.go rename to internal/server/jsonrpc.go index 2ce5f18..906efc5 100644 --- a/jsonrpc.go +++ b/internal/server/jsonrpc.go @@ -1,4 +1,4 @@ -package kvm +package server import ( "context" @@ -11,6 +11,12 @@ import ( "path/filepath" "reflect" + "github.com/jetkvm/kvm/internal/config" + "github.com/jetkvm/kvm/internal/hardware" + "github.com/jetkvm/kvm/internal/jiggler" + "github.com/jetkvm/kvm/internal/logging" + "github.com/jetkvm/kvm/internal/network" + "github.com/jetkvm/kvm/internal/wol" "github.com/pion/webrtc/v4" ) @@ -34,7 +40,7 @@ type JSONRPCEvent struct { Params interface{} `json:"params,omitempty"` } -func writeJSONRPCResponse(response JSONRPCResponse, session *Session) { +func WriteJSONRPCResponse(response JSONRPCResponse, session *Session) { responseBytes, err := json.Marshal(response) if err != nil { log.Println("Error marshalling JSONRPC response:", err) @@ -47,7 +53,7 @@ func writeJSONRPCResponse(response JSONRPCResponse, session *Session) { } } -func writeJSONRPCEvent(event string, params interface{}, session *Session) { +func WriteJSONRPCEvent(event string, params interface{}, session *Session) { request := JSONRPCEvent{ JSONRPC: "2.0", Method: event, @@ -69,7 +75,7 @@ func writeJSONRPCEvent(event string, params interface{}, session *Session) { } } -func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { +func OnRPCMessage(message webrtc.DataChannelMessage, session *Session) { var request JSONRPCRequest err := json.Unmarshal(message.Data, &request) if err != nil { @@ -81,7 +87,7 @@ func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { }, ID: 0, } - writeJSONRPCResponse(errorResponse, session) + WriteJSONRPCResponse(errorResponse, session) return } @@ -96,7 +102,7 @@ func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { }, ID: request.ID, } - writeJSONRPCResponse(errorResponse, session) + WriteJSONRPCResponse(errorResponse, session) return } @@ -111,7 +117,7 @@ func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { }, ID: request.ID, } - writeJSONRPCResponse(errorResponse, session) + WriteJSONRPCResponse(errorResponse, session) return } @@ -120,7 +126,7 @@ func onRPCMessage(message webrtc.DataChannelMessage, session *Session) { Result: result, ID: request.ID, } - writeJSONRPCResponse(response, session) + WriteJSONRPCResponse(response, session) } func rpcPing() (string, error) { @@ -128,7 +134,7 @@ func rpcPing() (string, error) { } func rpcGetDeviceID() (string, error) { - return GetDeviceID(), nil + return hardware.GetDeviceID(), nil } var streamFactor = 1.0 @@ -139,7 +145,7 @@ func rpcGetStreamQualityFactor() (float64, error) { func rpcSetStreamQualityFactor(factor float64) error { log.Printf("Setting stream quality factor to: %f", factor) - var _, err = CallCtrlAction("set_video_quality_factor", map[string]interface{}{"quality_factor": factor}) + var _, err = hardware.CallCtrlAction("set_video_quality_factor", map[string]interface{}{"quality_factor": factor}) if err != nil { return err } @@ -149,19 +155,21 @@ func rpcSetStreamQualityFactor(factor float64) error { } func rpcGetAutoUpdateState() (bool, error) { - return config.AutoUpdateEnabled, nil + cfg := config.LoadConfig() + return cfg.AutoUpdateEnabled, nil } func rpcSetAutoUpdateState(enabled bool) (bool, error) { - config.AutoUpdateEnabled = enabled - if err := SaveConfig(); err != nil { - return config.AutoUpdateEnabled, fmt.Errorf("failed to save config: %w", err) + cfg := config.LoadConfig() + cfg.AutoUpdateEnabled = enabled + if err := config.SaveConfig(cfg); err != nil { + return cfg.AutoUpdateEnabled, fmt.Errorf("failed to save config: %w", err) } return enabled, nil } func rpcGetEDID() (string, error) { - resp, err := CallCtrlAction("get_edid", nil) + resp, err := hardware.CallCtrlAction("get_edid", nil) if err != nil { return "", err } @@ -179,7 +187,7 @@ func rpcSetEDID(edid string) error { } else { log.Printf("Setting EDID to: %s", edid) } - _, err := CallCtrlAction("set_edid", map[string]interface{}{"edid": edid}) + _, err := hardware.CallCtrlAction("set_edid", map[string]interface{}{"edid": edid}) if err != nil { return err } @@ -187,20 +195,23 @@ func rpcSetEDID(edid string) error { } func rpcGetDevChannelState() (bool, error) { - return config.IncludePreRelease, nil + cfg := config.LoadConfig() + return cfg.IncludePreRelease, nil } func rpcSetDevChannelState(enabled bool) error { - config.IncludePreRelease = enabled - if err := SaveConfig(); err != nil { + cfg := config.LoadConfig() + cfg.IncludePreRelease = enabled + if err := config.SaveConfig(cfg); err != nil { return fmt.Errorf("failed to save config: %w", err) } return nil } -func rpcGetUpdateStatus() (*UpdateStatus, error) { - includePreRelease := config.IncludePreRelease - updateStatus, err := GetUpdateStatus(context.Background(), GetDeviceID(), includePreRelease) +func rpcGetUpdateStatus() (*network.UpdateStatus, error) { + cfg := config.LoadConfig() + includePreRelease := cfg.IncludePreRelease + updateStatus, err := network.GetUpdateStatus(context.Background(), hardware.GetDeviceID(), includePreRelease) if err != nil { return nil, fmt.Errorf("error checking for updates: %w", err) } @@ -209,11 +220,12 @@ func rpcGetUpdateStatus() (*UpdateStatus, error) { } func rpcTryUpdate() error { - includePreRelease := config.IncludePreRelease + cfg := config.LoadConfig() + includePreRelease := cfg.IncludePreRelease go func() { - err := TryUpdate(context.Background(), GetDeviceID(), includePreRelease) + err := network.TryUpdate(context.Background(), hardware.GetDeviceID(), includePreRelease) if err != nil { - logger.Warnf("failed to try update: %v", err) + logging.Logger.Warnf("failed to try update: %v", err) } }() return nil @@ -258,7 +270,7 @@ func rpcSetDevModeState(enabled bool) error { return fmt.Errorf("failed to create devmode file: %w", err) } } else { - logger.Debug("dev mode already enabled") + logging.Logger.Debug("dev mode already enabled") return nil } } else { @@ -267,7 +279,7 @@ func rpcSetDevModeState(enabled bool) error { return fmt.Errorf("failed to remove devmode file: %w", err) } } else if os.IsNotExist(err) { - logger.Debug("dev mode already disabled") + logging.Logger.Debug("dev mode already disabled") return nil } else { return fmt.Errorf("error checking dev mode file: %w", err) @@ -277,7 +289,7 @@ func rpcSetDevModeState(enabled bool) error { cmd := exec.Command("dropbear.sh") output, err := cmd.CombinedOutput() if err != nil { - logger.Warnf("Failed to start/stop SSH: %v, %v", err, output) + logging.Logger.Warnf("Failed to start/stop SSH: %v, %v", err, output) return fmt.Errorf("failed to start/stop SSH, you may need to reboot for changes to take effect") } @@ -429,7 +441,7 @@ func rpcSetMassStorageMode(mode string) (string, error) { log.Printf("[jsonrpc.go:rpcSetMassStorageMode] Setting mass storage mode to: %s", mode) - err := setMassStorageMode(cdrom) + err := hardware.SetMassStorageMode(cdrom) if err != nil { return "", fmt.Errorf("failed to set mass storage mode: %w", err) } @@ -441,7 +453,7 @@ func rpcSetMassStorageMode(mode string) (string, error) { } func rpcGetMassStorageMode() (string, error) { - cdrom, err := getMassStorageMode() + cdrom, err := hardware.GetMassStorageMode() if err != nil { return "", fmt.Errorf("failed to get mass storage mode: %w", err) } @@ -454,7 +466,7 @@ func rpcGetMassStorageMode() (string, error) { } func rpcIsUpdatePending() (bool, error) { - return IsUpdatePending(), nil + return network.IsUpdatePending(), nil } var udcFilePath = filepath.Join("/sys/bus/platform/drivers/dwc3", udc) @@ -478,28 +490,26 @@ func rpcSetUsbEmulationState(enabled bool) error { } } -func rpcGetWakeOnLanDevices() ([]WakeOnLanDevice, error) { - LoadConfig() - if config.WakeOnLanDevices == nil { - return []WakeOnLanDevice{}, nil +func rpcGetWakeOnLanDevices() ([]config.WakeOnLanDevice, error) { + cfg := config.LoadConfig() + if cfg.WakeOnLanDevices == nil { + return []config.WakeOnLanDevice{}, nil } - return config.WakeOnLanDevices, nil + return cfg.WakeOnLanDevices, nil } type SetWakeOnLanDevicesParams struct { - Devices []WakeOnLanDevice `json:"devices"` + Devices []config.WakeOnLanDevice `json:"devices"` } func rpcSetWakeOnLanDevices(params SetWakeOnLanDevicesParams) error { - LoadConfig() - config.WakeOnLanDevices = params.Devices - return SaveConfig() + cfg := config.LoadConfig() + cfg.WakeOnLanDevices = params.Devices + return config.SaveConfig(cfg) } func rpcResetConfig() error { - LoadConfig() - config = defaultConfig - if err := SaveConfig(); err != nil { + if err := config.SaveConfig(&config.Config{}); err != nil { return fmt.Errorf("failed to reset config: %w", err) } @@ -511,18 +521,18 @@ func rpcResetConfig() error { var rpcHandlers = map[string]RPCHandler{ "ping": {Func: rpcPing}, "getDeviceID": {Func: rpcGetDeviceID}, - "deregisterDevice": {Func: rpcDeregisterDevice}, - "getCloudState": {Func: rpcGetCloudState}, - "keyboardReport": {Func: rpcKeyboardReport, Params: []string{"modifier", "keys"}}, - "absMouseReport": {Func: rpcAbsMouseReport, Params: []string{"x", "y", "buttons"}}, - "wheelReport": {Func: rpcWheelReport, Params: []string{"wheelY"}}, + "deregisterDevice": {Func: RPCDeregisterDevice}, + "getCloudState": {Func: RPCGetCloudState}, + "keyboardReport": {Func: hardware.RPCKeyboardReport, Params: []string{"modifier", "keys"}}, + "absMouseReport": {Func: hardware.RPCAbsMouseReport, Params: []string{"x", "y", "buttons"}}, + "wheelReport": {Func: hardware.RPCWheelReport, Params: []string{"wheelY"}}, "getVideoState": {Func: rpcGetVideoState}, - "getUSBState": {Func: rpcGetUSBState}, - "unmountImage": {Func: rpcUnmountImage}, - "rpcMountBuiltInImage": {Func: rpcMountBuiltInImage, Params: []string{"filename"}}, - "setJigglerState": {Func: rpcSetJigglerState, Params: []string{"enabled"}}, - "getJigglerState": {Func: rpcGetJigglerState}, - "sendWOLMagicPacket": {Func: rpcSendWOLMagicPacket, Params: []string{"macAddress"}}, + "getUSBState": {Func: hardware.RPCGetUSBState}, + "unmountImage": {Func: hardware.RPCUnmountImage}, + "rpcMountBuiltInImage": {Func: hardware.RPCMountBuiltInImage, Params: []string{"filename"}}, + "setJigglerState": {Func: jiggler.RPCSetJigglerState, Params: []string{"enabled"}}, + "getJigglerState": {Func: jiggler.RPCGetJigglerState}, + "sendWOLMagicPacket": {Func: wol.RPCSendWolMagicPacket, Params: []string{"macAddress"}}, "getStreamQualityFactor": {Func: rpcGetStreamQualityFactor}, "setStreamQualityFactor": {Func: rpcSetStreamQualityFactor, Params: []string{"factor"}}, "getAutoUpdateState": {Func: rpcGetAutoUpdateState}, @@ -542,15 +552,15 @@ var rpcHandlers = map[string]RPCHandler{ "isUpdatePending": {Func: rpcIsUpdatePending}, "getUsbEmulationState": {Func: rpcGetUsbEmulationState}, "setUsbEmulationState": {Func: rpcSetUsbEmulationState, Params: []string{"enabled"}}, - "checkMountUrl": {Func: rpcCheckMountUrl, Params: []string{"url"}}, - "getVirtualMediaState": {Func: rpcGetVirtualMediaState}, - "getStorageSpace": {Func: rpcGetStorageSpace}, - "mountWithHTTP": {Func: rpcMountWithHTTP, Params: []string{"url", "mode"}}, - "mountWithWebRTC": {Func: rpcMountWithWebRTC, Params: []string{"filename", "size", "mode"}}, - "mountWithStorage": {Func: rpcMountWithStorage, Params: []string{"filename", "mode"}}, - "listStorageFiles": {Func: rpcListStorageFiles}, - "deleteStorageFile": {Func: rpcDeleteStorageFile, Params: []string{"filename"}}, - "startStorageFileUpload": {Func: rpcStartStorageFileUpload, Params: []string{"filename", "size"}}, + "checkMountUrl": {Func: hardware.RPCCheckMountUrl, Params: []string{"url"}}, + "getVirtualMediaState": {Func: hardware.RPCGetVirtualMediaState}, + "getStorageSpace": {Func: hardware.RPCGetStorageSpace}, + "mountWithHTTP": {Func: hardware.RPCMountWithHTTP, Params: []string{"url", "mode"}}, + "mountWithWebRTC": {Func: hardware.RPCMountWithWebRTC, Params: []string{"filename", "size", "mode"}}, + "mountWithStorage": {Func: hardware.RPCMountWithStorage, Params: []string{"filename", "mode"}}, + "listStorageFiles": {Func: hardware.RPCListStorageFiles}, + "deleteStorageFile": {Func: hardware.RPCDeleteStorageFile, Params: []string{"filename"}}, + "startStorageFileUpload": {Func: hardware.RPCStartStorageFileUpload, Params: []string{"filename", "size"}}, "getWakeOnLanDevices": {Func: rpcGetWakeOnLanDevices}, "setWakeOnLanDevices": {Func: rpcSetWakeOnLanDevices, Params: []string{"params"}}, "resetConfig": {Func: rpcResetConfig}, diff --git a/remote_mount.go b/internal/server/remote_mount.go similarity index 90% rename from remote_mount.go rename to internal/server/remote_mount.go index e6e7322..4ddac4e 100644 --- a/remote_mount.go +++ b/internal/server/remote_mount.go @@ -1,4 +1,4 @@ -package kvm +package server import ( "context" @@ -40,12 +40,12 @@ func (w *WebRTCDiskReader) Read(ctx context.Context, offset int64, size int64) ( return nil, err } - if currentSession == nil || currentSession.DiskChannel == nil { + if CurrentSession == nil || CurrentSession.DiskChannel == nil { return nil, errors.New("not active session") } logger.Debugf("reading from webrtc %v", string(jsonBytes)) - err = currentSession.DiskChannel.SendText(string(jsonBytes)) + err = CurrentSession.DiskChannel.SendText(string(jsonBytes)) if err != nil { return nil, err } diff --git a/terminal.go b/internal/server/terminal.go similarity index 72% rename from terminal.go rename to internal/server/terminal.go index 1a1ac1c..3c497cd 100644 --- a/terminal.go +++ b/internal/server/terminal.go @@ -1,4 +1,4 @@ -package kvm +package server import ( "encoding/json" @@ -7,6 +7,7 @@ import ( "os/exec" "github.com/creack/pty" + "github.com/jetkvm/kvm/internal/logging" "github.com/pion/webrtc/v4" ) @@ -15,7 +16,7 @@ type TerminalSize struct { Cols int `json:"cols"` } -func handleTerminalChannel(d *webrtc.DataChannel) { +func HandleTerminalChannel(d *webrtc.DataChannel) { var ptmx *os.File var cmd *exec.Cmd d.OnOpen(func() { @@ -23,7 +24,7 @@ func handleTerminalChannel(d *webrtc.DataChannel) { var err error ptmx, err = pty.Start(cmd) if err != nil { - logger.Errorf("Failed to start pty: %v", err) + logging.Logger.Errorf("Failed to start pty: %v", err) d.Close() return } @@ -34,13 +35,13 @@ func handleTerminalChannel(d *webrtc.DataChannel) { n, err := ptmx.Read(buf) if err != nil { if err != io.EOF { - logger.Errorf("Failed to read from pty: %v", err) + logging.Logger.Errorf("Failed to read from pty: %v", err) } break } err = d.Send(buf[:n]) if err != nil { - logger.Errorf("Failed to send pty output: %v", err) + logging.Logger.Errorf("Failed to send pty output: %v", err) break } } @@ -61,11 +62,11 @@ func handleTerminalChannel(d *webrtc.DataChannel) { }) return } - logger.Errorf("Failed to parse terminal size: %v", err) + logging.Logger.Errorf("Failed to parse terminal size: %v", err) } _, err := ptmx.Write(msg.Data) if err != nil { - logger.Errorf("Failed to write to pty: %v", err) + logging.Logger.Errorf("Failed to write to pty: %v", err) } }) diff --git a/video.go b/internal/server/video.go similarity index 74% rename from video.go rename to internal/server/video.go index 8fc2bfa..8f1f99e 100644 --- a/video.go +++ b/internal/server/video.go @@ -1,8 +1,10 @@ -package kvm +package server import ( "encoding/json" "log" + + "github.com/jetkvm/kvm/internal/hardware" ) // max frame size for 1080p video, specified in mpp venc setting @@ -16,7 +18,7 @@ func writeCtrlAction(action string) error { if err != nil { return err } - err = WriteCtrlMessage(jsonMessage) + err = hardware.WriteCtrlMessage(jsonMessage) return err } @@ -30,12 +32,12 @@ type VideoInputState struct { var lastVideoState VideoInputState -func triggerVideoStateUpdate() { +func TriggerVideoStateUpdate() { go func() { - writeJSONRPCEvent("videoInputState", lastVideoState, currentSession) + WriteJSONRPCEvent("videoInputState", lastVideoState, CurrentSession) }() } -func HandleVideoStateMessage(event CtrlResponse) { +func HandleVideoStateMessage(event hardware.CtrlResponse) { videoState := VideoInputState{} err := json.Unmarshal(event.Data, &videoState) if err != nil { @@ -43,8 +45,8 @@ func HandleVideoStateMessage(event CtrlResponse) { return } lastVideoState = videoState - triggerVideoStateUpdate() - requestDisplayUpdate() + TriggerVideoStateUpdate() + hardware.RequestDisplayUpdate() } func rpcGetVideoState() (VideoInputState, error) { diff --git a/web.go b/internal/server/web.go similarity index 76% rename from web.go rename to internal/server/web.go index 64f8de7..e50cdf7 100644 --- a/web.go +++ b/internal/server/web.go @@ -1,4 +1,4 @@ -package kvm +package server import ( "embed" @@ -10,6 +10,9 @@ import ( "github.com/gin-gonic/gin" "github.com/google/uuid" + "github.com/jetkvm/kvm/internal/config" + "github.com/jetkvm/kvm/internal/hardware" + "github.com/jetkvm/kvm/internal/server" "golang.org/x/crypto/bcrypt" ) @@ -83,14 +86,14 @@ func setupRouter() *gin.Engine { protected.Use(protectedMiddleware()) { protected.POST("/webrtc/session", handleWebRTCSession) - protected.POST("/cloud/register", handleCloudRegister) + protected.POST("/cloud/register", server.HandleCloudRegister) protected.GET("/device", handleDevice) protected.POST("/auth/logout", handleLogout) protected.POST("/auth/password-local", handleCreatePassword) protected.PUT("/auth/password-local", handleUpdatePassword) protected.DELETE("/auth/local-password", handleDeletePassword) - protected.POST("/storage/upload", handleUploadHttp) + protected.POST("/storage/upload", hardware.HandleUploadHttp) } // Catch-all route for SPA @@ -106,7 +109,7 @@ func setupRouter() *gin.Engine { } // TODO: support multiple sessions? -var currentSession *Session +var CurrentSession *Session func handleWebRTCSession(c *gin.Context) { var req WebRTCSessionRequest @@ -116,7 +119,7 @@ func handleWebRTCSession(c *gin.Context) { return } - session, err := newSession() + session, err := server.NewSession() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err}) return @@ -127,22 +130,22 @@ func handleWebRTCSession(c *gin.Context) { c.JSON(http.StatusInternalServerError, gin.H{"error": err}) return } - if currentSession != nil { - writeJSONRPCEvent("otherSessionConnected", nil, currentSession) - peerConn := currentSession.peerConnection + if CurrentSession != nil { + WriteJSONRPCEvent("otherSessionConnected", nil, CurrentSession) + peerConn := CurrentSession.PeerConnection go func() { time.Sleep(1 * time.Second) _ = peerConn.Close() }() } - currentSession = session + CurrentSession = session c.JSON(http.StatusOK, gin.H{"sd": sd}) } func handleLogin(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() - if config.LocalAuthMode == "noPassword" { + if cfg.LocalAuthMode == "noPassword" { c.JSON(http.StatusBadRequest, gin.H{"error": "Login is disabled in noPassword mode"}) return } @@ -154,25 +157,24 @@ func handleLogin(c *gin.Context) { return } - LoadConfig() - err := bcrypt.CompareHashAndPassword([]byte(config.HashedPassword), []byte(req.Password)) + err := bcrypt.CompareHashAndPassword([]byte(cfg.HashedPassword), []byte(req.Password)) if err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid password"}) return } - config.LocalAuthToken = uuid.New().String() + cfg.LocalAuthToken = uuid.New().String() // Set the cookie - c.SetCookie("authToken", config.LocalAuthToken, 7*24*60*60, "/", "", false, true) + c.SetCookie("authToken", cfg.LocalAuthToken, 7*24*60*60, "/", "", false, true) c.JSON(http.StatusOK, gin.H{"message": "Login successful"}) } func handleLogout(c *gin.Context) { - LoadConfig() - config.LocalAuthToken = "" - if err := SaveConfig(); err != nil { + cfg := config.LoadConfig() + cfg.LocalAuthToken = "" + if err := config.SaveConfig(cfg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration"}) return } @@ -184,15 +186,15 @@ func handleLogout(c *gin.Context) { func protectedMiddleware() gin.HandlerFunc { return func(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() - if config.LocalAuthMode == "noPassword" { + if cfg.LocalAuthMode == "noPassword" { c.Next() return } authToken, err := c.Cookie("authToken") - if err != nil || authToken != config.LocalAuthToken || authToken == "" { + if err != nil || authToken != cfg.LocalAuthToken || authToken == "" { c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) c.Abort() return @@ -214,20 +216,20 @@ func RunWebServer() { } func handleDevice(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() response := LocalDevice{ - AuthMode: &config.LocalAuthMode, - DeviceID: GetDeviceID(), + AuthMode: &cfg.LocalAuthMode, + DeviceID: hardware.GetDeviceID(), } c.JSON(http.StatusOK, response) } func handleCreatePassword(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() - if config.HashedPassword != "" { + if cfg.HashedPassword != "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Password already set"}) return } @@ -235,7 +237,7 @@ func handleCreatePassword(c *gin.Context) { // We only allow users with noPassword mode to set a new password // Users with password mode are not allowed to set a new password without providing the old password // We have a PUT endpoint for changing the password, use that instead - if config.LocalAuthMode != "noPassword" { + if cfg.LocalAuthMode != "noPassword" { c.JSON(http.StatusBadRequest, gin.H{"error": "Password mode is not enabled"}) return } @@ -252,31 +254,31 @@ func handleCreatePassword(c *gin.Context) { return } - config.HashedPassword = string(hashedPassword) - config.LocalAuthToken = uuid.New().String() - config.LocalAuthMode = "password" - if err := SaveConfig(); err != nil { + cfg.HashedPassword = string(hashedPassword) + cfg.LocalAuthToken = uuid.New().String() + cfg.LocalAuthMode = "password" + if err := config.SaveConfig(cfg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration"}) return } // Set the cookie - c.SetCookie("authToken", config.LocalAuthToken, 7*24*60*60, "/", "", false, true) + c.SetCookie("authToken", cfg.LocalAuthToken, 7*24*60*60, "/", "", false, true) c.JSON(http.StatusCreated, gin.H{"message": "Password set successfully"}) } func handleUpdatePassword(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() - if config.HashedPassword == "" { + if cfg.HashedPassword == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Password is not set"}) return } // We only allow users with password mode to change their password // Users with noPassword mode are not allowed to change their password - if config.LocalAuthMode != "password" { + if cfg.LocalAuthMode != "password" { c.JSON(http.StatusBadRequest, gin.H{"error": "Password mode is not enabled"}) return } @@ -287,7 +289,7 @@ func handleUpdatePassword(c *gin.Context) { return } - if err := bcrypt.CompareHashAndPassword([]byte(config.HashedPassword), []byte(req.OldPassword)); err != nil { + if err := bcrypt.CompareHashAndPassword([]byte(cfg.HashedPassword), []byte(req.OldPassword)); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Incorrect old password"}) return } @@ -298,28 +300,28 @@ func handleUpdatePassword(c *gin.Context) { return } - config.HashedPassword = string(hashedPassword) - config.LocalAuthToken = uuid.New().String() - if err := SaveConfig(); err != nil { + cfg.HashedPassword = string(hashedPassword) + cfg.LocalAuthToken = uuid.New().String() + if err := config.SaveConfig(cfg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration"}) return } // Set the cookie - c.SetCookie("authToken", config.LocalAuthToken, 7*24*60*60, "/", "", false, true) + c.SetCookie("authToken", cfg.LocalAuthToken, 7*24*60*60, "/", "", false, true) c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully"}) } func handleDeletePassword(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() - if config.HashedPassword == "" { + if cfg.HashedPassword == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Password is not set"}) return } - if config.LocalAuthMode != "password" { + if cfg.LocalAuthMode != "password" { c.JSON(http.StatusBadRequest, gin.H{"error": "Password mode is not enabled"}) return } @@ -330,16 +332,16 @@ func handleDeletePassword(c *gin.Context) { return } - if err := bcrypt.CompareHashAndPassword([]byte(config.HashedPassword), []byte(req.Password)); err != nil { + if err := bcrypt.CompareHashAndPassword([]byte(cfg.HashedPassword), []byte(req.Password)); err != nil { c.JSON(http.StatusUnauthorized, gin.H{"error": "Incorrect password"}) return } // Disable password - config.HashedPassword = "" - config.LocalAuthToken = "" - config.LocalAuthMode = "noPassword" - if err := SaveConfig(); err != nil { + cfg.HashedPassword = "" + cfg.LocalAuthToken = "" + cfg.LocalAuthMode = "noPassword" + if err := config.SaveConfig(cfg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save configuration"}) return } @@ -350,20 +352,20 @@ func handleDeletePassword(c *gin.Context) { } func handleDeviceStatus(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() response := DeviceStatus{ - IsSetup: config.LocalAuthMode != "", + IsSetup: cfg.LocalAuthMode != "", } c.JSON(http.StatusOK, response) } func handleSetup(c *gin.Context) { - LoadConfig() + cfg := config.LoadConfig() // Check if the device is already set up - if config.LocalAuthMode != "" || config.HashedPassword != "" { + if cfg.LocalAuthMode != "" || cfg.HashedPassword != "" { c.JSON(http.StatusBadRequest, gin.H{"error": "Device is already set up"}) return } @@ -380,7 +382,7 @@ func handleSetup(c *gin.Context) { return } - config.LocalAuthMode = req.LocalAuthMode + cfg.LocalAuthMode = req.LocalAuthMode if req.LocalAuthMode == "password" { if req.Password == "" { @@ -395,19 +397,19 @@ func handleSetup(c *gin.Context) { return } - config.HashedPassword = string(hashedPassword) - config.LocalAuthToken = uuid.New().String() + cfg.HashedPassword = string(hashedPassword) + cfg.LocalAuthToken = uuid.New().String() // Set the cookie - c.SetCookie("authToken", config.LocalAuthToken, 7*24*60*60, "/", "", false, true) + c.SetCookie("authToken", cfg.LocalAuthToken, 7*24*60*60, "/", "", false, true) } else { // For noPassword mode, ensure the password field is empty - config.HashedPassword = "" - config.LocalAuthToken = "" + cfg.HashedPassword = "" + cfg.LocalAuthToken = "" } - err := SaveConfig() + err := config.SaveConfig(cfg) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save config"}) return diff --git a/webrtc.go b/internal/server/webrtc.go similarity index 75% rename from webrtc.go rename to internal/server/webrtc.go index 20ffb99..91fe8bd 100644 --- a/webrtc.go +++ b/internal/server/webrtc.go @@ -1,4 +1,4 @@ -package kvm +package server import ( "encoding/base64" @@ -6,11 +6,14 @@ import ( "fmt" "strings" + "github.com/jetkvm/kvm/internal/hardware" + "github.com/jetkvm/kvm/internal/logging" + "github.com/jetkvm/kvm/internal/server" "github.com/pion/webrtc/v4" ) type Session struct { - peerConnection *webrtc.PeerConnection + PeerConnection *webrtc.PeerConnection VideoTrack *webrtc.TrackLocalStaticSample ControlChannel *webrtc.DataChannel RPCChannel *webrtc.DataChannel @@ -30,21 +33,21 @@ func (s *Session) ExchangeOffer(offerStr string) (string, error) { return "", err } // Set the remote SessionDescription - if err = s.peerConnection.SetRemoteDescription(offer); err != nil { + if err = s.PeerConnection.SetRemoteDescription(offer); err != nil { return "", err } // Create answer - answer, err := s.peerConnection.CreateAnswer(nil) + answer, err := s.PeerConnection.CreateAnswer(nil) if err != nil { return "", err } // Create channel that is blocked until ICE Gathering is complete - gatherComplete := webrtc.GatheringCompletePromise(s.peerConnection) + gatherComplete := webrtc.GatheringCompletePromise(s.PeerConnection) // Sets the LocalDescription, and starts our UDP listeners - if err = s.peerConnection.SetLocalDescription(answer); err != nil { + if err = s.PeerConnection.SetLocalDescription(answer); err != nil { return "", err } @@ -53,7 +56,7 @@ func (s *Session) ExchangeOffer(offerStr string) (string, error) { // in a production application you should exchange ICE Candidates via OnICECandidate <-gatherComplete - localDescription, err := json.Marshal(s.peerConnection.LocalDescription()) + localDescription, err := json.Marshal(s.PeerConnection.LocalDescription()) if err != nil { return "", err } @@ -61,14 +64,14 @@ func (s *Session) ExchangeOffer(offerStr string) (string, error) { return base64.StdEncoding.EncodeToString(localDescription), nil } -func newSession() (*Session, error) { +func NewSession() (*Session, error) { peerConnection, err := webrtc.NewPeerConnection(webrtc.Configuration{ ICEServers: []webrtc.ICEServer{{}}, }) if err != nil { return nil, err } - session := &Session{peerConnection: peerConnection} + session := &Session{PeerConnection: peerConnection} peerConnection.OnDataChannel(func(d *webrtc.DataChannel) { fmt.Printf("New DataChannel %s %d\n", d.Label(), d.ID()) @@ -76,19 +79,19 @@ func newSession() (*Session, error) { case "rpc": session.RPCChannel = d d.OnMessage(func(msg webrtc.DataChannelMessage) { - go onRPCMessage(msg, session) + go server.OnRPCMessage(msg, session) }) - triggerOTAStateUpdate() - triggerVideoStateUpdate() - triggerUSBStateUpdate() + server.TriggerOTAStateUpdate() + server.TriggerVideoStateUpdate() + hardware.TriggerUSBStateUpdate() case "disk": session.DiskChannel = d - d.OnMessage(onDiskMessage) + d.OnMessage(hardware.OnDiskMessage) case "terminal": - handleTerminalChannel(d) + server.HandleTerminalChannel(d) default: - if strings.HasPrefix(d.Label(), uploadIdPrefix) { - go handleUploadChannel(d) + if strings.HasPrefix(d.Label(), hardware.UploadIdPrefix) { + go hardware.HandleUploadChannel(d) } } }) @@ -133,12 +136,12 @@ func newSession() (*Session, error) { _ = peerConnection.Close() } if connectionState == webrtc.ICEConnectionStateClosed { - if session == currentSession { - currentSession = nil + if session == CurrentSession { + CurrentSession = nil } if session.shouldUmountVirtualMedia { - err := rpcUnmountImage() - logger.Debugf("unmount image failed on connection close %v", err) + err := hardware.RPCUnmountImage() + logging.Logger.Debugf("unmount image failed on connection close %v", err) } if isConnected { isConnected = false @@ -156,7 +159,7 @@ func newSession() (*Session, error) { var actionSessions = 0 func onActiveSessionsChanged() { - requestDisplayUpdate() + hardware.RequestDisplayUpdate() } func onFirstSessionConnected() { diff --git a/wol.go b/internal/wol/wol.go similarity index 80% rename from wol.go rename to internal/wol/wol.go index 43c9e53..8c03948 100644 --- a/wol.go +++ b/internal/wol/wol.go @@ -1,4 +1,4 @@ -package kvm +package wol import ( "bytes" @@ -8,7 +8,7 @@ import ( ) // SendWOLMagicPacket sends a Wake-on-LAN magic packet to the specified MAC address -func rpcSendWOLMagicPacket(macAddress string) error { +func RPCSendWolMagicPacket(macAddress string) error { // Parse the MAC address mac, err := net.ParseMAC(macAddress) if err != nil { @@ -16,7 +16,7 @@ func rpcSendWOLMagicPacket(macAddress string) error { } // Create the magic packet - packet := createMagicPacket(mac) + packet := CreateMagicPacket(mac) // Set up UDP connection conn, err := net.Dial("udp", "255.255.255.255:9") @@ -34,8 +34,8 @@ func rpcSendWOLMagicPacket(macAddress string) error { return nil } -// createMagicPacket creates a Wake-on-LAN magic packet -func createMagicPacket(mac net.HardwareAddr) []byte { +// CreateMagicPacket creates a Wake-on-LAN magic packet +func CreateMagicPacket(mac net.HardwareAddr) []byte { var buf bytes.Buffer // Write 6 bytes of 0xFF diff --git a/log.go b/log.go deleted file mode 100644 index 89ad1d2..0000000 --- a/log.go +++ /dev/null @@ -1,8 +0,0 @@ -package kvm - -import "github.com/pion/logging" - -// we use logging framework from pion -// ref: https://github.com/pion/webrtc/wiki/Debugging-WebRTC -var logger = logging.NewDefaultLoggerFactory().NewLogger("jetkvm") -var usbLogger = logging.NewDefaultLoggerFactory().NewLogger("usb") diff --git a/main.go b/main.go deleted file mode 100644 index f94b24e..0000000 --- a/main.go +++ /dev/null @@ -1,85 +0,0 @@ -package kvm - -import ( - "context" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gwatts/rootcerts" -) - -var appCtx context.Context - -func Main() { - var cancel context.CancelFunc - appCtx, cancel = context.WithCancel(context.Background()) - defer cancel() - logger.Info("Starting JetKvm") - go runWatchdog() - go confirmCurrentSystem() - - http.DefaultClient.Timeout = 1 * time.Minute - LoadConfig() - logger.Debug("config loaded") - - err := rootcerts.UpdateDefaultTransport() - if err != nil { - logger.Errorf("failed to load CA certs: %v", err) - } - - go TimeSyncLoop() - - StartNativeCtrlSocketServer() - StartNativeVideoSocketServer() - - go func() { - err = ExtractAndRunNativeBin() - if err != nil { - logger.Errorf("failed to extract and run native bin: %v", err) - //TODO: prepare an error message screen buffer to show on kvm screen - } - }() - - go func() { - time.Sleep(15 * time.Minute) - for { - logger.Debugf("UPDATING - Auto update enabled: %v", config.AutoUpdateEnabled) - if config.AutoUpdateEnabled == false { - return - } - if currentSession != nil { - logger.Debugf("skipping update since a session is active") - time.Sleep(1 * time.Minute) - continue - } - includePreRelease := config.IncludePreRelease - err = TryUpdate(context.Background(), GetDeviceID(), includePreRelease) - if err != nil { - logger.Errorf("failed to auto update: %v", err) - } - time.Sleep(1 * time.Hour) - } - }() - //go RunFuseServer() - go RunWebServer() - go RunWebsocketClient() - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) - <-sigs - log.Println("JetKVM Shutting Down") - //if fuseServer != nil { - // err := setMassStorageImage(" ") - // if err != nil { - // log.Printf("Failed to unmount mass storage image: %v", err) - // } - // err = fuseServer.Unmount() - // if err != nil { - // log.Printf("Failed to unmount fuse: %v", err) - // } - - // os.Exit(0) -} diff --git a/pkg/.gitkeep b/pkg/.gitkeep new file mode 100644 index 0000000..e69de29