Compare commits

...

3 Commits

Author SHA1 Message Date
Siyuan Miao 9bde820321 move code to kvm package 2025-05-20 20:12:48 +02:00
Siyuan Miao 12dbe6fcf0 feat: add -version flag for jetkvm_app 2025-05-20 14:58:11 +02:00
Siyuan Miao 36cd649603 chore: always return local version if update check fails 2025-05-20 14:08:42 +02:00
9 changed files with 151 additions and 37 deletions

View File

@ -22,6 +22,9 @@ linters:
- linters: - linters:
- errcheck - errcheck
path: _test.go path: _test.go
- linters:
- forbidigo
path: cmd/main.go
paths: paths:
- third_party$ - third_party$
- builtin$ - builtin$

View File

@ -1,9 +1,27 @@
package main package main
import ( import (
"flag"
"fmt"
"os"
"github.com/jetkvm/kvm" "github.com/jetkvm/kvm"
) )
func main() { func main() {
versionPtr := flag.Bool("version", false, "print version and exit")
versionJsonPtr := flag.Bool("version-json", false, "print version as json and exit")
flag.Parse()
if *versionPtr || *versionJsonPtr {
versionData, err := kvm.GetVersionData(*versionJsonPtr)
if err != nil {
fmt.Printf("failed to get version data: %v\n", err)
os.Exit(1)
}
fmt.Println(string(versionData))
return
}
kvm.Main() kvm.Main()
} }

View File

@ -174,7 +174,7 @@ cd "${REMOTE_PATH}"
chmod +x jetkvm_app_debug chmod +x jetkvm_app_debug
# Run the application in the background # Run the application in the background
PION_LOG_TRACE=${LOG_TRACE_SCOPES} ./jetkvm_app_debug PION_LOG_TRACE=${LOG_TRACE_SCOPES} ./jetkvm_app_debug | tee -a /tmp/jetkvm_app_debug.log
EOF EOF
echo "Deployment complete." echo "Deployment complete."

View File

