Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
85f7f60
WIP: OTA refactor
ym Oct 28, 2025
b0e659b
refactor: mprove UI settings structure with NestedSettingsGroup
adamshiervani Oct 29, 2025
ab06e37
feat: add version update functionality to advanced settings
adamshiervani Oct 29, 2025
0a98a73
feat: downgrade
ym Oct 31, 2025
2b3f392
cleanup: ota state
ym Oct 31, 2025
f6b0b72
fix: update components
ym Oct 31, 2025
1e9dcc1
feat: allow configuration to be reset during update
ym Oct 31, 2025
aa7c6fe
feat: enhance version update settings with reset configuration option
adamshiervani Oct 31, 2025
95e5e15
feat: redirect to setup page after config reset
ym Oct 31, 2025
ef7e662
Apply suggestion from @Copilot
adamshiervani Nov 6, 2025
daeb9e3
Apply suggestion from @Copilot
adamshiervani Nov 6, 2025
32c66a3
Apply suggestion from @Copilot
adamshiervani Nov 6, 2025
e0ff671
feat: add acknowledgment checkbox for version changes in advanced set…
adamshiervani Nov 6, 2025
7c5dfd9
chore: update messages
ym Nov 7, 2025
252dcba
fix: rename redirectUrl to redirectTo
ym Nov 7, 2025
329ad02
fix: should return error if version is not available
ym Nov 7, 2025
1bca0c5
refactor: simplify version check and downgrade
ym Nov 7, 2025
882eb70
refactor: update version handling and simplify downgrade logic
adamshiervani Nov 7, 2025
ba76d5b
refactor(ui): simplify update dialog
ym Nov 7, 2025
9372afe
fix(ui): correct custom update components order
ym Nov 7, 2025
a246ef1
chore(ui): rename custom update query parameters
ym Nov 7, 2025
8bd3d4c
fix: undefined custom update versions
ym Nov 7, 2025
9832be2
refactor: remove downgrade attributes from ota state and jsonrpc
ym Nov 7, 2025
740d9b6
Merge branch 'dev' into r/ota
ym Nov 10, 2025
3fab951
fix(ota): should only check update if target version is specified
ym Nov 10, 2025
0cc84f0
fix(ui): shouldn't pass custom version to onConfirmCustomUpdate if no…
ym Nov 10, 2025
005505a
chore(ota): use []string instead of comma-separated string
ym Nov 10, 2025
8d085a6
refactor(ota): improve OTA state management
ym Nov 14, 2025
fe9523b
Merge branch 'dev' into r/ota
ym Nov 14, 2025
68bc480
fix: remove duplicate triggerOTAStateUpdate call
ym Nov 14, 2025
4411c45
fix: defer updating state update to after update is complete
ym Nov 14, 2025
0eff994
fix: update custom version update logic
ym Nov 14, 2025
1d7f8dd
clean up comments
ym Nov 14, 2025
1abf1f0
clean up comments and add default API URL
ym Nov 14, 2025
8527c7c
update translations
ym Nov 14, 2025
1880d5b
fix: set components to default components if not set
ym Nov 17, 2025
3b0efa7
chore: use json schema for ota test data
ym Nov 17, 2025
c19bd0d
fix: set updating to false when checking for updates failed
ym Nov 17, 2025
e1943c8
fix: do not set zero values in RPCState
ym Nov 17, 2025
eff4920
fix: remove mutex from updateApp & updateSystem
ym Nov 17, 2025
176b7d2
cleanup: remove CheckOnly from UpdateParams
ym Nov 17, 2025
d9f8054
feat: disable auto-update when custom version update is detected
ym Nov 17, 2025
d205df5
chore: add requestID to update status API calls and increase timeout …
ym Nov 18, 2025
d8ee8b4
Send error the frontend and reset updating state
adamshiervani Nov 18, 2025
aee9940
feat: mark updating state during app and system updates
adamshiervani Nov 18, 2025
7c79b3e
chore: remove redundant s.updating = false assignment, as it's alread…
ym Nov 18, 2025
95be473
chore: increase update status RPC timeout and max attempts for bad ne…
ym Nov 18, 2025
f1eb38f
chore: disable retry for checkUpdateComponents RPC
ym Nov 18, 2025
6aa740d
chore: remove early return in doUpdate when custom update is set
adamshiervani Nov 18, 2025
e79f03d
feat: add trace logging to OTA download
ym Nov 18, 2025
88becfe
chore(ota): rename component->downloadComponent in trace logging
ym Nov 18, 2025
fc8cfad
chore(ota): add trace logging to HTTP requests
ym Nov 18, 2025
ad8acd0
chore(ota): only show selected TLS connection state fields in trace l…
ym Nov 18, 2025
ae3b2b7
fix(hwReboot): only wait for extra settle time if delay is greater th…
ym Nov 18, 2025
5f76e30
chore: remove unused function readOtpEntropy
ym Nov 18, 2025
8113cd4
feat(ota): set updating immediately
adamshiervani Nov 18, 2025
013e73b
fix(localization): update auto-update description for clarity across …
adamshiervani Nov 18, 2025
f580631
fix: hwReboot delay should be delayMs
ym Nov 18, 2025
999c8c7
fix(ota): update redirect URL from /device/setup to /welcome in doUpd…
adamshiervani Nov 18, 2025
b8af39c
fix: set updating flag to false when update is complete
ym Nov 18, 2025
892e5f4
chore: reduce reboot delay to 3 seconds
ym Nov 18, 2025
ac79724
chore: increase reboot delay to 7 seconds
adamshiervani Nov 18, 2025
5e87d79
fix(ota): trigger OTA state update on channel open in newSession func…
adamshiervani Nov 18, 2025
8142954
feat(settings): integrate feature flag for version update settings wi…
adamshiervani Nov 19, 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
10 changes: 9 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,13 @@
},
"git.ignoreLimitWarning": true,
"cmake.sourceDirectory": "/workspaces/kvm-static-ip/internal/native/cgo",
"cmake.ignoreCMakeListsMissing": true
"cmake.ignoreCMakeListsMissing": true,
"json.schemas": [
{
"fileMatch": [
"/internal/ota/testdata/ota/*.json"
],
"url": "./internal/ota/testdata/ota.schema.json"
}
]
}
19 changes: 18 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"strconv"
"strings"
"sync"

