mirror of https://github.com/jetkvm/kvm.git
220 lines
6.8 KiB
Go
220 lines
6.8 KiB
Go
package ota
|
|
|
|
import (
|
|
"net/http"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/Masterminds/semver/v3"
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
var (
|
|
availableComponents = []string{"app", "system"}
|
|
defaultComponents = map[string]string{
|
|
"app": "",
|
|
"system": "",
|
|
}
|
|
)
|
|
|
|
// UpdateMetadata represents the metadata of an update
|
|
type UpdateMetadata struct {
|
|
AppVersion string `json:"appVersion"`
|
|
AppURL string `json:"appUrl"`
|
|
AppHash string `json:"appHash"`
|
|
SystemVersion string `json:"systemVersion"`
|
|
SystemURL string `json:"systemUrl"`
|
|
SystemHash string `json:"systemHash"`
|
|
}
|
|
|
|
// LocalMetadata represents the local metadata of the system
|
|
type LocalMetadata struct {
|
|
AppVersion string `json:"appVersion"`
|
|
SystemVersion string `json:"systemVersion"`
|
|
}
|
|
|
|
// UpdateStatus represents the current update status
|
|
type UpdateStatus struct {
|
|
Local *LocalMetadata `json:"local"`
|
|
Remote *UpdateMetadata `json:"remote"`
|
|
SystemUpdateAvailable bool `json:"systemUpdateAvailable"`
|
|
AppUpdateAvailable bool `json:"appUpdateAvailable"`
|
|
|
|
// for backwards compatibility
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// PostRebootAction represents the action to be taken after a reboot
|
|
// It is used to redirect the user to a specific page after a reboot
|
|
type PostRebootAction struct {
|
|
HealthCheck string `json:"healthCheck"` // The health check URL to call after the reboot
|
|
RedirectTo string `json:"redirectTo"` // The URL to redirect to after the reboot
|
|
}
|
|
|
|
// componentUpdateStatus represents the status of a component update
|
|
type componentUpdateStatus struct {
|
|
pending bool
|
|
available bool
|
|
availableReason string // why the component is available or not available
|
|
version string
|
|
localVersion string
|
|
url string
|
|
hash string
|
|
downloadProgress float32
|
|
downloadFinishedAt time.Time
|
|
verificationProgress float32
|
|
verifiedAt time.Time
|
|
updateProgress float32
|
|
updatedAt time.Time
|
|
dependsOn []string
|
|
}
|
|
|
|
func (c *componentUpdateStatus) getZerologLogger(l *zerolog.Logger) *zerolog.Logger {
|
|
logger := l.With().
|
|
Bool("pending", c.pending).
|
|
Bool("available", c.available).
|
|
Str("availableReason", c.availableReason).
|
|
Str("version", c.version).
|
|
Str("localVersion", c.localVersion).
|
|
Str("url", c.url).
|
|
Str("hash", c.hash).
|
|
Float32("downloadProgress", c.downloadProgress).
|
|
Time("downloadFinishedAt", c.downloadFinishedAt).
|
|
Float32("verificationProgress", c.verificationProgress).
|
|
Time("verifiedAt", c.verifiedAt).
|
|
Float32("updateProgress", c.updateProgress).
|
|
Time("updatedAt", c.updatedAt).
|
|
Strs("dependsOn", c.dependsOn).
|
|
Logger()
|
|
return &logger
|
|
}
|
|
|
|
// HwRebootFunc is a function that reboots the hardware
|
|
type HwRebootFunc func(force bool, postRebootAction *PostRebootAction, delay time.Duration) error
|
|
|
|
// ResetConfigFunc is a function that resets the config
|
|
type ResetConfigFunc func() error
|
|
|
|
// GetHTTPClientFunc is a function that returns the HTTP client
|
|
type GetHTTPClientFunc func() *http.Client
|
|
|
|
// OnStateUpdateFunc is a function that updates the state of the OTA
|
|
type OnStateUpdateFunc func(state *RPCState)
|
|
|
|
// OnProgressUpdateFunc is a function that updates the progress of the OTA
|
|
type OnProgressUpdateFunc func(progress float32)
|
|
|
|
// GetLocalVersionFunc is a function that returns the local version of the system and app
|
|
type GetLocalVersionFunc func() (systemVersion *semver.Version, appVersion *semver.Version, err error)
|
|
|
|
// State represents the current OTA state for the UI
|
|
type State struct {
|
|
releaseAPIEndpoint string
|
|
l *zerolog.Logger
|
|
mu sync.Mutex
|
|
updating bool
|
|
error string
|
|
metadataFetchedAt time.Time
|
|
rebootNeeded bool
|
|
componentUpdateStatuses map[string]componentUpdateStatus
|
|
client GetHTTPClientFunc
|
|
reboot HwRebootFunc
|
|
getLocalVersion GetLocalVersionFunc
|
|
onStateUpdate OnStateUpdateFunc
|
|
resetConfig ResetConfigFunc
|
|
}
|
|
|
|
func toUpdateStatus(appUpdate *componentUpdateStatus, systemUpdate *componentUpdateStatus, error string) *UpdateStatus {
|
|
return &UpdateStatus{
|
|
Local: &LocalMetadata{
|
|
AppVersion: appUpdate.localVersion,
|
|
SystemVersion: systemUpdate.localVersion,
|
|
},
|
|
Remote: &UpdateMetadata{
|
|
AppVersion: appUpdate.version,
|
|
AppURL: appUpdate.url,
|
|
AppHash: appUpdate.hash,
|
|
SystemVersion: systemUpdate.version,
|
|
SystemURL: systemUpdate.url,
|
|
SystemHash: systemUpdate.hash,
|
|
},
|
|
SystemUpdateAvailable: systemUpdate.available,
|
|
AppUpdateAvailable: appUpdate.available,
|
|
Error: error,
|
|
}
|
|
}
|
|
|
|
// ToUpdateStatus converts the State to the UpdateStatus
|
|
func (s *State) ToUpdateStatus() *UpdateStatus {
|
|
appUpdate, ok := s.componentUpdateStatuses["app"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
systemUpdate, ok := s.componentUpdateStatuses["system"]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
return toUpdateStatus(&appUpdate, &systemUpdate, s.error)
|
|
}
|
|
|
|
// IsUpdatePending returns true if an update is pending
|
|
func (s *State) IsUpdatePending() bool {
|
|
return s.updating
|
|
}
|
|
|
|
// Options represents the options for the OTA state
|
|
type Options struct {
|
|
Logger *zerolog.Logger
|
|
GetHTTPClient GetHTTPClientFunc
|
|
GetLocalVersion GetLocalVersionFunc
|
|
OnStateUpdate OnStateUpdateFunc
|
|
OnProgressUpdate OnProgressUpdateFunc
|
|
HwReboot HwRebootFunc
|
|
ReleaseAPIEndpoint string
|
|
ResetConfig ResetConfigFunc
|
|
SkipConfirmSystem bool
|
|
}
|
|
|
|
// NewState creates a new OTA state
|
|
func NewState(opts Options) *State {
|
|
components := make(map[string]componentUpdateStatus)
|
|
for _, component := range availableComponents {
|
|
components[component] = componentUpdateStatus{}
|
|
}
|
|
|
|
s := &State{
|
|
l: opts.Logger,
|
|
client: opts.GetHTTPClient,
|
|
reboot: opts.HwReboot,
|
|
onStateUpdate: opts.OnStateUpdate,
|
|
getLocalVersion: opts.GetLocalVersion,
|
|
componentUpdateStatuses: components,
|
|
releaseAPIEndpoint: opts.ReleaseAPIEndpoint,
|
|
resetConfig: opts.ResetConfig,
|
|
}
|
|
if !opts.SkipConfirmSystem {
|
|
go s.confirmCurrentSystem()
|
|
}
|
|
return s
|
|
}
|
|
|
|
// appUpdateStatus.url = remoteMetadata.AppURL
|
|
// appUpdateStatus.hash = remoteMetadata.AppHash
|
|
// appUpdateStatus.version = remoteMetadata.AppVersion
|
|
|
|
// systemUpdateStatus.url = remoteMetadata.SystemURL
|
|
// systemUpdateStatus.hash = remoteMetadata.SystemHash
|
|
// systemUpdateStatus.version = remoteMetadata.SystemVersion
|
|
|
|
// // Get remote versions
|
|
// systemVersionRemote, err := semver.NewVersion(remoteMetadata.SystemVersion)
|
|
//
|
|
// if err != nil {
|
|
// err = fmt.Errorf("error parsing remote system version: %w", err)
|
|
// return err
|
|
// }
|
|
//
|
|
// systemUpdateStatus.available = systemVersionRemote.GreaterThan(systemVersionLocal)
|