Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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 @@ -79,16 +80,16 @@ func Main() {
startVideoSleepModeTicker()

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 @@ -98,6 +99,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 @@ -107,6 +114,7 @@ func Main() {
time.Sleep(1 * time.Hour)
}
}()

//go RunFuseServer()
go RunWebServer()

Expand All @@ -123,7 +131,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
40 changes: 28 additions & 12 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 @@ -313,7 +318,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 Down Expand Up @@ -362,8 +367,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
otaState.Error = fmt.Sprintf("Error downloading app update: %v", err)
scopedLogger.Error().Err(err).Msg("Error downloading app update")
triggerOTAStateUpdate()
return err
return fmt.Errorf("error downloading app update: %w", err)
}

downloadFinished := time.Now()
otaState.AppDownloadFinishedAt = &downloadFinished
otaState.AppDownloadProgress = 1
Expand All @@ -379,17 +385,21 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
otaState.Error = fmt.Sprintf("Error verifying app update hash: %v", err)
scopedLogger.Error().Err(err).Msg("Error verifying app update hash")
triggerOTAStateUpdate()
return err
return fmt.Errorf("error verifying app update: %w", 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
triggerOTAStateUpdate()
} else {
scopedLogger.Info().Msg("App is up to date")
}
Expand All @@ -405,8 +415,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
otaState.Error = fmt.Sprintf("Error downloading system update: %v", err)
scopedLogger.Error().Err(err).Msg("Error downloading system update")
triggerOTAStateUpdate()
return err
return fmt.Errorf("error downloading system update: %w", err)
}

downloadFinished := time.Now()
otaState.SystemDownloadFinishedAt = &downloadFinished
otaState.SystemDownloadProgress = 1
Expand All @@ -422,8 +433,9 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
otaState.Error = fmt.Sprintf("Error verifying system update hash: %v", err)
scopedLogger.Error().Err(err).Msg("Error verifying system update hash")
triggerOTAStateUpdate()
return err
return fmt.Errorf("error verifying system update: %w", err)
}

scopedLogger.Info().Msg("System update downloaded")
verifyFinished := time.Now()
otaState.SystemVerifiedAt = &verifyFinished
Expand All @@ -439,8 +451,10 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
if err != nil {
otaState.Error = fmt.Sprintf("Error starting rk_ota command: %v", err)
scopedLogger.Error().Err(err).Msg("Error starting rk_ota command")
triggerOTAStateUpdate()
return fmt.Errorf("error starting rk_ota command: %w", err)
}

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

Expand Down Expand Up @@ -475,13 +489,15 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
Str("output", output).
Int("exitCode", cmd.ProcessState.ExitCode()).
Msg("Error executing rk_ota command")
triggerOTAStateUpdate()
return fmt.Errorf("error executing rk_ota command: %w\nOutput: %s", err, output)
}

scopedLogger.Info().Str("output", output).Msg("rk_ota success")
otaState.SystemUpdateProgress = 1
otaState.SystemUpdatedAt = &verifyFinished
triggerOTAStateUpdate()
rebootNeeded = true
triggerOTAStateUpdate()
} else {
scopedLogger.Info().Msg("System is up to date")
}
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 @@ -212,7 +212,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 @@ -230,7 +230,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
10 changes: 8 additions & 2 deletions ui/src/hooks/stores.ts
Original file line number Diff line number Diff line change
Expand Up @@ -573,14 +573,18 @@ export interface OtaState {
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 Down Expand Up @@ -611,8 +615,10 @@ export const useUpdateStore = create<UpdateState>(set => ({
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
6 changes: 3 additions & 3 deletions ui/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ export async function checkDeviceAuth() {
.GET(`${DEVICE_API}/device/status`)
.then(res => res.json() as Promise<DeviceStatus>);

if (!res.isSetup) return redirect("/welcome");
if (!res.isSetup) throw redirect("/welcome");

const deviceRes = await api.GET(`${DEVICE_API}/device`);
if (deviceRes.status === 401) return redirect("/login-local");
if (deviceRes.status === 401) throw redirect("/login-local");
if (deviceRes.ok) {
const device = (await deviceRes.json()) as LocalDevice;
return { authMode: device.authMode };
Expand All @@ -86,7 +86,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.deregister.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const loader: LoaderFunction = async ({ params }: LoaderFunctionArgs) => {
return { device, user };
} catch (e) {
console.error(e);
return { devices: [] };
return { user };
}
};

Expand Down
2 changes: 1 addition & 1 deletion ui/src/routes/devices.$id.rename.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const loader: LoaderFunction = async ({ params }: LoaderFunctionArgs) => {
return { device, user };
} catch (e) {
console.error(e);
return { devices: [] };
return { user };
}
};

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 @@ -8,12 +8,18 @@ import { m } from "@localizations/messages.js";
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(() => {
send("reboot", { force: true});
}, [send]);

return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
}

export function Dialog({
Expand Down
9 changes: 7 additions & 2 deletions ui/src/routes/devices.$id.settings.general.update.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export default function SettingsGeneralUpdateRoute() {
const { setModalView, otaState } = useUpdateStore();
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(() => {
send("tryUpdate", {});
setModalView("updating");
Expand All @@ -36,9 +41,9 @@ export default function SettingsGeneralUpdateRoute() {
} else {
setModalView("loading");
}
}, [otaState.updating, otaState.error, setModalView, updateSuccess]);
}, [otaState.error, otaState.updating, setModalView, updateSuccess]);

return <Dialog onClose={() => navigate("..")} onConfirmUpdate={onConfirmUpdate} />;
return <Dialog onClose={onClose} onConfirmUpdate={onConfirmUpdate} />;
}

export function Dialog({
Expand Down
Loading