178 lines
3.8 KiB
Go
178 lines
3.8 KiB
Go
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
|
|
}
|