fix: should return error if version is not available

This commit is contained in:
Siyuan 2025-11-07 08:48:26 +00:00
parent 252dcba7a1
commit 329ad025bf
5 changed files with 61 additions and 9 deletions

8
internal/ota/errors.go Normal file
View File

@ -0,0 +1,8 @@
package ota
import "errors"
var (
// ErrVersionNotFound is returned when the specified version is not found
ErrVersionNotFound = errors.New("specified version not found")
)

View File

@ -3,6 +3,7 @@ package ota
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
@ -23,12 +24,14 @@ func (s *State) GetReleaseAPIEndpoint() string {
}
// getUpdateURL returns the update URL for the given parameters
func (s *State) getUpdateURL(params UpdateParams) (string, error) {
func (s *State) getUpdateURL(params UpdateParams) (string, error, bool) {
updateURL, err := url.Parse(s.releaseAPIEndpoint)
if err != nil {
return "", fmt.Errorf("error parsing update metadata URL: %w", err)
return "", fmt.Errorf("error parsing update metadata URL: %w", err), false
}
isCustomVersion := false
appTargetVersion := s.GetTargetVersion("app")
if appTargetVersion != "" && params.AppTargetVersion == "" {
params.AppTargetVersion = appTargetVersion
@ -43,19 +46,21 @@ func (s *State) getUpdateURL(params UpdateParams) (string, error) {
query.Set("prerelease", fmt.Sprintf("%v", params.IncludePreRelease))
if params.AppTargetVersion != "" {
query.Set("appVersion", params.AppTargetVersion)
isCustomVersion = true
}
if params.SystemTargetVersion != "" {
query.Set("systemVersion", params.SystemTargetVersion)
isCustomVersion = true
}
updateURL.RawQuery = query.Encode()
return updateURL.String(), nil
return updateURL.String(), nil, isCustomVersion
}
func (s *State) fetchUpdateMetadata(ctx context.Context, params UpdateParams) (*UpdateMetadata, error) {
metadata := &UpdateMetadata{}
url, err := s.getUpdateURL(params)
url, err, isCustomVersion := s.getUpdateURL(params)
if err != nil {
return nil, fmt.Errorf("error getting update URL: %w", err)
}
@ -77,6 +82,10 @@ func (s *State) fetchUpdateMetadata(ctx context.Context, params UpdateParams) (*
}
defer resp.Body.Close()
if isCustomVersion && resp.StatusCode == http.StatusNotFound {
return nil, ErrVersionNotFound
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
@ -189,7 +198,7 @@ func (s *State) doUpdate(ctx context.Context, params UpdateParams) error {
postRebootAction := &PostRebootAction{
HealthCheck: "/device/status",
RedirectUrl: redirectUrl,
RedirectTo: redirectUrl,
}
if err := s.reboot(true, postRebootAction, 10*time.Second); err != nil {
@ -241,7 +250,11 @@ func (s *State) getUpdateStatus(
// Get remote metadata
remoteMetadata, err := s.fetchUpdateMetadata(ctx, params)
if err != nil {
err = fmt.Errorf("error checking for updates: %w", err)
if err == ErrVersionNotFound || errors.Unwrap(err) == ErrVersionNotFound {
err = ErrVersionNotFound
} else {
err = fmt.Errorf("error checking for updates: %w", err)
}
return
}
appUpdate.url = remoteMetadata.AppURL

31
ota.go
View File

@ -6,6 +6,7 @@ import (
"net/http"
"os"
"strings"
"time"
"github.com/Masterminds/semver/v3"
"github.com/jetkvm/kvm/internal/ota"
@ -158,11 +159,15 @@ func rpcTryUpdateComponents(components tryUpdateComponents, includePreRelease bo
logger.Info().Interface("components", components).Msg("components")
currentAppTargetVersion := otaState.GetTargetVersion("app")
appTargetVersionChanged := currentAppTargetVersion != components.AppTargetVersion
updateParams.AppTargetVersion = components.AppTargetVersion
if err := otaState.SetTargetVersion("app", components.AppTargetVersion); err != nil {
return fmt.Errorf("failed to set app target version: %w", err)
}
currentSystemTargetVersion := otaState.GetTargetVersion("system")
systemTargetVersionChanged := currentSystemTargetVersion != components.SystemTargetVersion
updateParams.SystemTargetVersion = components.SystemTargetVersion
if err := otaState.SetTargetVersion("system", components.SystemTargetVersion); err != nil {
return fmt.Errorf("failed to set system target version: %w", err)
@ -172,6 +177,32 @@ func rpcTryUpdateComponents(components tryUpdateComponents, includePreRelease bo
updateParams.Components = strings.Split(components.Components, ",")
}
// if it's a check only update, we don't need to try to update, we just need to check if the version is available
// and return the error immediately then revert the previous target versions
if checkOnly {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
_, err := otaState.GetUpdateStatus(ctx, updateParams)
if err == nil {
return nil
}
// revert the previous target versions
if appTargetVersionChanged {
if err := otaState.SetTargetVersion("app", currentAppTargetVersion); err != nil {
return fmt.Errorf("failed to revert app target version: %w", err)
}
}
if systemTargetVersionChanged {
if err := otaState.SetTargetVersion("system", currentSystemTargetVersion); err != nil {
return fmt.Errorf("failed to revert system target version: %w", err)
}
}
return err
}
go func() {
err := otaState.TryUpdate(context.Background(), updateParams)
if err != nil {

View File

@ -1,5 +1,4 @@
import { useCallback, useMemo } from "react";
import semver from "semver";
import { useCallback } from "react";
import { useDeviceStore } from "@/hooks/stores";
import { JsonRpcError, RpcMethodNotFound } from "@/hooks/useJsonRpc";
@ -69,6 +68,5 @@ export function useVersion() {
getLocalVersion,
appVersion,
systemVersion,
isOnDevVersion,
};
}

View File

@ -220,7 +220,9 @@ export interface SystemVersionInfo {
local: VersionInfo;
remote?: VersionInfo;
systemUpdateAvailable: boolean;
systemDowngradeAvailable: boolean;
appUpdateAvailable: boolean;
appDowngradeAvailable: boolean;
error?: string;
}