Skip to content

Commit f735f57

Browse files
committed
Revamp the OTA and reboot processing
OTA supplies port-reboot action to handle the rebooting device. Make sure we force-reload the page after redirection. Move reboot logic into hw.go and make it set the willReboot message with parameters if provided. Improve logging consistency.
1 parent d3b0f1b commit f735f57

File tree

8 files changed

+64
-50
lines changed

8 files changed

+64
-50
lines changed

hw.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package kvm
33
import (
44
"fmt"
55
"os"
6+
"os/exec"
67
"regexp"
78
"strings"
89
"sync"
@@ -36,6 +37,37 @@ func readOtpEntropy() ([]byte, error) { //nolint:unused
3637
return content[0x17:0x1C], nil
3738
}
3839

40+
func hwReboot(force bool, postRebootAction *PostRebootAction, delay time.Duration) error {
41+
logger.Info().Msgf("Reboot requested, rebooting in %d seconds...", delay)
42+
43+
writeJSONRPCEvent("willReboot", postRebootAction, currentSession)
44+
time.Sleep(1 * time.Second) // Wait for the JSONRPCEvent to be sent
45+
46+
nativeInstance.SwitchToScreenIfDifferent("rebooting_screen")
47+
time.Sleep(delay - (1 * time.Second)) // wait requested extra settle time
48+
49+
args := []string{}
50+
if force {
51+
args = append(args, "-f")
52+
}
53+
54+
cmd := exec.Command("reboot", args...)
55+
err := cmd.Start()
56+
if err != nil {
57+
logger.Error().Err(err).Msg("failed to reboot")
58+
switchToMainScreen()
59+
return fmt.Errorf("failed to reboot: %w", err)
60+
}
61+
62+
// If the reboot command is successful, exit the program after 5 seconds
63+
go func() {
64+
time.Sleep(5 * time.Second)
65+
os.Exit(0)
66+
}()
67+
68+
return nil
69+
}
70+
3971
var deviceID string
4072
var deviceIDOnce sync.Once
4173

jsonrpc.go

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -173,34 +173,8 @@ func rpcGetDeviceID() (string, error) {
173173
}
174174

175175
func rpcReboot(force bool) error {
176-
logger.Info().Msg("Got reboot request from JSONRPC, rebooting...")
177-
178-
writeJSONRPCEvent("willReboot", nil, currentSession)
179-
180-
// Wait for the JSONRPCEvent to be sent
181-
time.Sleep(1 * time.Second)
182-
nativeInstance.SwitchToScreenIfDifferent("rebooting_screen")
183-
184-
args := []string{}
185-
if force {
186-
args = append(args, "-f")
187-
}
188-
189-
cmd := exec.Command("reboot", args...)
190-
err := cmd.Start()
191-
if err != nil {
192-
logger.Error().Err(err).Msg("failed to reboot")
193-
switchToMainScreen()
194-
return fmt.Errorf("failed to reboot: %w", err)
195-
}
196-
197-
// If the reboot command is successful, exit the program after 5 seconds
198-
go func() {
199-
time.Sleep(5 * time.Second)
200-
os.Exit(0)
201-
}()
202-
203-
return nil
176+
logger.Info().Msg("Got reboot request via RPC")
177+
return hwReboot(force, nil, 0)
204178
}
205179

206180
var streamFactor = 1.0

native.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,17 @@ func initNative(systemVersion *semver.Version, appVersion *semver.Version) {
3737
nativeLogger.Trace().Str("event", event).Msg("rpc event received")
3838
switch event {
3939
case "resetConfig":
40+
nativeLogger.Info().Msg("Reset configuration request via native rpc event")
4041
err := rpcResetConfig()
4142
if err != nil {
4243
nativeLogger.Warn().Err(err).Msg("error resetting config")
4344
}
4445
_ = rpcReboot(true)
4546
case "reboot":
47+
nativeLogger.Info().Msg("Reboot request via native rpc event")
4648
_ = rpcReboot(true)
4749
case "toggleDHCPClient":
50+
nativeLogger.Info().Msg("Toggle DHCP request via native rpc event")
4851
_ = rpcToggleDHCPClient()
4952
default:
5053
nativeLogger.Warn().Str("event", event).Msg("unknown rpc event received")

network.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ func shouldRebootForNetworkChange(oldConfig, newConfig *types.NetworkConfig) (re
193193

194194
oldIPv4Mode := oldConfig.IPv4Mode.String
195195
newIPv4Mode := newConfig.IPv4Mode.String
196+
196197
// IPv4 mode change requires reboot
197198
if newIPv4Mode != oldIPv4Mode {
198199
rebootRequired = true
@@ -284,7 +285,8 @@ func rpcSetNetworkSettings(settings RpcNetworkSettings) (*RpcNetworkSettings, er
284285
}
285286

286287
if rebootRequired {
287-
if err := rpcReboot(false); err != nil {
288+
l.Info().Msg("Rebooting due to network changes")
289+
if err := hwReboot(true, postRebootAction, 0); err != nil {
288290
return nil, err
289291
}
290292
}

ota.go

Lines changed: 9 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -487,25 +487,15 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
487487
}
488488

489489
if rebootNeeded {
490-
scopedLogger.Info().Msg("System Rebooting in 10s")
491-
492-
// TODO: Future enhancement - send postRebootAction to redirect to release notes
493-
// Example:
494-
// postRebootAction := &PostRebootAction{
495-
// HealthCheck: "[..]/device/status",
496-
// RedirectUrl: "[..]/settings/general/update?version=X.Y.Z",
497-
// }
498-
// writeJSONRPCEvent("willReboot", postRebootAction, currentSession)
499-
500-
time.Sleep(10 * time.Second)
501-
cmd := exec.Command("reboot")
502-
err := cmd.Start()
503-
if err != nil {
504-
otaState.Error = fmt.Sprintf("Failed to start reboot: %v", err)
505-
scopedLogger.Error().Err(err).Msg("Failed to start reboot")
506-
return fmt.Errorf("failed to start reboot: %w", err)
507-
} else {
508-
os.Exit(0)
490+
scopedLogger.Info().Msg("System Rebooting due to OTA update")
491+
492+
postRebootAction := &PostRebootAction{
493+
HealthCheck: "/device/status",
494+
RedirectUrl: "/settings/general/update?version=" + remote.SystemVersion,
495+
}
496+
497+
if err := hwReboot(true, postRebootAction, 10*time.Second); err != nil {
498+
return fmt.Errorf("error requesting reboot: %w", err)
509499
}
510500
}
511501

ui/src/components/VideoOverlay.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ export function RebootingOverlay({ show, postRebootAction }: RebootingOverlayPro
476476
// Device is available, redirect to the specified URL
477477
console.log('Device is available, redirecting to:', postRebootAction.redirectUrl);
478478
window.location.href = postRebootAction.redirectUrl;
479+
window.location.reload();
479480
}
480481
} catch (err) {
481482
// Ignore errors - they're expected while device is rebooting

ui/src/hooks/useHidRpc.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
7878
try {
7979
data = message.marshal();
8080
} catch (e) {
81-
console.error("Failed to send HID RPC message", e);
81+
console.error("Failed to marshal HID RPC message", e);
8282
}
8383
if (!data) return;
8484

@@ -223,13 +223,19 @@ export function useHidRpc(onHidRpcMessage?: (payload: RpcMessage) => void) {
223223
setRpcHidProtocolVersion(null);
224224
};
225225

226+
const errorHandler = (e: Event) => {
227+
console.error(`Error on rpcHidChannel '${rpcHidChannel.label}': ${e}`)
228+
};
229+
226230
rpcHidChannel.addEventListener("message", messageHandler);
227231
rpcHidChannel.addEventListener("close", closeHandler);
232+
rpcHidChannel.addEventListener("error", errorHandler);
228233
rpcHidChannel.addEventListener("open", openHandler);
229234

230235
return () => {
231236
rpcHidChannel.removeEventListener("message", messageHandler);
232237
rpcHidChannel.removeEventListener("close", closeHandler);
238+
rpcHidChannel.removeEventListener("error", errorHandler);
233239
rpcHidChannel.removeEventListener("open", openHandler);
234240
};
235241
}, [

ui/src/routes/devices.$id.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,13 +485,15 @@ export default function KvmIdRoute() {
485485

486486
const rpcDataChannel = pc.createDataChannel("rpc");
487487
rpcDataChannel.onclose = () => console.log("rpcDataChannel has closed");
488-
rpcDataChannel.onerror = (e: Event) => console.error(`Error on DataChannel '${rpcDataChannel.label}': ${e}`);
488+
rpcDataChannel.onerror = (ev: Event) => console.error(`Error on DataChannel '${rpcDataChannel.label}': ${ev}`);
489489
rpcDataChannel.onopen = () => {
490490
setRpcDataChannel(rpcDataChannel);
491491
};
492492

493493
const rpcHidChannel = pc.createDataChannel("hidrpc");
494494
rpcHidChannel.binaryType = "arraybuffer";
495+
rpcHidChannel.onclose = () => console.log("rpcHidChannel has closed");
496+
rpcHidChannel.onerror = (ev: Event) => console.error(`Error on rpcHidChannel '${rpcHidChannel.label}': ${ev}`);
495497
rpcHidChannel.onopen = () => {
496498
setRpcHidChannel(rpcHidChannel);
497499
};
@@ -501,6 +503,8 @@ export default function KvmIdRoute() {
501503
maxRetransmits: 0,
502504
});
503505
rpcHidUnreliableChannel.binaryType = "arraybuffer";
506+
rpcHidUnreliableChannel.onclose = () => console.log("rpcHidUnreliableChannel has closed");
507+
rpcHidUnreliableChannel.onerror = (ev: Event) => console.error(`Error on rpcHidUnreliableChannel '${rpcHidUnreliableChannel.label}': ${ev}`);
504508
rpcHidUnreliableChannel.onopen = () => {
505509
setRpcHidUnreliableChannel(rpcHidUnreliableChannel);
506510
};
@@ -510,6 +514,8 @@ export default function KvmIdRoute() {
510514
maxRetransmits: 0,
511515
});
512516
rpcHidUnreliableNonOrderedChannel.binaryType = "arraybuffer";
517+
rpcHidUnreliableNonOrderedChannel.onclose = () => console.log("rpcHidUnreliableNonOrderedChannel has closed");
518+
rpcHidUnreliableNonOrderedChannel.onerror = (ev: Event) => console.error(`Error on rpcHidUnreliableNonOrderedChannel '${rpcHidUnreliableNonOrderedChannel.label}': ${ev}`);
513519
rpcHidUnreliableNonOrderedChannel.onopen = () => {
514520
setRpcHidUnreliableNonOrderedChannel(rpcHidUnreliableNonOrderedChannel);
515521
};

0 commit comments

Comments
 (0)