Skip to content

Commit 7955ee9

Browse files
authored
Improves OTA update reporting and process (#838)
1 parent 1ce6366 commit 7955ee9

12 files changed

+102
-62
lines changed

main.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
var appCtx context.Context
1515

1616
func Main() {
17+
logger.Log().Msg("JetKVM Starting Up")
1718
LoadConfig()
1819

1920
var cancel context.CancelFunc
@@ -79,16 +80,16 @@ func Main() {
7980
startVideoSleepModeTicker()
8081

8182
go func() {
83+
// wait for 15 minutes before starting auto-update checks
84+
// this is to avoid interfering with initial setup processes
85+
// and to ensure the system is stable before checking for updates
8286
time.Sleep(15 * time.Minute)
87+
8388
for {
84-
logger.Debug().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("UPDATING")
89+
logger.Info().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("auto-update check")
8590
if !config.AutoUpdateEnabled {
86-
return
87-
}
88-
89-
if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
90-
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
91-
time.Sleep(30 * time.Second)
91+
logger.Debug().Msg("auto-update disabled")
92+
time.Sleep(5 * time.Minute) // we'll check if auto-updates are enabled in five minutes
9293
continue
9394
}
9495

@@ -98,6 +99,12 @@ func Main() {
9899
continue
99100
}
100101

102+
if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
103+
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
104+
time.Sleep(30 * time.Second)
105+
continue
106+
}
107+
101108
includePreRelease := config.IncludePreRelease
102109
err = TryUpdate(context.Background(), GetDeviceID(), includePreRelease)
103110
if err != nil {
@@ -107,6 +114,7 @@ func Main() {
107114
time.Sleep(1 * time.Hour)
108115
}
109116
}()
117+
110118
//go RunFuseServer()
111119
go RunWebServer()
112120

@@ -123,7 +131,8 @@ func Main() {
123131
sigs := make(chan os.Signal, 1)
124132
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
125133
<-sigs
126-
logger.Info().Msg("JetKVM Shutting Down")
134+
135+
logger.Log().Msg("JetKVM Shutting Down")
127136
//if fuseServer != nil {
128137
// err := setMassStorageImage(" ")
129138
// if err != nil {

ota.go

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress
176176
if nr > 0 {
177177
nw, ew := file.Write(buf[0:nr])
178178
if nw < nr {
179-
return fmt.Errorf("short write: %d < %d", nw, nr)
179+
return fmt.Errorf("short file write: %d < %d", nw, nr)
180180
}
181181
written += int64(nw)
182182
if ew != nil {
@@ -240,7 +240,7 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
240240
if nr > 0 {
241241
nw, ew := hash.Write(buf[0:nr])
242242
if nw < nr {
243-
return fmt.Errorf("short write: %d < %d", nw, nr)
243+
return fmt.Errorf("short hash write: %d < %d", nw, nr)
244244
}
245245
verified += int64(nw)
246246
if ew != nil {
@@ -260,11 +260,16 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
260260
}
261261
}
262262

263-
hashSum := hash.Sum(nil)
264-
scopedLogger.Info().Str("path", path).Str("hash", hex.EncodeToString(hashSum)).Msg("SHA256 hash of")
263+
// close the file so we can rename below
264+
if err := fileToHash.Close(); err != nil {
265+
return fmt.Errorf("error closing file: %w", err)
266+
}
267+
268+
hashSum := hex.EncodeToString(hash.Sum(nil))
269+
scopedLogger.Info().Str("path", path).Str("hash", hashSum).Msg("SHA256 hash of")
265270

266-
if hex.EncodeToString(hashSum) != expectedHash {
267-
return fmt.Errorf("hash mismatch: %x != %s", hashSum, expectedHash)
271+
if hashSum != expectedHash {
272+
return fmt.Errorf("hash mismatch: %s != %s", hashSum, expectedHash)
268273
}
269274

270275
if err := os.Rename(unverifiedPath, path); err != nil {
@@ -313,7 +318,7 @@ func triggerOTAStateUpdate() {
313318
func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) error {
314319
scopedLogger := otaLogger.With().
315320
Str("deviceId", deviceId).
316-
Str("includePreRelease", fmt.Sprintf("%v", includePreRelease)).
321+
Bool("includePreRelease", includePreRelease).
317322
Logger()
318323

319324
scopedLogger.Info().Msg("Trying to update...")
@@ -362,8 +367,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
362367
otaState.Error = fmt.Sprintf("Error downloading app update: %v", err)
363368
scopedLogger.Error().Err(err).Msg("Error downloading app update")
364369
triggerOTAStateUpdate()
365-
return err
370+
return fmt.Errorf("error downloading app update: %w", err)
366371
}
372+
367373
downloadFinished := time.Now()
368374
otaState.AppDownloadFinishedAt = &downloadFinished
369375
otaState.AppDownloadProgress = 1
@@ -379,17 +385,21 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
379385
otaState.Error = fmt.Sprintf("Error verifying app update hash: %v", err)
380386
scopedLogger.Error().Err(err).Msg("Error verifying app update hash")
381387
triggerOTAStateUpdate()
382-
return err
388+
return fmt.Errorf("error verifying app update: %w", err)
383389
}
390+
384391
verifyFinished := time.Now()
385392
otaState.AppVerifiedAt = &verifyFinished
386393
otaState.AppVerificationProgress = 1
394+
triggerOTAStateUpdate()
395+
387396
otaState.AppUpdatedAt = &verifyFinished
388397
otaState.AppUpdateProgress = 1
389398
triggerOTAStateUpdate()
390399

391400
scopedLogger.Info().Msg("App update downloaded")
392401
rebootNeeded = true
402+
triggerOTAStateUpdate()
393403
} else {
394404
scopedLogger.Info().Msg("App is up to date")
395405
}
@@ -405,8 +415,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
405415
otaState.Error = fmt.Sprintf("Error downloading system update: %v", err)
406416
scopedLogger.Error().Err(err).Msg("Error downloading system update")
407417
triggerOTAStateUpdate()
408-
return err
418+
return fmt.Errorf("error downloading system update: %w", err)
409419
}
420+
410421
downloadFinished := time.Now()
411422
otaState.SystemDownloadFinishedAt = &downloadFinished
412423
otaState.SystemDownloadProgress = 1
@@ -422,8 +433,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
422433
otaState.Error = fmt.Sprintf("Error verifying system update hash: %v", err)
423434
scopedLogger.Error().Err(err).Msg("Error verifying system update hash")
424435
triggerOTAStateUpdate()
425-
return err
436+
return fmt.Errorf("error verifying system update: %w", err)
426437
}
438+
427439
scopedLogger.Info().Msg("System update downloaded")
428440
verifyFinished := time.Now()
429441
otaState.SystemVerifiedAt = &verifyFinished
@@ -439,8 +451,10 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
439451
if err != nil {
440452
otaState.Error = fmt.Sprintf("Error starting rk_ota command: %v", err)
441453
scopedLogger.Error().Err(err).Msg("Error starting rk_ota command")
454+
triggerOTAStateUpdate()
442455
return fmt.Errorf("error starting rk_ota command: %w", err)
443456
}
457+
444458
ctx, cancel := context.WithCancel(context.Background())
445459
defer cancel()
446460

@@ -475,13 +489,15 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
475489
Str("output", output).
476490
Int("exitCode", cmd.ProcessState.ExitCode()).
477491
Msg("Error executing rk_ota command")
492+
triggerOTAStateUpdate()
478493
return fmt.Errorf("error executing rk_ota command: %w\nOutput: %s", err, output)
479494
}
495+
480496
scopedLogger.Info().Str("output", output).Msg("rk_ota success")
481497
otaState.SystemUpdateProgress = 1
482498
otaState.SystemUpdatedAt = &verifyFinished
483-
triggerOTAStateUpdate()
484499
rebootNeeded = true
500+
triggerOTAStateUpdate()
485501
} else {
486502
scopedLogger.Info().Msg("System is up to date")
487503
}

ui/src/components/Button.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
212212
Button.displayName = "Button";
213213

214214
type LinkPropsType = Pick<LinkProps, "to"> &
215-
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean };
215+
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean, reloadDocument?: boolean };
216216
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
217217
const classes = cx(
218218
"group outline-hidden",
@@ -230,7 +230,7 @@ export const LinkButton = ({ to, ...props }: LinkPropsType) => {
230230
);
231231
} else {
232232
return (
233-
<Link to={to} className={classes}>
233+
<Link to={to} reloadDocument={props.reloadDocument} className={classes}>
234234
<ButtonContent {...props} />
235235
</Link>
236236
);

ui/src/hooks/stores.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -573,14 +573,18 @@ export interface OtaState {
573573
export interface UpdateState {
574574
isUpdatePending: boolean;
575575
setIsUpdatePending: (isPending: boolean) => void;
576+
576577
updateDialogHasBeenMinimized: boolean;
578+
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
579+
577580
otaState: OtaState;
578581
setOtaState: (state: OtaState) => void;
579-
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;
582+
580583
modalView: UpdateModalViews
581584
setModalView: (view: UpdateModalViews) => void;
582-
setUpdateErrorMessage: (errorMessage: string) => void;
585+
583586
updateErrorMessage: string | null;
587+
setUpdateErrorMessage: (errorMessage: string) => void;
584588
}
585589

586590
export const useUpdateStore = create<UpdateState>(set => ({
@@ -611,8 +615,10 @@ export const useUpdateStore = create<UpdateState>(set => ({
611615
updateDialogHasBeenMinimized: false,
612616
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) =>
613617
set({ updateDialogHasBeenMinimized: hasBeenMinimized }),
618+
614619
modalView: "loading",
615620
setModalView: (view: UpdateModalViews) => set({ modalView: view }),
621+
616622
updateErrorMessage: null,
617623
setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
618624
}));

ui/src/main.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ export async function checkDeviceAuth() {
7373
.GET(`${DEVICE_API}/device/status`)
7474
.then(res => res.json() as Promise<DeviceStatus>);
7575

76-
if (!res.isSetup) return redirect("/welcome");
76+
if (!res.isSetup) throw redirect("/welcome");
7777

7878
const deviceRes = await api.GET(`${DEVICE_API}/device`);
79-
if (deviceRes.status === 401) return redirect("/login-local");
79+
if (deviceRes.status === 401) throw redirect("/login-local");
8080
if (deviceRes.ok) {
8181
const device = (await deviceRes.json()) as LocalDevice;
8282
return { authMode: device.authMode };
@@ -86,7 +86,7 @@ export async function checkDeviceAuth() {
8686
}
8787

8888
export async function checkAuth() {
89-
return import.meta.env.MODE === "device" ? checkDeviceAuth() : checkCloudAuth();
89+
return isOnDevice ? checkDeviceAuth() : checkCloudAuth();
9090
}
9191

9292
let router;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const loader: LoaderFunction = async ({ params }: LoaderFunctionArgs) => {
5858
return { device, user };
5959
} catch (e) {
6060
console.error(e);
61-
return { devices: [] };
61+
return { user };
6262
}
6363
};
6464

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const loader: LoaderFunction = async ({ params }: LoaderFunctionArgs) => {
5454
return { device, user };
5555
} catch (e) {
5656
console.error(e);
57-
return { devices: [] };
57+
return { user };
5858
}
5959
};
6060

ui/src/routes/devices.$id.settings.access._index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export default function SettingsAccessIndexRoute() {
9898
}
9999

100100
getCloudState();
101-
// In cloud mode, we need to navigate to the device overview page, as we don't a connection anymore
101+
// In cloud mode, we need to navigate to the device overview page, as we don't have a connection anymore
102102
if (!isOnDevice) navigate("/");
103103
return;
104104
});

ui/src/routes/devices.$id.settings.general.reboot.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,18 @@ import { m } from "@localizations/messages.js";
88
export default function SettingsGeneralRebootRoute() {
99
const navigate = useNavigate();
1010
const { send } = useJsonRpc();
11+
12+
const onClose = useCallback(() => {
13+
navigate(".."); // back to the devices.$id.settings page
14+
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
15+
}, [navigate]);
16+
1117

1218
const onConfirmUpdate = useCallback(() => {
1319
send("reboot", { force: true});
1420
}, [send]);
1521

16-
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
22+
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
1723
}
1824

1925
export function Dialog({

ui/src/routes/devices.$id.settings.general.update.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export default function SettingsGeneralUpdateRoute() {
2121
const { setModalView, otaState } = useUpdateStore();
2222
const { send } = useJsonRpc();
2323

24+
const onClose = useCallback(() => {
25+
navigate(".."); // back to the devices.$id.settings page
26+
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
27+
}, [navigate]);
28+
2429
const onConfirmUpdate = useCallback(() => {
2530
send("tryUpdate", {});
2631
setModalView("updating");
@@ -36,9 +41,9 @@ export default function SettingsGeneralUpdateRoute() {
3641
} else {
3742
setModalView("loading");
3843
}
39-
}, [otaState.updating, otaState.error, setModalView, updateSuccess]);
44+
}, [otaState.error, otaState.updating, setModalView, updateSuccess]);
4045

41-
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
46+
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
4247
}
4348

4449
export function Dialog({

0 commit comments

Comments
 (0)