feat: update button (download, verify, stage)

This commit is contained in:
fsylvain committed 2026-10-01 18:13:45 +02:00
1 parent 834493a4f5
commit 70460e4170
41 files changed
+5345 -97

No files matched your search

+177
View File
@@ -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
}