feat: update button (download, verify, stage)
This commit is contained in:
1 parent
834493a4f5
commit
70460e4170
41 files changed
+5345
-97
No files matched your search
@@ -19,17 +19,25 @@ type ReleaseNotes struct {
|
||||
Items []string `json:"items"`
|
||||
}
|
||||
|
||||
type Asset struct {
|
||||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
type Release struct {
|
||||
Tag string
|
||||
URL string
|
||||
PublishedAt time.Time
|
||||
Notes ReleaseNotes
|
||||
Assets []Asset
|
||||
}
|
||||
|
||||
type CheckResult struct {
|
||||
Latest string `json:"latest"`
|
||||
ReleaseURL string `json:"release_url"`
|
||||
Notes ReleaseNotes `json:"notes"`
|
||||
Assets []Asset `json:"assets"`
|
||||
CheckedAt time.Time `json:"checked_at"`
|
||||
}
|
||||
|
||||
@@ -58,6 +66,7 @@ func NewResult(release Release, checkedAt time.Time) CheckResult {
|
||||
Latest: release.Tag,
|
||||
ReleaseURL: release.URL,
|
||||
Notes: release.Notes,
|
||||
Assets: release.Assets,
|
||||
CheckedAt: checkedAt.UTC(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
StateDownloading = "downloading"
|
||||
StateVerifying = "verifying"
|
||||
StateReady = "ready"
|
||||
StateFailed = "failed"
|
||||
|
||||
ErrorKindDownload = "download"
|
||||
ErrorKindVerification = "verification"
|
||||
|
||||
ChecksumsAssetName = "checksums.txt"
|
||||
SignatureAssetName = "checksums.txt.sig"
|
||||
|
||||
windowsOS = "windows"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotPrepared = errors.New("this server cannot apply updates")
|
||||
ErrJobInProgress = errors.New("an update job is already running or ready")
|
||||
ErrNothingToUpdate = errors.New("there is nothing to update")
|
||||
ErrPreflightRefused = errors.New("the update cannot be downloaded")
|
||||
)
|
||||
|
||||
type Platform struct {
|
||||
OS string
|
||||
Arch string
|
||||
}
|
||||
|
||||
type Job struct {
|
||||
Tag string `json:"tag"`
|
||||
State string `json:"state"`
|
||||
Error string `json:"error"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
DownloadedBytes int64 `json:"downloaded_bytes"`
|
||||
TotalBytes int64 `json:"total_bytes"`
|
||||
StagedBinary string `json:"staged_binary"`
|
||||
}
|
||||
|
||||
type ProgressFunc func(done int64, total int64)
|
||||
|
||||
type AssetFetcher interface {
|
||||
Fetch(asset Asset, destination string, report ProgressFunc) error
|
||||
}
|
||||
|
||||
type SignatureVerifier interface {
|
||||
Verify(checksumsPath string, signaturePath string) error
|
||||
}
|
||||
|
||||
type StagedBinaryRunner interface {
|
||||
Version(binaryPath string) (string, error)
|
||||
}
|
||||
|
||||
type DatabaseBackup interface {
|
||||
Backup() (string, error)
|
||||
}
|
||||
|
||||
type StagingArea interface {
|
||||
Prepare(tag string) error
|
||||
PathFor(tag string, name string) string
|
||||
Size(path string) (int64, error)
|
||||
ReadChecksums(path string) (string, error)
|
||||
MakeExecutable(path string) error
|
||||
Remove(tag string) error
|
||||
}
|
||||
|
||||
type HelperDetector interface {
|
||||
Installed() bool
|
||||
}
|
||||
|
||||
type JobStore interface {
|
||||
Load() (Job, bool)
|
||||
Save(job Job) error
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Printf(format string, args ...any)
|
||||
}
|
||||
|
||||
func (j Job) IsRunning() bool {
|
||||
return j.State == StateDownloading || j.State == StateVerifying
|
||||
}
|
||||
|
||||
func (j Job) Occupies() bool {
|
||||
return j.IsRunning() || j.State == StateReady
|
||||
}
|
||||
|
||||
func (j Job) Progress() *Progress {
|
||||
if j.State != StateDownloading || j.TotalBytes <= 0 {
|
||||
return nil
|
||||
}
|
||||
return &Progress{
|
||||
Percent: int(j.DownloadedBytes * 100 / j.TotalBytes),
|
||||
DownloadedBytes: j.DownloadedBytes,
|
||||
TotalBytes: j.TotalBytes,
|
||||
}
|
||||
}
|
||||
|
||||
func (s UpdateStatus) WithJob(job Job) UpdateStatus {
|
||||
s.State = job.State
|
||||
s.Progress = job.Progress()
|
||||
s.Error = nil
|
||||
if job.Error != "" {
|
||||
kind := job.Error
|
||||
s.Error = &kind
|
||||
}
|
||||
startedAt := job.StartedAt
|
||||
s.StartedAt = &startedAt
|
||||
return s
|
||||
}
|
||||
|
||||
func AssetName(tag string, platform Platform) string {
|
||||
name := "renewcms_" + tag + "_" + platform.OS + "_" + platform.Arch
|
||||
if platform.OS == windowsOS {
|
||||
name += ".exe"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func FindAsset(assets []Asset, name string) (Asset, bool) {
|
||||
for _, asset := range assets {
|
||||
if asset.Name == name {
|
||||
return asset, true
|
||||
}
|
||||
}
|
||||
return Asset{}, false
|
||||
}
|
||||
|
||||
func HostOf(rawURL string) string {
|
||||
parsed, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsed.Host
|
||||
}
|
||||
|
||||
func IsAllowedAssetURL(rawURL string, allowedHost string) bool {
|
||||
parsed, err := url.Parse(rawURL)
|
||||
if err != nil || parsed.Scheme != "https" {
|
||||
return false
|
||||
}
|
||||
return allowedHost != "" && parsed.Host == allowedHost
|
||||
}
|
||||
|
||||
func ChecksumFor(checksums string, assetName string) (string, bool) {
|
||||
for _, line := range strings.Split(checksums, "\n") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) == 2 && strings.TrimPrefix(fields[1], "*") == assetName {
|
||||
return strings.ToLower(fields[0]), true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func FileSHA256(path string) (string, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||
}
|
||||
Reference in new issue
Block a user