Skip to content
25 changes: 17 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
var appCtx context.Context

func Main() {
logger.Log().Msg("JetKVM Starting Up")
LoadConfig()

var cancel context.CancelFunc
Expand Down Expand Up @@ -78,16 +79,16 @@ func Main() {
initDisplay()

go func() {
// wait for 15 minutes before starting auto-update checks
// this is to avoid interfering with initial setup processes
// and to ensure the system is stable before checking for updates
time.Sleep(15 * time.Minute)

for {
logger.Debug().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("UPDATING")
logger.Info().Bool("auto_update_enabled", config.AutoUpdateEnabled).Msg("auto-update check")
if !config.AutoUpdateEnabled {
return
}

if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
time.Sleep(30 * time.Second)
logger.Debug().Msg("auto-update disabled")
time.Sleep(5 * time.Minute) // we'll check if auto-updates are enabled in five minutes
continue
}

Expand All @@ -97,6 +98,12 @@ func Main() {
continue
}

if isTimeSyncNeeded() || !timeSync.IsSyncSuccess() {
logger.Debug().Msg("system time is not synced, will retry in 30 seconds")
time.Sleep(30 * time.Second)
continue
}

includePreRelease := config.IncludePreRelease
err = TryUpdate(context.Background(), GetDeviceID(), includePreRelease)
if err != nil {
Expand All @@ -106,6 +113,7 @@ func Main() {
time.Sleep(1 * time.Hour)
}
}()

//go RunFuseServer()
go RunWebServer()

Expand All @@ -122,7 +130,8 @@ func Main() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
logger.Info().Msg("JetKVM Shutting Down")

logger.Log().Msg("JetKVM Shutting Down")
//if fuseServer != nil {
// err := setMassStorageImage(" ")
// if err != nil {
Expand Down
47 changes: 29 additions & 18 deletions ota.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func downloadFile(ctx context.Context, path string, url string, downloadProgress
if nr > 0 {
nw, ew := file.Write(buf[0:nr])
if nw < nr {
return fmt.Errorf("short write: %d < %d", nw, nr)
return fmt.Errorf("short file write: %d < %d", nw, nr)
}
written += int64(nw)
if ew != nil {
Expand Down Expand Up @@ -240,7 +240,7 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
if nr > 0 {
nw, ew := hash.Write(buf[0:nr])
if nw < nr {
return fmt.Errorf("short write: %d < %d", nw, nr)
return fmt.Errorf("short hash write: %d < %d", nw, nr)
}
verified += int64(nw)
if ew != nil {
Expand All @@ -260,11 +260,16 @@ func verifyFile(path string, expectedHash string, verifyProgress *float32, scope
}
}

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

hashSum := hex.EncodeToString(hash.Sum(nil))
scopedLogger.Info().Str("path", path).Str("hash", hashSum).Msg("SHA256 hash of")

if hex.EncodeToString(hashSum) != expectedHash {
return fmt.Errorf("hash mismatch: %x != %s", hashSum, expectedHash)
if hashSum != expectedHash {
return fmt.Errorf("hash mismatch: %s != %s", hashSum, expectedHash)
}

if err := os.Rename(unverifiedPath, path); err != nil {
Expand Down Expand Up @@ -296,6 +301,8 @@ type OTAState struct {
AppUpdatedAt *time.Time `json:"appUpdatedAt,omitempty"`
SystemUpdateProgress float32 `json:"systemUpdateProgress,omitempty"` //TODO: port rk_ota, then implement
SystemUpdatedAt *time.Time `json:"systemUpdatedAt,omitempty"`
RebootNeeded bool `json:"rebootNeeded,omitempty"`
Rebooting bool `json:"rebooting,omitempty"`
}

var otaState = OTAState{}
Expand All @@ -313,7 +320,7 @@ func triggerOTAStateUpdate() {
func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) error {
scopedLogger := otaLogger.With().
Str("deviceId", deviceId).
Str("includePreRelease", fmt.Sprintf("%v", includePreRelease)).
Bool("includePreRelease", includePreRelease).
Logger()

scopedLogger.Info().Msg("Trying to update...")
Expand All @@ -322,7 +329,8 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
}

otaState = OTAState{
Updating: true,
Updating: true,
RebootNeeded: false,
}
triggerOTAStateUpdate()

Expand All @@ -335,7 +343,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
if err != nil {
otaState.Error = fmt.Sprintf("Error checking for updates: %v", err)
scopedLogger.Error().Err(err).Msg("Error checking for updates")
return fmt.Errorf("error checking for updates: %w", err)
return err
}

now := time.Now()
Expand All @@ -349,8 +357,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
appUpdateAvailable := updateStatus.AppUpdateAvailable
systemUpdateAvailable := updateStatus.SystemUpdateAvailable

rebootNeeded := false

if appUpdateAvailable {
scopedLogger.Info().
Str("local", local.AppVersion).
Expand All @@ -361,7 +367,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
if err != nil {
otaState.Error = fmt.Sprintf("Error downloading app update: %v", err)
scopedLogger.Error().Err(err).Msg("Error downloading app update")
triggerOTAStateUpdate()
return err
}
downloadFinished := time.Now()
Expand All @@ -378,18 +383,20 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
if err != nil {
otaState.Error = fmt.Sprintf("Error verifying app update hash: %v", err)
scopedLogger.Error().Err(err).Msg("Error verifying app update hash")
triggerOTAStateUpdate()
return err
}
verifyFinished := time.Now()
otaState.AppVerifiedAt = &verifyFinished
otaState.AppVerificationProgress = 1
triggerOTAStateUpdate()

otaState.AppUpdatedAt = &verifyFinished
otaState.AppUpdateProgress = 1
triggerOTAStateUpdate()

scopedLogger.Info().Msg("App update downloaded")
rebootNeeded = true
otaState.RebootNeeded = true
triggerOTAStateUpdate()
} else {
scopedLogger.Info().Msg("App is up to date")
}
Expand All @@ -404,7 +411,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
if err != nil {
otaState.Error = fmt.Sprintf("Error downloading system update: %v", err)
scopedLogger.Error().Err(err).Msg("Error downloading system update")
triggerOTAStateUpdate()
return err
}
downloadFinished := time.Now()
Expand All @@ -421,7 +427,6 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
if err != nil {
otaState.Error = fmt.Sprintf("Error verifying system update hash: %v", err)
scopedLogger.Error().Err(err).Msg("Error verifying system update hash")
triggerOTAStateUpdate()
return err
}
scopedLogger.Info().Msg("System update downloaded")
Expand All @@ -441,6 +446,7 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
scopedLogger.Error().Err(err).Msg("Error starting rk_ota command")
return fmt.Errorf("error starting rk_ota command: %w", err)
}

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

Expand Down Expand Up @@ -481,14 +487,19 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
otaState.SystemUpdateProgress = 1
otaState.SystemUpdatedAt = &verifyFinished
triggerOTAStateUpdate()
rebootNeeded = true

otaState.RebootNeeded = true
triggerOTAStateUpdate()
} else {
scopedLogger.Info().Msg("System is up to date")
}

if rebootNeeded {
if otaState.RebootNeeded {
scopedLogger.Info().Msg("System Rebooting in 10s")
time.Sleep(10 * time.Second)
otaState.Rebooting = true
triggerOTAStateUpdate()

cmd := exec.Command("reboot")
err := cmd.Start()
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions ui/src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonPropsType>(
Button.displayName = "Button";

type LinkPropsType = Pick<LinkProps, "to"> &
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean };
React.ComponentProps<typeof ButtonContent> & { disabled?: boolean, reloadDocument?: boolean };
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
const classes = cx(
"group outline-hidden",
Expand All @@ -231,7 +231,7 @@ export const LinkButton = ({ to, ...props }: LinkPropsType) => {
);
} else {
return (
<Link to={to} className={classes}>
<Link to={to} reloadDocument={props.reloadDocument} className={classes}>
<ButtonContent {...props} />
</Link>
);
Expand Down
16 changes: 14 additions & 2 deletions ui/src/hooks/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ export type UpdateModalViews =
| "upToDate"
| "updateAvailable"
| "updateCompleted"
| "rebooting"
| "error";

export interface OtaState {
Expand Down Expand Up @@ -549,19 +550,26 @@ export interface OtaState {

systemUpdateProgress: number;
systemUpdatedAt: string | null;

rebootNeeded: boolean;
rebooting: boolean;
};

export interface UpdateState {
isUpdatePending: boolean;
setIsUpdatePending: (isPending: boolean) => void;

updateDialogHasBeenMinimized: boolean;
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;

otaState: OtaState;
setOtaState: (state: OtaState) => void;
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) => void;

modalView: UpdateModalViews
setModalView: (view: UpdateModalViews) => void;
setUpdateErrorMessage: (errorMessage: string) => void;

updateErrorMessage: string | null;
setUpdateErrorMessage: (errorMessage: string) => void;
}

export const useUpdateStore = create<UpdateState>(set => ({
Expand All @@ -587,13 +595,17 @@ export const useUpdateStore = create<UpdateState>(set => ({
appUpdatedAt: null,
systemUpdateProgress: 0,
systemUpdatedAt: null,
rebootNeeded: false,
rebooting: false,
},

updateDialogHasBeenMinimized: false,
setUpdateDialogHasBeenMinimized: (hasBeenMinimized: boolean) =>
set({ updateDialogHasBeenMinimized: hasBeenMinimized }),

modalView: "loading",
setModalView: (view: UpdateModalViews) => set({ modalView: view }),

updateErrorMessage: null,
setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
}));
Expand Down
2 changes: 1 addition & 1 deletion ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export async function checkDeviceAuth() {
}

export async function checkAuth() {
return import.meta.env.MODE === "device" ? checkDeviceAuth() : checkCloudAuth();
return isOnDevice ? checkDeviceAuth() : checkCloudAuth();
}

let router;
Expand Down
2 changes: 1 addition & 1 deletion ui/src/routes/devices.$id.settings.access._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export default function SettingsAccessIndexRoute() {
}

getCloudState();
// In cloud mode, we need to navigate to the device overview page, as we don't a connection anymore
// In cloud mode, we need to navigate to the device overview page, as we don't have a connection anymore
if (!isOnDevice) navigate("/");
return;
});
Expand Down
8 changes: 7 additions & 1 deletion ui/src/routes/devices.$id.settings.general.reboot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import { Button } from "@components/Button";
export default function SettingsGeneralRebootRoute() {
const navigate = useNavigate();
const { send } = useJsonRpc();

const onClose = useCallback(() => {
navigate(".."); // back to the devices.$id.settings page
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
}, [navigate]);


const onConfirmUpdate = useCallback(() => {
// This is where we send the RPC to the golang binary
Expand All @@ -16,7 +22,7 @@ export default function SettingsGeneralRebootRoute() {
{
/* TODO: Migrate to using URLs instead of the global state. To simplify the refactoring, we'll keep the global state for now. */
}
return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
}

export function Dialog({
Expand Down
Loading