Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
7ebf7ba
chore(network): improve connectivity check
ym Apr 11, 2025
f712cb1
refactor(network): rewrite network and timesync component
ym Apr 12, 2025
edba5c9
feat(display): show cloud connection status
ym Apr 12, 2025
338d8fd
chore: change logging verbosity
ym Apr 12, 2025
8eecb31
chore(websecure): update log message
ym Apr 13, 2025
d9eae34
fix(ota): validate root certificate when downloading update
ym Apr 13, 2025
fd3a8cb
feat(ui): add network settings tab
ym Apr 13, 2025
d136a90
fix(display): cloud connecting animation
ym Apr 13, 2025
eb8bac4
fix: golintci issues
ym Apr 13, 2025
73b8355
feat: add network settings tab
ym Apr 14, 2025
e470371
feat(timesync): query servers in parallel
ym Apr 14, 2025
4fe8cb4
refactor(network): move to internal/network package
ym Apr 14, 2025
5614b26
feat(timesync): add metrics
ym Apr 14, 2025
08021f9
refactor(log): move log to internal/logging package
ym Apr 14, 2025
b24191d
refactor(mdms): move mdns to internal/mdns package
ym Apr 14, 2025
5860571
feat(developer): add pprof endpoint
ym Apr 14, 2025
d5950f1
feat(logging): add a simple logging streaming endpoint
ym Apr 15, 2025
3d84008
fix(mdns): do not start mdns until network is up
ym Apr 15, 2025
8a55ea3
feat(network): allow users to update network settings from ui
ym Apr 15, 2025
4643af0
fix(network): handle errors when net.IPAddr is nil
ym Apr 15, 2025
ea350a6
fix(mdns): scopedLogger SIGSEGV
ym Apr 15, 2025
cf9e90c
fix(dhcp): watch directory instead of file to catch fsnotify.Create e…
ym Apr 15, 2025
daa7185
refactor(nbd): move platform-specific code to different files
ym Apr 15, 2025
db2ce25
refactor(native): move platform-specific code to different files
ym Apr 15, 2025
e504276
chore: fix linter issues
ym Apr 15, 2025
886b58d
Merge branch 'dev' into chore/network-enhancement
ym Apr 15, 2025
a520149
chore(dev_deploy): allow to override PION_LOG_TRACE
ym Apr 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions block_device.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"os"
"time"

"github.com/pojntfx/go-nbd/pkg/client"
"github.com/pojntfx/go-nbd/pkg/server"
"github.com/rs/zerolog"
)
Expand Down Expand Up @@ -149,30 +148,3 @@ func (d *NBDDevice) runServerConn() {

d.l.Info().Err(err).Msg("nbd server exited")
}

func (d *NBDDevice) runClientConn() {
err := client.Connect(d.clientConn, d.dev, &client.Options{
ExportName: "jetkvm",
BlockSize: uint32(4 * 1024),
})
d.l.Info().Err(err).Msg("nbd client exited")
}

func (d *NBDDevice) Close() {
if d.dev != nil {
err := client.Disconnect(d.dev)
if err != nil {
d.l.Warn().Err(err).Msg("error disconnecting nbd client")
}
_ = d.dev.Close()
}
if d.listener != nil {
_ = d.listener.Close()
}
if d.clientConn != nil {
_ = d.clientConn.Close()
}
if d.serverConn != nil {
_ = d.serverConn.Close()
}
}
34 changes: 34 additions & 0 deletions block_device_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build linux

package kvm

import (
"github.com/pojntfx/go-nbd/pkg/client"
)

func (d *NBDDevice) runClientConn() {
err := client.Connect(d.clientConn, d.dev, &client.Options{
ExportName: "jetkvm",
BlockSize: uint32(4 * 1024),
})
d.l.Info().Err(err).Msg("nbd client exited")
}

func (d *NBDDevice) Close() {
if d.dev != nil {
err := client.Disconnect(d.dev)
if err != nil {
d.l.Warn().Err(err).Msg("error disconnecting nbd client")
}
_ = d.dev.Close()
}
if d.listener != nil {
_ = d.listener.Close()
}
if d.clientConn != nil {
_ = d.clientConn.Close()
}
if d.serverConn != nil {
_ = d.serverConn.Close()
}
}
17 changes: 17 additions & 0 deletions block_device_notlinux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build !linux

package kvm

import (
"os"
)

func (d *NBDDevice) runClientConn() {
d.l.Error().Msg("platform not supported")
os.Exit(1)
}

