[WIP] Error and state handleing improvments, better logs

This commit is contained in:
Vladimir Stoilov 2024-09-19 12:38:23 +03:00
parent 072d7e6971
commit b3ff6f14f1
No known key found for this signature in database
GPG key ID: 2F190B67A43A81AF
24 changed files with 146 additions and 1981 deletions

View file

@ -107,27 +107,29 @@ func registerAPIEndpoints() error {
}
if err := api.RegisterEndpoint(api.Endpoint{
Path: "updates/check",
Read: api.PermitUser,
Path: "updates/check",
WriteMethod: "POST",
Write: api.PermitUser,
ActionFunc: func(ar *api.Request) (string, error) {
module.instance.BinaryUpdates().TriggerUpdateCheck()
module.instance.IntelUpdates().TriggerUpdateCheck()
return "update check triggered", nil
},
Name: "Get the ID of the calling profile",
Name: "Trigger updates check event",
}); err != nil {
return err
}
if err := api.RegisterEndpoint(api.Endpoint{
Path: "updates/apply",
Read: api.PermitUser,
Path: "updates/apply",
WriteMethod: "POST",
Write: api.PermitUser,
ActionFunc: func(ar *api.Request) (string, error) {
module.instance.BinaryUpdates().TriggerApplyUpdates()
module.instance.IntelUpdates().TriggerApplyUpdates()
return "upgrade triggered", nil
},
Name: "Get the ID of the calling profile",
Name: "Trigger updates apply event",
}); err != nil {
return err
}

View file

@ -18,7 +18,6 @@ import (
"github.com/safing/portmaster/service/network/netutils"
"github.com/safing/portmaster/service/network/packet"
"github.com/safing/portmaster/service/process"
"github.com/safing/portmaster/service/updates"
)
const (
@ -133,7 +132,8 @@ func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bo
var originalPid int
// Get authenticated path.
authenticatedPath := updates.RootPath()
// FIXME(vladimir): provide a better check for detecting filepath. Note there is exception on linux with portmaster ui.
authenticatedPath := "" // updates.RootPath()
if authenticatedPath == "" {
return false, fmt.Errorf(deniedMsgMisconfigured, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user
}

View file

@ -133,13 +133,14 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
return nil, err
}
binaryUpdateIndex = updates.UpdateIndex{
Directory: binaryFolder, // Default: C:/Program Files/Portmaster/binary
Directory: binaryFolder, // Default: C:/Program Files/Portmaster
DownloadDirectory: os.ExpandEnv("%ProgramData%/Portmaster/new_binary"),
PurgeDirectory: os.ExpandEnv("%ProgramData%/Portmaster/old_binary"),
Ignore: []string{"databases", "intel", "config.json"},
IndexURLs: []string{"http://192.168.88.11:8000/test-binary.json"},
IndexFile: "bin-index.json",
AutoApply: false,
NeedsRestart: true,
}
intelUpdateIndex = updates.UpdateIndex{
@ -149,6 +150,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
IndexURLs: []string{"http://192.168.88.11:8000/test-intel.json"},
IndexFile: "intel-index.json",
AutoApply: true,
NeedsRestart: false,
}
} else if go_runtime.GOOS == "linux" {
binaryUpdateIndex = updates.UpdateIndex{
@ -159,6 +161,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
IndexURLs: []string{"http://localhost:8000/test-binary.json"},
IndexFile: "bin-index.json",
AutoApply: false,
NeedsRestart: true,
}
intelUpdateIndex = updates.UpdateIndex{
@ -168,6 +171,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
IndexURLs: []string{"http://localhost:8000/test-intel.json"},
IndexFile: "intel-index.json",
AutoApply: true,
NeedsRestart: false,
}
}

View file