"github.com/jetkvm/kvm/internal/confparser"
Expand All @@ -15,6 +16,10 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
)

const (
DefaultAPIURL = "https://api.jetkvm.com"
)

type WakeOnLanDevice struct {
Name string `json:"name"`
MacAddress string `json:"macAddress"`
Expand Down Expand Up @@ -80,6 +85,7 @@ func (m *KeyboardMacro) Validate() error {

type Config struct {
CloudURL string `json:"cloud_url"`
UpdateAPIURL string `json:"update_api_url"`
CloudAppURL string `json:"cloud_app_url"`
CloudToken string `json:"cloud_token"`
GoogleIdentity string `json:"google_identity"`
Expand Down Expand Up @@ -109,6 +115,15 @@ type Config struct {
VideoQualityFactor float64 `json:"video_quality_factor"`
}

// GetUpdateAPIURL returns the update API URL
func (c *Config) GetUpdateAPIURL() string {
if c.UpdateAPIURL == "" {
return DefaultAPIURL
}
return strings.TrimSuffix(c.UpdateAPIURL, "/") + "/releases"
}

// GetDisplayRotation returns the display rotation
func (c *Config) GetDisplayRotation() uint16 {
rotationInt, err := strconv.ParseUint(c.DisplayRotation, 10, 16)
if err != nil {
Expand All @@ -118,6 +133,7 @@ func (c *Config) GetDisplayRotation() uint16 {
return uint16(rotationInt)
}

// SetDisplayRotation sets the display rotation
func (c *Config) SetDisplayRotation(rotation string) error {
_, err := strconv.ParseUint(rotation, 10, 16)
if err != nil {
Expand Down Expand Up @@ -156,7 +172,8 @@ var (

func getDefaultConfig() Config {
return Config{
CloudURL: "https://api.jetkvm.com",
CloudURL: DefaultAPIURL,
UpdateAPIURL: DefaultAPIURL,
CloudAppURL: "https://app.jetkvm.com",
AutoUpdateEnabled: true, // Set a default value
ActiveExtension: "",
Expand Down
18 changes: 7 additions & 11 deletions hw.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strings"
"sync"
"time"

"github.com/jetkvm/kvm/internal/ota"
)

func extractSerialNumber() (string, error) {
Expand All @@ -29,22 +31,16 @@ func extractSerialNumber() (string, error) {
return matches[1], nil
}

func readOtpEntropy() ([]byte, error) { //nolint:unused
content, err := os.ReadFile("/sys/bus/nvmem/devices/rockchip-otp0/nvmem")
if err != nil {
return nil, err
}
return content[0x17:0x1C], nil
}

func hwReboot(force bool, postRebootAction *PostRebootAction, delay time.Duration) error {
logger.Info().Msgf("Reboot requested, rebooting in %d seconds...", delay)
func hwReboot(force bool, postRebootAction *ota.PostRebootAction, delay time.Duration) error {
logger.Info().Dur("delayMs", delay).Msg("reboot requested")

writeJSONRPCEvent("willReboot", postRebootAction, currentSession)
time.Sleep(1 * time.Second) // Wait for the JSONRPCEvent to be sent

nativeInstance.SwitchToScreenIfDifferent("rebooting_screen")
time.Sleep(delay - (1 * time.Second)) // wait requested extra settle time
if delay > 1*time.Second {
time.Sleep(delay - 1*time.Second) // wait requested extra settle time
}

args := []string{}
if force {
Expand Down
45 changes: 45 additions & 0 deletions internal/ota/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package ota

import (
"context"
"time"
)

const (
appUpdatePath = "/userdata/jetkvm/jetkvm_app.update"
)

// DO NOT call it directly, it's not thread safe
// Mutex is currently held by the caller, e.g. doUpdate
func (s *State) updateApp(ctx context.Context, appUpdate *componentUpdateStatus) error {
l := s.l.With().Str("path", appUpdatePath).Logger()

if err := s.downloadFile(ctx, appUpdatePath, appUpdate.url, "app"); err != nil {
return s.componentUpdateError("Error downloading app update", err, &l)
}

downloadFinished := time.Now()
appUpdate.downloadFinishedAt = downloadFinished
appUpdate.downloadProgress = 1
s.triggerComponentUpdateState("app", appUpdate)

if err := s.verifyFile(
appUpdatePath,
appUpdate.hash,
&appUpdate.verificationProgress,
); err != nil {
return s.componentUpdateError("Error verifying app update hash", err, &l)
}
verifyFinished := time.Now()
appUpdate.verifiedAt = verifyFinished
appUpdate.verificationProgress = 1
appUpdate.updatedAt = verifyFinished
appUpdate.updateProgress = 1
s.triggerComponentUpdateState("app", appUpdate)

l.Info().Msg("App update downloaded")

s.rebootNeeded = true

return nil
}
24 changes: 24 additions & 0 deletions internal/ota/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ota

import (
"errors"
"fmt"

"github.com/rs/zerolog"
)

var (
// ErrVersionNotFound is returned when the specified version is not found
ErrVersionNotFound = errors.New("specified version not found")
)

func (s *State) componentUpdateError(prefix string, err error, l *zerolog.Logger) error {
if l == nil {
l = s.l
}
l.Error().Err(err).Msg(prefix)
s.error = fmt.Sprintf("%s: %v", prefix, err)
s.updating = false
s.triggerStateUpdate()
return err
}
Loading