chore(reorganization): part one

This commit is contained in:
Jordan Jones 2025-01-06 12:48:45 -08:00
parent 8ffe66a1bc
commit 9eced85559
No known key found for this signature in database
GPG Key ID: 4DEF745773DABD4C
26 changed files with 444 additions and 407 deletions

View File

@ -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)
}

2
go.mod
View File

@ -1,4 +1,4 @@
module kvm
module github.com/jetkvm/kvm
go 1.21.0

View File

@ -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)
}

View File

@ -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")
}

View File

@ -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()
}()
}

View File

@ -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)

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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")

View File

@ -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()
}
}

View File

@ -1,4 +1,4 @@
package kvm
package network
import (
"errors"

View File

@ -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))

View File

@ -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)
}

View File

@ -1,4 +1,4 @@
package kvm
package server
import (
"context"

View File

@ -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},

View File

@ -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
}

View File

@ -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)
}
})

View File

@ -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) {

View File

@ -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

View File

@ -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() {

View File

@ -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

8
log.go
View File

@ -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")

85
main.go
View File

@ -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)
}

0
pkg/.gitkeep Normal file
View File