@ -2,11 +2,9 @@ package process
import (
"errors"
"os"
"sync/atomic"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/service/updates"
)
type ProcessModule struct {
@ -19,10 +17,6 @@ func (pm *ProcessModule) Manager() *mgr.Manager {
}
func (pm *ProcessModule) Start() error {
updatesPath = updates.RootPath()
if updatesPath != "" {
updatesPath += string(os.PathSeparator)
}
return nil
}
@ -30,8 +24,6 @@ func (pm *ProcessModule) Stop() error {
return nil
}
var updatesPath string
func prep() error {
if err := registerConfiguration(); err != nil {
return err

View file

@ -72,19 +72,20 @@ func (p *Process) getSpecialProfileID() (specialProfileID string) {
specialProfileID = profile.PortmasterProfileID
default:
// Check if this is another Portmaster component.
if updatesPath != "" && strings.HasPrefix(p.Path, updatesPath) {
switch {
case strings.Contains(p.Path, "portmaster-app"):
specialProfileID = profile.PortmasterAppProfileID
case strings.Contains(p.Path, "portmaster-notifier"):
specialProfileID = profile.PortmasterNotifierProfileID
default:
// Unexpected binary from within the Portmaster updates directpry.
log.Warningf("process: unexpected binary in the updates directory: %s", p.Path)
// TODO: Assign a fully restricted profile in the future when we are
// sure that we won't kill any of our own things.
}
}
// FIXME(vladimir): provide a better check for detecting filepath. Note there is exception on linux with portmaster ui.
// if updatesPath != "" && strings.HasPrefix(p.Path, updatesPath) {
// switch {
// case strings.Contains(p.Path, "portmaster-app"):
// specialProfileID = profile.PortmasterAppProfileID
// case strings.Contains(p.Path, "portmaster-notifier"):
// specialProfileID = profile.PortmasterNotifierProfileID
// default:
// // Unexpected binary from within the Portmaster updates directpry.
// log.Warningf("process: unexpected binary in the updates directory: %s", p.Path)
// // TODO: Assign a fully restricted profile in the future when we are
// // sure that we won't kill any of our own things.
// }
// }
// Check if this is the system resolver.
switch runtime.GOOS {
case "windows":

View file

@ -3,7 +3,6 @@ package profile
import (
"errors"
"fmt"
"os"
"sync/atomic"
"github.com/safing/portmaster/base/config"
@ -14,13 +13,9 @@ import (
_ "github.com/safing/portmaster/service/core/base"
"github.com/safing/portmaster/service/mgr"
"github.com/safing/portmaster/service/profile/binmeta"
"github.com/safing/portmaster/service/updates"
)
var (
migrations = migration.New("core:migrations/profile")
updatesPath string
)
var migrations = migration.New("core:migrations/profile")
// Events.
const (
@ -80,11 +75,6 @@ func prep() error {
}
func start() error {
updatesPath = updates.RootPath()
if updatesPath != "" {
updatesPath += string(os.PathSeparator)
}
if err := loadProfilesMetadata(); err != nil {
if !errors.Is(err, database.ErrNotFound) {
log.Warningf("profile: failed to load profiles metadata, falling back to empty state: %s", err)

View file

@ -1,161 +0,0 @@
package updates
import (
// "bytes"
// "io"
// "net/http"
// "os"
// "path/filepath"
// "strings"
// "github.com/ghodss/yaml"
// "github.com/safing/portmaster/base/api"
// "github.com/safing/portmaster/base/log"
// "github.com/safing/portmaster/base/utils"
)
const (
apiPathCheckForUpdates = "updates/check"
)
// func registerAPIEndpoints() error {
// if err := api.RegisterEndpoint(api.Endpoint{
// Name: "Check for Updates",
// Description: "Checks if new versions are available. If automatic updates are enabled, they are also downloaded and applied.",
// Parameters: []api.Parameter{{
// Method: http.MethodPost,
// Field: "download",
// Value: "",
// Description: "Force downloading and applying of all updates, regardless of auto-update settings.",
// }},
// Path: apiPathCheckForUpdates,
// Write: api.PermitUser,
// ActionFunc: func(r *api.Request) (msg string, err error) {
// // Check if we should also download regardless of settings.
// downloadAll := r.URL.Query().Has("download")
// // Trigger update task.
// err = TriggerUpdate(true, downloadAll)
// if err != nil {
// return "", err
// }
// // Report how we triggered.
// if downloadAll {
// return "downloading all updates...", nil
// }
// return "checking for updates...", nil
// },
// }); err != nil {
// return err
// }
// if err := api.RegisterEndpoint(api.Endpoint{
// Name: "Get Resource",
// Description: "Returns the requested resource from the udpate system",
// Path: `updates/get/{identifier:[A-Za-z0-9/\.\-_]{1,255}}`,
// Read: api.PermitUser,
// ReadMethod: http.MethodGet,
// HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
// // Get identifier from URL.
// var identifier string
// if ar := api.GetAPIRequest(r); ar != nil {
// identifier = ar.URLVars["identifier"]
// }
// if identifier == "" {
// http.Error(w, "no resource speicified", http.StatusBadRequest)
// return
// }
// // Get resource.
// resource, err := registry.GetFile(identifier)
// if err != nil {
// http.Error(w, err.Error(), http.StatusNotFound)
// return
// }
// // Open file for reading.
// file, err := os.Open(resource.Path())
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// defer file.Close() //nolint:errcheck,gosec
// // Assign file to reader
// var reader io.Reader = file
// // Add version to header.
// w.Header().Set("Resource-Version", resource.Version())
// // Set Content-Type.
// contentType, _ := utils.MimeTypeByExtension(filepath.Ext(resource.Path()))
// w.Header().Set("Content-Type", contentType)
// // Check if the content type may be returned.
// accept := r.Header.Get("Accept")
// if accept != "" {
// mimeTypes := strings.Split(accept, ",")
// // First, clean mime types.
// for i, mimeType := range mimeTypes {
// mimeType = strings.TrimSpace(mimeType)
// mimeType, _, _ = strings.Cut(mimeType, ";")
// mimeTypes[i] = mimeType
// }
// // Second, check if we may return anything.
// var acceptsAny bool
// for _, mimeType := range mimeTypes {
// switch mimeType {
// case "*", "*/*":
// acceptsAny = true
// }
// }
// // Third, check if we can convert.
// if !acceptsAny {
// var converted bool
// sourceType, _, _ := strings.Cut(contentType, ";")
// findConvertiblePair:
// for _, mimeType := range mimeTypes {
// switch {
// case sourceType == "application/yaml" && mimeType == "application/json":
// yamlData, err := io.ReadAll(reader)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// jsonData, err := yaml.YAMLToJSON(yamlData)
// if err != nil {
// http.Error(w, err.Error(), http.StatusInternalServerError)
// return
// }
// reader = bytes.NewReader(jsonData)
// converted = true
// break findConvertiblePair
// }
// }
// // If we could not convert to acceptable format, return an error.
// if !converted {
// http.Error(w, "conversion to requested format not supported", http.StatusNotAcceptable)
// return
// }
// }
// }
// // Write file.
// w.WriteHeader(http.StatusOK)
// if r.Method != http.MethodHead {
// _, err = io.Copy(w, reader)
// if err != nil {
// log.Errorf("updates: failed to serve resource file: %s", err)
// return
// }
// }
// },
// }); err != nil {
// return err
// }
// return nil
// }

View file

@ -4,6 +4,7 @@ import (
"archive/zip"
"bytes"
"compress/gzip"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
@ -106,7 +107,7 @@ func (bundle Bundle) CopyMatchingFilesFromCurrent(current Bundle, currentDir, ne
if err != nil {
return fmt.Errorf("failed to write to file %s: %w", destFilePath, err)
}
log.Debugf("updates: file copied from current version: %s", newArtifact.Filename)
}
break new
}
@ -115,7 +116,7 @@ func (bundle Bundle) CopyMatchingFilesFromCurrent(current Bundle, currentDir, ne
return nil
}
func (bundle Bundle) DownloadAndVerify(client *http.Client, dir string) {
func (bundle Bundle) DownloadAndVerify(ctx context.Context, client *http.Client, dir string) {
// Make sure dir exists
_ = os.MkdirAll(dir, defaultDirMode)
@ -130,7 +131,7 @@ func (bundle Bundle) DownloadAndVerify(client *http.Client, dir string) {
}
// Download artifact
err := processArtifact(client, artifact, filePath)
err := processArtifact(ctx, client, artifact, filePath)
if err != nil {
log.Errorf("updates: %s", err)
}
@ -179,14 +180,15 @@ func checkIfFileIsValid(filename string, artifact Artifact) (bool, error) {
return true, nil
}
func processArtifact(client *http.Client, artifact Artifact, filePath string) error {
func processArtifact(ctx context.Context, client *http.Client, artifact Artifact, filePath string) error {
providedHash, err := hex.DecodeString(artifact.SHA256)
if err != nil || len(providedHash) != sha256.Size {
return fmt.Errorf("invalid provided hash %s: %w", artifact.SHA256, err)
}
// Download
content, err := downloadFile(client, artifact.URLs)
log.Debugf("updates: downloading file: %s", artifact.Filename)
content, err := downloadFile(ctx, client, artifact.URLs)
if err != nil {
return fmt.Errorf("failed to download artifact: %w", err)
}
@ -222,13 +224,23 @@ func processArtifact(client *http.Client, artifact Artifact, filePath string) er
return fmt.Errorf("failed to rename file: %w", err)
}
log.Infof("updates: file downloaded and verified: %s", artifact.Filename)
return nil
}
func downloadFile(client *http.Client, urls []string) ([]byte, error) {
func downloadFile(ctx context.Context, client *http.Client, urls []string) ([]byte, error) {
for _, url := range urls {
// Try to make the request
resp, err := client.Get(url)
req, err := http.NewRequestWithContext(ctx, "GET", url, http.NoBody)
if err != nil {
log.Warningf("failed to create GET request to %s: %s", url, err)
continue
}
if UserAgent != "" {
req.Header.Set("User-Agent", UserAgent)
}
resp, err := client.Do(req)
if err != nil {
log.Warningf("failed a get file request to: %s", err)
continue

View file

@ -1,178 +0,0 @@
package updates
import (
"github.com/tevino/abool"
"github.com/safing/portmaster/base/config"
// "github.com/safing/portmaster/base/log"
// "github.com/safing/portmaster/service/mgr"
// "github.com/safing/portmaster/service/updates/helper"
)
const cfgDevModeKey = "core/devMode"
var (
releaseChannel config.StringOption
devMode config.BoolOption
enableSoftwareUpdates config.BoolOption
enableIntelUpdates config.BoolOption
initialReleaseChannel string
previousReleaseChannel string
softwareUpdatesCurrentlyEnabled bool
intelUpdatesCurrentlyEnabled bool
previousDevMode bool
forceCheck = abool.New()
forceDownload = abool.New()
)
// func registerConfig() error {
// err := config.Register(&config.Option{
// Name: "Release Channel",
// Key: helper.ReleaseChannelKey,
// Description: `Use "Stable" for the best experience. The "Beta" channel will have the newest features and fixes, but may also break and cause interruption. Use others only temporarily and when instructed.`,
// OptType: config.OptTypeString,
// ExpertiseLevel: config.ExpertiseLevelExpert,
// ReleaseLevel: config.ReleaseLevelStable,
// RequiresRestart: true,
// DefaultValue: helper.ReleaseChannelStable,
// PossibleValues: []config.PossibleValue{
// {
// Name: "Stable",
// Description: "Production releases.",
// Value: helper.ReleaseChannelStable,
// },
// {
// Name: "Beta",
// Description: "Production releases for testing new features that may break and cause interruption.",
// Value: helper.ReleaseChannelBeta,
// },
// {
// Name: "Support",
// Description: "Support releases or version changes for troubleshooting. Only use temporarily and when instructed.",
// Value: helper.ReleaseChannelSupport,
// },
// {
// Name: "Staging",
// Description: "Dangerous development releases for testing random things and experimenting. Only use temporarily and when instructed.",
// Value: helper.ReleaseChannelStaging,
// },
// },
// Annotations: config.Annotations{
// config.DisplayOrderAnnotation: -4,
// config.DisplayHintAnnotation: config.DisplayHintOneOf,
// config.CategoryAnnotation: "Updates",
// },
// })
// if err != nil {
// return err
// }
// err = config.Register(&config.Option{
// Name: "Automatic Software Updates",
// Key: enableSoftwareUpdatesKey,
// Description: "Automatically check for and download software updates. This does not include intelligence data updates.",
// OptType: config.OptTypeBool,
// ExpertiseLevel: config.ExpertiseLevelExpert,
// ReleaseLevel: config.ReleaseLevelStable,
// RequiresRestart: false,
// DefaultValue: true,
// Annotations: config.Annotations{
// config.DisplayOrderAnnotation: -12,
// config.CategoryAnnotation: "Updates",
// },
// })
// if err != nil {
// return err
// }
// err = config.Register(&config.Option{
// Name: "Automatic Intelligence Data Updates",
// Key: enableIntelUpdatesKey,
// Description: "Automatically check for and download intelligence data updates. This includes filter lists, geo-ip data, and more. Does not include software updates.",
// OptType: config.OptTypeBool,
// ExpertiseLevel: config.ExpertiseLevelExpert,
// ReleaseLevel: config.ReleaseLevelStable,
// RequiresRestart: false,
// DefaultValue: true,
// Annotations: config.Annotations{
// config.DisplayOrderAnnotation: -11,
// config.CategoryAnnotation: "Updates",
// },
// })
// if err != nil {
// return err
// }
// return nil
// }
// func initConfig() {
// releaseChannel = config.Concurrent.GetAsString(helper.ReleaseChannelKey, helper.ReleaseChannelStable)
// initialReleaseChannel = releaseChannel()
// previousReleaseChannel = releaseChannel()
// enableSoftwareUpdates = config.Concurrent.GetAsBool(enableSoftwareUpdatesKey, true)
// enableIntelUpdates = config.Concurrent.GetAsBool(enableIntelUpdatesKey, true)
// softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates()
// intelUpdatesCurrentlyEnabled = enableIntelUpdates()
// devMode = config.Concurrent.GetAsBool(cfgDevModeKey, false)
// previousDevMode = devMode()
// }
// func updateRegistryConfig(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
// changed := false
// if enableSoftwareUpdates() != softwareUpdatesCurrentlyEnabled {
// softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates()
// changed = true
// }
// if enableIntelUpdates() != intelUpdatesCurrentlyEnabled {
// intelUpdatesCurrentlyEnabled = enableIntelUpdates()
// changed = true
// }
// if devMode() != previousDevMode {
// registry.SetDevMode(devMode())
// previousDevMode = devMode()
// changed = true
// }
// if releaseChannel() != previousReleaseChannel {
// previousReleaseChannel = releaseChannel()
// changed = true
// }
// if changed {
// // Update indexes based on new settings.
// warning := helper.SetIndexes(
// registry,
// releaseChannel(),
// true,
// softwareUpdatesCurrentlyEnabled,
// intelUpdatesCurrentlyEnabled,
// )
// if warning != nil {
// log.Warningf("updates: %s", warning)
// }
// // Select versions depending on new indexes and modes.
// registry.SelectVersions()
// module.EventVersionsUpdated.Submit(struct{}{})
// if softwareUpdatesCurrentlyEnabled || intelUpdatesCurrentlyEnabled {
// module.states.Clear()
// if err := TriggerUpdate(true, false); err != nil {
// log.Warningf("updates: failed to trigger update: %s", err)
// }
// log.Infof("updates: automatic updates are now enabled")
// } else {
// log.Warningf("updates: automatic updates are now completely disabled")
// }
// }
// return false, nil
// }

View file

@ -1,237 +0,0 @@
package updates
// import (
// "fmt"
// "sort"
// "sync"
// "github.com/safing/portmaster/base/database/record"
// "github.com/safing/portmaster/base/info"
// "github.com/safing/portmaster/base/log"
// "github.com/safing/portmaster/base/updater"
// "github.com/safing/portmaster/base/utils/debug"
// "github.com/safing/portmaster/service/mgr"
// "github.com/safing/portmaster/service/updates/helper"
// )
// const (
// // versionsDBKey is the database key for update version information.
// versionsDBKey = "core:status/versions"
// // versionsDBKey is the database key for simple update version information.
// simpleVersionsDBKey = "core:status/simple-versions"
// // updateStatusDBKey is the database key for update status information.
// updateStatusDBKey = "core:status/updates"
// )
// // Versions holds update versions and status information.
// type Versions struct {
// record.Base
// sync.Mutex
// Core *info.Info
// Resources map[string]*updater.Resource
// Channel string
// Beta bool
// Staging bool
// }
// // SimpleVersions holds simplified update versions and status information.
// type SimpleVersions struct {
// record.Base
// sync.Mutex
// Build *info.Info
// Resources map[string]*SimplifiedResourceVersion
// Channel string
// }
// // SimplifiedResourceVersion holds version information about one resource.
// type SimplifiedResourceVersion struct {
// Version string
// }
// // UpdateStateExport is a wrapper to export the updates state.
// type UpdateStateExport struct {
// record.Base
// sync.Mutex
// *updater.UpdateState
// }
// // GetVersions returns the update versions and status information.
// // Resources must be locked when accessed.
// func GetVersions() *Versions {
// return &Versions{
// Core: info.GetInfo(),
// Resources: nil,
// Channel: initialReleaseChannel,
// Beta: initialReleaseChannel == helper.ReleaseChannelBeta,
// Staging: initialReleaseChannel == helper.ReleaseChannelStaging,
// }
// }
// // GetSimpleVersions returns the simplified update versions and status information.
// func GetSimpleVersions() *SimpleVersions {
// // Fill base info.
// v := &SimpleVersions{
// Build: info.GetInfo(),
// Resources: make(map[string]*SimplifiedResourceVersion),
// Channel: initialReleaseChannel,
// }
// // Iterate through all versions and add version info.
// // for id, resource := range registry.Export() {
// // func() {
// // resource.Lock()
// // defer resource.Unlock()
// // // Get current in-used or selected version.
// // var rv *updater.ResourceVersion
// // switch {
// // case resource.ActiveVersion != nil:
// // rv = resource.ActiveVersion
// // case resource.SelectedVersion != nil:
// // rv = resource.SelectedVersion
// // }
// // // Get information from resource.
// // if rv != nil {
// // v.Resources[id] = &SimplifiedResourceVersion{
// // Version: rv.VersionNumber,
// // }
// // }
// // }()
// // }
// return v
// }
// // GetStateExport gets the update state from the registry and returns it in an
// // exportable struct.
// func GetStateExport() *UpdateStateExport {
// // export := registry.GetState()
// return &UpdateStateExport{
// // UpdateState: &export.Updates,
// }
// }
// // LoadStateExport loads the exported update state from the database.
// func LoadStateExport() (*UpdateStateExport, error) {
// r, err := db.Get(updateStatusDBKey)
// if err != nil {
// return nil, err
// }
// // unwrap
// if r.IsWrapped() {
// // only allocate a new struct, if we need it
// newRecord := &UpdateStateExport{}
// err = record.Unwrap(r, newRecord)
// if err != nil {
// return nil, err
// }
// return newRecord, nil
// }
// // or adjust type
// newRecord, ok := r.(*UpdateStateExport)
// if !ok {
// return nil, fmt.Errorf("record not of type *UpdateStateExport, but %T", r)
// }
// return newRecord, nil
// }
// func initVersionExport() (err error) {
// if err := GetVersions().save(); err != nil {
// log.Warningf("updates: failed to export version information: %s", err)
// }
// if err := GetSimpleVersions().save(); err != nil {
// log.Warningf("updates: failed to export version information: %s", err)
// }
// // module.EventVersionsUpdated.AddCallback("export version status", export)
// return nil
// }
// func (v *Versions) save() error {
// if !v.KeyIsSet() {
// v.SetKey(versionsDBKey)
// }
// return db.Put(v)
// }
// func (v *SimpleVersions) save() error {
// if !v.KeyIsSet() {
// v.SetKey(simpleVersionsDBKey)
// }
// return db.Put(v)
// }
// func (s *UpdateStateExport) save() error {
// if !s.KeyIsSet() {
// s.SetKey(updateStatusDBKey)
// }
// return db.Put(s)
// }
// // export is an event hook.
// func export(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
// // Export versions.
// if err := GetVersions().save(); err != nil {
// return false, err
// }
// if err := GetSimpleVersions().save(); err != nil {
// return false, err
// }
// // Export udpate state.
// if err := GetStateExport().save(); err != nil {
// return false, err
// }
// return false, nil
// }
// // AddToDebugInfo adds the update system status to the given debug.Info.
// func AddToDebugInfo(di *debug.Info) {
// // Get resources from registry.
// // resources := registry.Export()
// // platformPrefix := helper.PlatformIdentifier("")
// // Collect data for debug info.
// var active, selected []string
// var activeCnt, totalCnt int
// // for id, r := range resources {
// // // Ignore resources for other platforms.
// // if !strings.HasPrefix(id, "all/") && !strings.HasPrefix(id, platformPrefix) {
// // continue
// // }
// // totalCnt++
// // if r.ActiveVersion != nil {
// // activeCnt++
// // active = append(active, fmt.Sprintf("%s: %s", id, r.ActiveVersion.VersionNumber))
// // }
// // if r.SelectedVersion != nil {
// // selected = append(selected, fmt.Sprintf("%s: %s", id, r.SelectedVersion.VersionNumber))
// // }
// // }
// sort.Strings(active)
// sort.Strings(selected)
// // Compile to one list.
// lines := make([]string, 0, len(active)+len(selected)+3)
// lines = append(lines, "Active:")
// lines = append(lines, active...)
// lines = append(lines, "")
// lines = append(lines, "Selected:")
// lines = append(lines, selected...)
// // Add section.
// di.AddSection(
// fmt.Sprintf("Updates: %s (%d/%d)", initialReleaseChannel, activeCnt, totalCnt),
// debug.UseCodeSection|debug.AddContentLineBreaks,
// lines...,
// )
// }

View file

@ -1,65 +0,0 @@
package updates
// GetPlatformFile returns the latest platform specific file identified by the given identifier.
// func GetPlatformFile(identifier string) (*updater.File, error) {
// identifier = helper.PlatformIdentifier(identifier)
// file, err := registry.GetFile(identifier)
// if err != nil {
// return nil, err
// }
// module.EventVersionsUpdated.Submit(struct{}{})
// return file, nil
// }
// GetFile returns the latest generic file identified by the given identifier.
// func GetFile(identifier string) (*updater.File, error) {
// identifier = path.Join("all", identifier)
// file, err := registry.GetFile(identifier)
// if err != nil {
// return nil, err
// }
// module.EventVersionsUpdated.Submit(struct{}{})
// return file, nil
// }
// GetPlatformVersion returns the selected platform specific version of the
// given identifier.
// The returned resource version may not be modified.
// func GetPlatformVersion(identifier string) (*updater.ResourceVersion, error) {
// identifier = helper.PlatformIdentifier(identifier)
// rv, err := registry.GetVersion(identifier)
// if err != nil {
// return nil, err
// }
// return rv, nil
// }
// GetVersion returns the selected generic version of the given identifier.
// The returned resource version may not be modified.
// func GetVersion(identifier string) (*updater.ResourceVersion, error) {
// identifier = path.Join("all", identifier)
// rv, err := registry.GetVersion(identifier)
// if err != nil {
// return nil, err
// }
// return rv, nil
// }
// GetVersionWithFullID returns the selected generic version of the given full identifier.
// The returned resource version may not be modified.
// func GetVersionWithFullID(identifier string) (*updater.ResourceVersion, error) {
// rv, err := registry.GetVersion(identifier)
// if err != nil {
// return nil, err
// }
// return rv, nil
// }

View file

@ -1,58 +0,0 @@
package helper
// import (
// "errors"
// "fmt"
// "os"
// "path/filepath"
// "runtime"
// "strings"
// "github.com/safing/portmaster/base/log"
// "github.com/safing/portmaster/base/updater"
// "github.com/safing/portmaster/service/updates/registry"
// )
// var pmElectronUpdate *registry.File
// const suidBitWarning = `Failed to set SUID permissions for chrome-sandbox. This is required for Linux kernel versions that do not have unprivileged user namespaces (CONFIG_USER_NS_UNPRIVILEGED) enabled. If you're running and up-to-date distribution kernel you can likely ignore this warning. If you encounter issue starting the user interface please either update your kernel or set the SUID bit (mode 0%0o) on %s`
// // EnsureChromeSandboxPermissions makes sure the chrome-sandbox distributed
// // by our app-electron package has the SUID bit set on systems that do not
// // allow unprivileged CLONE_NEWUSER (clone(3)).
// // On non-linux systems or systems that have kernel.unprivileged_userns_clone
// // set to 1 EnsureChromeSandboPermissions is a NO-OP.
// func EnsureChromeSandboxPermissions(reg *updater.ResourceRegistry) error {
// if runtime.GOOS != "linux" {
// return nil
// }
// if pmElectronUpdate != nil && !pmElectronUpdate.UpgradeAvailable() {
// return nil
// }
// identifier := PlatformIdentifier("app/portmaster-app.zip")
// var err error
// pmElectronUpdate, err = reg.GetFile(identifier)
// if err != nil {
// if errors.Is(err, updater.ErrNotAvailableLocally) {
// return nil
// }
// return fmt.Errorf("failed to get file: %w", err)
// }
// unpackedPath := strings.TrimSuffix(
// pmElectronUpdate.Path(),
// filepath.Ext(pmElectronUpdate.Path()),
// )
// sandboxFile := filepath.Join(unpackedPath, "chrome-sandbox")
// if err := os.Chmod(sandboxFile, 0o0755|os.ModeSetuid); err != nil {
// log.Errorf(suidBitWarning, 0o0755|os.ModeSetuid, sandboxFile)
// return fmt.Errorf("failed to chmod: %w", err)
// }
// log.Debugf("updates: fixed SUID permission for chrome-sandbox")
// return nil
// }

View file

@ -1,136 +0,0 @@
package helper
// import (
// "errors"
// "fmt"
// "io/fs"
// "os"
// "path/filepath"
// "github.com/safing/jess/filesig"
// "github.com/safing/portmaster/base/updater"
// )
// // Release Channel Configuration Keys.
// const (
// ReleaseChannelKey = "core/releaseChannel"
// ReleaseChannelJSONKey = "core.releaseChannel"
// )
// // Release Channels.
// const (
// ReleaseChannelStable = "stable"
// ReleaseChannelBeta = "beta"
// ReleaseChannelStaging = "staging"
// ReleaseChannelSupport = "support"
// )
// const jsonSuffix = ".json"
// // SetIndexes sets the update registry indexes and also configures the registry
// // to use pre-releases based on the channel.
// func SetIndexes(
// registry *updater.ResourceRegistry,
// releaseChannel string,
// deleteUnusedIndexes bool,
// autoDownload bool,
// autoDownloadIntel bool,
// ) (warning error) {
// usePreReleases := false
// // Be reminded that the order is important, as indexes added later will
// // override the current release from earlier indexes.
// // Reset indexes before adding them (again).
// registry.ResetIndexes()
// // Add the intel index first, in order to be able to override it with the
// // other indexes when needed.
// registry.AddIndex(updater.Index{
// Path: "all/intel/intel.json",
// AutoDownload: autoDownloadIntel,
// })
// // Always add the stable index as a base.
// registry.AddIndex(updater.Index{
// Path: ReleaseChannelStable + jsonSuffix,
// AutoDownload: autoDownload,
// })
// // Add beta index if in beta or staging channel.
// indexPath := ReleaseChannelBeta + jsonSuffix
// if releaseChannel == ReleaseChannelBeta ||
// releaseChannel == ReleaseChannelStaging ||
// (releaseChannel == "" && indexExists(registry, indexPath)) {
// registry.AddIndex(updater.Index{
// Path: indexPath,
// PreRelease: true,
// AutoDownload: autoDownload,
// })
// usePreReleases = true
// } else if deleteUnusedIndexes {
// err := deleteIndex(registry, indexPath)
// if err != nil {
// warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
// }
// }
// // Add staging index if in staging channel.
// indexPath = ReleaseChannelStaging + jsonSuffix
// if releaseChannel == ReleaseChannelStaging ||
// (releaseChannel == "" && indexExists(registry, indexPath)) {
// registry.AddIndex(updater.Index{
// Path: indexPath,
// PreRelease: true,
// AutoDownload: autoDownload,
// })
// usePreReleases = true
// } else if deleteUnusedIndexes {
// err := deleteIndex(registry, indexPath)
// if err != nil {
// warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
// }
// }
// // Add support index if in support channel.
// indexPath = ReleaseChannelSupport + jsonSuffix
// if releaseChannel == ReleaseChannelSupport ||
// (releaseChannel == "" && indexExists(registry, indexPath)) {
// registry.AddIndex(updater.Index{
// Path: indexPath,
// AutoDownload: autoDownload,
// })
// usePreReleases = true
// } else if deleteUnusedIndexes {
// err := deleteIndex(registry, indexPath)
// if err != nil {
// warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
// }
// }
// // Set pre-release usage.
// registry.SetUsePreReleases(usePreReleases)
// return warning
// }
// func indexExists(registry *updater.ResourceRegistry, indexPath string) bool {
// _, err := os.Stat(filepath.Join(registry.StorageDir().Path, indexPath))
// return err == nil
// }
// func deleteIndex(registry *updater.ResourceRegistry, indexPath string) error {
// // Remove index itself.
// err := os.Remove(filepath.Join(registry.StorageDir().Path, indexPath))
// if err != nil && !errors.Is(err, fs.ErrNotExist) {
// return err
// }
// // Remove any accompanying signature.
// err = os.Remove(filepath.Join(registry.StorageDir().Path, indexPath+filesig.Extension))
// if err != nil && !errors.Is(err, fs.ErrNotExist) {
// return err
// }
// return nil
// }

View file

@ -1,42 +0,0 @@
package helper
// import (
// "github.com/safing/jess"
// "github.com/safing/portmaster/base/updater"
// )
// var (
// // VerificationConfig holds the complete verification configuration for the registry.
// VerificationConfig = map[string]*updater.VerificationOptions{
// "": { // Default.
// TrustStore: BinarySigningTrustStore,
// DownloadPolicy: updater.SignaturePolicyRequire,
// DiskLoadPolicy: updater.SignaturePolicyWarn,
// },
// "all/intel/": nil, // Disable until IntelHub supports signing.
// }
// // BinarySigningKeys holds the signing keys in text format.
// BinarySigningKeys = []string{
// // Safing Code Signing Key #1
// "recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD",
// // Safing Code Signing Key #2
// "recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3",
// }
// // BinarySigningTrustStore is an in-memory trust store with the signing keys.
// BinarySigningTrustStore = jess.NewMemTrustStore()
// )
// func init() {
// for _, signingKey := range BinarySigningKeys {
// rcpt, err := jess.RecipientFromTextFormat(signingKey)
// if err != nil {
// panic(err)
// }
// err = BinarySigningTrustStore.StoreSignet(rcpt)
// if err != nil {
// panic(err)
// }
// }
// }

View file

@ -1,95 +0,0 @@
package helper
// import (
// "fmt"
// "runtime"
// "github.com/tevino/abool"
// )
// const onWindows = runtime.GOOS == "windows"
// var intelOnly = abool.New()
// // IntelOnly specifies that only intel data is mandatory.
// func IntelOnly() {
// intelOnly.Set()
// }
// // PlatformIdentifier converts identifier for the current platform.
// func PlatformIdentifier(identifier string) string {
// // From https://golang.org/pkg/runtime/#GOARCH
// // GOOS is the running program's operating system target: one of darwin, freebsd, linux, and so on.
// // GOARCH is the running program's architecture target: one of 386, amd64, arm, s390x, and so on.
// return fmt.Sprintf("%s_%s/%s", runtime.GOOS, runtime.GOARCH, identifier)
// }
// // MandatoryUpdates returns mandatory updates that should be loaded on install
// // or reset.
// func MandatoryUpdates() (identifiers []string) {
// // Intel
// identifiers = append(
// identifiers,
// // Filter lists data
// "all/intel/lists/index.dsd",
// "all/intel/lists/base.dsdl",
// "all/intel/lists/intermediate.dsdl",
// "all/intel/lists/urgent.dsdl",
// // Geo IP data
// "all/intel/geoip/geoipv4.mmdb.gz",
// "all/intel/geoip/geoipv6.mmdb.gz",
// )
// // Stop here if we only want intel data.
// if intelOnly.IsSet() {
// return identifiers
// }
// // Binaries
// if onWindows {
// identifiers = append(
// identifiers,
// PlatformIdentifier("core/portmaster-core.exe"),
// PlatformIdentifier("kext/portmaster-kext.sys"),
// PlatformIdentifier("kext/portmaster-kext.pdb"),
// PlatformIdentifier("start/portmaster-start.exe"),
// PlatformIdentifier("notifier/portmaster-notifier.exe"),
// PlatformIdentifier("notifier/portmaster-wintoast.dll"),
// PlatformIdentifier("app2/portmaster-app.zip"),
// )
// } else {
// identifiers = append(
// identifiers,
// PlatformIdentifier("core/portmaster-core"),
// PlatformIdentifier("start/portmaster-start"),
// PlatformIdentifier("notifier/portmaster-notifier"),
// PlatformIdentifier("app2/portmaster-app"),
// )
// }
// // Components, Assets and Data
// identifiers = append(
// identifiers,
// // User interface components
// PlatformIdentifier("app/portmaster-app.zip"),
// "all/ui/modules/portmaster.zip",
// "all/ui/modules/assets.zip",
// )
// return identifiers
// }
// // AutoUnpackUpdates returns assets that need unpacking.
// func AutoUnpackUpdates() []string {
// if intelOnly.IsSet() {
// return []string{}
// }
// return []string{
// PlatformIdentifier("app/portmaster-app.zip"),
// PlatformIdentifier("app2/portmaster-app.zip"),
// }
// }

View file

@ -1,6 +1,7 @@
package updates
import (
"context"
"fmt"
"io"
"net/http"
@ -18,13 +19,14 @@ type UpdateIndex struct {
IndexURLs []string
IndexFile string
AutoApply bool
NeedsRestart bool
}
func (ui *UpdateIndex) DownloadIndexFile(client *http.Client) (err error) {
func (ui *UpdateIndex) DownloadIndexFile(ctx context.Context, client *http.Client) (err error) {
// Make sure dir exists
_ = os.MkdirAll(ui.DownloadDirectory, defaultDirMode)
for _, url := range ui.IndexURLs {
err = ui.downloadIndexFileFromURL(client, url)
err = ui.downloadIndexFileFromURL(ctx, client, url)
if err != nil {
log.Warningf("updates: failed while downloading index file %s", err)
continue
@ -36,9 +38,18 @@ func (ui *UpdateIndex) DownloadIndexFile(client *http.Client) (err error) {
return
}
func (ui *UpdateIndex) downloadIndexFileFromURL(client *http.Client, url string) error {
func (ui *UpdateIndex) downloadIndexFileFromURL(ctx context.Context, client *http.Client, url string) error {
// Request the index file
resp, err := client.Get(url)
req, err := http.NewRequestWithContext(ctx, "GET", url, http.NoBody)
if err != nil {
return fmt.Errorf("failed to create GET request to %s: %w", url, err)
}
if UserAgent != "" {
req.Header.Set("User-Agent", UserAgent)
}
// Perform request
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed GET request to %s: %w", url, err)
}

View file

@ -1,70 +1 @@
package updates
import (
"fmt"
"runtime"
"time"
"github.com/safing/portmaster/base/database"
)
const (
onWindows = runtime.GOOS == "windows"
enableSoftwareUpdatesKey = "core/automaticUpdates"
enableIntelUpdatesKey = "core/automaticIntelUpdates"
// VersionUpdateEvent is emitted every time a new
// version of a monitored resource is selected.
// During module initialization VersionUpdateEvent
// is also emitted.
VersionUpdateEvent = "active version update"
// ResourceUpdateEvent is emitted every time the
// updater successfully performed a resource update.
// ResourceUpdateEvent is emitted even if no new
// versions are available. Subscribers are expected
// to check if new versions of their resources are
// available by checking File.UpgradeAvailable().
ResourceUpdateEvent = "resource update"
)
var (
userAgentFromFlag string
updateServerFromFlag string
db = database.NewInterface(&database.Options{
Local: true,
Internal: true,
})
// UserAgent is an HTTP User-Agent that is used to add
// more context to requests made by the registry when
// fetching resources from the update server.
UserAgent = fmt.Sprintf("Portmaster (%s %s)", runtime.GOOS, runtime.GOARCH)
)
const (
updateTaskRepeatDuration = 1 * time.Hour
)
func stop() error {
// if registry != nil {
// err := registry.Cleanup()
// if err != nil {
// log.Warningf("updates: failed to clean up registry: %s", err)
// }
// }
return nil
}
// RootPath returns the root path used for storing updates.
func RootPath() string {
// if !module.Online() {
// return ""
// }
// return registry.StorageDir().Path
return ""
}

View file

@ -1,10 +1,12 @@
package updates
import (
"context"
"errors"
"fmt"
"net/http"
"os"
"runtime"
"time"
semver "github.com/hashicorp/go-version"
@ -15,7 +17,29 @@ import (
"github.com/safing/portmaster/service/mgr"
)
const updateAvailableNotificationID = "updates:update-available"
const (
updateTaskRepeatDuration = 1 * time.Hour
updateAvailableNotificationID = "updates:update-available"
// VersionUpdateEvent is emitted every time a new
// version of a monitored resource is selected.
// During module initialization VersionUpdateEvent
// is also emitted.
VersionUpdateEvent = "active version update"
// ResourceUpdateEvent is emitted every time the
// updater successfully performed a resource update.
// ResourceUpdateEvent is emitted even if no new
// versions are available. Subscribers are expected
// to check if new versions of their resources are
// available by checking File.UpgradeAvailable().
ResourceUpdateEvent = "resource update"
)
// UserAgent is an HTTP User-Agent that is used to add
// more context to requests made by the registry when
// fetching resources from the update server.
var UserAgent = fmt.Sprintf("Portmaster (%s %s)", runtime.GOOS, runtime.GOARCH)
type File struct {
id string
@ -75,7 +99,7 @@ func New(instance instance, name string, index UpdateIndex) (*Updates, error) {
// Events
module.updateCheckWorkerMgr = m.NewWorkerMgr("update checker", module.checkForUpdates, nil)
module.updateCheckWorkerMgr.Repeat(1 * time.Hour)
module.updateCheckWorkerMgr.Repeat(updateTaskRepeatDuration)
module.upgraderWorkerMgr = m.NewWorkerMgr("upgrader", module.applyUpdates, nil)
var err error
@ -86,44 +110,10 @@ func New(instance instance, name string, index UpdateIndex) (*Updates, error) {
// Add bundle artifacts to registry.
module.processBundle(module.bundle)
err = module.registerEndpoints()
if err != nil {
log.Errorf("failed to register endpoints: %s", err)
}
return module, nil
}
func (u *Updates) registerEndpoints() error {
if err := api.RegisterEndpoint(api.Endpoint{
Name: "Check for update",
Description: "Trigger update check",
Path: "updates/check",
Read: api.PermitAnyone,
ActionFunc: func(ar *api.Request) (msg string, err error) {
u.updateCheckWorkerMgr.Go()
return "Check for updates triggered", nil
},
}); err != nil {
return err
}
if err := api.RegisterEndpoint(api.Endpoint{
Name: "Apply update",
Description: "Triggers update",
Path: "updates/apply",
Read: api.PermitAnyone,
ActionFunc: func(ar *api.Request) (msg string, err error) {
u.upgraderWorkerMgr.Go()
return "Apply updates triggered", nil
},
}); err != nil {
return err
}
return nil
}
func (reg *Updates) processBundle(bundle *Bundle) {
for _, artifact := range bundle.Artifacts {
artifactPath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, artifact.Filename)
@ -131,9 +121,9 @@ func (reg *Updates) processBundle(bundle *Bundle) {
}
}
func (u *Updates) checkForUpdates(_ *mgr.WorkerCtx) error {
func (u *Updates) checkForUpdates(wc *mgr.WorkerCtx) error {
httpClient := http.Client{}
err := u.updateIndex.DownloadIndexFile(&httpClient)
err := u.updateIndex.DownloadIndexFile(wc.Ctx(), &httpClient)
if err != nil {
return fmt.Errorf("failed to download index file: %s", err)
}
@ -155,16 +145,23 @@ func (u *Updates) checkForUpdates(_ *mgr.WorkerCtx) error {
}
log.Infof("updates: check complete: downloading new version: %s %s", u.updateBundle.Name, u.updateBundle.Version)
err = u.downloadUpdates(&httpClient)
err = u.downloadUpdates(wc.Ctx(), &httpClient)
if err != nil {
log.Errorf("updates: failed to download bundle: %s", err)
} else {
notifications.NotifyPrompt(updateAvailableNotificationID, "Update available", "Apply update and restart?", notifications.Action{
ID: "apply",
Text: "Apply",
Type: notifications.ActionTypeInjectEvent,
Payload: "apply-updates",
})
if u.updateIndex.AutoApply {
u.upgraderWorkerMgr.Go()
} else {
notifications.NotifyPrompt(updateAvailableNotificationID, "Update available", "Apply update and restart?", notifications.Action{
ID: "apply",
Text: "Apply",
Type: notifications.ActionTypeWebhook,
Payload: notifications.ActionTypeWebhookPayload{
Method: "POST",
URL: "updates/apply",
},
})
}
}
return nil
}
@ -183,7 +180,7 @@ func (u *Updates) checkVersionIncrement() (bool, error) {
return downloadVersion.GreaterThan(currentVersion), nil
}
func (u *Updates) downloadUpdates(client *http.Client) error {
func (u *Updates) downloadUpdates(ctx context.Context, client *http.Client) error {
if u.updateBundle == nil {
// checkForUpdates needs to be called before this.
return fmt.Errorf("no valid update bundle found")
@ -193,7 +190,7 @@ func (u *Updates) downloadUpdates(client *http.Client) error {
if err != nil {
log.Warningf("updates: error while coping file from current to update: %s", err)
}
u.updateBundle.DownloadAndVerify(client, u.updateIndex.DownloadDirectory)
u.updateBundle.DownloadAndVerify(ctx, client, u.updateIndex.DownloadDirectory)
return nil
}
@ -208,18 +205,23 @@ func (u *Updates) applyUpdates(_ *mgr.WorkerCtx) error {
return fmt.Errorf("there is no new version to apply")
}
// Verify files of the downloaded files.
err = u.updateBundle.Verify(u.updateIndex.DownloadDirectory)
if err != nil {
return fmt.Errorf("failed to apply update: %s", err)
return fmt.Errorf("failed to verify downloaded files: %s", err)
}
// New version is downloaded and verified. Start the update process
log.Infof("update: starting update: %s %s -> %s", u.bundle.Name, u.bundle.Version, u.updateBundle.Version)
err = switchFolders(u.updateIndex, *u.updateBundle)
if err != nil {
// TODO(vladimir): Send notification to UI
log.Errorf("updates: failed to apply updates: %s", err)
} else {
// TODO(vladimir): Prompt user to restart?
u.instance.Restart()
if u.updateIndex.NeedsRestart {
u.instance.Restart()
}
}
return nil
}
@ -274,7 +276,7 @@ func (u *Updates) GetFile(id string) (*File, error) {
// Stop stops the module.
func (u *Updates) Stop() error {
return stop()
return nil
}
type instance interface {

View file

@ -1,176 +0,0 @@
package updates
import (
"sync/atomic"
"time"
)
const (
updateFailed = "updates:failed"
updateSuccess = "updates:success"
updateSuccessPending = "updates:success-pending"
updateSuccessDownloaded = "updates:success-downloaded"
failedUpdateNotifyDurationThreshold = 24 * time.Hour
failedUpdateNotifyCountThreshold = 3
)
var updateFailedCnt = new(atomic.Int32)
func (u *Updates) notificationsEnabled() bool {
return u.instance.Notifications() != nil
}
// func notifyUpdateSuccess(force bool) {
// if !module.notificationsEnabled() {
// return
// }
// updateFailedCnt.Store(0)
// module.states.Clear()
// updateState := registry.GetState().Updates
// flavor := updateSuccess
// switch {
// case len(updateState.PendingDownload) > 0:
// // Show notification if there are pending downloads.
// flavor = updateSuccessPending
// case updateState.LastDownloadAt != nil &&
// time.Since(*updateState.LastDownloadAt) < 5*time.Second:
// // Show notification if we downloaded something within the last minute.
// flavor = updateSuccessDownloaded
// case force:
// // Always show notification if update was manually triggered.
// default:
// // Otherwise, the update was uneventful. Do not show notification.
// return
// }
// switch flavor {
// case updateSuccess:
// notifications.Notify(&notifications.Notification{
// EventID: updateSuccess,
// Type: notifications.Info,
// Title: "Portmaster Is Up-To-Date",
// Message: "Portmaster successfully checked for updates. Everything is up to date.\n\n" + getUpdatingInfoMsg(),
// Expires: time.Now().Add(1 * time.Minute).Unix(),
// AvailableActions: []*notifications.Action{
// {
// ID: "ack",
// Text: "OK",
// },
// },
// })
// case updateSuccessPending:
// msg := fmt.Sprintf(
// `%d updates are available for download:
// - %s
// Press "Download Now" to download and automatically apply all pending updates. You will be notified of important updates that need restarting.`,
// len(updateState.PendingDownload),
// strings.Join(updateState.PendingDownload, "\n- "),
// )
// notifications.Notify(&notifications.Notification{
// EventID: updateSuccess,
// Type: notifications.Info,
// Title: fmt.Sprintf("%d Updates Available", len(updateState.PendingDownload)),
// Message: msg,
// AvailableActions: []*notifications.Action{
// {
// ID: "ack",
// Text: "OK",
// },
// {
// ID: "download",
// Text: "Download Now",
// Type: notifications.ActionTypeWebhook,
// Payload: &notifications.ActionTypeWebhookPayload{
// URL: apiPathCheckForUpdates + "?download",
// ResultAction: "display",
// },
// },
// },
// })
// case updateSuccessDownloaded:
// msg := fmt.Sprintf(
// `%d updates were downloaded and applied:
// - %s
// %s
// `,
// len(updateState.LastDownload),
// strings.Join(updateState.LastDownload, "\n- "),
// getUpdatingInfoMsg(),
// )
// notifications.Notify(&notifications.Notification{
// EventID: updateSuccess,
// Type: notifications.Info,
// Title: fmt.Sprintf("%d Updates Applied", len(updateState.LastDownload)),
// Message: msg,
// Expires: time.Now().Add(1 * time.Minute).Unix(),
// AvailableActions: []*notifications.Action{
// {
// ID: "ack",
// Text: "OK",
// },
// },
// })
// }
// }
func getUpdatingInfoMsg() string {
switch {
case enableSoftwareUpdates() && enableIntelUpdates():
return "You will be notified of important updates that need restarting."
case enableIntelUpdates():
return "Automatic software updates are disabled, but you will be notified when a new software update is ready to be downloaded and applied."
default:
return "Automatic software updates are disabled. Please check for updates regularly yourself."
}
}
// func notifyUpdateCheckFailed(force bool, err error) {
// if !module.notificationsEnabled() {
// return
// }
// failedCnt := updateFailedCnt.Add(1)
// lastSuccess := registry.GetState().Updates.LastSuccessAt
// switch {
// case force:
// // Always show notification if update was manually triggered.
// case failedCnt < failedUpdateNotifyCountThreshold:
// // Not failed often enough for notification.
// return
// case lastSuccess == nil:
// // No recorded successful update.
// case time.Now().Add(-failedUpdateNotifyDurationThreshold).Before(*lastSuccess):
// // Failed too recently for notification.
// return
// }
// notifications.NotifyWarn(
// updateFailed,
// "Update Check Failed",
// fmt.Sprintf(
// "Portmaster failed to check for updates. This might be a temporary issue of your device, your network or the update servers. The Portmaster will automatically try again later. The error was: %s",
// err,
// ),
// notifications.Action{
// Text: "Try Again Now",
// Type: notifications.ActionTypeWebhook,
// Payload: &notifications.ActionTypeWebhookPayload{
// URL: apiPathCheckForUpdates,
// ResultAction: "display",
// },
// },
// ).SyncWithState(module.states)
// }

View file

@ -1,8 +0,0 @@
//go:build !linux
// +build !linux
package updates
func upgradeSystemIntegration() error {
return nil
}

View file

@ -1,201 +0,0 @@
package updates
// import (
// "crypto/sha256"
// _ "embed"
// "encoding/hex"
// "errors"
// "fmt"
// "io/fs"
// "os"
// "path/filepath"
// "github.com/tevino/abool"
// "golang.org/x/exp/slices"
// "github.com/safing/portmaster/base/dataroot"
// "github.com/safing/portmaster/base/log"
// )
// var (
// portmasterCoreServiceFilePath = "portmaster.service"
// portmasterNotifierServiceFilePath = "portmaster_notifier.desktop"
// backupExtension = ".backup"
// //go:embed assets/portmaster.service
// currentPortmasterCoreServiceFile []byte
// checkedSystemIntegration = abool.New()
// // ErrRequiresManualUpgrade is returned when a system integration file requires a manual upgrade.
// ErrRequiresManualUpgrade = errors.New("requires a manual upgrade")
// )
// func upgradeSystemIntegration() {
// // Check if we already checked the system integration.
// if !checkedSystemIntegration.SetToIf(false, true) {
// return
// }
// // Upgrade portmaster core systemd service.
// err := upgradeSystemIntegrationFile(
// "portmaster core systemd service",
// filepath.Join(dataroot.Root().Path, portmasterCoreServiceFilePath),
// 0o0600,
// currentPortmasterCoreServiceFile,
// []string{
// "bc26dd37e6953af018ad3676ee77570070e075f2b9f5df6fa59d65651a481468", // Commit 19c76c7 on 2022-01-25
// "cc0cb49324dfe11577e8c066dd95cc03d745b50b2153f32f74ca35234c3e8cb5", // Commit ef479e5 on 2022-01-24
// "d08a3b5f3aee351f8e120e6e2e0a089964b94c9e9d0a9e5fa822e60880e315fd", // Commit b64735e on 2021-12-07
// },
// )
// if err != nil {
// log.Warningf("updates: %s", err)
// return
// }
// // Upgrade portmaster notifier systemd user service.
// // Permissions only!
// err = upgradeSystemIntegrationFile(
// "portmaster notifier systemd user service",
// filepath.Join(dataroot.Root().Path, portmasterNotifierServiceFilePath),
// 0o0644,
// nil, // Do not update contents.
// nil, // Do not update contents.
// )
// if err != nil {
// log.Warningf("updates: %s", err)
// return
// }
// }
// // upgradeSystemIntegrationFile upgrades the file contents and permissions.
// // System integration files are not necessarily present and may also be
// // edited by third parties, such as the OS itself or other installers.
// // The supplied hashes must be sha256 hex-encoded.
// func upgradeSystemIntegrationFile(
// name string,
// filePath string,
// fileMode fs.FileMode,
// fileData []byte,
// permittedUpgradeHashes []string,
// ) error {
// // Upgrade file contents.
// if len(fileData) > 0 {
// if err := upgradeSystemIntegrationFileContents(name, filePath, fileData, permittedUpgradeHashes); err != nil {
// return err
// }
// }
// // Upgrade file permissions.
// if fileMode != 0 {
// if err := upgradeSystemIntegrationFilePermissions(name, filePath, fileMode); err != nil {
// return err
// }
// }
// return nil
// }
// // upgradeSystemIntegrationFileContents upgrades the file contents.
// // System integration files are not necessarily present and may also be
// // edited by third parties, such as the OS itself or other installers.
// // The supplied hashes must be sha256 hex-encoded.
// func upgradeSystemIntegrationFileContents(
// name string,
// filePath string,
// fileData []byte,
// permittedUpgradeHashes []string,
// ) error {
// // Read existing file.
// existingFileData, err := os.ReadFile(filePath)
// if err != nil {
// if errors.Is(err, os.ErrNotExist) {
// return nil
// }
// return fmt.Errorf("failed to read %s at %s: %w", name, filePath, err)
// }
// // Check if file is already the current version.
// existingSum := sha256.Sum256(existingFileData)
// existingHexSum := hex.EncodeToString(existingSum[:])
// currentSum := sha256.Sum256(fileData)
// currentHexSum := hex.EncodeToString(currentSum[:])
// if existingHexSum == currentHexSum {
// log.Debugf("updates: %s at %s is up to date", name, filePath)
// return nil
// }
// // Check if we are allowed to upgrade from the existing file.
// if !slices.Contains[[]string, string](permittedUpgradeHashes, existingHexSum) {
// return fmt.Errorf("%s at %s (sha256:%s) %w, as it is not a previously published version and cannot be automatically upgraded - try installing again", name, filePath, existingHexSum, ErrRequiresManualUpgrade)
// }
// // Start with upgrade!
// // Make backup of existing file.
// err = CopyFile(filePath, filePath+backupExtension)
// if err != nil {
// return fmt.Errorf(
// "failed to create backup of %s from %s to %s: %w",
// name,
// filePath,
// filePath+backupExtension,
// err,
// )
// }
// // Open destination file for writing.
// // atomicDstFile, err := renameio.TempFile(registry.TmpDir().Path, filePath)
// // if err != nil {
// // return fmt.Errorf("failed to create tmp file to update %s at %s: %w", name, filePath, err)
// // }
// // defer atomicDstFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway
// // // Write file.
// // _, err = io.Copy(atomicDstFile, bytes.NewReader(fileData))
// // if err != nil {
// // return err
// // }
// // // Finalize file.
// // err = atomicDstFile.CloseAtomicallyReplace()
// // if err != nil {
// // return fmt.Errorf("failed to finalize update of %s at %s: %w", name, filePath, err)
// // }
// log.Warningf("updates: %s at %s was upgraded to %s - a reboot may be required", name, filePath, currentHexSum)
// return nil
// }
// // upgradeSystemIntegrationFilePermissions upgrades the file permissions.
// // System integration files are not necessarily present and may also be
// // edited by third parties, such as the OS itself or other installers.
// func upgradeSystemIntegrationFilePermissions(
// name string,
// filePath string,
// fileMode fs.FileMode,
// ) error {
// // Get current file permissions.
// stat, err := os.Stat(filePath)
// if err != nil {
// if errors.Is(err, os.ErrNotExist) {
// return nil
// }
// return fmt.Errorf("failed to read %s file metadata at %s: %w", name, filePath, err)
// }
// // If permissions are as expected, do nothing.
// if stat.Mode().Perm() == fileMode {
// return nil
// }
// // Otherwise, set correct permissions.
// err = os.Chmod(filePath, fileMode)
// if err != nil {
// return fmt.Errorf("failed to update %s file permissions at %s: %w", name, filePath, err)
// }
// log.Warningf("updates: %s file permissions at %s updated to %v", name, filePath, fileMode)
// return nil
// }

View file

@ -1,49 +0,0 @@
package updates
// import (
// "github.com/safing/portmaster/base/database/record"
// "github.com/safing/portmaster/base/runtime"
// "github.com/safing/portmaster/base/updater"
// )
// var pushRegistryStatusUpdate runtime.PushFunc
// // RegistryStateExport is a wrapper to export the registry state.
// type RegistryStateExport struct {
// record.Base
// *updater.RegistryState
// }
// func exportRegistryState(s *updater.RegistryState) *RegistryStateExport {
// // if s == nil {
// // state := registry.GetState()
// // s = &state
// // }
// export := &RegistryStateExport{
// RegistryState: s,
// }
// export.CreateMeta()
// export.SetKey("runtime:core/updates/state")
// return export
// }
// func pushRegistryState(s *updater.RegistryState) {
// export := exportRegistryState(s)
// pushRegistryStatusUpdate(export)
// }
// func registerRegistryStateProvider() (err error) {
// registryStateProvider := runtime.SimpleValueGetterFunc(func(_ string) ([]record.Record, error) {
// return []record.Record{exportRegistryState(nil)}, nil
// })
// pushRegistryStatusUpdate, err = runtime.Register("core/updates/state", registryStateProvider)
// if err != nil {
// return err
// }
// return nil
// }

View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/safing/portmaster/base/log"
@ -29,6 +30,7 @@ func switchFolders(updateIndex UpdateIndex, newBundle Bundle) error {
}
// Move current version files into purge folder.
log.Debugf("updates: removing the old version")
for _, file := range files {
currentFilepath := filepath.Join(updateIndex.Directory, file.Name())
purgePath := filepath.Join(updateIndex.PurgeDirectory, file.Name())
@ -39,6 +41,7 @@ func switchFolders(updateIndex UpdateIndex, newBundle Bundle) error {
}
// Move the new index file
log.Debugf("updates: installing the new version")
indexFile := filepath.Join(updateIndex.DownloadDirectory, updateIndex.IndexFile)
newIndexFile := filepath.Join(updateIndex.Directory, updateIndex.IndexFile)
err = os.Rename(indexFile, newIndexFile)
@ -52,9 +55,27 @@ func switchFolders(updateIndex UpdateIndex, newBundle Bundle) error {
toFilepath := filepath.Join(updateIndex.Directory, artifact.Filename)
err = os.Rename(fromFilepath, toFilepath)
if err != nil {
return fmt.Errorf("failed to move file %s: %w", fromFilepath, err)
log.Errorf("failed to move file %s: %s", fromFilepath, err)
} else {
log.Debugf("updates: %s moved", artifact.Filename)
}
// Special case for linux.
// When installed the portmaster ui path is `/usr/bin/portmaster`. During update the ui will be placed in `/usr/lib/portmaster/portmaster`
// After an update the original binary should be deleted and replaced by symlink
// `/usr/bin/portmaster` -> `/usr/lib/portmaster/portmaster`
if runtime.GOOS == "linux" && artifact.Filename == "portmaster" && artifact.Platform == currentPlatform {
err = makeSymlinkForUI(updateIndex.Directory)
if err != nil {
log.Errorf("failed to create symlink for the ui: %s", err)
} else {
log.Infof("ui symlink successfully created")
}
}
}
log.Debugf("updates: update complete")
return nil
}
@ -76,3 +97,11 @@ func deleteUnfinishedDownloads(rootDir string) error {
}
return nil
}
func makeSymlinkForUI(directory string) error {
err := os.Symlink(filepath.Join(directory, "portmaster"), "/usr/bin/portmaster")
if err != nil {
return fmt.Errorf("failed to create symlink: %w", err)
}
return nil
}

View file

@ -1,403 +0,0 @@
package updates
// import (
// "context"
// "fmt"
// "os"
// "os/exec"
// "path/filepath"
// "regexp"
// "strings"
// "time"
// processInfo "github.com/shirou/gopsutil/process"
// "github.com/tevino/abool"
// "github.com/safing/portmaster/base/dataroot"
// "github.com/safing/portmaster/base/info"
// "github.com/safing/portmaster/base/log"
// "github.com/safing/portmaster/base/notifications"
// "github.com/safing/portmaster/base/rng"
// "github.com/safing/portmaster/base/updater"
// "github.com/safing/portmaster/service/mgr"
// )
// const (
// upgradedSuffix = "-upgraded"
// exeExt = ".exe"
// )
// var (
// upgraderActive = abool.NewBool(false)
// pmCtrlUpdate *updater.File
// pmCoreUpdate *updater.File
// spnHubUpdate *updater.File
// rawVersionRegex = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+b?\*?$`)
// )
// func initUpgrader() error {
// // module.EventResourcesUpdated.AddCallback("run upgrades", upgrader)
// return nil
// }
// func upgrader(m *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
// // Lock runs, but discard additional runs.
// if !upgraderActive.SetToIf(false, true) {
// return false, nil
// }
// defer upgraderActive.SetTo(false)
// // Upgrade portmaster-start.
// err = upgradePortmasterStart()
// if err != nil {
// log.Warningf("updates: failed to upgrade portmaster-start: %s", err)
// }
// // Upgrade based on binary.
// binBaseName := strings.Split(filepath.Base(os.Args[0]), "_")[0]
// switch binBaseName {
// case "portmaster-core":
// // Notify about upgrade.
// if err := upgradeCoreNotify(); err != nil {
// log.Warningf("updates: failed to notify about core upgrade: %s", err)
// }
// // Fix chrome sandbox permissions.
// // if err := helper.EnsureChromeSandboxPermissions(registry); err != nil {
// // log.Warningf("updates: failed to handle electron upgrade: %s", err)
// // }
// // Upgrade system integration.
// upgradeSystemIntegration()
// case "spn-hub":
// // Trigger upgrade procedure.
// if err := upgradeHub(); err != nil {
// log.Warningf("updates: failed to initiate hub upgrade: %s", err)
// }
// }
// return false, nil
// }
// func upgradeCoreNotify() error {
// if pmCoreUpdate != nil && !pmCoreUpdate.UpgradeAvailable() {
// return nil
// }
// // make identifier
// identifier := "core/portmaster-core" // identifier, use forward slash!
// if onWindows {
// identifier += exeExt
// }
// // get newest portmaster-core
// // newFile, err := GetPlatformFile(identifier)
// // if err != nil {
// // return err
// // }
// // pmCoreUpdate = newFile
// // check for new version
// if info.VersionNumber() != pmCoreUpdate.Version() {
// n := notifications.Notify(&notifications.Notification{
// EventID: "updates:core-update-available",
// Type: notifications.Info,
// Title: fmt.Sprintf(
// "Portmaster Update v%s Is Ready!",
// pmCoreUpdate.Version(),
// ),
// Category: "Core",
// Message: fmt.Sprintf(
// `A new Portmaster version is ready to go! Restart the Portmaster to upgrade to %s.`,
// pmCoreUpdate.Version(),
// ),
// ShowOnSystem: true,
// AvailableActions: []*notifications.Action{
// // TODO: Use special UI action in order to reload UI on restart.
// {
// ID: "restart",
// Text: "Restart",
// },
// {
// ID: "later",
// Text: "Not now",
// },
// },
// })
// n.SetActionFunction(upgradeCoreNotifyActionHandler)
// log.Debugf("updates: new portmaster version available, sending notification to user")
// }
// return nil
// }
// func upgradeCoreNotifyActionHandler(_ context.Context, n *notifications.Notification) error {
// switch n.SelectedActionID {
// case "restart":
// log.Infof("updates: user triggered restart via core update notification")
// RestartNow()
// case "later":
// n.Delete()
// }
// return nil
// }
// func upgradeHub() error {
// if spnHubUpdate != nil && !spnHubUpdate.UpgradeAvailable() {
// return nil
// }
// // Make identifier for getting file from updater.
// identifier := "hub/spn-hub" // identifier, use forward slash!
// if onWindows {
// identifier += exeExt
// }
// // Get newest spn-hub file.
// // newFile, err := GetPlatformFile(identifier)
// // if err != nil {
// // return err
// // }
// // spnHubUpdate = newFile
// // Check if the new version is different.
// if info.GetInfo().Version != spnHubUpdate.Version() {
// // Get random delay with up to three hours.
// delayMinutes, err := rng.Number(3 * 60)
// if err != nil {
// return err
// }
// // Delay restart for at least one hour for preparations.
// DelayedRestart(time.Duration(delayMinutes+60) * time.Minute)
// // Increase update checks in order to detect aborts better.
// // if !disableTaskSchedule {
// // module.updateBinaryWorkerMgr.Repeat(10 * time.Minute)
// // }
// } else {
// AbortRestart()
// // Set update task schedule back to normal.
// // if !disableTaskSchedule {
// // module.updateBinaryWorkerMgr.Repeat(updateTaskRepeatDuration)
// // }
// }
// return nil
// }
// func upgradePortmasterStart() error {
// filename := "portmaster-start"
// if onWindows {
// filename += exeExt
// }
// // check if we can upgrade
// if pmCtrlUpdate == nil || pmCtrlUpdate.UpgradeAvailable() {
// // get newest portmaster-start
// // newFile, err := GetPlatformFile("start/" + filename) // identifier, use forward slash!
// // if err != nil {
// // return err
// // }
// // pmCtrlUpdate = newFile
// } else {
// return nil
// }
// // update portmaster-start in data root
// rootPmStartPath := filepath.Join(dataroot.Root().Path, filename)
// err := upgradeBinary(rootPmStartPath, pmCtrlUpdate)
// if err != nil {
// return err
// }
// return nil
// }
// func warnOnIncorrectParentPath() {
// expectedFileName := "portmaster-start"
// if onWindows {
// expectedFileName += exeExt
// }
// // upgrade parent process, if it's portmaster-start
// parent, err := processInfo.NewProcess(int32(os.Getppid()))
// if err != nil {
// log.Tracef("could not get parent process: %s", err)
// return
// }
// parentName, err := parent.Name()
// if err != nil {
// log.Tracef("could not get parent process name: %s", err)
// return
// }
// if parentName != expectedFileName {
// // Only warn about this if not in dev mode.
// if !devMode() {
// log.Warningf("updates: parent process does not seem to be portmaster-start, name is %s", parentName)
// }
// // TODO(ppacher): once we released a new installer and folks had time
// // to update we should send a module warning/hint to the
// // UI notifying the user that he's still using portmaster-control.
// return
// }
// // parentPath, err := parent.Exe()
// // if err != nil {
// // log.Tracef("could not get parent process path: %s", err)
// // return
// // }
// // absPath, err := filepath.Abs(parentPath)
// // if err != nil {
// // log.Tracef("could not get absolut parent process path: %s", err)
// // return
// // }
// // root := filepath.Dir(registry.StorageDir().Path)
// // if !strings.HasPrefix(absPath, root) {
// // log.Warningf("detected unexpected path %s for portmaster-start", absPath)
// // notifications.NotifyWarn(
// // "updates:unsupported-parent",
// // "Unsupported Launcher",
// // fmt.Sprintf(
// // "The Portmaster has been launched by an unexpected %s binary at %s. Please configure your system to use the binary at %s as this version will be kept up to date automatically.",
// // expectedFileName,
// // absPath,
// // filepath.Join(root, expectedFileName),
// // ),
// // )
// // }
// }
// func upgradeBinary(fileToUpgrade string, file *updater.File) error {
// fileExists := false
// _, err := os.Stat(fileToUpgrade)
// if err == nil {
// // file exists and is accessible
// fileExists = true
// }
// if fileExists {
// // get current version
// var currentVersion string
// cmd := exec.Command(fileToUpgrade, "version", "--short")
// out, err := cmd.Output()
// if err == nil {
// // abort if version matches
// currentVersion = strings.Trim(strings.TrimSpace(string(out)), "*")
// if currentVersion == file.Version() {
// log.Debugf("updates: %s is already v%s", fileToUpgrade, file.Version())
// // already up to date!
// return nil
// }
// } else {
// log.Warningf("updates: failed to run %s to get version for upgrade check: %s", fileToUpgrade, err)
// currentVersion = "0.0.0"
// }
// // test currentVersion for sanity
// if !rawVersionRegex.MatchString(currentVersion) {
// log.Debugf("updates: version string returned by %s is invalid: %s", fileToUpgrade, currentVersion)
// }
// // try removing old version
// err = os.Remove(fileToUpgrade)
// if err != nil {
// // ensure tmp dir is here
// // err = registry.TmpDir().Ensure()
// // if err != nil {
// // return fmt.Errorf("could not prepare tmp directory for moving file that needs upgrade: %w", err)
// // }
// // maybe we're on windows and it's in use, try moving
// // err = os.Rename(fileToUpgrade, filepath.Join(
// // registry.TmpDir().Path,
// // fmt.Sprintf(
// // "%s-%d%s",
// // filepath.Base(fileToUpgrade),
// // time.Now().UTC().Unix(),
// // upgradedSuffix,
// // ),
// // ))
// // if err != nil {
// // return fmt.Errorf("unable to move file that needs upgrade: %w", err)
// // }
// }
// }
// // copy upgrade
// err = CopyFile(file.Path(), fileToUpgrade)
// if err != nil {
// // try again
// time.Sleep(1 * time.Second)
// err = CopyFile(file.Path(), fileToUpgrade)
// if err != nil {
// return err
// }
// }
// // check permissions
// if !onWindows {
// info, err := os.Stat(fileToUpgrade)
// if err != nil {
// return fmt.Errorf("failed to get file info on %s: %w", fileToUpgrade, err)
// }
// if info.Mode() != 0o0755 {
// err := os.Chmod(fileToUpgrade, 0o0755) //nolint:gosec // Set execute permissions.
// if err != nil {
// return fmt.Errorf("failed to set permissions on %s: %w", fileToUpgrade, err)
// }
// }
// }
// log.Infof("updates: upgraded %s to v%s", fileToUpgrade, file.Version())
// return nil
// }
// // CopyFile atomically copies a file using the update registry's tmp dir.
// func CopyFile(srcPath, dstPath string) error {
// // check tmp dir
// // err := registry.TmpDir().Ensure()
// // if err != nil {
// // return fmt.Errorf("could not prepare tmp directory for copying file: %w", err)
// // }
// // open file for writing
// // atomicDstFile, err := renameio.TempFile(registry.TmpDir().Path, dstPath)
// // if err != nil {
// // return fmt.Errorf("could not create temp file for atomic copy: %w", err)
// // }
// // defer atomicDstFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway
// // // open source
// // srcFile, err := os.Open(srcPath)
// // if err != nil {
// // return err
// // }
// // defer func() {
// // _ = srcFile.Close()
// // }()
// // // copy data
// // _, err = io.Copy(atomicDstFile, srcFile)
// // if err != nil {
// // return err
// // }
// // // finalize file
// // err = atomicDstFile.CloseAtomicallyReplace()
// // if err != nil {
// // return fmt.Errorf("updates: failed to finalize copy to file %s: %w", dstPath, err)
// // }
// return nil
// }