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

31
ota.go
View File

@ -6,6 +6,7 @@ import (
"net/http" "net/http"
"os" "os"
"strings" "strings"
"time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/jetkvm/kvm/internal/ota" "github.com/jetkvm/kvm/internal/ota"
@ -158,11 +159,15 @@ func rpcTryUpdateComponents(components tryUpdateComponents, includePreRelease bo
logger.Info().Interface("components", components).Msg("components") logger.Info().Interface("components", components).Msg("components")
currentAppTargetVersion := otaState.GetTargetVersion("app")
appTargetVersionChanged := currentAppTargetVersion != components.AppTargetVersion
updateParams.AppTargetVersion = components.AppTargetVersion updateParams.AppTargetVersion = components.AppTargetVersion
if err := otaState.SetTargetVersion("app", components.AppTargetVersion); err != nil { if err := otaState.SetTargetVersion("app", components.AppTargetVersion); err != nil {
return fmt.Errorf("failed to set app target version: %w", err) return fmt.Errorf("failed to set app target version: %w", err)
} }
currentSystemTargetVersion := otaState.GetTargetVersion("system")
systemTargetVersionChanged := currentSystemTargetVersion != components.SystemTargetVersion
updateParams.SystemTargetVersion = components.SystemTargetVersion updateParams.SystemTargetVersion = components.SystemTargetVersion
if err := otaState.SetTargetVersion("system", components.SystemTargetVersion); err != nil { if err := otaState.SetTargetVersion("system", components.SystemTargetVersion); err != nil {
return fmt.Errorf("failed to set system target version: %w", err) 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, ",") 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() { go func() {
err := otaState.TryUpdate(context.Background(), updateParams) err := otaState.TryUpdate(context.Background(), updateParams)
if err != nil { if err != nil {

View File

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

View File

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