@ -266,9 +266,14 @@ func rpcSetDevChannelState(enabled bool) error {
func rpcGetUpdateStatus() (*UpdateStatus, error) { func rpcGetUpdateStatus() (*UpdateStatus, error) {
includePreRelease := config.IncludePreRelease includePreRelease := config.IncludePreRelease
updateStatus, err := GetUpdateStatus(context.Background(), GetDeviceID(), includePreRelease) updateStatus, err := GetUpdateStatus(context.Background(), GetDeviceID(), includePreRelease)
// to ensure backwards compatibility,
// if there's an error, we won't return an error, but we will set the error field
if err != nil { if err != nil {
if updateStatus == nil {
return nil, fmt.Errorf("error checking for updates: %w", err) return nil, fmt.Errorf("error checking for updates: %w", err)
} }
updateStatus.Error = err.Error()
}
return updateStatus, nil return updateStatus, nil
} }

View File

@ -8,6 +8,7 @@ import (
"io" "io"
"net" "net"
"os" "os"
"strings"
"sync" "sync"
"time" "time"
@ -282,6 +283,22 @@ func shouldOverwrite(destPath string, srcHash []byte) bool {
return !bytes.Equal(srcHash, dstHash) return !bytes.Equal(srcHash, dstHash)
} }
func getNativeSha256() ([]byte, error) {
version, err := resource.ResourceFS.ReadFile("jetkvm_native.sha256")
if err != nil {
return nil, err
}
return version, nil
}
func GetNativeVersion() (string, error) {
version, err := getNativeSha256()
if err != nil {
return "", err
}
return strings.TrimSpace(string(version)), nil
}
func ensureBinaryUpdated(destPath string) error { func ensureBinaryUpdated(destPath string) error {
srcFile, err := resource.ResourceFS.Open("jetkvm_native") srcFile, err := resource.ResourceFS.Open("jetkvm_native")
if err != nil { if err != nil {
@ -289,7 +306,7 @@ func ensureBinaryUpdated(destPath string) error {
} }
defer srcFile.Close() defer srcFile.Close()
srcHash, err := resource.ResourceFS.ReadFile("jetkvm_native.sha256") srcHash, err := getNativeSha256()
if err != nil { if err != nil {
nativeLogger.Debug().Msg("error reading embedded jetkvm_native.sha256, proceeding with update") nativeLogger.Debug().Msg("error reading embedded jetkvm_native.sha256, proceeding with update")
srcHash = nil srcHash = nil

44
ota.go
View File

@ -41,12 +41,19 @@ type UpdateStatus struct {
Remote *UpdateMetadata `json:"remote"` Remote *UpdateMetadata `json:"remote"`
SystemUpdateAvailable bool `json:"systemUpdateAvailable"` SystemUpdateAvailable bool `json:"systemUpdateAvailable"`
AppUpdateAvailable bool `json:"appUpdateAvailable"` AppUpdateAvailable bool `json:"appUpdateAvailable"`
// for backwards compatibility
Error string `json:"error,omitempty"`
} }
const UpdateMetadataUrl = "https://api.jetkvm.com/releases" const UpdateMetadataUrl = "https://api.jetkvm.com/releases"
var builtAppVersion = "0.1.0+dev" var builtAppVersion = "0.1.0+dev"
func GetBuiltAppVersion() string {
return builtAppVersion
}
func GetLocalVersion() (systemVersion *semver.Version, appVersion *semver.Version, err error) { func GetLocalVersion() (systemVersion *semver.Version, appVersion *semver.Version, err error) {
appVersion, err = semver.NewVersion(builtAppVersion) appVersion, err = semver.NewVersion(builtAppVersion)
if err != nil { if err != nil {
@ -489,52 +496,47 @@ func TryUpdate(ctx context.Context, deviceId string, includePreRelease bool) err
} }
func GetUpdateStatus(ctx context.Context, deviceId string, includePreRelease bool) (*UpdateStatus, error) { func GetUpdateStatus(ctx context.Context, deviceId string, includePreRelease bool) (*UpdateStatus, error) {
updateStatus := &UpdateStatus{}
// Get local versions // Get local versions
systemVersionLocal, appVersionLocal, err := GetLocalVersion() systemVersionLocal, appVersionLocal, err := GetLocalVersion()
if err != nil { if err != nil {
return nil, fmt.Errorf("error getting local version: %w", err) return updateStatus, fmt.Errorf("error getting local version: %w", err)
}
updateStatus.Local = &LocalMetadata{
AppVersion: appVersionLocal.String(),
SystemVersion: systemVersionLocal.String(),
} }
// Get remote metadata // Get remote metadata
remoteMetadata, err := fetchUpdateMetadata(ctx, deviceId, includePreRelease) remoteMetadata, err := fetchUpdateMetadata(ctx, deviceId, includePreRelease)
if err != nil { if err != nil {
return nil, fmt.Errorf("error checking for updates: %w", err) return updateStatus, fmt.Errorf("error checking for updates: %w", err)
}
// Build local UpdateMetadata
localMetadata := &LocalMetadata{
AppVersion: appVersionLocal.String(),
SystemVersion: systemVersionLocal.String(),
} }
updateStatus.Remote = remoteMetadata
// Get remote versions
systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion) systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing remote system version: %w", err) return updateStatus, fmt.Errorf("error parsing remote system version: %w", err)
} }
appVersionRemote, err := semver.NewVersion(remoteMetadata.AppVersion) appVersionRemote, err := semver.NewVersion(remoteMetadata.AppVersion)
if err != nil { if err != nil {
return nil, fmt.Errorf("error parsing remote app version: %w, %s", err, remoteMetadata.AppVersion) return updateStatus, fmt.Errorf("error parsing remote app version: %w, %s", err, remoteMetadata.AppVersion)
} }
systemUpdateAvailable := systemVersionRemote.GreaterThan(systemVersionLocal) updateStatus.SystemUpdateAvailable = systemVersionRemote.GreaterThan(systemVersionLocal)
appUpdateAvailable := appVersionRemote.GreaterThan(appVersionLocal) updateStatus.AppUpdateAvailable = appVersionRemote.GreaterThan(appVersionLocal)
// Handle pre-release updates // Handle pre-release updates
isRemoteSystemPreRelease := systemVersionRemote.Prerelease() != "" isRemoteSystemPreRelease := systemVersionRemote.Prerelease() != ""
isRemoteAppPreRelease := appVersionRemote.Prerelease() != "" isRemoteAppPreRelease := appVersionRemote.Prerelease() != ""
if isRemoteSystemPreRelease && !includePreRelease { if isRemoteSystemPreRelease && !includePreRelease {
systemUpdateAvailable = false updateStatus.SystemUpdateAvailable = false
} }
if isRemoteAppPreRelease && !includePreRelease { if isRemoteAppPreRelease && !includePreRelease {
appUpdateAvailable = false updateStatus.AppUpdateAvailable = false
}
updateStatus := &UpdateStatus{
Local: localMetadata,
Remote: remoteMetadata,
SystemUpdateAvailable: systemUpdateAvailable,
AppUpdateAvailable: appUpdateAvailable,
} }
return updateStatus, nil return updateStatus, nil

View File

@ -43,9 +43,10 @@ export default function SettingsGeneralUpdateRoute() {
export interface SystemVersionInfo { export interface SystemVersionInfo {
local: { appVersion: string; systemVersion: string }; local: { appVersion: string; systemVersion: string };
remote: { appVersion: string; systemVersion: string }; remote?: { appVersion: string; systemVersion: string };
systemUpdateAvailable: boolean; systemUpdateAvailable: boolean;
appUpdateAvailable: boolean; appUpdateAvailable: boolean;
error?: string;
} }
export function Dialog({ export function Dialog({
@ -142,14 +143,20 @@ function LoadingState({
return new Promise<SystemVersionInfo>((resolve, reject) => { return new Promise<SystemVersionInfo>((resolve, reject) => {
send("getUpdateStatus", {}, async resp => { send("getUpdateStatus", {}, async resp => {
if ("error" in resp) { if ("error" in resp) {
notifications.error("Failed to check for updates"); notifications.error(`Failed to check for updates: ${resp.error}`);
reject(new Error("Failed to check for updates")); reject(new Error("Failed to check for updates"));
} else { } else {
const result = resp.result as SystemVersionInfo; const result = resp.result as SystemVersionInfo;
setAppVersion(result.local.appVersion); setAppVersion(result.local.appVersion);
setSystemVersion(result.local.systemVersion); setSystemVersion(result.local.systemVersion);
if (result.error) {
notifications.error(`Failed to check for updates: ${result.error}`);
reject(new Error("Failed to check for updates"));
} else {
resolve(result); resolve(result);
} }
}
}); });
}); });
}, [send, setAppVersion, setSystemVersion]); }, [send, setAppVersion, setSystemVersion]);
@ -442,13 +449,14 @@ function UpdateAvailableState({
{versionInfo?.systemUpdateAvailable ? ( {versionInfo?.systemUpdateAvailable ? (
<> <>
<span className="font-semibold">System:</span>{" "} <span className="font-semibold">System:</span>{" "}
{versionInfo?.remote.systemVersion} {versionInfo?.remote?.systemVersion}
<br /> <br />
</> </>
) : null} ) : null}
{versionInfo?.appUpdateAvailable ? ( {versionInfo?.appUpdateAvailable ? (
<> <>
<span className="font-semibold">App:</span> {versionInfo?.remote.appVersion} <span className="font-semibold">App:</span>{" "}
{versionInfo?.remote?.appVersion}
</> </>
) : null} ) : null}
</p> </p>

View File

@ -703,12 +703,17 @@ export default function KvmIdRoute() {
send("getUpdateStatus", {}, async resp => { send("getUpdateStatus", {}, async resp => {
if ("error" in resp) { if ("error" in resp) {
notifications.error("Failed to get device version"); notifications.error(`Failed to get device version: ${resp.error}`);
} else { return
}
const result = resp.result as SystemVersionInfo; const result = resp.result as SystemVersionInfo;
if (result.error) {
notifications.error(`Failed to get device version: ${result.error}`);
}
setAppVersion(result.local.appVersion); setAppVersion(result.local.appVersion);
setSystemVersion(result.local.systemVersion); setSystemVersion(result.local.systemVersion);
}
}); });
}, [appVersion, send, setAppVersion, setSystemVersion]); }, [appVersion, send, setAppVersion, setSystemVersion]);

56
version.go Normal file
View File

@ -0,0 +1,56 @@
package kvm
import (
"bytes"
"encoding/json"
"html/template"
"runtime"
"github.com/prometheus/common/version"
)
var versionInfoTmpl = `
JetKVM Application, version {{.version}} (branch: {{.branch}}, revision: {{.revision}})
build date: {{.buildDate}}
go version: {{.goVersion}}
platform: {{.platform}}
{{if .nativeVersion}}
JetKVM Native, version {{.nativeVersion}}
{{end}}
`
func GetVersionData(isJson bool) ([]byte, error) {
version.Version = GetBuiltAppVersion()
m := map[string]string{
"version": version.Version,
"revision": version.GetRevision(),
"branch": version.Branch,
"buildDate": version.BuildDate,
"goVersion": version.GoVersion,
"platform": runtime.GOOS + "/" + runtime.GOARCH,
}
nativeVersion, err := GetNativeVersion()
if err == nil {
m["nativeVersion"] = nativeVersion
}
if isJson {
jsonData, err := json.Marshal(m)
if err != nil {
return nil, err
}
return jsonData, nil
}
t := template.Must(template.New("version").Parse(versionInfoTmpl))
var buf bytes.Buffer
if err := t.ExecuteTemplate(&buf, "version", m); err != nil {
return nil, err
}
return buf.Bytes(), nil
}