func (d *NBDDevice) Close() {
d.l.Error().Msg("platform not supported")
os.Exit(1)
}
57 changes: 49 additions & 8 deletions cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,40 @@ var (
)
)

type CloudConnectionState uint8

const (
CloudConnectionStateNotConfigured CloudConnectionState = iota
CloudConnectionStateDisconnected
CloudConnectionStateConnecting
CloudConnectionStateConnected
)

var (
cloudConnectionState CloudConnectionState = CloudConnectionStateNotConfigured
cloudConnectionStateLock = &sync.Mutex{}

cloudDisconnectChan chan error
cloudDisconnectLock = &sync.Mutex{}
)

func setCloudConnectionState(state CloudConnectionState) {
cloudConnectionStateLock.Lock()
defer cloudConnectionStateLock.Unlock()

if cloudConnectionState == CloudConnectionStateDisconnected &&
(config.CloudToken == "" || config.CloudURL == "") {
state = CloudConnectionStateNotConfigured
}

previousState := cloudConnectionState
cloudConnectionState = state

go waitCtrlAndRequestDisplayUpdate(
previousState != state,
)
}

func wsResetMetrics(established bool, sourceType string, source string) {
metricConnectionLastPingTimestamp.WithLabelValues(sourceType, source).Set(-1)
metricConnectionLastPingDuration.WithLabelValues(sourceType, source).Set(-1)
Expand Down Expand Up @@ -285,6 +314,8 @@ func runWebsocketClient() error {
wsURL.Scheme = "wss"
}

setCloudConnectionState(CloudConnectionStateConnecting)

header := http.Header{}
header.Set("X-Device-ID", GetDeviceID())
header.Set("X-App-Version", builtAppVersion)
Expand All @@ -302,20 +333,26 @@ func runWebsocketClient() error {
c, resp, err := websocket.Dial(dialCtx, wsURL.String(), &websocket.DialOptions{
HTTPHeader: header,
OnPingReceived: func(ctx context.Context, payload []byte) bool {
scopedLogger.Info().Bytes("payload", payload).Int("length", len(payload)).Msg("ping frame received")
scopedLogger.Debug().Bytes("payload", payload).Int("length", len(payload)).Msg("ping frame received")

metricConnectionTotalPingReceivedCount.WithLabelValues("cloud", wsURL.Host).Inc()
metricConnectionLastPingReceivedTimestamp.WithLabelValues("cloud", wsURL.Host).SetToCurrentTime()

setCloudConnectionState(CloudConnectionStateConnected)

return true
},
})

// get the request id from the response header
connectionId := resp.Header.Get("X-Request-ID")
if connectionId == "" {
connectionId = resp.Header.Get("Cf-Ray")
var connectionId string
if resp != nil {
// get the request id from the response header
connectionId = resp.Header.Get("X-Request-ID")
if connectionId == "" {
connectionId = resp.Header.Get("Cf-Ray")
}
}

if connectionId == "" {
connectionId = uuid.New().String()
scopedLogger.Warn().
Expand All @@ -332,6 +369,8 @@ func runWebsocketClient() error {
if err != nil {
if errors.Is(err, context.Canceled) {
cloudLogger.Info().Msg("websocket connection canceled")
setCloudConnectionState(CloudConnectionStateDisconnected)

return nil
}
return err
Expand Down Expand Up @@ -450,14 +489,14 @@ func RunWebsocketClient() {
}

// If the network is not up, well, we can't connect to the cloud.
if !networkState.Up {
cloudLogger.Warn().Msg("waiting for network to be up, will retry in 3 seconds")
if !networkState.IsOnline() {
cloudLogger.Warn().Msg("waiting for network to be online, will retry in 3 seconds")
time.Sleep(3 * time.Second)
continue
}

// If the system time is not synchronized, the API request will fail anyway because the TLS handshake will fail.
if isTimeSyncNeeded() && !timeSyncSuccess {
if isTimeSyncNeeded() && !timeSync.IsSyncSuccess() {
cloudLogger.Warn().Msg("system time is not synced, will retry in 3 seconds")
time.Sleep(3 * time.Second)
continue
Expand Down Expand Up @@ -520,6 +559,8 @@ func rpcDeregisterDevice() error {
cloudLogger.Info().Msg("device deregistered, disconnecting from cloud")
disconnectCloud(fmt.Errorf("device deregistered"))

setCloudConnectionState(CloudConnectionStateNotConfigured)

return nil
}

Expand Down
56 changes: 33 additions & 23 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os"
"sync"

"github.com/jetkvm/kvm/internal/logging"
"github.com/jetkvm/kvm/internal/network"
"github.com/jetkvm/kvm/internal/usbgadget"
)

Expand Down Expand Up @@ -73,27 +75,28 @@ func (m *KeyboardMacro) Validate() error {
}

type Config struct {
CloudURL string `json:"cloud_url"`
CloudAppURL string `json:"cloud_app_url"`
CloudToken string `json:"cloud_token"`
GoogleIdentity string `json:"google_identity"`
JigglerEnabled bool `json:"jiggler_enabled"`
AutoUpdateEnabled bool `json:"auto_update_enabled"`
IncludePreRelease bool `json:"include_pre_release"`
HashedPassword string `json:"hashed_password"`
LocalAuthToken string `json:"local_auth_token"`
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
EdidString string `json:"hdmi_edid_string"`
ActiveExtension string `json:"active_extension"`
DisplayMaxBrightness int `json:"display_max_brightness"`
DisplayDimAfterSec int `json:"display_dim_after_sec"`
DisplayOffAfterSec int `json:"display_off_after_sec"`
TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", ""
UsbConfig *usbgadget.Config `json:"usb_config"`
UsbDevices *usbgadget.Devices `json:"usb_devices"`
DefaultLogLevel string `json:"default_log_level"`
CloudURL string `json:"cloud_url"`
CloudAppURL string `json:"cloud_app_url"`
CloudToken string `json:"cloud_token"`
GoogleIdentity string `json:"google_identity"`
JigglerEnabled bool `json:"jiggler_enabled"`
AutoUpdateEnabled bool `json:"auto_update_enabled"`
IncludePreRelease bool `json:"include_pre_release"`
HashedPassword string `json:"hashed_password"`
LocalAuthToken string `json:"local_auth_token"`
LocalAuthMode string `json:"localAuthMode"` //TODO: fix it with migration
WakeOnLanDevices []WakeOnLanDevice `json:"wake_on_lan_devices"`
KeyboardMacros []KeyboardMacro `json:"keyboard_macros"`
EdidString string `json:"hdmi_edid_string"`
ActiveExtension string `json:"active_extension"`
DisplayMaxBrightness int `json:"display_max_brightness"`
DisplayDimAfterSec int `json:"display_dim_after_sec"`
DisplayOffAfterSec int `json:"display_off_after_sec"`
TLSMode string `json:"tls_mode"` // options: "self-signed", "user-defined", ""
UsbConfig *usbgadget.Config `json:"usb_config"`
UsbDevices *usbgadget.Devices `json:"usb_devices"`
NetworkConfig *network.NetworkConfig `json:"network_config"`
DefaultLogLevel string `json:"default_log_level"`
}

const configPath = "/userdata/kvm_config.json"
Expand Down Expand Up @@ -121,6 +124,7 @@ var defaultConfig = &Config{
Keyboard: true,
MassStorage: true,
},
NetworkConfig: &network.NetworkConfig{},
DefaultLogLevel: "INFO",
}

Expand All @@ -134,7 +138,7 @@ func LoadConfig() {
defer configLock.Unlock()

if config != nil {
logger.Info().Msg("config already loaded, skipping")
logger.Debug().Msg("config already loaded, skipping")
return
}

Expand Down Expand Up @@ -164,9 +168,15 @@ func LoadConfig() {
loadedConfig.UsbDevices = defaultConfig.UsbDevices
}

if loadedConfig.NetworkConfig == nil {
loadedConfig.NetworkConfig = defaultConfig.NetworkConfig
}

config = &loadedConfig

rootLogger.UpdateLogLevel()
logging.GetRootLogger().UpdateLogLevel(config.DefaultLogLevel)

logger.Info().Str("path", configPath).Msg("config loaded")
}

func SaveConfig() error {
Expand Down
3 changes: 2 additions & 1 deletion dev_deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ show_help() {
REMOTE_USER="root"
REMOTE_PATH="/userdata/jetkvm/bin"
SKIP_UI_BUILD=false
LOG_TRACE_SCOPES="${LOG_TRACE_SCOPES:-jetkvm,cloud,websocket,native,jsonrpc}"

# Parse command line arguments
while [[ $# -gt 0 ]]; do
Expand Down Expand Up @@ -91,7 +92,7 @@ cd "${REMOTE_PATH}"
chmod +x jetkvm_app_debug

# Run the application in the background
PION_LOG_TRACE=jetkvm,cloud,websocket ./jetkvm_app_debug
PION_LOG_TRACE=${LOG_TRACE_SCOPES} ./jetkvm_app_debug
EOF

echo "Deployment complete."
Loading