[WIP] New updater first working prototype
This commit is contained in:
@@ -1,15 +1,15 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
// Export exports the list of resources.
|
// // Export exports the list of resources.
|
||||||
func (reg *ResourceRegistry) Export() map[string]*Resource {
|
// func (reg *ResourceRegistry) Export() map[string]*Resource {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
// copy the map
|
// // copy the map
|
||||||
copiedResources := make(map[string]*Resource)
|
// copiedResources := make(map[string]*Resource)
|
||||||
for key, val := range reg.resources {
|
// for key, val := range reg.resources {
|
||||||
copiedResources[key] = val.Export()
|
// copiedResources[key] = val.Export()
|
||||||
}
|
// }
|
||||||
|
|
||||||
return copiedResources
|
// return copiedResources
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,347 +1,347 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"bytes"
|
// "bytes"
|
||||||
"context"
|
// "context"
|
||||||
"errors"
|
// "errors"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"hash"
|
// "hash"
|
||||||
"io"
|
// "io"
|
||||||
"net/http"
|
// "net/http"
|
||||||
"net/url"
|
// "net/url"
|
||||||
"os"
|
// "os"
|
||||||
"path"
|
// "path"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"time"
|
// "time"
|
||||||
|
|
||||||
"github.com/safing/jess/filesig"
|
// "github.com/safing/jess/filesig"
|
||||||
"github.com/safing/jess/lhash"
|
// "github.com/safing/jess/lhash"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/utils/renameio"
|
// "github.com/safing/portmaster/base/utils/renameio"
|
||||||
)
|
// )
|
||||||
|
|
||||||
func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client, rv *ResourceVersion, tries int) error {
|
// func (reg *ResourceRegistry) fetchFile(ctx context.Context, client *http.Client, rv *ResourceVersion, tries int) error {
|
||||||
// backoff when retrying
|
// // backoff when retrying
|
||||||
if tries > 0 {
|
// if tries > 0 {
|
||||||
select {
|
// select {
|
||||||
case <-ctx.Done():
|
// case <-ctx.Done():
|
||||||
return nil // module is shutting down
|
// return nil // module is shutting down
|
||||||
case <-time.After(time.Duration(tries*tries) * time.Second):
|
// case <-time.After(time.Duration(tries*tries) * time.Second):
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// check destination dir
|
// // check destination dir
|
||||||
dirPath := filepath.Dir(rv.storagePath())
|
// dirPath := filepath.Dir(rv.storagePath())
|
||||||
err := reg.storageDir.EnsureAbsPath(dirPath)
|
// err := reg.storageDir.EnsureAbsPath(dirPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("could not create updates folder: %s", dirPath)
|
// return fmt.Errorf("could not create updates folder: %s", dirPath)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// If verification is enabled, download signature first.
|
// // If verification is enabled, download signature first.
|
||||||
var (
|
// var (
|
||||||
verifiedHash *lhash.LabeledHash
|
// verifiedHash *lhash.LabeledHash
|
||||||
sigFileData []byte
|
// sigFileData []byte
|
||||||
)
|
// )
|
||||||
if rv.resource.VerificationOptions != nil {
|
// if rv.resource.VerificationOptions != nil {
|
||||||
verifiedHash, sigFileData, err = reg.fetchAndVerifySigFile(
|
// verifiedHash, sigFileData, err = reg.fetchAndVerifySigFile(
|
||||||
ctx, client,
|
// ctx, client,
|
||||||
rv.resource.VerificationOptions,
|
// rv.resource.VerificationOptions,
|
||||||
rv.versionedSigPath(), rv.SigningMetadata(),
|
// rv.versionedSigPath(), rv.SigningMetadata(),
|
||||||
tries,
|
// tries,
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
switch rv.resource.VerificationOptions.DownloadPolicy {
|
// switch rv.resource.VerificationOptions.DownloadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return fmt.Errorf("signature verification failed: %w", err)
|
// return fmt.Errorf("signature verification failed: %w", err)
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
// log.Warningf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
// log.Debugf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// open file for writing
|
// // open file for writing
|
||||||
atomicFile, err := renameio.TempFile(reg.tmpDir.Path, rv.storagePath())
|
// atomicFile, err := renameio.TempFile(reg.tmpDir.Path, rv.storagePath())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("could not create temp file for download: %w", err)
|
// return fmt.Errorf("could not create temp file for download: %w", err)
|
||||||
}
|
// }
|
||||||
defer atomicFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway
|
// defer atomicFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway
|
||||||
|
|
||||||
// start file download
|
// // start file download
|
||||||
resp, downloadURL, err := reg.makeRequest(ctx, client, rv.versionedPath(), tries)
|
// resp, downloadURL, err := reg.makeRequest(ctx, client, rv.versionedPath(), tries)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
defer func() {
|
// defer func() {
|
||||||
_ = resp.Body.Close()
|
// _ = resp.Body.Close()
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
// Write to the hasher at the same time, if needed.
|
// // Write to the hasher at the same time, if needed.
|
||||||
var hasher hash.Hash
|
// var hasher hash.Hash
|
||||||
var writeDst io.Writer = atomicFile
|
// var writeDst io.Writer = atomicFile
|
||||||
if verifiedHash != nil {
|
// if verifiedHash != nil {
|
||||||
hasher = verifiedHash.Algorithm().RawHasher()
|
// hasher = verifiedHash.Algorithm().RawHasher()
|
||||||
writeDst = io.MultiWriter(hasher, atomicFile)
|
// writeDst = io.MultiWriter(hasher, atomicFile)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Download and write file.
|
// // Download and write file.
|
||||||
n, err := io.Copy(writeDst, resp.Body)
|
// n, err := io.Copy(writeDst, resp.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to download %q: %w", downloadURL, err)
|
// return fmt.Errorf("failed to download %q: %w", downloadURL, err)
|
||||||
}
|
// }
|
||||||
if resp.ContentLength != n {
|
// if resp.ContentLength != n {
|
||||||
return fmt.Errorf("failed to finish download of %q: written %d out of %d bytes", downloadURL, n, resp.ContentLength)
|
// return fmt.Errorf("failed to finish download of %q: written %d out of %d bytes", downloadURL, n, resp.ContentLength)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Before file is finalized, check if hash, if available.
|
// // Before file is finalized, check if hash, if available.
|
||||||
if hasher != nil {
|
// if hasher != nil {
|
||||||
downloadDigest := hasher.Sum(nil)
|
// downloadDigest := hasher.Sum(nil)
|
||||||
if verifiedHash.EqualRaw(downloadDigest) {
|
// if verifiedHash.EqualRaw(downloadDigest) {
|
||||||
log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
|
// log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
|
||||||
} else {
|
// } else {
|
||||||
switch rv.resource.VerificationOptions.DownloadPolicy {
|
// switch rv.resource.VerificationOptions.DownloadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return errors.New("file does not match signed checksum")
|
// return errors.New("file does not match signed checksum")
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: checksum does not match file from %s", reg.Name, downloadURL)
|
// log.Warningf("%s: checksum does not match file from %s", reg.Name, downloadURL)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: checksum does not match file from %s", reg.Name, downloadURL)
|
// log.Debugf("%s: checksum does not match file from %s", reg.Name, downloadURL)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Reset hasher to signal that the sig should not be written.
|
// // Reset hasher to signal that the sig should not be written.
|
||||||
hasher = nil
|
// hasher = nil
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Write signature file, if we have one and if verification succeeded.
|
// // Write signature file, if we have one and if verification succeeded.
|
||||||
if len(sigFileData) > 0 && hasher != nil {
|
// if len(sigFileData) > 0 && hasher != nil {
|
||||||
sigFilePath := rv.storagePath() + filesig.Extension
|
// sigFilePath := rv.storagePath() + filesig.Extension
|
||||||
err := os.WriteFile(sigFilePath, sigFileData, 0o0644) //nolint:gosec
|
// err := os.WriteFile(sigFilePath, sigFileData, 0o0644) //nolint:gosec
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
switch rv.resource.VerificationOptions.DownloadPolicy {
|
// switch rv.resource.VerificationOptions.DownloadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return fmt.Errorf("failed to write signature file %s: %w", sigFilePath, err)
|
// return fmt.Errorf("failed to write signature file %s: %w", sigFilePath, err)
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: failed to write signature file %s: %s", reg.Name, sigFilePath, err)
|
// log.Warningf("%s: failed to write signature file %s: %s", reg.Name, sigFilePath, err)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: failed to write signature file %s: %s", reg.Name, sigFilePath, err)
|
// log.Debugf("%s: failed to write signature file %s: %s", reg.Name, sigFilePath, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// finalize file
|
// // finalize file
|
||||||
err = atomicFile.CloseAtomicallyReplace()
|
// err = atomicFile.CloseAtomicallyReplace()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("%s: failed to finalize file %s: %w", reg.Name, rv.storagePath(), err)
|
// return fmt.Errorf("%s: failed to finalize file %s: %w", reg.Name, rv.storagePath(), err)
|
||||||
}
|
// }
|
||||||
// set permissions
|
// // set permissions
|
||||||
if !onWindows {
|
// if !onWindows {
|
||||||
// TODO: only set executable files to 0755, set other to 0644
|
// // TODO: only set executable files to 0755, set other to 0644
|
||||||
err = os.Chmod(rv.storagePath(), 0o0755) //nolint:gosec // See TODO above.
|
// err = os.Chmod(rv.storagePath(), 0o0755) //nolint:gosec // See TODO above.
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
|
// log.Warningf("%s: failed to set permissions on downloaded file %s: %s", reg.Name, rv.storagePath(), err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Debugf("%s: fetched %s and stored to %s", reg.Name, downloadURL, rv.storagePath())
|
// log.Debugf("%s: fetched %s and stored to %s", reg.Name, downloadURL, rv.storagePath())
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) fetchMissingSig(ctx context.Context, client *http.Client, rv *ResourceVersion, tries int) error {
|
// func (reg *ResourceRegistry) fetchMissingSig(ctx context.Context, client *http.Client, rv *ResourceVersion, tries int) error {
|
||||||
// backoff when retrying
|
// // backoff when retrying
|
||||||
if tries > 0 {
|
// if tries > 0 {
|
||||||
select {
|
// select {
|
||||||
case <-ctx.Done():
|
// case <-ctx.Done():
|
||||||
return nil // module is shutting down
|
// return nil // module is shutting down
|
||||||
case <-time.After(time.Duration(tries*tries) * time.Second):
|
// case <-time.After(time.Duration(tries*tries) * time.Second):
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check destination dir.
|
// // Check destination dir.
|
||||||
dirPath := filepath.Dir(rv.storagePath())
|
// dirPath := filepath.Dir(rv.storagePath())
|
||||||
err := reg.storageDir.EnsureAbsPath(dirPath)
|
// err := reg.storageDir.EnsureAbsPath(dirPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("could not create updates folder: %s", dirPath)
|
// return fmt.Errorf("could not create updates folder: %s", dirPath)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Download and verify the missing signature.
|
// // Download and verify the missing signature.
|
||||||
verifiedHash, sigFileData, err := reg.fetchAndVerifySigFile(
|
// verifiedHash, sigFileData, err := reg.fetchAndVerifySigFile(
|
||||||
ctx, client,
|
// ctx, client,
|
||||||
rv.resource.VerificationOptions,
|
// rv.resource.VerificationOptions,
|
||||||
rv.versionedSigPath(), rv.SigningMetadata(),
|
// rv.versionedSigPath(), rv.SigningMetadata(),
|
||||||
tries,
|
// tries,
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
switch rv.resource.VerificationOptions.DownloadPolicy {
|
// switch rv.resource.VerificationOptions.DownloadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return fmt.Errorf("signature verification failed: %w", err)
|
// return fmt.Errorf("signature verification failed: %w", err)
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
// log.Warningf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
// log.Debugf("%s: failed to verify downloaded signature of %s: %s", reg.Name, rv.versionedPath(), err)
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if the signature matches the resource file.
|
// // Check if the signature matches the resource file.
|
||||||
ok, err := verifiedHash.MatchesFile(rv.storagePath())
|
// ok, err := verifiedHash.MatchesFile(rv.storagePath())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
switch rv.resource.VerificationOptions.DownloadPolicy {
|
// switch rv.resource.VerificationOptions.DownloadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return fmt.Errorf("error while verifying resource file: %w", err)
|
// return fmt.Errorf("error while verifying resource file: %w", err)
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
|
// log.Warningf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
|
// log.Debugf("%s: error while verifying resource file %s", reg.Name, rv.storagePath())
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
if !ok {
|
// if !ok {
|
||||||
switch rv.resource.VerificationOptions.DownloadPolicy {
|
// switch rv.resource.VerificationOptions.DownloadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return errors.New("resource file does not match signed checksum")
|
// return errors.New("resource file does not match signed checksum")
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
|
// log.Warningf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
|
// log.Debugf("%s: checksum does not match resource file from %s", reg.Name, rv.storagePath())
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Write signature file.
|
// // Write signature file.
|
||||||
err = os.WriteFile(rv.storageSigPath(), sigFileData, 0o0644) //nolint:gosec
|
// err = os.WriteFile(rv.storageSigPath(), sigFileData, 0o0644) //nolint:gosec
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
switch rv.resource.VerificationOptions.DownloadPolicy {
|
// switch rv.resource.VerificationOptions.DownloadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return fmt.Errorf("failed to write signature file %s: %w", rv.storageSigPath(), err)
|
// return fmt.Errorf("failed to write signature file %s: %w", rv.storageSigPath(), err)
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
|
// log.Warningf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
|
// log.Debugf("%s: failed to write signature file %s: %s", reg.Name, rv.storageSigPath(), err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Debugf("%s: fetched %s and stored to %s", reg.Name, rv.versionedSigPath(), rv.storageSigPath())
|
// log.Debugf("%s: fetched %s and stored to %s", reg.Name, rv.versionedSigPath(), rv.storageSigPath())
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) fetchAndVerifySigFile(ctx context.Context, client *http.Client, verifOpts *VerificationOptions, sigFilePath string, requiredMetadata map[string]string, tries int) (*lhash.LabeledHash, []byte, error) {
|
// func (reg *ResourceRegistry) fetchAndVerifySigFile(ctx context.Context, client *http.Client, verifOpts *VerificationOptions, sigFilePath string, requiredMetadata map[string]string, tries int) (*lhash.LabeledHash, []byte, error) {
|
||||||
// Download signature file.
|
// // Download signature file.
|
||||||
resp, _, err := reg.makeRequest(ctx, client, sigFilePath, tries)
|
// resp, _, err := reg.makeRequest(ctx, client, sigFilePath, tries)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, nil, err
|
// return nil, nil, err
|
||||||
}
|
// }
|
||||||
defer func() {
|
// defer func() {
|
||||||
_ = resp.Body.Close()
|
// _ = resp.Body.Close()
|
||||||
}()
|
// }()
|
||||||
sigFileData, err := io.ReadAll(resp.Body)
|
// sigFileData, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, nil, err
|
// return nil, nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Extract all signatures.
|
// // Extract all signatures.
|
||||||
sigs, err := filesig.ParseSigFile(sigFileData)
|
// sigs, err := filesig.ParseSigFile(sigFileData)
|
||||||
switch {
|
// switch {
|
||||||
case len(sigs) == 0 && err != nil:
|
// case len(sigs) == 0 && err != nil:
|
||||||
return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
// return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
||||||
case len(sigs) == 0:
|
// case len(sigs) == 0:
|
||||||
return nil, nil, errors.New("no signatures found in signature file")
|
// return nil, nil, errors.New("no signatures found in signature file")
|
||||||
case err != nil:
|
// case err != nil:
|
||||||
return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
// return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Verify all signatures.
|
// // Verify all signatures.
|
||||||
var verifiedHash *lhash.LabeledHash
|
// var verifiedHash *lhash.LabeledHash
|
||||||
for _, sig := range sigs {
|
// for _, sig := range sigs {
|
||||||
fd, err := filesig.VerifyFileData(
|
// fd, err := filesig.VerifyFileData(
|
||||||
sig,
|
// sig,
|
||||||
requiredMetadata,
|
// requiredMetadata,
|
||||||
verifOpts.TrustStore,
|
// verifOpts.TrustStore,
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, sigFileData, err
|
// return nil, sigFileData, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Save or check verified hash.
|
// // Save or check verified hash.
|
||||||
if verifiedHash == nil {
|
// if verifiedHash == nil {
|
||||||
verifiedHash = fd.FileHash()
|
// verifiedHash = fd.FileHash()
|
||||||
} else if !fd.FileHash().Equal(verifiedHash) {
|
// } else if !fd.FileHash().Equal(verifiedHash) {
|
||||||
// Return an error if two valid hashes mismatch.
|
// // Return an error if two valid hashes mismatch.
|
||||||
// For simplicity, all hash algorithms must be the same for now.
|
// // For simplicity, all hash algorithms must be the same for now.
|
||||||
return nil, sigFileData, errors.New("file hashes from different signatures do not match")
|
// return nil, sigFileData, errors.New("file hashes from different signatures do not match")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return verifiedHash, sigFileData, nil
|
// return verifiedHash, sigFileData, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) fetchData(ctx context.Context, client *http.Client, downloadPath string, tries int) (fileData []byte, downloadedFrom string, err error) {
|
// func (reg *ResourceRegistry) fetchData(ctx context.Context, client *http.Client, downloadPath string, tries int) (fileData []byte, downloadedFrom string, err error) {
|
||||||
// backoff when retrying
|
// // backoff when retrying
|
||||||
if tries > 0 {
|
// if tries > 0 {
|
||||||
select {
|
// select {
|
||||||
case <-ctx.Done():
|
// case <-ctx.Done():
|
||||||
return nil, "", nil // module is shutting down
|
// return nil, "", nil // module is shutting down
|
||||||
case <-time.After(time.Duration(tries*tries) * time.Second):
|
// case <-time.After(time.Duration(tries*tries) * time.Second):
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// start file download
|
// // start file download
|
||||||
resp, downloadURL, err := reg.makeRequest(ctx, client, downloadPath, tries)
|
// resp, downloadURL, err := reg.makeRequest(ctx, client, downloadPath, tries)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, downloadURL, err
|
// return nil, downloadURL, err
|
||||||
}
|
// }
|
||||||
defer func() {
|
// defer func() {
|
||||||
_ = resp.Body.Close()
|
// _ = resp.Body.Close()
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
// download and write file
|
// // download and write file
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, resp.ContentLength))
|
// buf := bytes.NewBuffer(make([]byte, 0, resp.ContentLength))
|
||||||
n, err := io.Copy(buf, resp.Body)
|
// n, err := io.Copy(buf, resp.Body)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, downloadURL, fmt.Errorf("failed to download %q: %w", downloadURL, err)
|
// return nil, downloadURL, fmt.Errorf("failed to download %q: %w", downloadURL, err)
|
||||||
}
|
// }
|
||||||
if resp.ContentLength != n {
|
// if resp.ContentLength != n {
|
||||||
return nil, downloadURL, fmt.Errorf("failed to finish download of %q: written %d out of %d bytes", downloadURL, n, resp.ContentLength)
|
// return nil, downloadURL, fmt.Errorf("failed to finish download of %q: written %d out of %d bytes", downloadURL, n, resp.ContentLength)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return buf.Bytes(), downloadURL, nil
|
// return buf.Bytes(), downloadURL, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) makeRequest(ctx context.Context, client *http.Client, downloadPath string, tries int) (resp *http.Response, downloadURL string, err error) {
|
// func (reg *ResourceRegistry) makeRequest(ctx context.Context, client *http.Client, downloadPath string, tries int) (resp *http.Response, downloadURL string, err error) {
|
||||||
// parse update URL
|
// // parse update URL
|
||||||
updateBaseURL := reg.UpdateURLs[tries%len(reg.UpdateURLs)]
|
// updateBaseURL := reg.UpdateURLs[tries%len(reg.UpdateURLs)]
|
||||||
u, err := url.Parse(updateBaseURL)
|
// u, err := url.Parse(updateBaseURL)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, "", fmt.Errorf("failed to parse update URL %q: %w", updateBaseURL, err)
|
// return nil, "", fmt.Errorf("failed to parse update URL %q: %w", updateBaseURL, err)
|
||||||
}
|
// }
|
||||||
// add download path
|
// // add download path
|
||||||
u.Path = path.Join(u.Path, downloadPath)
|
// u.Path = path.Join(u.Path, downloadPath)
|
||||||
// compile URL
|
// // compile URL
|
||||||
downloadURL = u.String()
|
// downloadURL = u.String()
|
||||||
|
|
||||||
// create request
|
// // create request
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, http.NoBody)
|
// req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, http.NoBody)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, "", fmt.Errorf("failed to create request for %q: %w", downloadURL, err)
|
// return nil, "", fmt.Errorf("failed to create request for %q: %w", downloadURL, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// set user agent
|
// // set user agent
|
||||||
if reg.UserAgent != "" {
|
// if reg.UserAgent != "" {
|
||||||
req.Header.Set("User-Agent", reg.UserAgent)
|
// req.Header.Set("User-Agent", reg.UserAgent)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// start request
|
// // start request
|
||||||
resp, err = client.Do(req)
|
// resp, err = client.Do(req)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, "", fmt.Errorf("failed to make request to %q: %w", downloadURL, err)
|
// return nil, "", fmt.Errorf("failed to make request to %q: %w", downloadURL, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// check return code
|
// // check return code
|
||||||
if resp.StatusCode != http.StatusOK {
|
// if resp.StatusCode != http.StatusOK {
|
||||||
_ = resp.Body.Close()
|
// _ = resp.Body.Close()
|
||||||
return nil, "", fmt.Errorf("failed to fetch %q: %d %s", downloadURL, resp.StatusCode, resp.Status)
|
// return nil, "", fmt.Errorf("failed to fetch %q: %d %s", downloadURL, resp.StatusCode, resp.Status)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return resp, downloadURL, err
|
// return resp, downloadURL, err
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,105 +1,97 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
semver "github.com/hashicorp/go-version"
|
// semver "github.com/hashicorp/go-version"
|
||||||
|
|
||||||
"github.com/safing/jess/filesig"
|
|
||||||
"github.com/safing/portmaster/base/log"
|
|
||||||
"github.com/safing/portmaster/base/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// File represents a file from the update system.
|
// File represents a file from the update system.
|
||||||
type File struct {
|
// type File struct {
|
||||||
resource *Resource
|
// resource *Resource
|
||||||
version *ResourceVersion
|
// version *ResourceVersion
|
||||||
notifier *notifier
|
// notifier *notifier
|
||||||
versionedPath string
|
// versionedPath string
|
||||||
storagePath string
|
// storagePath string
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Identifier returns the identifier of the file.
|
// // Identifier returns the identifier of the file.
|
||||||
func (file *File) Identifier() string {
|
// func (file *File) Identifier() string {
|
||||||
return file.resource.Identifier
|
// return file.resource.Identifier
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Version returns the version of the file.
|
// // Version returns the version of the file.
|
||||||
func (file *File) Version() string {
|
// func (file *File) Version() string {
|
||||||
return file.version.VersionNumber
|
// return file.version.VersionNumber
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SemVer returns the semantic version of the file.
|
// // SemVer returns the semantic version of the file.
|
||||||
func (file *File) SemVer() *semver.Version {
|
// func (file *File) SemVer() *semver.Version {
|
||||||
return file.version.semVer
|
// return file.version.semVer
|
||||||
}
|
// }
|
||||||
|
|
||||||
// EqualsVersion normalizes the given version and checks equality with semver.
|
// // EqualsVersion normalizes the given version and checks equality with semver.
|
||||||
func (file *File) EqualsVersion(version string) bool {
|
// func (file *File) EqualsVersion(version string) bool {
|
||||||
return file.version.EqualsVersion(version)
|
// return file.version.EqualsVersion(version)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Path returns the absolute filepath of the file.
|
// // Path returns the absolute filepath of the file.
|
||||||
func (file *File) Path() string {
|
// func (file *File) Path() string {
|
||||||
return file.storagePath
|
// return file.storagePath
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SigningMetadata returns the metadata to be included in signatures.
|
// // SigningMetadata returns the metadata to be included in signatures.
|
||||||
func (file *File) SigningMetadata() map[string]string {
|
// func (file *File) SigningMetadata() map[string]string {
|
||||||
return map[string]string{
|
// return map[string]string{
|
||||||
"id": file.Identifier(),
|
// "id": file.Identifier(),
|
||||||
"version": file.Version(),
|
// "version": file.Version(),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Verify verifies the given file.
|
// Verify verifies the given file.
|
||||||
func (file *File) Verify() ([]*filesig.FileData, error) {
|
// func (file *File) Verify() ([]*filesig.FileData, error) {
|
||||||
// Check if verification is configured.
|
// // Check if verification is configured.
|
||||||
if file.resource.VerificationOptions == nil {
|
// if file.resource.VerificationOptions == nil {
|
||||||
return nil, ErrVerificationNotConfigured
|
// return nil, ErrVerificationNotConfigured
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Verify file.
|
// // Verify file.
|
||||||
fileData, err := filesig.VerifyFile(
|
// fileData, err := filesig.VerifyFile(
|
||||||
file.storagePath,
|
// file.storagePath,
|
||||||
file.storagePath+filesig.Extension,
|
// file.storagePath+filesig.Extension,
|
||||||
file.SigningMetadata(),
|
// file.SigningMetadata(),
|
||||||
file.resource.VerificationOptions.TrustStore,
|
// file.resource.VerificationOptions.TrustStore,
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
switch file.resource.VerificationOptions.DiskLoadPolicy {
|
// switch file.resource.VerificationOptions.DiskLoadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return nil, err
|
// return nil, err
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: failed to verify %s: %s", file.resource.registry.Name, file.storagePath, err)
|
// log.Warningf("%s: failed to verify %s: %s", file.resource.registry.Name, file.storagePath, err)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: failed to verify %s: %s", file.resource.registry.Name, file.storagePath, err)
|
// log.Debugf("%s: failed to verify %s: %s", file.resource.registry.Name, file.storagePath, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return fileData, nil
|
// return fileData, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Blacklist notifies the update system that this file is somehow broken, and should be ignored from now on, until restarted.
|
// Blacklist notifies the update system that this file is somehow broken, and should be ignored from now on, until restarted.
|
||||||
func (file *File) Blacklist() error {
|
// func (file *File) Blacklist() error {
|
||||||
return file.resource.Blacklist(file.version.VersionNumber)
|
// return file.resource.Blacklist(file.version.VersionNumber)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// markActiveWithLocking marks the file as active, locking the resource in the process.
|
// markActiveWithLocking marks the file as active, locking the resource in the process.
|
||||||
func (file *File) markActiveWithLocking() {
|
// func (file *File) markActiveWithLocking() {
|
||||||
file.resource.Lock()
|
// file.resource.Lock()
|
||||||
defer file.resource.Unlock()
|
// defer file.resource.Unlock()
|
||||||
|
|
||||||
// update last used version
|
// // update last used version
|
||||||
if file.resource.ActiveVersion != file.version {
|
// if file.resource.ActiveVersion != file.version {
|
||||||
log.Debugf("updater: setting active version of resource %s from %s to %s", file.resource.Identifier, file.resource.ActiveVersion, file.version.VersionNumber)
|
// log.Debugf("updater: setting active version of resource %s from %s to %s", file.resource.Identifier, file.resource.ActiveVersion, file.version.VersionNumber)
|
||||||
file.resource.ActiveVersion = file.version
|
// file.resource.ActiveVersion = file.version
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Unpacker describes the function that is passed to
|
// Unpacker describes the function that is passed to
|
||||||
// File.Unpack. It receives a reader to the compressed/packed
|
// File.Unpack. It receives a reader to the compressed/packed
|
||||||
@@ -107,50 +99,50 @@ func (file *File) markActiveWithLocking() {
|
|||||||
// unpacked file contents. If the returned reader implements
|
// unpacked file contents. If the returned reader implements
|
||||||
// io.Closer it's close method is invoked when an error
|
// io.Closer it's close method is invoked when an error
|
||||||
// or io.EOF is returned from Read().
|
// or io.EOF is returned from Read().
|
||||||
type Unpacker func(io.Reader) (io.Reader, error)
|
// type Unpacker func(io.Reader) (io.Reader, error)
|
||||||
|
|
||||||
// Unpack returns the path to the unpacked version of file and
|
// Unpack returns the path to the unpacked version of file and
|
||||||
// unpacks it on demand using unpacker.
|
// unpacks it on demand using unpacker.
|
||||||
func (file *File) Unpack(suffix string, unpacker Unpacker) (string, error) {
|
// func (file *File) Unpack(suffix string, unpacker Unpacker) (string, error) {
|
||||||
path := strings.TrimSuffix(file.Path(), suffix)
|
// path := strings.TrimSuffix(file.Path(), suffix)
|
||||||
|
|
||||||
if suffix == "" {
|
// if suffix == "" {
|
||||||
path += "-unpacked"
|
// path += "-unpacked"
|
||||||
}
|
// }
|
||||||
|
|
||||||
_, err := os.Stat(path)
|
// _, err := os.Stat(path)
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
return path, nil
|
// return path, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
if !errors.Is(err, fs.ErrNotExist) {
|
// if !errors.Is(err, fs.ErrNotExist) {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
f, err := os.Open(file.Path())
|
// f, err := os.Open(file.Path())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
defer func() {
|
// defer func() {
|
||||||
_ = f.Close()
|
// _ = f.Close()
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
r, err := unpacker(f)
|
// r, err := unpacker(f)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
ioErr := utils.CreateAtomic(path, r, &utils.AtomicFileOptions{
|
// ioErr := utils.CreateAtomic(path, r, &utils.AtomicFileOptions{
|
||||||
TempDir: file.resource.registry.TmpDir().Path,
|
// TempDir: file.resource.registry.TmpDir().Path,
|
||||||
})
|
// })
|
||||||
|
|
||||||
if c, ok := r.(io.Closer); ok {
|
// if c, ok := r.(io.Closer); ok {
|
||||||
if err := c.Close(); err != nil && ioErr == nil {
|
// if err := c.Close(); err != nil && ioErr == nil {
|
||||||
// if ioErr is already set we ignore the error from
|
// // if ioErr is already set we ignore the error from
|
||||||
// closing the unpacker.
|
// // closing the unpacker.
|
||||||
ioErr = err
|
// ioErr = err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return path, ioErr
|
// return path, ioErr
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package updater
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors returned by the updater package.
|
// Errors returned by the updater package.
|
||||||
@@ -14,75 +13,75 @@ var (
|
|||||||
|
|
||||||
// GetFile returns the selected (mostly newest) file with the given
|
// GetFile returns the selected (mostly newest) file with the given
|
||||||
// identifier or an error, if it fails.
|
// identifier or an error, if it fails.
|
||||||
func (reg *ResourceRegistry) GetFile(identifier string) (*File, error) {
|
// func (reg *ResourceRegistry) GetFile(identifier string) (*File, error) {
|
||||||
return nil, fmt.Errorf("invalid file: %s", identifier)
|
// return nil, fmt.Errorf("invalid file: %s", identifier)
|
||||||
// reg.RLock()
|
// reg.RLock()
|
||||||
// res, ok := reg.resources[identifier]
|
// res, ok := reg.resources[identifier]
|
||||||
// reg.RUnlock()
|
// reg.RUnlock()
|
||||||
// if !ok {
|
// if !ok {
|
||||||
// return nil, ErrNotFound
|
// return nil, ErrNotFound
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// file := res.GetFile()
|
// file := res.GetFile()
|
||||||
// // check if file is available locally
|
// // check if file is available locally
|
||||||
// if file.version.Available {
|
// if file.version.Available {
|
||||||
// file.markActiveWithLocking()
|
// file.markActiveWithLocking()
|
||||||
|
|
||||||
// // Verify file, if configured.
|
// // Verify file, if configured.
|
||||||
// _, err := file.Verify()
|
// _, err := file.Verify()
|
||||||
// if err != nil && !errors.Is(err, ErrVerificationNotConfigured) {
|
// if err != nil && !errors.Is(err, ErrVerificationNotConfigured) {
|
||||||
// // TODO: If verification is required, try deleting the resource and downloading it again.
|
// // TODO: If verification is required, try deleting the resource and downloading it again.
|
||||||
// return nil, fmt.Errorf("failed to verify file: %w", err)
|
// return nil, fmt.Errorf("failed to verify file: %w", err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// return file, nil
|
// return file, nil
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // check if online
|
// // check if online
|
||||||
// if !reg.Online {
|
// if !reg.Online {
|
||||||
// return nil, ErrNotAvailableLocally
|
// return nil, ErrNotAvailableLocally
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // check download dir
|
// // check download dir
|
||||||
// err := reg.tmpDir.Ensure()
|
// err := reg.tmpDir.Ensure()
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return nil, fmt.Errorf("could not prepare tmp directory for download: %w", err)
|
// return nil, fmt.Errorf("could not prepare tmp directory for download: %w", err)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // Start registry operation.
|
// // Start registry operation.
|
||||||
// reg.state.StartOperation(StateFetching)
|
// reg.state.StartOperation(StateFetching)
|
||||||
// defer reg.state.EndOperation()
|
// defer reg.state.EndOperation()
|
||||||
|
|
||||||
// // download file
|
// // download file
|
||||||
// log.Tracef("%s: starting download of %s", reg.Name, file.versionedPath)
|
// log.Tracef("%s: starting download of %s", reg.Name, file.versionedPath)
|
||||||
// client := &http.Client{}
|
// client := &http.Client{}
|
||||||
// for tries := range 5 {
|
// for tries := range 5 {
|
||||||
// err = reg.fetchFile(context.TODO(), client, file.version, tries)
|
// err = reg.fetchFile(context.TODO(), client, file.version, tries)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// log.Tracef("%s: failed to download %s: %s, retrying (%d)", reg.Name, file.versionedPath, err, tries+1)
|
// log.Tracef("%s: failed to download %s: %s, retrying (%d)", reg.Name, file.versionedPath, err, tries+1)
|
||||||
// } else {
|
// } else {
|
||||||
// file.markActiveWithLocking()
|
// file.markActiveWithLocking()
|
||||||
|
|
||||||
// // TODO: We just download the file - should we verify it again?
|
// // TODO: We just download the file - should we verify it again?
|
||||||
// return file, nil
|
// return file, nil
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// log.Warningf("%s: failed to download %s: %s", reg.Name, file.versionedPath, err)
|
// log.Warningf("%s: failed to download %s: %s", reg.Name, file.versionedPath, err)
|
||||||
// return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetVersion returns the selected version of the given identifier.
|
// GetVersion returns the selected version of the given identifier.
|
||||||
// The returned resource version may not be modified.
|
// The returned resource version may not be modified.
|
||||||
func (reg *ResourceRegistry) GetVersion(identifier string) (*ResourceVersion, error) {
|
// func (reg *ResourceRegistry) GetVersion(identifier string) (*ResourceVersion, error) {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
res, ok := reg.resources[identifier]
|
// res, ok := reg.resources[identifier]
|
||||||
reg.RUnlock()
|
// reg.RUnlock()
|
||||||
if !ok {
|
// if !ok {
|
||||||
return nil, ErrNotFound
|
// return nil, ErrNotFound
|
||||||
}
|
// }
|
||||||
|
|
||||||
res.Lock()
|
// res.Lock()
|
||||||
defer res.Unlock()
|
// defer res.Unlock()
|
||||||
|
|
||||||
return res.SelectedVersion, nil
|
// return res.SelectedVersion, nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/tevino/abool"
|
// "github.com/tevino/abool"
|
||||||
)
|
// )
|
||||||
|
|
||||||
type notifier struct {
|
// type notifier struct {
|
||||||
upgradeAvailable *abool.AtomicBool
|
// upgradeAvailable *abool.AtomicBool
|
||||||
notifyChannel chan struct{}
|
// notifyChannel chan struct{}
|
||||||
}
|
// }
|
||||||
|
|
||||||
func newNotifier() *notifier {
|
// func newNotifier() *notifier {
|
||||||
return ¬ifier{
|
// return ¬ifier{
|
||||||
upgradeAvailable: abool.NewBool(false),
|
// upgradeAvailable: abool.NewBool(false),
|
||||||
notifyChannel: make(chan struct{}),
|
// notifyChannel: make(chan struct{}),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (n *notifier) markAsUpgradeable() {
|
// func (n *notifier) markAsUpgradeable() {
|
||||||
if n.upgradeAvailable.SetToIf(false, true) {
|
// if n.upgradeAvailable.SetToIf(false, true) {
|
||||||
close(n.notifyChannel)
|
// close(n.notifyChannel)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// UpgradeAvailable returns whether an upgrade is available for this file.
|
// // UpgradeAvailable returns whether an upgrade is available for this file.
|
||||||
func (file *File) UpgradeAvailable() bool {
|
// func (file *File) UpgradeAvailable() bool {
|
||||||
return file.notifier.upgradeAvailable.IsSet()
|
// return file.notifier.upgradeAvailable.IsSet()
|
||||||
}
|
// }
|
||||||
|
|
||||||
// WaitForAvailableUpgrade blocks (selectable) until an upgrade for this file is available.
|
// // WaitForAvailableUpgrade blocks (selectable) until an upgrade for this file is available.
|
||||||
func (file *File) WaitForAvailableUpgrade() <-chan struct{} {
|
// func (file *File) WaitForAvailableUpgrade() <-chan struct{} {
|
||||||
return file.notifier.notifyChannel
|
// return file.notifier.notifyChannel
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,270 +1,270 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"errors"
|
// "errors"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"os"
|
// "os"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"runtime"
|
// "runtime"
|
||||||
"strings"
|
// "strings"
|
||||||
"sync"
|
// "sync"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/utils"
|
// "github.com/safing/portmaster/base/utils"
|
||||||
)
|
// )
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
onWindows = runtime.GOOS == "windows"
|
// onWindows = runtime.GOOS == "windows"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// ResourceRegistry is a registry for managing update resources.
|
// ResourceRegistry is a registry for managing update resources.
|
||||||
type ResourceRegistry struct {
|
// type ResourceRegistry struct {
|
||||||
sync.RWMutex
|
// sync.RWMutex
|
||||||
|
|
||||||
Name string
|
// Name string
|
||||||
storageDir *utils.DirStructure
|
// storageDir *utils.DirStructure
|
||||||
tmpDir *utils.DirStructure
|
// tmpDir *utils.DirStructure
|
||||||
indexes []*Index
|
// indexes []*Index
|
||||||
state *RegistryState
|
// state *RegistryState
|
||||||
|
|
||||||
resources map[string]*Resource
|
// resources map[string]*Resource
|
||||||
UpdateURLs []string
|
// UpdateURLs []string
|
||||||
UserAgent string
|
// UserAgent string
|
||||||
MandatoryUpdates []string
|
// MandatoryUpdates []string
|
||||||
AutoUnpack []string
|
// AutoUnpack []string
|
||||||
|
|
||||||
// Verification holds a map of VerificationOptions assigned to their
|
// // Verification holds a map of VerificationOptions assigned to their
|
||||||
// applicable identifier path prefix.
|
// // applicable identifier path prefix.
|
||||||
// Use an empty string to denote the default.
|
// // Use an empty string to denote the default.
|
||||||
// Use empty options to disable verification for a path prefix.
|
// // Use empty options to disable verification for a path prefix.
|
||||||
Verification map[string]*VerificationOptions
|
// Verification map[string]*VerificationOptions
|
||||||
|
|
||||||
// UsePreReleases signifies that pre-releases should be used when selecting a
|
// // UsePreReleases signifies that pre-releases should be used when selecting a
|
||||||
// version. Even if false, a pre-release version will still be used if it is
|
// // version. Even if false, a pre-release version will still be used if it is
|
||||||
// defined as the current version by an index.
|
// // defined as the current version by an index.
|
||||||
UsePreReleases bool
|
// UsePreReleases bool
|
||||||
|
|
||||||
// DevMode specifies if a local 0.0.0 version should be always chosen, when available.
|
// // DevMode specifies if a local 0.0.0 version should be always chosen, when available.
|
||||||
DevMode bool
|
// DevMode bool
|
||||||
|
|
||||||
// Online specifies if resources may be downloaded if not available locally.
|
// // Online specifies if resources may be downloaded if not available locally.
|
||||||
Online bool
|
// Online bool
|
||||||
|
|
||||||
// StateNotifyFunc may be set to receive any changes to the registry state.
|
// // StateNotifyFunc may be set to receive any changes to the registry state.
|
||||||
// The specified function may lock the state, but may not block or take a
|
// // The specified function may lock the state, but may not block or take a
|
||||||
// lot of time.
|
// // lot of time.
|
||||||
StateNotifyFunc func(*RegistryState)
|
// StateNotifyFunc func(*RegistryState)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// AddIndex adds a new index to the resource registry.
|
// // AddIndex adds a new index to the resource registry.
|
||||||
// The order is important, as indexes added later will override the current
|
// // The order is important, as indexes added later will override the current
|
||||||
// release from earlier indexes.
|
// // release from earlier indexes.
|
||||||
func (reg *ResourceRegistry) AddIndex(idx Index) {
|
// func (reg *ResourceRegistry) AddIndex(idx Index) {
|
||||||
reg.Lock()
|
// reg.Lock()
|
||||||
defer reg.Unlock()
|
// defer reg.Unlock()
|
||||||
|
|
||||||
// Get channel name from path.
|
// // Get channel name from path.
|
||||||
idx.Channel = strings.TrimSuffix(
|
// idx.Channel = strings.TrimSuffix(
|
||||||
filepath.Base(idx.Path), filepath.Ext(idx.Path),
|
// filepath.Base(idx.Path), filepath.Ext(idx.Path),
|
||||||
)
|
// )
|
||||||
|
|
||||||
reg.indexes = append(reg.indexes, &idx)
|
// reg.indexes = append(reg.indexes, &idx)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// PreInitUpdateState sets the initial update state of the registry before initialization.
|
// // PreInitUpdateState sets the initial update state of the registry before initialization.
|
||||||
func (reg *ResourceRegistry) PreInitUpdateState(s UpdateState) error {
|
// func (reg *ResourceRegistry) PreInitUpdateState(s UpdateState) error {
|
||||||
if reg.state != nil {
|
// if reg.state != nil {
|
||||||
return errors.New("registry already initialized")
|
// return errors.New("registry already initialized")
|
||||||
}
|
// }
|
||||||
|
|
||||||
reg.state = &RegistryState{
|
// reg.state = &RegistryState{
|
||||||
Updates: s,
|
// Updates: s,
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Initialize initializes a raw registry struct and makes it ready for usage.
|
// // Initialize initializes a raw registry struct and makes it ready for usage.
|
||||||
func (reg *ResourceRegistry) Initialize(storageDir *utils.DirStructure) error {
|
// func (reg *ResourceRegistry) Initialize(storageDir *utils.DirStructure) error {
|
||||||
// check if storage dir is available
|
// // check if storage dir is available
|
||||||
err := storageDir.Ensure()
|
// err := storageDir.Ensure()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// set default name
|
// // set default name
|
||||||
if reg.Name == "" {
|
// if reg.Name == "" {
|
||||||
reg.Name = "updater"
|
// reg.Name = "updater"
|
||||||
}
|
// }
|
||||||
|
|
||||||
// initialize private attributes
|
// // initialize private attributes
|
||||||
reg.storageDir = storageDir
|
// reg.storageDir = storageDir
|
||||||
reg.tmpDir = storageDir.ChildDir("tmp", 0o0700)
|
// reg.tmpDir = storageDir.ChildDir("tmp", 0o0700)
|
||||||
reg.resources = make(map[string]*Resource)
|
// reg.resources = make(map[string]*Resource)
|
||||||
if reg.state == nil {
|
// if reg.state == nil {
|
||||||
reg.state = &RegistryState{}
|
// reg.state = &RegistryState{}
|
||||||
}
|
// }
|
||||||
reg.state.ID = StateReady
|
// reg.state.ID = StateReady
|
||||||
reg.state.reg = reg
|
// reg.state.reg = reg
|
||||||
|
|
||||||
// remove tmp dir to delete old entries
|
// // remove tmp dir to delete old entries
|
||||||
err = reg.Cleanup()
|
// err = reg.Cleanup()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to remove tmp dir: %s", reg.Name, err)
|
// log.Warningf("%s: failed to remove tmp dir: %s", reg.Name, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// (re-)create tmp dir
|
// // (re-)create tmp dir
|
||||||
err = reg.tmpDir.Ensure()
|
// err = reg.tmpDir.Ensure()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to create tmp dir: %s", reg.Name, err)
|
// log.Warningf("%s: failed to create tmp dir: %s", reg.Name, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check verification options.
|
// // Check verification options.
|
||||||
if reg.Verification != nil {
|
// if reg.Verification != nil {
|
||||||
for prefix, opts := range reg.Verification {
|
// for prefix, opts := range reg.Verification {
|
||||||
// Check if verification is disable for this prefix.
|
// // Check if verification is disable for this prefix.
|
||||||
if opts == nil {
|
// if opts == nil {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
// If enabled, a trust store is required.
|
// // If enabled, a trust store is required.
|
||||||
if opts.TrustStore == nil {
|
// if opts.TrustStore == nil {
|
||||||
return fmt.Errorf("verification enabled for prefix %q, but no trust store configured", prefix)
|
// return fmt.Errorf("verification enabled for prefix %q, but no trust store configured", prefix)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// DownloadPolicy must be equal or stricter than DiskLoadPolicy.
|
// // DownloadPolicy must be equal or stricter than DiskLoadPolicy.
|
||||||
if opts.DiskLoadPolicy < opts.DownloadPolicy {
|
// if opts.DiskLoadPolicy < opts.DownloadPolicy {
|
||||||
return errors.New("verification download policy must be equal or stricter than the disk load policy")
|
// return errors.New("verification download policy must be equal or stricter than the disk load policy")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Warn if all policies are disabled.
|
// // Warn if all policies are disabled.
|
||||||
if opts.DownloadPolicy == SignaturePolicyDisable &&
|
// if opts.DownloadPolicy == SignaturePolicyDisable &&
|
||||||
opts.DiskLoadPolicy == SignaturePolicyDisable {
|
// opts.DiskLoadPolicy == SignaturePolicyDisable {
|
||||||
log.Warningf("%s: verification enabled for prefix %q, but all policies set to disable", reg.Name, prefix)
|
// log.Warningf("%s: verification enabled for prefix %q, but all policies set to disable", reg.Name, prefix)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// StorageDir returns the main storage dir of the resource registry.
|
// // StorageDir returns the main storage dir of the resource registry.
|
||||||
func (reg *ResourceRegistry) StorageDir() *utils.DirStructure {
|
// func (reg *ResourceRegistry) StorageDir() *utils.DirStructure {
|
||||||
return reg.storageDir
|
// return reg.storageDir
|
||||||
}
|
// }
|
||||||
|
|
||||||
// TmpDir returns the temporary working dir of the resource registry.
|
// // TmpDir returns the temporary working dir of the resource registry.
|
||||||
func (reg *ResourceRegistry) TmpDir() *utils.DirStructure {
|
// func (reg *ResourceRegistry) TmpDir() *utils.DirStructure {
|
||||||
return reg.tmpDir
|
// return reg.tmpDir
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SetDevMode sets the development mode flag.
|
// // SetDevMode sets the development mode flag.
|
||||||
func (reg *ResourceRegistry) SetDevMode(on bool) {
|
// func (reg *ResourceRegistry) SetDevMode(on bool) {
|
||||||
reg.Lock()
|
// reg.Lock()
|
||||||
defer reg.Unlock()
|
// defer reg.Unlock()
|
||||||
|
|
||||||
reg.DevMode = on
|
// reg.DevMode = on
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SetUsePreReleases sets the UsePreReleases flag.
|
// // SetUsePreReleases sets the UsePreReleases flag.
|
||||||
func (reg *ResourceRegistry) SetUsePreReleases(yes bool) {
|
// func (reg *ResourceRegistry) SetUsePreReleases(yes bool) {
|
||||||
reg.Lock()
|
// reg.Lock()
|
||||||
defer reg.Unlock()
|
// defer reg.Unlock()
|
||||||
|
|
||||||
reg.UsePreReleases = yes
|
// reg.UsePreReleases = yes
|
||||||
}
|
// }
|
||||||
|
|
||||||
// AddResource adds a resource to the registry. Does _not_ select new version.
|
// // AddResource adds a resource to the registry. Does _not_ select new version.
|
||||||
func (reg *ResourceRegistry) AddResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
|
// func (reg *ResourceRegistry) AddResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
|
||||||
reg.Lock()
|
// reg.Lock()
|
||||||
defer reg.Unlock()
|
// defer reg.Unlock()
|
||||||
|
|
||||||
err := reg.addResource(identifier, version, index, available, currentRelease, preRelease)
|
// err := reg.addResource(identifier, version, index, available, currentRelease, preRelease)
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) addResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
|
// func (reg *ResourceRegistry) addResource(identifier, version string, index *Index, available, currentRelease, preRelease bool) error {
|
||||||
res, ok := reg.resources[identifier]
|
// res, ok := reg.resources[identifier]
|
||||||
if !ok {
|
// if !ok {
|
||||||
res = reg.newResource(identifier)
|
// res = reg.newResource(identifier)
|
||||||
reg.resources[identifier] = res
|
// reg.resources[identifier] = res
|
||||||
}
|
// }
|
||||||
res.Index = index
|
// res.Index = index
|
||||||
|
|
||||||
return res.AddVersion(version, available, currentRelease, preRelease)
|
// return res.AddVersion(version, available, currentRelease, preRelease)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// AddResources adds resources to the registry. Errors are logged, the last one is returned. Despite errors, non-failing resources are still added. Does _not_ select new versions.
|
// // AddResources adds resources to the registry. Errors are logged, the last one is returned. Despite errors, non-failing resources are still added. Does _not_ select new versions.
|
||||||
func (reg *ResourceRegistry) AddResources(versions map[string]string, index *Index, available, currentRelease, preRelease bool) error {
|
// func (reg *ResourceRegistry) AddResources(versions map[string]string, index *Index, available, currentRelease, preRelease bool) error {
|
||||||
reg.Lock()
|
// reg.Lock()
|
||||||
defer reg.Unlock()
|
// defer reg.Unlock()
|
||||||
|
|
||||||
// add versions and their flags to registry
|
// // add versions and their flags to registry
|
||||||
var lastError error
|
// var lastError error
|
||||||
for identifier, version := range versions {
|
// for identifier, version := range versions {
|
||||||
lastError = reg.addResource(identifier, version, index, available, currentRelease, preRelease)
|
// lastError = reg.addResource(identifier, version, index, available, currentRelease, preRelease)
|
||||||
if lastError != nil {
|
// if lastError != nil {
|
||||||
log.Warningf("%s: failed to add resource %s: %s", reg.Name, identifier, lastError)
|
// log.Warningf("%s: failed to add resource %s: %s", reg.Name, identifier, lastError)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return lastError
|
// return lastError
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SelectVersions selects new resource versions depending on the current registry state.
|
// // SelectVersions selects new resource versions depending on the current registry state.
|
||||||
func (reg *ResourceRegistry) SelectVersions() {
|
// func (reg *ResourceRegistry) SelectVersions() {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
for _, res := range reg.resources {
|
// for _, res := range reg.resources {
|
||||||
res.Lock()
|
// res.Lock()
|
||||||
res.selectVersion()
|
// res.selectVersion()
|
||||||
res.Unlock()
|
// res.Unlock()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetSelectedVersions returns a list of the currently selected versions.
|
// // GetSelectedVersions returns a list of the currently selected versions.
|
||||||
func (reg *ResourceRegistry) GetSelectedVersions() (versions map[string]string) {
|
// func (reg *ResourceRegistry) GetSelectedVersions() (versions map[string]string) {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
for _, res := range reg.resources {
|
// for _, res := range reg.resources {
|
||||||
res.Lock()
|
// res.Lock()
|
||||||
versions[res.Identifier] = res.SelectedVersion.VersionNumber
|
// versions[res.Identifier] = res.SelectedVersion.VersionNumber
|
||||||
res.Unlock()
|
// res.Unlock()
|
||||||
}
|
// }
|
||||||
|
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Purge deletes old updates, retaining a certain amount, specified by the keep
|
// // Purge deletes old updates, retaining a certain amount, specified by the keep
|
||||||
// parameter. Will at least keep 2 updates per resource.
|
// // parameter. Will at least keep 2 updates per resource.
|
||||||
func (reg *ResourceRegistry) Purge(keep int) {
|
// func (reg *ResourceRegistry) Purge(keep int) {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
for _, res := range reg.resources {
|
// for _, res := range reg.resources {
|
||||||
res.Purge(keep)
|
// res.Purge(keep)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ResetResources removes all resources from the registry.
|
// // ResetResources removes all resources from the registry.
|
||||||
func (reg *ResourceRegistry) ResetResources() {
|
// func (reg *ResourceRegistry) ResetResources() {
|
||||||
reg.Lock()
|
// reg.Lock()
|
||||||
defer reg.Unlock()
|
// defer reg.Unlock()
|
||||||
|
|
||||||
reg.resources = make(map[string]*Resource)
|
// reg.resources = make(map[string]*Resource)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ResetIndexes removes all indexes from the registry.
|
// // ResetIndexes removes all indexes from the registry.
|
||||||
func (reg *ResourceRegistry) ResetIndexes() {
|
// func (reg *ResourceRegistry) ResetIndexes() {
|
||||||
reg.Lock()
|
// reg.Lock()
|
||||||
defer reg.Unlock()
|
// defer reg.Unlock()
|
||||||
|
|
||||||
reg.indexes = make([]*Index, 0, len(reg.indexes))
|
// reg.indexes = make([]*Index, 0, len(reg.indexes))
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Cleanup removes temporary files.
|
// // Cleanup removes temporary files.
|
||||||
func (reg *ResourceRegistry) Cleanup() error {
|
// func (reg *ResourceRegistry) Cleanup() error {
|
||||||
// delete download tmp dir
|
// // delete download tmp dir
|
||||||
return os.RemoveAll(reg.tmpDir.Path)
|
// return os.RemoveAll(reg.tmpDir.Path)
|
||||||
}
|
// }
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +1,49 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"github.com/safing/jess"
|
// "github.com/safing/jess"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// VerificationOptions holds options for verification of files.
|
// // VerificationOptions holds options for verification of files.
|
||||||
type VerificationOptions struct {
|
// type VerificationOptions struct {
|
||||||
TrustStore jess.TrustStore
|
// TrustStore jess.TrustStore
|
||||||
DownloadPolicy SignaturePolicy
|
// DownloadPolicy SignaturePolicy
|
||||||
DiskLoadPolicy SignaturePolicy
|
// DiskLoadPolicy SignaturePolicy
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetVerificationOptions returns the verification options for the given identifier.
|
// // GetVerificationOptions returns the verification options for the given identifier.
|
||||||
func (reg *ResourceRegistry) GetVerificationOptions(identifier string) *VerificationOptions {
|
// func (reg *ResourceRegistry) GetVerificationOptions(identifier string) *VerificationOptions {
|
||||||
if reg.Verification == nil {
|
// if reg.Verification == nil {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
longestPrefix = -1
|
// longestPrefix = -1
|
||||||
bestMatch *VerificationOptions
|
// bestMatch *VerificationOptions
|
||||||
)
|
// )
|
||||||
for prefix, opts := range reg.Verification {
|
// for prefix, opts := range reg.Verification {
|
||||||
if len(prefix) > longestPrefix && strings.HasPrefix(identifier, prefix) {
|
// if len(prefix) > longestPrefix && strings.HasPrefix(identifier, prefix) {
|
||||||
longestPrefix = len(prefix)
|
// longestPrefix = len(prefix)
|
||||||
bestMatch = opts
|
// bestMatch = opts
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return bestMatch
|
// return bestMatch
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SignaturePolicy defines behavior in case of errors.
|
// // SignaturePolicy defines behavior in case of errors.
|
||||||
type SignaturePolicy uint8
|
// type SignaturePolicy uint8
|
||||||
|
|
||||||
// Signature Policies.
|
// // Signature Policies.
|
||||||
const (
|
// const (
|
||||||
// SignaturePolicyRequire fails on any error.
|
// // SignaturePolicyRequire fails on any error.
|
||||||
SignaturePolicyRequire = iota
|
// SignaturePolicyRequire = iota
|
||||||
|
|
||||||
// SignaturePolicyWarn only warns on errors.
|
// // SignaturePolicyWarn only warns on errors.
|
||||||
SignaturePolicyWarn
|
// SignaturePolicyWarn
|
||||||
|
|
||||||
// SignaturePolicyDisable only downloads signatures, but does not verify them.
|
// // SignaturePolicyDisable only downloads signatures, but does not verify them.
|
||||||
SignaturePolicyDisable
|
// SignaturePolicyDisable
|
||||||
)
|
// )
|
||||||
|
|||||||
@@ -1,180 +1,180 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"sort"
|
// "sort"
|
||||||
"sync"
|
// "sync"
|
||||||
"time"
|
// "time"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/utils"
|
// "github.com/safing/portmaster/base/utils"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Registry States.
|
// // Registry States.
|
||||||
const (
|
// const (
|
||||||
StateReady = "ready" // Default idle state.
|
// StateReady = "ready" // Default idle state.
|
||||||
StateChecking = "checking" // Downloading indexes.
|
// StateChecking = "checking" // Downloading indexes.
|
||||||
StateDownloading = "downloading" // Downloading updates.
|
// StateDownloading = "downloading" // Downloading updates.
|
||||||
StateFetching = "fetching" // Fetching a single file.
|
// StateFetching = "fetching" // Fetching a single file.
|
||||||
)
|
// )
|
||||||
|
|
||||||
// RegistryState describes the registry state.
|
// // RegistryState describes the registry state.
|
||||||
type RegistryState struct {
|
// type RegistryState struct {
|
||||||
sync.Mutex
|
// sync.Mutex
|
||||||
reg *ResourceRegistry
|
// reg *ResourceRegistry
|
||||||
|
|
||||||
// ID holds the ID of the state the registry is currently in.
|
// // ID holds the ID of the state the registry is currently in.
|
||||||
ID string
|
// ID string
|
||||||
|
|
||||||
// Details holds further information about the current state.
|
// // Details holds further information about the current state.
|
||||||
Details any
|
// Details any
|
||||||
|
|
||||||
// Updates holds generic information about the current status of pending
|
// // Updates holds generic information about the current status of pending
|
||||||
// and recently downloaded updates.
|
// // and recently downloaded updates.
|
||||||
Updates UpdateState
|
// Updates UpdateState
|
||||||
|
|
||||||
// operationLock locks the operation of any state changing operation.
|
// // operationLock locks the operation of any state changing operation.
|
||||||
// This is separate from the registry lock, which locks access to the
|
// // This is separate from the registry lock, which locks access to the
|
||||||
// registry struct.
|
// // registry struct.
|
||||||
operationLock sync.Mutex
|
// operationLock sync.Mutex
|
||||||
}
|
// }
|
||||||
|
|
||||||
// StateDownloadingDetails holds details of the downloading state.
|
// // StateDownloadingDetails holds details of the downloading state.
|
||||||
type StateDownloadingDetails struct {
|
// type StateDownloadingDetails struct {
|
||||||
// Resources holds the resource IDs that are being downloaded.
|
// // Resources holds the resource IDs that are being downloaded.
|
||||||
Resources []string
|
// Resources []string
|
||||||
|
|
||||||
// FinishedUpTo holds the index of Resources that is currently being
|
// // FinishedUpTo holds the index of Resources that is currently being
|
||||||
// downloaded. Previous resources have finished downloading.
|
// // downloaded. Previous resources have finished downloading.
|
||||||
FinishedUpTo int
|
// FinishedUpTo int
|
||||||
}
|
// }
|
||||||
|
|
||||||
// UpdateState holds generic information about the current status of pending
|
// // UpdateState holds generic information about the current status of pending
|
||||||
// and recently downloaded updates.
|
// // and recently downloaded updates.
|
||||||
type UpdateState struct {
|
// type UpdateState struct {
|
||||||
// LastCheckAt holds the time of the last update check.
|
// // LastCheckAt holds the time of the last update check.
|
||||||
LastCheckAt *time.Time
|
// LastCheckAt *time.Time
|
||||||
// LastCheckError holds the error of the last check.
|
// // LastCheckError holds the error of the last check.
|
||||||
LastCheckError error
|
// LastCheckError error
|
||||||
// PendingDownload holds the resources that are pending download.
|
// // PendingDownload holds the resources that are pending download.
|
||||||
PendingDownload []string
|
// PendingDownload []string
|
||||||
|
|
||||||
// LastDownloadAt holds the time when resources were downloaded the last time.
|
// // LastDownloadAt holds the time when resources were downloaded the last time.
|
||||||
LastDownloadAt *time.Time
|
// LastDownloadAt *time.Time
|
||||||
// LastDownloadError holds the error of the last download.
|
// // LastDownloadError holds the error of the last download.
|
||||||
LastDownloadError error
|
// LastDownloadError error
|
||||||
// LastDownload holds the resources that we downloaded the last time updates
|
// // LastDownload holds the resources that we downloaded the last time updates
|
||||||
// were downloaded.
|
// // were downloaded.
|
||||||
LastDownload []string
|
// LastDownload []string
|
||||||
|
|
||||||
// LastSuccessAt holds the time of the last successful update (check).
|
// // LastSuccessAt holds the time of the last successful update (check).
|
||||||
LastSuccessAt *time.Time
|
// LastSuccessAt *time.Time
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetState returns the current registry state.
|
// // GetState returns the current registry state.
|
||||||
// The returned data must not be modified.
|
// // The returned data must not be modified.
|
||||||
func (reg *ResourceRegistry) GetState() RegistryState {
|
// func (reg *ResourceRegistry) GetState() RegistryState {
|
||||||
reg.state.Lock()
|
// reg.state.Lock()
|
||||||
defer reg.state.Unlock()
|
// defer reg.state.Unlock()
|
||||||
|
|
||||||
return RegistryState{
|
// return RegistryState{
|
||||||
ID: reg.state.ID,
|
// ID: reg.state.ID,
|
||||||
Details: reg.state.Details,
|
// Details: reg.state.Details,
|
||||||
Updates: reg.state.Updates,
|
// Updates: reg.state.Updates,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// StartOperation starts an operation.
|
// // StartOperation starts an operation.
|
||||||
func (s *RegistryState) StartOperation(id string) bool {
|
// func (s *RegistryState) StartOperation(id string) bool {
|
||||||
defer s.notify()
|
// defer s.notify()
|
||||||
|
|
||||||
s.operationLock.Lock()
|
// s.operationLock.Lock()
|
||||||
|
|
||||||
s.Lock()
|
// s.Lock()
|
||||||
defer s.Unlock()
|
// defer s.Unlock()
|
||||||
|
|
||||||
s.ID = id
|
// s.ID = id
|
||||||
return true
|
// return true
|
||||||
}
|
// }
|
||||||
|
|
||||||
// UpdateOperationDetails updates the details of an operation.
|
// // UpdateOperationDetails updates the details of an operation.
|
||||||
// The supplied struct should be a copy and must not be changed after calling
|
// // The supplied struct should be a copy and must not be changed after calling
|
||||||
// this function.
|
// // this function.
|
||||||
func (s *RegistryState) UpdateOperationDetails(details any) {
|
// func (s *RegistryState) UpdateOperationDetails(details any) {
|
||||||
defer s.notify()
|
// defer s.notify()
|
||||||
|
|
||||||
s.Lock()
|
// s.Lock()
|
||||||
defer s.Unlock()
|
// defer s.Unlock()
|
||||||
|
|
||||||
s.Details = details
|
// s.Details = details
|
||||||
}
|
// }
|
||||||
|
|
||||||
// EndOperation ends an operation.
|
// // EndOperation ends an operation.
|
||||||
func (s *RegistryState) EndOperation() {
|
// func (s *RegistryState) EndOperation() {
|
||||||
defer s.notify()
|
// defer s.notify()
|
||||||
defer s.operationLock.Unlock()
|
// defer s.operationLock.Unlock()
|
||||||
|
|
||||||
s.Lock()
|
// s.Lock()
|
||||||
defer s.Unlock()
|
// defer s.Unlock()
|
||||||
|
|
||||||
s.ID = StateReady
|
// s.ID = StateReady
|
||||||
s.Details = nil
|
// s.Details = nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ReportUpdateCheck reports an update check to the registry state.
|
// // ReportUpdateCheck reports an update check to the registry state.
|
||||||
func (s *RegistryState) ReportUpdateCheck(pendingDownload []string, failed error) {
|
// func (s *RegistryState) ReportUpdateCheck(pendingDownload []string, failed error) {
|
||||||
defer s.notify()
|
// defer s.notify()
|
||||||
|
|
||||||
sort.Strings(pendingDownload)
|
// sort.Strings(pendingDownload)
|
||||||
|
|
||||||
s.Lock()
|
// s.Lock()
|
||||||
defer s.Unlock()
|
// defer s.Unlock()
|
||||||
|
|
||||||
now := time.Now()
|
// now := time.Now()
|
||||||
s.Updates.LastCheckAt = &now
|
// s.Updates.LastCheckAt = &now
|
||||||
s.Updates.LastCheckError = failed
|
// s.Updates.LastCheckError = failed
|
||||||
s.Updates.PendingDownload = pendingDownload
|
// s.Updates.PendingDownload = pendingDownload
|
||||||
|
|
||||||
if failed == nil {
|
// if failed == nil {
|
||||||
s.Updates.LastSuccessAt = &now
|
// s.Updates.LastSuccessAt = &now
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// ReportDownloads reports downloaded updates to the registry state.
|
// // ReportDownloads reports downloaded updates to the registry state.
|
||||||
func (s *RegistryState) ReportDownloads(downloaded []string, failed error) {
|
// func (s *RegistryState) ReportDownloads(downloaded []string, failed error) {
|
||||||
defer s.notify()
|
// defer s.notify()
|
||||||
|
|
||||||
sort.Strings(downloaded)
|
// sort.Strings(downloaded)
|
||||||
|
|
||||||
s.Lock()
|
// s.Lock()
|
||||||
defer s.Unlock()
|
// defer s.Unlock()
|
||||||
|
|
||||||
now := time.Now()
|
// now := time.Now()
|
||||||
s.Updates.LastDownloadAt = &now
|
// s.Updates.LastDownloadAt = &now
|
||||||
s.Updates.LastDownloadError = failed
|
// s.Updates.LastDownloadError = failed
|
||||||
s.Updates.LastDownload = downloaded
|
// s.Updates.LastDownload = downloaded
|
||||||
|
|
||||||
// Remove downloaded resources from the pending list.
|
// // Remove downloaded resources from the pending list.
|
||||||
if len(s.Updates.PendingDownload) > 0 {
|
// if len(s.Updates.PendingDownload) > 0 {
|
||||||
newPendingDownload := make([]string, 0, len(s.Updates.PendingDownload))
|
// newPendingDownload := make([]string, 0, len(s.Updates.PendingDownload))
|
||||||
for _, pending := range s.Updates.PendingDownload {
|
// for _, pending := range s.Updates.PendingDownload {
|
||||||
if !utils.StringInSlice(downloaded, pending) {
|
// if !utils.StringInSlice(downloaded, pending) {
|
||||||
newPendingDownload = append(newPendingDownload, pending)
|
// newPendingDownload = append(newPendingDownload, pending)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
s.Updates.PendingDownload = newPendingDownload
|
// s.Updates.PendingDownload = newPendingDownload
|
||||||
}
|
// }
|
||||||
|
|
||||||
if failed == nil {
|
// if failed == nil {
|
||||||
s.Updates.LastSuccessAt = &now
|
// s.Updates.LastSuccessAt = &now
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s *RegistryState) notify() {
|
// func (s *RegistryState) notify() {
|
||||||
switch {
|
// switch {
|
||||||
case s.reg == nil:
|
// case s.reg == nil:
|
||||||
return
|
// return
|
||||||
case s.reg.StateNotifyFunc == nil:
|
// case s.reg.StateNotifyFunc == nil:
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
s.reg.StateNotifyFunc(s)
|
// s.reg.StateNotifyFunc(s)
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,272 +1,272 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
"errors"
|
// "errors"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"io/fs"
|
// "io/fs"
|
||||||
"net/http"
|
// "net/http"
|
||||||
"os"
|
// "os"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"github.com/safing/jess/filesig"
|
// "github.com/safing/jess/filesig"
|
||||||
"github.com/safing/jess/lhash"
|
// "github.com/safing/jess/lhash"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/utils"
|
// "github.com/safing/portmaster/base/utils"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// ScanStorage scans root within the storage dir and adds found
|
// // ScanStorage scans root within the storage dir and adds found
|
||||||
// resources to the registry. If an error occurred, it is logged
|
// // resources to the registry. If an error occurred, it is logged
|
||||||
// and the last error is returned. Everything that was found
|
// // and the last error is returned. Everything that was found
|
||||||
// despite errors is added to the registry anyway. Leave root
|
// // despite errors is added to the registry anyway. Leave root
|
||||||
// empty to scan the full storage dir.
|
// // empty to scan the full storage dir.
|
||||||
func (reg *ResourceRegistry) ScanStorage(root string) error {
|
// func (reg *ResourceRegistry) ScanStorage(root string) error {
|
||||||
var lastError error
|
// var lastError error
|
||||||
|
|
||||||
// prep root
|
// // prep root
|
||||||
if root == "" {
|
// if root == "" {
|
||||||
root = reg.storageDir.Path
|
// root = reg.storageDir.Path
|
||||||
} else {
|
// } else {
|
||||||
var err error
|
// var err error
|
||||||
root, err = filepath.Abs(root)
|
// root, err = filepath.Abs(root)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
if !strings.HasPrefix(root, reg.storageDir.Path) {
|
// if !strings.HasPrefix(root, reg.storageDir.Path) {
|
||||||
return errors.New("supplied scan root path not within storage")
|
// return errors.New("supplied scan root path not within storage")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// walk fs
|
// // walk fs
|
||||||
_ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
// _ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
// skip tmp dir (including errors trying to read it)
|
// // skip tmp dir (including errors trying to read it)
|
||||||
if strings.HasPrefix(path, reg.tmpDir.Path) {
|
// if strings.HasPrefix(path, reg.tmpDir.Path) {
|
||||||
return filepath.SkipDir
|
// return filepath.SkipDir
|
||||||
}
|
// }
|
||||||
|
|
||||||
// handle walker error
|
// // handle walker error
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
lastError = fmt.Errorf("%s: could not read %s: %w", reg.Name, path, err)
|
// lastError = fmt.Errorf("%s: could not read %s: %w", reg.Name, path, err)
|
||||||
log.Warning(lastError.Error())
|
// log.Warning(lastError.Error())
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Ignore file signatures.
|
// // Ignore file signatures.
|
||||||
if strings.HasSuffix(path, filesig.Extension) {
|
// if strings.HasSuffix(path, filesig.Extension) {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// get relative path to storage
|
// // get relative path to storage
|
||||||
relativePath, err := filepath.Rel(reg.storageDir.Path, path)
|
// relativePath, err := filepath.Rel(reg.storageDir.Path, path)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
lastError = fmt.Errorf("%s: could not get relative path of %s: %w", reg.Name, path, err)
|
// lastError = fmt.Errorf("%s: could not get relative path of %s: %w", reg.Name, path, err)
|
||||||
log.Warning(lastError.Error())
|
// log.Warning(lastError.Error())
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// convert to identifier and version
|
// // convert to identifier and version
|
||||||
relativePath = filepath.ToSlash(relativePath)
|
// relativePath = filepath.ToSlash(relativePath)
|
||||||
identifier, version, ok := GetIdentifierAndVersion(relativePath)
|
// identifier, version, ok := GetIdentifierAndVersion(relativePath)
|
||||||
if !ok {
|
// if !ok {
|
||||||
// file does not conform to format
|
// // file does not conform to format
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// fully ignore directories that also have an identifier - these will be unpacked resources
|
// // fully ignore directories that also have an identifier - these will be unpacked resources
|
||||||
if info.IsDir() {
|
// if info.IsDir() {
|
||||||
return filepath.SkipDir
|
// return filepath.SkipDir
|
||||||
}
|
// }
|
||||||
|
|
||||||
// save
|
// // save
|
||||||
err = reg.AddResource(identifier, version, nil, true, false, false)
|
// err = reg.AddResource(identifier, version, nil, true, false, false)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
lastError = fmt.Errorf("%s: could not get add resource %s v%s: %w", reg.Name, identifier, version, err)
|
// lastError = fmt.Errorf("%s: could not get add resource %s v%s: %w", reg.Name, identifier, version, err)
|
||||||
log.Warning(lastError.Error())
|
// log.Warning(lastError.Error())
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
})
|
// })
|
||||||
|
|
||||||
return lastError
|
// return lastError
|
||||||
}
|
// }
|
||||||
|
|
||||||
// LoadIndexes loads the current release indexes from disk
|
// // LoadIndexes loads the current release indexes from disk
|
||||||
// or will fetch a new version if not available and the
|
// // or will fetch a new version if not available and the
|
||||||
// registry is marked as online.
|
// // registry is marked as online.
|
||||||
func (reg *ResourceRegistry) LoadIndexes(ctx context.Context) error {
|
// func (reg *ResourceRegistry) LoadIndexes(ctx context.Context) error {
|
||||||
var firstErr error
|
// var firstErr error
|
||||||
client := &http.Client{}
|
// client := &http.Client{}
|
||||||
for _, idx := range reg.getIndexes() {
|
// for _, idx := range reg.getIndexes() {
|
||||||
err := reg.loadIndexFile(idx)
|
// err := reg.loadIndexFile(idx)
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
log.Debugf("%s: loaded index %s", reg.Name, idx.Path)
|
// log.Debugf("%s: loaded index %s", reg.Name, idx.Path)
|
||||||
} else if reg.Online {
|
// } else if reg.Online {
|
||||||
// try to download the index file if a local disk version
|
// // try to download the index file if a local disk version
|
||||||
// does not exist or we don't have permission to read it.
|
// // does not exist or we don't have permission to read it.
|
||||||
if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) {
|
// if errors.Is(err, fs.ErrNotExist) || errors.Is(err, fs.ErrPermission) {
|
||||||
err = reg.downloadIndex(ctx, client, idx)
|
// err = reg.downloadIndex(ctx, client, idx)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err != nil && firstErr == nil {
|
// if err != nil && firstErr == nil {
|
||||||
firstErr = err
|
// firstErr = err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return firstErr
|
// return firstErr
|
||||||
}
|
// }
|
||||||
|
|
||||||
// getIndexes returns a copy of the index.
|
// // getIndexes returns a copy of the index.
|
||||||
// The indexes itself are references.
|
// // The indexes itself are references.
|
||||||
func (reg *ResourceRegistry) getIndexes() []*Index {
|
// func (reg *ResourceRegistry) getIndexes() []*Index {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
indexes := make([]*Index, len(reg.indexes))
|
// indexes := make([]*Index, len(reg.indexes))
|
||||||
copy(indexes, reg.indexes)
|
// copy(indexes, reg.indexes)
|
||||||
return indexes
|
// return indexes
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) loadIndexFile(idx *Index) error {
|
// func (reg *ResourceRegistry) loadIndexFile(idx *Index) error {
|
||||||
indexPath := filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path))
|
// indexPath := filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path))
|
||||||
indexData, err := os.ReadFile(indexPath)
|
// indexData, err := os.ReadFile(indexPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to read index file %s: %w", idx.Path, err)
|
// return fmt.Errorf("failed to read index file %s: %w", idx.Path, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Verify signature, if enabled.
|
// // Verify signature, if enabled.
|
||||||
if verifOpts := reg.GetVerificationOptions(idx.Path); verifOpts != nil {
|
// if verifOpts := reg.GetVerificationOptions(idx.Path); verifOpts != nil {
|
||||||
// Load and check signature.
|
// // Load and check signature.
|
||||||
verifiedHash, _, err := reg.loadAndVerifySigFile(verifOpts, indexPath+filesig.Extension)
|
// verifiedHash, _, err := reg.loadAndVerifySigFile(verifOpts, indexPath+filesig.Extension)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
switch verifOpts.DiskLoadPolicy {
|
// switch verifOpts.DiskLoadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return fmt.Errorf("failed to verify signature of index %s: %w", idx.Path, err)
|
// return fmt.Errorf("failed to verify signature of index %s: %w", idx.Path, err)
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: failed to verify signature of index %s: %s", reg.Name, idx.Path, err)
|
// log.Warningf("%s: failed to verify signature of index %s: %s", reg.Name, idx.Path, err)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: failed to verify signature of index %s: %s", reg.Name, idx.Path, err)
|
// log.Debugf("%s: failed to verify signature of index %s: %s", reg.Name, idx.Path, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if signature checksum matches the index data.
|
// // Check if signature checksum matches the index data.
|
||||||
if err == nil && !verifiedHash.Matches(indexData) {
|
// if err == nil && !verifiedHash.Matches(indexData) {
|
||||||
switch verifOpts.DiskLoadPolicy {
|
// switch verifOpts.DiskLoadPolicy {
|
||||||
case SignaturePolicyRequire:
|
// case SignaturePolicyRequire:
|
||||||
return fmt.Errorf("index file %s does not match signature", idx.Path)
|
// return fmt.Errorf("index file %s does not match signature", idx.Path)
|
||||||
case SignaturePolicyWarn:
|
// case SignaturePolicyWarn:
|
||||||
log.Warningf("%s: index file %s does not match signature", reg.Name, idx.Path)
|
// log.Warningf("%s: index file %s does not match signature", reg.Name, idx.Path)
|
||||||
case SignaturePolicyDisable:
|
// case SignaturePolicyDisable:
|
||||||
log.Debugf("%s: index file %s does not match signature", reg.Name, idx.Path)
|
// log.Debugf("%s: index file %s does not match signature", reg.Name, idx.Path)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Parse the index file.
|
// // Parse the index file.
|
||||||
indexFile, err := ParseIndexFile(indexData, idx.Channel, idx.LastRelease)
|
// indexFile, err := ParseIndexFile(indexData, idx.Channel, idx.LastRelease)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to parse index file %s: %w", idx.Path, err)
|
// return fmt.Errorf("failed to parse index file %s: %w", idx.Path, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Update last seen release.
|
// // Update last seen release.
|
||||||
idx.LastRelease = indexFile.Published
|
// idx.LastRelease = indexFile.Published
|
||||||
|
|
||||||
// Warn if there aren't any releases in the index.
|
// // Warn if there aren't any releases in the index.
|
||||||
if len(indexFile.Releases) == 0 {
|
// if len(indexFile.Releases) == 0 {
|
||||||
log.Debugf("%s: index %s has no releases", reg.Name, idx.Path)
|
// log.Debugf("%s: index %s has no releases", reg.Name, idx.Path)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Add index releases to available resources.
|
// // Add index releases to available resources.
|
||||||
err = reg.AddResources(indexFile.Releases, idx, false, true, idx.PreRelease)
|
// err = reg.AddResources(indexFile.Releases, idx, false, true, idx.PreRelease)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to add resource: %s", reg.Name, err)
|
// log.Warningf("%s: failed to add resource: %s", reg.Name, err)
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) loadAndVerifySigFile(verifOpts *VerificationOptions, sigFilePath string) (*lhash.LabeledHash, []byte, error) {
|
// func (reg *ResourceRegistry) loadAndVerifySigFile(verifOpts *VerificationOptions, sigFilePath string) (*lhash.LabeledHash, []byte, error) {
|
||||||
// Load signature file.
|
// // Load signature file.
|
||||||
sigFileData, err := os.ReadFile(sigFilePath)
|
// sigFileData, err := os.ReadFile(sigFilePath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to read signature file: %w", err)
|
// return nil, nil, fmt.Errorf("failed to read signature file: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Extract all signatures.
|
// // Extract all signatures.
|
||||||
sigs, err := filesig.ParseSigFile(sigFileData)
|
// sigs, err := filesig.ParseSigFile(sigFileData)
|
||||||
switch {
|
// switch {
|
||||||
case len(sigs) == 0 && err != nil:
|
// case len(sigs) == 0 && err != nil:
|
||||||
return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
// return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
||||||
case len(sigs) == 0:
|
// case len(sigs) == 0:
|
||||||
return nil, nil, errors.New("no signatures found in signature file")
|
// return nil, nil, errors.New("no signatures found in signature file")
|
||||||
case err != nil:
|
// case err != nil:
|
||||||
return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
// return nil, nil, fmt.Errorf("failed to parse signature file: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Verify all signatures.
|
// // Verify all signatures.
|
||||||
var verifiedHash *lhash.LabeledHash
|
// var verifiedHash *lhash.LabeledHash
|
||||||
for _, sig := range sigs {
|
// for _, sig := range sigs {
|
||||||
fd, err := filesig.VerifyFileData(
|
// fd, err := filesig.VerifyFileData(
|
||||||
sig,
|
// sig,
|
||||||
nil,
|
// nil,
|
||||||
verifOpts.TrustStore,
|
// verifOpts.TrustStore,
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, sigFileData, err
|
// return nil, sigFileData, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Save or check verified hash.
|
// // Save or check verified hash.
|
||||||
if verifiedHash == nil {
|
// if verifiedHash == nil {
|
||||||
verifiedHash = fd.FileHash()
|
// verifiedHash = fd.FileHash()
|
||||||
} else if !fd.FileHash().Equal(verifiedHash) {
|
// } else if !fd.FileHash().Equal(verifiedHash) {
|
||||||
// Return an error if two valid hashes mismatch.
|
// // Return an error if two valid hashes mismatch.
|
||||||
// For simplicity, all hash algorithms must be the same for now.
|
// // For simplicity, all hash algorithms must be the same for now.
|
||||||
return nil, sigFileData, errors.New("file hashes from different signatures do not match")
|
// return nil, sigFileData, errors.New("file hashes from different signatures do not match")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return verifiedHash, sigFileData, nil
|
// return verifiedHash, sigFileData, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// CreateSymlinks creates a directory structure with unversioned symlinks to the given updates list.
|
// // CreateSymlinks creates a directory structure with unversioned symlinks to the given updates list.
|
||||||
func (reg *ResourceRegistry) CreateSymlinks(symlinkRoot *utils.DirStructure) error {
|
// func (reg *ResourceRegistry) CreateSymlinks(symlinkRoot *utils.DirStructure) error {
|
||||||
err := os.RemoveAll(symlinkRoot.Path)
|
// err := os.RemoveAll(symlinkRoot.Path)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to wipe symlink root: %w", err)
|
// return fmt.Errorf("failed to wipe symlink root: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = symlinkRoot.Ensure()
|
// err = symlinkRoot.Ensure()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to create symlink root: %w", err)
|
// return fmt.Errorf("failed to create symlink root: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
for _, res := range reg.resources {
|
// for _, res := range reg.resources {
|
||||||
if res.SelectedVersion == nil {
|
// if res.SelectedVersion == nil {
|
||||||
return fmt.Errorf("no selected version available for %s", res.Identifier)
|
// return fmt.Errorf("no selected version available for %s", res.Identifier)
|
||||||
}
|
// }
|
||||||
|
|
||||||
targetPath := res.SelectedVersion.storagePath()
|
// targetPath := res.SelectedVersion.storagePath()
|
||||||
linkPath := filepath.Join(symlinkRoot.Path, filepath.FromSlash(res.Identifier))
|
// linkPath := filepath.Join(symlinkRoot.Path, filepath.FromSlash(res.Identifier))
|
||||||
linkPathDir := filepath.Dir(linkPath)
|
// linkPathDir := filepath.Dir(linkPath)
|
||||||
|
|
||||||
err = symlinkRoot.EnsureAbsPath(linkPathDir)
|
// err = symlinkRoot.EnsureAbsPath(linkPathDir)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to create dir for link: %w", err)
|
// return fmt.Errorf("failed to create dir for link: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
relativeTargetPath, err := filepath.Rel(linkPathDir, targetPath)
|
// relativeTargetPath, err := filepath.Rel(linkPathDir, targetPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to get relative target path: %w", err)
|
// return fmt.Errorf("failed to get relative target path: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = os.Symlink(relativeTargetPath, linkPath)
|
// err = os.Symlink(relativeTargetPath, linkPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to link %s: %w", res.Identifier, err)
|
// return fmt.Errorf("failed to link %s: %w", res.Identifier, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,195 +1,195 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"archive/zip"
|
// "archive/zip"
|
||||||
"compress/gzip"
|
// "compress/gzip"
|
||||||
"errors"
|
// "errors"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"io"
|
// "io"
|
||||||
"io/fs"
|
// "io/fs"
|
||||||
"os"
|
// "os"
|
||||||
"path"
|
// "path"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
// "github.com/hashicorp/go-multierror"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/utils"
|
// "github.com/safing/portmaster/base/utils"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// MaxUnpackSize specifies the maximum size that will be unpacked.
|
// // MaxUnpackSize specifies the maximum size that will be unpacked.
|
||||||
const MaxUnpackSize = 1000000000 // 1GB
|
// const MaxUnpackSize = 1000000000 // 1GB
|
||||||
|
|
||||||
// UnpackGZIP unpacks a GZIP compressed reader r
|
// // UnpackGZIP unpacks a GZIP compressed reader r
|
||||||
// and returns a new reader. It's suitable to be
|
// // and returns a new reader. It's suitable to be
|
||||||
// used with registry.GetPackedFile.
|
// // used with registry.GetPackedFile.
|
||||||
func UnpackGZIP(r io.Reader) (io.Reader, error) {
|
// func UnpackGZIP(r io.Reader) (io.Reader, error) {
|
||||||
return gzip.NewReader(r)
|
// return gzip.NewReader(r)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// UnpackResources unpacks all resources defined in the AutoUnpack list.
|
// // UnpackResources unpacks all resources defined in the AutoUnpack list.
|
||||||
func (reg *ResourceRegistry) UnpackResources() error {
|
// func (reg *ResourceRegistry) UnpackResources() error {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
var multierr *multierror.Error
|
// var multierr *multierror.Error
|
||||||
for _, res := range reg.resources {
|
// for _, res := range reg.resources {
|
||||||
if utils.StringInSlice(reg.AutoUnpack, res.Identifier) {
|
// if utils.StringInSlice(reg.AutoUnpack, res.Identifier) {
|
||||||
err := res.UnpackArchive()
|
// err := res.UnpackArchive()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
multierr = multierror.Append(
|
// multierr = multierror.Append(
|
||||||
multierr,
|
// multierr,
|
||||||
fmt.Errorf("%s: %w", res.Identifier, err),
|
// fmt.Errorf("%s: %w", res.Identifier, err),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return multierr.ErrorOrNil()
|
// return multierr.ErrorOrNil()
|
||||||
}
|
// }
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
zipSuffix = ".zip"
|
// zipSuffix = ".zip"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// UnpackArchive unpacks the archive the resource refers to. The contents are
|
// // UnpackArchive unpacks the archive the resource refers to. The contents are
|
||||||
// unpacked into a directory with the same name as the file, excluding the
|
// // unpacked into a directory with the same name as the file, excluding the
|
||||||
// suffix. If the destination folder already exists, it is assumed that the
|
// // suffix. If the destination folder already exists, it is assumed that the
|
||||||
// contents have already been correctly unpacked.
|
// // contents have already been correctly unpacked.
|
||||||
func (res *Resource) UnpackArchive() error {
|
// func (res *Resource) UnpackArchive() error {
|
||||||
res.Lock()
|
// res.Lock()
|
||||||
defer res.Unlock()
|
// defer res.Unlock()
|
||||||
|
|
||||||
// Only unpack selected versions.
|
// // Only unpack selected versions.
|
||||||
if res.SelectedVersion == nil {
|
// if res.SelectedVersion == nil {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
switch {
|
// switch {
|
||||||
case strings.HasSuffix(res.Identifier, zipSuffix):
|
// case strings.HasSuffix(res.Identifier, zipSuffix):
|
||||||
return res.unpackZipArchive()
|
// return res.unpackZipArchive()
|
||||||
default:
|
// default:
|
||||||
return fmt.Errorf("unsupported file type for unpacking")
|
// return fmt.Errorf("unsupported file type for unpacking")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (res *Resource) unpackZipArchive() error {
|
// func (res *Resource) unpackZipArchive() error {
|
||||||
// Get file and directory paths.
|
// // Get file and directory paths.
|
||||||
archiveFile := res.SelectedVersion.storagePath()
|
// archiveFile := res.SelectedVersion.storagePath()
|
||||||
destDir := strings.TrimSuffix(archiveFile, zipSuffix)
|
// destDir := strings.TrimSuffix(archiveFile, zipSuffix)
|
||||||
tmpDir := filepath.Join(
|
// tmpDir := filepath.Join(
|
||||||
res.registry.tmpDir.Path,
|
// res.registry.tmpDir.Path,
|
||||||
filepath.FromSlash(strings.TrimSuffix(
|
// filepath.FromSlash(strings.TrimSuffix(
|
||||||
path.Base(res.SelectedVersion.versionedPath()),
|
// path.Base(res.SelectedVersion.versionedPath()),
|
||||||
zipSuffix,
|
// zipSuffix,
|
||||||
)),
|
// )),
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Check status of destination.
|
// // Check status of destination.
|
||||||
dstStat, err := os.Stat(destDir)
|
// dstStat, err := os.Stat(destDir)
|
||||||
switch {
|
// switch {
|
||||||
case errors.Is(err, fs.ErrNotExist):
|
// case errors.Is(err, fs.ErrNotExist):
|
||||||
// The destination does not exist, continue with unpacking.
|
// // The destination does not exist, continue with unpacking.
|
||||||
case err != nil:
|
// case err != nil:
|
||||||
return fmt.Errorf("cannot access destination for unpacking: %w", err)
|
// return fmt.Errorf("cannot access destination for unpacking: %w", err)
|
||||||
case !dstStat.IsDir():
|
// case !dstStat.IsDir():
|
||||||
return fmt.Errorf("destination for unpacking is blocked by file: %s", dstStat.Name())
|
// return fmt.Errorf("destination for unpacking is blocked by file: %s", dstStat.Name())
|
||||||
default:
|
// default:
|
||||||
// Archive already seems to be unpacked.
|
// // Archive already seems to be unpacked.
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Create the tmp directory for unpacking.
|
// // Create the tmp directory for unpacking.
|
||||||
err = res.registry.tmpDir.EnsureAbsPath(tmpDir)
|
// err = res.registry.tmpDir.EnsureAbsPath(tmpDir)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to create tmp dir for unpacking: %w", err)
|
// return fmt.Errorf("failed to create tmp dir for unpacking: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Defer clean up of directories.
|
// // Defer clean up of directories.
|
||||||
defer func() {
|
// defer func() {
|
||||||
// Always clean up the tmp dir.
|
// // Always clean up the tmp dir.
|
||||||
_ = os.RemoveAll(tmpDir)
|
// _ = os.RemoveAll(tmpDir)
|
||||||
// Cleanup the destination in case of an error.
|
// // Cleanup the destination in case of an error.
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
_ = os.RemoveAll(destDir)
|
// _ = os.RemoveAll(destDir)
|
||||||
}
|
// }
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
// Open the archive for reading.
|
// // Open the archive for reading.
|
||||||
var archiveReader *zip.ReadCloser
|
// var archiveReader *zip.ReadCloser
|
||||||
archiveReader, err = zip.OpenReader(archiveFile)
|
// archiveReader, err = zip.OpenReader(archiveFile)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to open zip reader: %w", err)
|
// return fmt.Errorf("failed to open zip reader: %w", err)
|
||||||
}
|
// }
|
||||||
defer func() {
|
// defer func() {
|
||||||
_ = archiveReader.Close()
|
// _ = archiveReader.Close()
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
// Save all files to the tmp dir.
|
// // Save all files to the tmp dir.
|
||||||
for _, file := range archiveReader.File {
|
// for _, file := range archiveReader.File {
|
||||||
err = copyFromZipArchive(
|
// err = copyFromZipArchive(
|
||||||
file,
|
// file,
|
||||||
filepath.Join(tmpDir, filepath.FromSlash(file.Name)),
|
// filepath.Join(tmpDir, filepath.FromSlash(file.Name)),
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to extract archive file %s: %w", file.Name, err)
|
// return fmt.Errorf("failed to extract archive file %s: %w", file.Name, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Make the final move.
|
// // Make the final move.
|
||||||
err = os.Rename(tmpDir, destDir)
|
// err = os.Rename(tmpDir, destDir)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to move the extracted archive from %s to %s: %w", tmpDir, destDir, err)
|
// return fmt.Errorf("failed to move the extracted archive from %s to %s: %w", tmpDir, destDir, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fix permissions on the destination dir.
|
// // Fix permissions on the destination dir.
|
||||||
err = res.registry.storageDir.EnsureAbsPath(destDir)
|
// err = res.registry.storageDir.EnsureAbsPath(destDir)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to apply directory permissions on %s: %w", destDir, err)
|
// return fmt.Errorf("failed to apply directory permissions on %s: %w", destDir, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Infof("%s: unpacked %s", res.registry.Name, res.SelectedVersion.versionedPath())
|
// log.Infof("%s: unpacked %s", res.registry.Name, res.SelectedVersion.versionedPath())
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func copyFromZipArchive(archiveFile *zip.File, dstPath string) error {
|
// func copyFromZipArchive(archiveFile *zip.File, dstPath string) error {
|
||||||
// If file is a directory, create it and continue.
|
// // If file is a directory, create it and continue.
|
||||||
if archiveFile.FileInfo().IsDir() {
|
// if archiveFile.FileInfo().IsDir() {
|
||||||
err := os.Mkdir(dstPath, archiveFile.Mode())
|
// err := os.Mkdir(dstPath, archiveFile.Mode())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to create directory %s: %w", dstPath, err)
|
// return fmt.Errorf("failed to create directory %s: %w", dstPath, err)
|
||||||
}
|
// }
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Open archived file for reading.
|
// // Open archived file for reading.
|
||||||
fileReader, err := archiveFile.Open()
|
// fileReader, err := archiveFile.Open()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to open file in archive: %w", err)
|
// return fmt.Errorf("failed to open file in archive: %w", err)
|
||||||
}
|
// }
|
||||||
defer func() {
|
// defer func() {
|
||||||
_ = fileReader.Close()
|
// _ = fileReader.Close()
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
// Open destination file for writing.
|
// // Open destination file for writing.
|
||||||
dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, archiveFile.Mode())
|
// dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, archiveFile.Mode())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to open destination file %s: %w", dstPath, err)
|
// return fmt.Errorf("failed to open destination file %s: %w", dstPath, err)
|
||||||
}
|
// }
|
||||||
defer func() {
|
// defer func() {
|
||||||
_ = dstFile.Close()
|
// _ = dstFile.Close()
|
||||||
}()
|
// }()
|
||||||
|
|
||||||
// Copy full file from archive to dst.
|
// // Copy full file from archive to dst.
|
||||||
if _, err := io.CopyN(dstFile, fileReader, MaxUnpackSize); err != nil {
|
// if _, err := io.CopyN(dstFile, fileReader, MaxUnpackSize); err != nil {
|
||||||
// EOF is expected here as the archive is likely smaller
|
// // EOF is expected here as the archive is likely smaller
|
||||||
// thane MaxUnpackSize
|
// // thane MaxUnpackSize
|
||||||
if errors.Is(err, io.EOF) {
|
// if errors.Is(err, io.EOF) {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,359 +1,359 @@
|
|||||||
package updater
|
package updater
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"net/http"
|
// "net/http"
|
||||||
"os"
|
// "os"
|
||||||
"path"
|
// "path"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"golang.org/x/exp/slices"
|
// "golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/safing/jess/filesig"
|
// "github.com/safing/jess/filesig"
|
||||||
"github.com/safing/jess/lhash"
|
// "github.com/safing/jess/lhash"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/utils"
|
// "github.com/safing/portmaster/base/utils"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// UpdateIndexes downloads all indexes. An error is only returned when all
|
// // UpdateIndexes downloads all indexes. An error is only returned when all
|
||||||
// indexes fail to update.
|
// // indexes fail to update.
|
||||||
func (reg *ResourceRegistry) UpdateIndexes(ctx context.Context) error {
|
// func (reg *ResourceRegistry) UpdateIndexes(ctx context.Context) error {
|
||||||
var lastErr error
|
// var lastErr error
|
||||||
var anySuccess bool
|
// var anySuccess bool
|
||||||
|
|
||||||
// Start registry operation.
|
// // Start registry operation.
|
||||||
reg.state.StartOperation(StateChecking)
|
// reg.state.StartOperation(StateChecking)
|
||||||
defer reg.state.EndOperation()
|
// defer reg.state.EndOperation()
|
||||||
|
|
||||||
client := &http.Client{}
|
// client := &http.Client{}
|
||||||
for _, idx := range reg.getIndexes() {
|
// for _, idx := range reg.getIndexes() {
|
||||||
if err := reg.downloadIndex(ctx, client, idx); err != nil {
|
// if err := reg.downloadIndex(ctx, client, idx); err != nil {
|
||||||
lastErr = err
|
// lastErr = err
|
||||||
log.Warningf("%s: failed to update index %s: %s", reg.Name, idx.Path, err)
|
// log.Warningf("%s: failed to update index %s: %s", reg.Name, idx.Path, err)
|
||||||
} else {
|
// } else {
|
||||||
anySuccess = true
|
// anySuccess = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// If all indexes failed to update, fail.
|
// // If all indexes failed to update, fail.
|
||||||
if !anySuccess {
|
// if !anySuccess {
|
||||||
err := fmt.Errorf("failed to update all indexes, last error was: %w", lastErr)
|
// err := fmt.Errorf("failed to update all indexes, last error was: %w", lastErr)
|
||||||
reg.state.ReportUpdateCheck(nil, err)
|
// reg.state.ReportUpdateCheck(nil, err)
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get pending resources and update status.
|
// // Get pending resources and update status.
|
||||||
pendingResourceVersions, _ := reg.GetPendingDownloads(true, false)
|
// pendingResourceVersions, _ := reg.GetPendingDownloads(true, false)
|
||||||
reg.state.ReportUpdateCheck(
|
// reg.state.ReportUpdateCheck(
|
||||||
humanInfoFromResourceVersions(pendingResourceVersions),
|
// humanInfoFromResourceVersions(pendingResourceVersions),
|
||||||
nil,
|
// nil,
|
||||||
)
|
// )
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (reg *ResourceRegistry) downloadIndex(ctx context.Context, client *http.Client, idx *Index) error {
|
// func (reg *ResourceRegistry) downloadIndex(ctx context.Context, client *http.Client, idx *Index) error {
|
||||||
var (
|
// var (
|
||||||
// Index.
|
// // Index.
|
||||||
indexErr error
|
// indexErr error
|
||||||
indexData []byte
|
// indexData []byte
|
||||||
downloadURL string
|
// downloadURL string
|
||||||
|
|
||||||
// Signature.
|
// // Signature.
|
||||||
sigErr error
|
// sigErr error
|
||||||
verifiedHash *lhash.LabeledHash
|
// verifiedHash *lhash.LabeledHash
|
||||||
sigFileData []byte
|
// sigFileData []byte
|
||||||
verifOpts = reg.GetVerificationOptions(idx.Path)
|
// verifOpts = reg.GetVerificationOptions(idx.Path)
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Upgrade to v2 index if verification is enabled.
|
// // Upgrade to v2 index if verification is enabled.
|
||||||
downloadIndexPath := idx.Path
|
// downloadIndexPath := idx.Path
|
||||||
if verifOpts != nil {
|
// if verifOpts != nil {
|
||||||
downloadIndexPath = strings.TrimSuffix(downloadIndexPath, baseIndexExtension) + v2IndexExtension
|
// downloadIndexPath = strings.TrimSuffix(downloadIndexPath, baseIndexExtension) + v2IndexExtension
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Download new index and signature.
|
// // Download new index and signature.
|
||||||
for tries := range 3 {
|
// for tries := range 3 {
|
||||||
// Index and signature need to be fetched together, so that they are
|
// // Index and signature need to be fetched together, so that they are
|
||||||
// fetched from the same source. One source should always have a matching
|
// // fetched from the same source. One source should always have a matching
|
||||||
// index and signature. Backup sources may be behind a little.
|
// // index and signature. Backup sources may be behind a little.
|
||||||
// If the signature verification fails, another source should be tried.
|
// // If the signature verification fails, another source should be tried.
|
||||||
|
|
||||||
// Get index data.
|
// // Get index data.
|
||||||
indexData, downloadURL, indexErr = reg.fetchData(ctx, client, downloadIndexPath, tries)
|
// indexData, downloadURL, indexErr = reg.fetchData(ctx, client, downloadIndexPath, tries)
|
||||||
if indexErr != nil {
|
// if indexErr != nil {
|
||||||
log.Debugf("%s: failed to fetch index %s: %s", reg.Name, downloadURL, indexErr)
|
// log.Debugf("%s: failed to fetch index %s: %s", reg.Name, downloadURL, indexErr)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get signature and verify it.
|
// // Get signature and verify it.
|
||||||
if verifOpts != nil {
|
// if verifOpts != nil {
|
||||||
verifiedHash, sigFileData, sigErr = reg.fetchAndVerifySigFile(
|
// verifiedHash, sigFileData, sigErr = reg.fetchAndVerifySigFile(
|
||||||
ctx, client,
|
// ctx, client,
|
||||||
verifOpts, downloadIndexPath+filesig.Extension, nil,
|
// verifOpts, downloadIndexPath+filesig.Extension, nil,
|
||||||
tries,
|
// tries,
|
||||||
)
|
// )
|
||||||
if sigErr != nil {
|
// if sigErr != nil {
|
||||||
log.Debugf("%s: failed to verify signature of %s: %s", reg.Name, downloadURL, sigErr)
|
// log.Debugf("%s: failed to verify signature of %s: %s", reg.Name, downloadURL, sigErr)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if the index matches the verified hash.
|
// // Check if the index matches the verified hash.
|
||||||
if verifiedHash.Matches(indexData) {
|
// if verifiedHash.Matches(indexData) {
|
||||||
log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
|
// log.Infof("%s: verified signature of %s", reg.Name, downloadURL)
|
||||||
} else {
|
// } else {
|
||||||
sigErr = ErrIndexChecksumMismatch
|
// sigErr = ErrIndexChecksumMismatch
|
||||||
log.Debugf("%s: checksum does not match file from %s", reg.Name, downloadURL)
|
// log.Debugf("%s: checksum does not match file from %s", reg.Name, downloadURL)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
if indexErr != nil {
|
// if indexErr != nil {
|
||||||
return fmt.Errorf("failed to fetch index %s: %w", downloadIndexPath, indexErr)
|
// return fmt.Errorf("failed to fetch index %s: %w", downloadIndexPath, indexErr)
|
||||||
}
|
// }
|
||||||
if sigErr != nil {
|
// if sigErr != nil {
|
||||||
return fmt.Errorf("failed to fetch or verify index %s signature: %w", downloadIndexPath, sigErr)
|
// return fmt.Errorf("failed to fetch or verify index %s signature: %w", downloadIndexPath, sigErr)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Parse the index file.
|
// // Parse the index file.
|
||||||
indexFile, err := ParseIndexFile(indexData, idx.Channel, idx.LastRelease)
|
// indexFile, err := ParseIndexFile(indexData, idx.Channel, idx.LastRelease)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to parse index %s: %w", idx.Path, err)
|
// return fmt.Errorf("failed to parse index %s: %w", idx.Path, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Add index data to registry.
|
// // Add index data to registry.
|
||||||
if len(indexFile.Releases) > 0 {
|
// if len(indexFile.Releases) > 0 {
|
||||||
// Check if all resources are within the indexes' authority.
|
// // Check if all resources are within the indexes' authority.
|
||||||
authoritativePath := path.Dir(idx.Path) + "/"
|
// authoritativePath := path.Dir(idx.Path) + "/"
|
||||||
if authoritativePath == "./" {
|
// if authoritativePath == "./" {
|
||||||
// Fix path for indexes at the storage root.
|
// // Fix path for indexes at the storage root.
|
||||||
authoritativePath = ""
|
// authoritativePath = ""
|
||||||
}
|
// }
|
||||||
cleanedData := make(map[string]string, len(indexFile.Releases))
|
// cleanedData := make(map[string]string, len(indexFile.Releases))
|
||||||
for key, version := range indexFile.Releases {
|
// for key, version := range indexFile.Releases {
|
||||||
if strings.HasPrefix(key, authoritativePath) {
|
// if strings.HasPrefix(key, authoritativePath) {
|
||||||
cleanedData[key] = version
|
// cleanedData[key] = version
|
||||||
} else {
|
// } else {
|
||||||
log.Warningf("%s: index %s oversteps it's authority by defining version for %s", reg.Name, idx.Path, key)
|
// log.Warningf("%s: index %s oversteps it's authority by defining version for %s", reg.Name, idx.Path, key)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// add resources to registry
|
// // add resources to registry
|
||||||
err = reg.AddResources(cleanedData, idx, false, true, idx.PreRelease)
|
// err = reg.AddResources(cleanedData, idx, false, true, idx.PreRelease)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to add resources: %s", reg.Name, err)
|
// log.Warningf("%s: failed to add resources: %s", reg.Name, err)
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
log.Debugf("%s: index %s is empty", reg.Name, idx.Path)
|
// log.Debugf("%s: index %s is empty", reg.Name, idx.Path)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if dest dir exists.
|
// // Check if dest dir exists.
|
||||||
indexDir := filepath.FromSlash(path.Dir(idx.Path))
|
// indexDir := filepath.FromSlash(path.Dir(idx.Path))
|
||||||
err = reg.storageDir.EnsureRelPath(indexDir)
|
// err = reg.storageDir.EnsureRelPath(indexDir)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to ensure directory for updated index %s: %s", reg.Name, idx.Path, err)
|
// log.Warningf("%s: failed to ensure directory for updated index %s: %s", reg.Name, idx.Path, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Index files must be readable by portmaster-staert with user permissions in order to load the index.
|
// // Index files must be readable by portmaster-staert with user permissions in order to load the index.
|
||||||
err = os.WriteFile( //nolint:gosec
|
// err = os.WriteFile( //nolint:gosec
|
||||||
filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path)),
|
// filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path)),
|
||||||
indexData, 0o0644,
|
// indexData, 0o0644,
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to save updated index %s: %s", reg.Name, idx.Path, err)
|
// log.Warningf("%s: failed to save updated index %s: %s", reg.Name, idx.Path, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Write signature file, if we have one.
|
// // Write signature file, if we have one.
|
||||||
if len(sigFileData) > 0 {
|
// if len(sigFileData) > 0 {
|
||||||
err = os.WriteFile( //nolint:gosec
|
// err = os.WriteFile( //nolint:gosec
|
||||||
filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path)+filesig.Extension),
|
// filepath.Join(reg.storageDir.Path, filepath.FromSlash(idx.Path)+filesig.Extension),
|
||||||
sigFileData, 0o0644,
|
// sigFileData, 0o0644,
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("%s: failed to save updated index signature %s: %s", reg.Name, idx.Path+filesig.Extension, err)
|
// log.Warningf("%s: failed to save updated index signature %s: %s", reg.Name, idx.Path+filesig.Extension, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Infof("%s: updated index %s with %d entries", reg.Name, idx.Path, len(indexFile.Releases))
|
// log.Infof("%s: updated index %s with %d entries", reg.Name, idx.Path, len(indexFile.Releases))
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// DownloadUpdates checks if updates are available and downloads updates of used components.
|
// // DownloadUpdates checks if updates are available and downloads updates of used components.
|
||||||
func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context, includeManual bool) error {
|
// func (reg *ResourceRegistry) DownloadUpdates(ctx context.Context, includeManual bool) error {
|
||||||
// Start registry operation.
|
// // Start registry operation.
|
||||||
reg.state.StartOperation(StateDownloading)
|
// reg.state.StartOperation(StateDownloading)
|
||||||
defer reg.state.EndOperation()
|
// defer reg.state.EndOperation()
|
||||||
|
|
||||||
// Get pending updates.
|
// // Get pending updates.
|
||||||
toUpdate, missingSigs := reg.GetPendingDownloads(includeManual, true)
|
// toUpdate, missingSigs := reg.GetPendingDownloads(includeManual, true)
|
||||||
downloadDetailsResources := humanInfoFromResourceVersions(toUpdate)
|
// downloadDetailsResources := humanInfoFromResourceVersions(toUpdate)
|
||||||
reg.state.UpdateOperationDetails(&StateDownloadingDetails{
|
// reg.state.UpdateOperationDetails(&StateDownloadingDetails{
|
||||||
Resources: downloadDetailsResources,
|
// Resources: downloadDetailsResources,
|
||||||
})
|
// })
|
||||||
|
|
||||||
// nothing to update
|
// // nothing to update
|
||||||
if len(toUpdate) == 0 && len(missingSigs) == 0 {
|
// if len(toUpdate) == 0 && len(missingSigs) == 0 {
|
||||||
log.Infof("%s: everything up to date", reg.Name)
|
// log.Infof("%s: everything up to date", reg.Name)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// check download dir
|
// // check download dir
|
||||||
if err := reg.tmpDir.Ensure(); err != nil {
|
// if err := reg.tmpDir.Ensure(); err != nil {
|
||||||
return fmt.Errorf("could not prepare tmp directory for download: %w", err)
|
// return fmt.Errorf("could not prepare tmp directory for download: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// download updates
|
// // download updates
|
||||||
log.Infof("%s: starting to download %d updates", reg.Name, len(toUpdate))
|
// log.Infof("%s: starting to download %d updates", reg.Name, len(toUpdate))
|
||||||
client := &http.Client{}
|
// client := &http.Client{}
|
||||||
var reportError error
|
// var reportError error
|
||||||
|
|
||||||
for i, rv := range toUpdate {
|
// for i, rv := range toUpdate {
|
||||||
log.Infof(
|
// log.Infof(
|
||||||
"%s: downloading update [%d/%d]: %s version %s",
|
// "%s: downloading update [%d/%d]: %s version %s",
|
||||||
reg.Name,
|
// reg.Name,
|
||||||
i+1, len(toUpdate),
|
// i+1, len(toUpdate),
|
||||||
rv.resource.Identifier, rv.VersionNumber,
|
// rv.resource.Identifier, rv.VersionNumber,
|
||||||
)
|
// )
|
||||||
var err error
|
// var err error
|
||||||
for tries := range 3 {
|
// for tries := range 3 {
|
||||||
err = reg.fetchFile(ctx, client, rv, tries)
|
// err = reg.fetchFile(ctx, client, rv, tries)
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
// Update resource version state.
|
// // Update resource version state.
|
||||||
rv.resource.Lock()
|
// rv.resource.Lock()
|
||||||
rv.Available = true
|
// rv.Available = true
|
||||||
if rv.resource.VerificationOptions != nil {
|
// if rv.resource.VerificationOptions != nil {
|
||||||
rv.SigAvailable = true
|
// rv.SigAvailable = true
|
||||||
}
|
// }
|
||||||
rv.resource.Unlock()
|
// rv.resource.Unlock()
|
||||||
|
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
reportError := fmt.Errorf("failed to download %s version %s: %w", rv.resource.Identifier, rv.VersionNumber, err)
|
// reportError := fmt.Errorf("failed to download %s version %s: %w", rv.resource.Identifier, rv.VersionNumber, err)
|
||||||
log.Warningf("%s: %s", reg.Name, reportError)
|
// log.Warningf("%s: %s", reg.Name, reportError)
|
||||||
}
|
// }
|
||||||
|
|
||||||
reg.state.UpdateOperationDetails(&StateDownloadingDetails{
|
// reg.state.UpdateOperationDetails(&StateDownloadingDetails{
|
||||||
Resources: downloadDetailsResources,
|
// Resources: downloadDetailsResources,
|
||||||
FinishedUpTo: i + 1,
|
// FinishedUpTo: i + 1,
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
if len(missingSigs) > 0 {
|
// if len(missingSigs) > 0 {
|
||||||
log.Infof("%s: downloading %d missing signatures", reg.Name, len(missingSigs))
|
// log.Infof("%s: downloading %d missing signatures", reg.Name, len(missingSigs))
|
||||||
|
|
||||||
for _, rv := range missingSigs {
|
// for _, rv := range missingSigs {
|
||||||
var err error
|
// var err error
|
||||||
for tries := range 3 {
|
// for tries := range 3 {
|
||||||
err = reg.fetchMissingSig(ctx, client, rv, tries)
|
// err = reg.fetchMissingSig(ctx, client, rv, tries)
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
// Update resource version state.
|
// // Update resource version state.
|
||||||
rv.resource.Lock()
|
// rv.resource.Lock()
|
||||||
rv.SigAvailable = true
|
// rv.SigAvailable = true
|
||||||
rv.resource.Unlock()
|
// rv.resource.Unlock()
|
||||||
|
|
||||||
break
|
// break
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
reportError := fmt.Errorf("failed to download missing sig of %s version %s: %w", rv.resource.Identifier, rv.VersionNumber, err)
|
// reportError := fmt.Errorf("failed to download missing sig of %s version %s: %w", rv.resource.Identifier, rv.VersionNumber, err)
|
||||||
log.Warningf("%s: %s", reg.Name, reportError)
|
// log.Warningf("%s: %s", reg.Name, reportError)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
reg.state.ReportDownloads(
|
// reg.state.ReportDownloads(
|
||||||
downloadDetailsResources,
|
// downloadDetailsResources,
|
||||||
reportError,
|
// reportError,
|
||||||
)
|
// )
|
||||||
log.Infof("%s: finished downloading updates", reg.Name)
|
// log.Infof("%s: finished downloading updates", reg.Name)
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// DownloadUpdates checks if updates are available and downloads updates of used components.
|
// // DownloadUpdates checks if updates are available and downloads updates of used components.
|
||||||
|
|
||||||
// GetPendingDownloads returns the list of pending downloads.
|
// // GetPendingDownloads returns the list of pending downloads.
|
||||||
// If manual is set, indexes with AutoDownload=false will be checked.
|
// // If manual is set, indexes with AutoDownload=false will be checked.
|
||||||
// If auto is set, indexes with AutoDownload=true will be checked.
|
// // If auto is set, indexes with AutoDownload=true will be checked.
|
||||||
func (reg *ResourceRegistry) GetPendingDownloads(manual, auto bool) (resources, sigs []*ResourceVersion) {
|
// func (reg *ResourceRegistry) GetPendingDownloads(manual, auto bool) (resources, sigs []*ResourceVersion) {
|
||||||
reg.RLock()
|
// reg.RLock()
|
||||||
defer reg.RUnlock()
|
// defer reg.RUnlock()
|
||||||
|
|
||||||
// create list of downloads
|
// // create list of downloads
|
||||||
var toUpdate []*ResourceVersion
|
// var toUpdate []*ResourceVersion
|
||||||
var missingSigs []*ResourceVersion
|
// var missingSigs []*ResourceVersion
|
||||||
|
|
||||||
for _, res := range reg.resources {
|
// for _, res := range reg.resources {
|
||||||
func() {
|
// func() {
|
||||||
res.Lock()
|
// res.Lock()
|
||||||
defer res.Unlock()
|
// defer res.Unlock()
|
||||||
|
|
||||||
// Skip resources without index or indexes that should not be reported
|
// // Skip resources without index or indexes that should not be reported
|
||||||
// according to parameters.
|
// // according to parameters.
|
||||||
switch {
|
// switch {
|
||||||
case res.Index == nil:
|
// case res.Index == nil:
|
||||||
// Cannot download if resource is not part of an index.
|
// // Cannot download if resource is not part of an index.
|
||||||
return
|
// return
|
||||||
case manual && !res.Index.AutoDownload:
|
// case manual && !res.Index.AutoDownload:
|
||||||
// Manual update report and index is not auto-download.
|
// // Manual update report and index is not auto-download.
|
||||||
case auto && res.Index.AutoDownload:
|
// case auto && res.Index.AutoDownload:
|
||||||
// Auto update report and index is auto-download.
|
// // Auto update report and index is auto-download.
|
||||||
default:
|
// default:
|
||||||
// Resource should not be reported.
|
// // Resource should not be reported.
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Skip resources we don't need.
|
// // Skip resources we don't need.
|
||||||
switch {
|
// switch {
|
||||||
case res.inUse():
|
// case res.inUse():
|
||||||
// Update if resource is in use.
|
// // Update if resource is in use.
|
||||||
case res.available():
|
// case res.available():
|
||||||
// Update if resource is available locally, ie. was used in the past.
|
// // Update if resource is available locally, ie. was used in the past.
|
||||||
case utils.StringInSlice(reg.MandatoryUpdates, res.Identifier):
|
// case utils.StringInSlice(reg.MandatoryUpdates, res.Identifier):
|
||||||
// Update is set as mandatory.
|
// // Update is set as mandatory.
|
||||||
default:
|
// default:
|
||||||
// Resource does not need to be updated.
|
// // Resource does not need to be updated.
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Go through all versions until we find versions that need updating.
|
// // Go through all versions until we find versions that need updating.
|
||||||
for _, rv := range res.Versions {
|
// for _, rv := range res.Versions {
|
||||||
switch {
|
// switch {
|
||||||
case !rv.CurrentRelease:
|
// case !rv.CurrentRelease:
|
||||||
// We are not interested in older releases.
|
// // We are not interested in older releases.
|
||||||
case !rv.Available:
|
// case !rv.Available:
|
||||||
// File not available locally, download!
|
// // File not available locally, download!
|
||||||
toUpdate = append(toUpdate, rv)
|
// toUpdate = append(toUpdate, rv)
|
||||||
case !rv.SigAvailable && res.VerificationOptions != nil:
|
// case !rv.SigAvailable && res.VerificationOptions != nil:
|
||||||
// File signature is not available and verification is enabled, download signature!
|
// // File signature is not available and verification is enabled, download signature!
|
||||||
missingSigs = append(missingSigs, rv)
|
// missingSigs = append(missingSigs, rv)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}()
|
// }()
|
||||||
}
|
// }
|
||||||
|
|
||||||
slices.SortFunc(toUpdate, func(a, b *ResourceVersion) int {
|
// slices.SortFunc(toUpdate, func(a, b *ResourceVersion) int {
|
||||||
return strings.Compare(a.resource.Identifier, b.resource.Identifier)
|
// return strings.Compare(a.resource.Identifier, b.resource.Identifier)
|
||||||
})
|
// })
|
||||||
slices.SortFunc(missingSigs, func(a, b *ResourceVersion) int {
|
// slices.SortFunc(missingSigs, func(a, b *ResourceVersion) int {
|
||||||
return strings.Compare(a.resource.Identifier, b.resource.Identifier)
|
// return strings.Compare(a.resource.Identifier, b.resource.Identifier)
|
||||||
})
|
// })
|
||||||
|
|
||||||
return toUpdate, missingSigs
|
// return toUpdate, missingSigs
|
||||||
}
|
// }
|
||||||
|
|
||||||
func humanInfoFromResourceVersions(resourceVersions []*ResourceVersion) []string {
|
// func humanInfoFromResourceVersions(resourceVersions []*ResourceVersion) []string {
|
||||||
identifiers := make([]string, len(resourceVersions))
|
// identifiers := make([]string, len(resourceVersions))
|
||||||
|
|
||||||
for i, rv := range resourceVersions {
|
// for i, rv := range resourceVersions {
|
||||||
identifiers[i] = fmt.Sprintf("%s v%s", rv.resource.Identifier, rv.VersionNumber)
|
// identifiers[i] = fmt.Sprintf("%s v%s", rv.resource.Identifier, rv.VersionNumber)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return identifiers
|
// return identifiers
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"github.com/safing/portmaster/base/config"
|
"github.com/safing/portmaster/base/config"
|
||||||
"github.com/safing/portmaster/service/intel/geoip"
|
"github.com/safing/portmaster/service/intel/geoip"
|
||||||
"github.com/safing/portmaster/service/netenv"
|
"github.com/safing/portmaster/service/netenv"
|
||||||
"github.com/safing/portmaster/service/updates"
|
|
||||||
"github.com/safing/portmaster/spn/access"
|
"github.com/safing/portmaster/spn/access"
|
||||||
"github.com/safing/portmaster/spn/access/account"
|
"github.com/safing/portmaster/spn/access/account"
|
||||||
"github.com/safing/portmaster/spn/captain"
|
"github.com/safing/portmaster/spn/captain"
|
||||||
@@ -18,18 +17,19 @@ var portmasterStarted = time.Now()
|
|||||||
func collectData() interface{} {
|
func collectData() interface{} {
|
||||||
data := make(map[string]interface{})
|
data := make(map[string]interface{})
|
||||||
|
|
||||||
|
// TODO(vladimir)
|
||||||
// Get data about versions.
|
// Get data about versions.
|
||||||
versions := updates.GetSimpleVersions()
|
// versions := updates.GetSimpleVersions()
|
||||||
data["Updates"] = versions
|
// data["Updates"] = versions
|
||||||
data["Version"] = versions.Build.Version
|
// data["Version"] = versions.Build.Version
|
||||||
numericVersion, err := MakeNumericVersion(versions.Build.Version)
|
// numericVersion, err := MakeNumericVersion(versions.Build.Version)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
data["NumericVersion"] = &DataError{
|
// data["NumericVersion"] = &DataError{
|
||||||
Error: err,
|
// Error: err,
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
data["NumericVersion"] = numericVersion
|
// data["NumericVersion"] = numericVersion
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get data about install.
|
// Get data about install.
|
||||||
installInfo, err := GetInstallInfo()
|
installInfo, err := GetInstallInfo()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/safing/portmaster/base/database"
|
"github.com/safing/portmaster/base/database"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
|
"github.com/safing/portmaster/service/updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Broadcasts struct {
|
type Broadcasts struct {
|
||||||
@@ -91,4 +92,6 @@ func New(instance instance) (*Broadcasts, error) {
|
|||||||
return module, nil
|
return module, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type instance interface{}
|
type instance interface {
|
||||||
|
Updates() *updates.Updates
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/notifications"
|
"github.com/safing/portmaster/base/notifications"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/service/updates"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -68,7 +67,7 @@ type BroadcastNotification struct {
|
|||||||
|
|
||||||
func broadcastNotify(ctx *mgr.WorkerCtx) error {
|
func broadcastNotify(ctx *mgr.WorkerCtx) error {
|
||||||
// Get broadcast notifications file, load it from disk and parse it.
|
// Get broadcast notifications file, load it from disk and parse it.
|
||||||
broadcastsResource, err := updates.GetFile(broadcastsResourcePath)
|
broadcastsResource, err := module.instance.Updates().GetFile(broadcastsResourcePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get broadcast notifications update: %w", err)
|
return fmt.Errorf("failed to get broadcast notifications update: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ func debugInfo(ar *api.Request) (data []byte, err error) {
|
|||||||
config.AddToDebugInfo(di)
|
config.AddToDebugInfo(di)
|
||||||
|
|
||||||
// Detailed information.
|
// Detailed information.
|
||||||
updates.AddToDebugInfo(di)
|
// TODO(vladimir): updates.AddToDebugInfo(di)
|
||||||
compat.AddToDebugInfo(di)
|
compat.AddToDebugInfo(di)
|
||||||
module.instance.AddWorkerInfoToDebugInfo(di)
|
module.instance.AddWorkerInfoToDebugInfo(di)
|
||||||
di.AddGoroutineStack()
|
di.AddGoroutineStack()
|
||||||
|
|||||||
@@ -14,15 +14,14 @@ import (
|
|||||||
"github.com/safing/portmaster/base/database"
|
"github.com/safing/portmaster/base/database"
|
||||||
"github.com/safing/portmaster/base/database/record"
|
"github.com/safing/portmaster/base/database/record"
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/updater"
|
"github.com/safing/portmaster/service/updates/registry"
|
||||||
"github.com/safing/portmaster/service/updates"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
baseListFilePath = "intel/lists/base.dsdl"
|
baseListFilePath = "base.dsdl"
|
||||||
intermediateListFilePath = "intel/lists/intermediate.dsdl"
|
intermediateListFilePath = "intermediate.dsdl"
|
||||||
urgentListFilePath = "intel/lists/urgent.dsdl"
|
urgentListFilePath = "urgent.dsdl"
|
||||||
listIndexFilePath = "intel/lists/index.dsd"
|
listIndexFilePath = "index.dsd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// default bloomfilter element sizes (estimated).
|
// default bloomfilter element sizes (estimated).
|
||||||
@@ -40,9 +39,9 @@ var (
|
|||||||
filterListLock sync.RWMutex
|
filterListLock sync.RWMutex
|
||||||
|
|
||||||
// Updater files for tracking upgrades.
|
// Updater files for tracking upgrades.
|
||||||
baseFile *updater.File
|
baseFile *registry.File
|
||||||
intermediateFile *updater.File
|
intermediateFile *registry.File
|
||||||
urgentFile *updater.File
|
urgentFile *registry.File
|
||||||
|
|
||||||
filterListsLoaded chan struct{}
|
filterListsLoaded chan struct{}
|
||||||
)
|
)
|
||||||
@@ -56,11 +55,10 @@ var cache = database.NewInterface(&database.Options{
|
|||||||
// getFileFunc is the function used to get a file from
|
// getFileFunc is the function used to get a file from
|
||||||
// the updater. It's basically updates.GetFile and used
|
// the updater. It's basically updates.GetFile and used
|
||||||
// for unit testing.
|
// for unit testing.
|
||||||
type getFileFunc func(string) (*updater.File, error)
|
|
||||||
|
|
||||||
// getFile points to updates.GetFile but may be set to
|
// getFile points to updates.GetFile but may be set to
|
||||||
// something different during unit testing.
|
// something different during unit testing.
|
||||||
var getFile getFileFunc = updates.GetFile
|
// var getFile getFileFunc = registry.GetFile
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
filterListsLoaded = make(chan struct{})
|
filterListsLoaded = make(chan struct{})
|
||||||
@@ -79,7 +77,7 @@ func isLoaded() bool {
|
|||||||
|
|
||||||
// processListFile opens the latest version of file and decodes it's DSDL
|
// processListFile opens the latest version of file and decodes it's DSDL
|
||||||
// content. It calls processEntry for each decoded filterlists entry.
|
// content. It calls processEntry for each decoded filterlists entry.
|
||||||
func processListFile(ctx context.Context, filter *scopedBloom, file *updater.File) error {
|
func processListFile(ctx context.Context, filter *scopedBloom, file *registry.File) error {
|
||||||
f, err := os.Open(file.Path())
|
f, err := os.Open(file.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -4,14 +4,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/database"
|
"github.com/safing/portmaster/base/database"
|
||||||
"github.com/safing/portmaster/base/database/record"
|
"github.com/safing/portmaster/base/database/record"
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/updater"
|
"github.com/safing/portmaster/service/updates/registry"
|
||||||
"github.com/safing/portmaster/service/updates"
|
|
||||||
"github.com/safing/structures/dsd"
|
"github.com/safing/structures/dsd"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -164,7 +162,7 @@ func getListIndexFromCache() (*ListIndexFile, error) {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// listIndexUpdate must only be used by updateListIndex.
|
// listIndexUpdate must only be used by updateListIndex.
|
||||||
listIndexUpdate *updater.File
|
listIndexUpdate *registry.File
|
||||||
listIndexUpdateLock sync.Mutex
|
listIndexUpdateLock sync.Mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -177,24 +175,24 @@ func updateListIndex() error {
|
|||||||
case listIndexUpdate == nil:
|
case listIndexUpdate == nil:
|
||||||
// This is the first time this function is run, get updater file for index.
|
// This is the first time this function is run, get updater file for index.
|
||||||
var err error
|
var err error
|
||||||
listIndexUpdate, err = updates.GetFile(listIndexFilePath)
|
listIndexUpdate, err = module.instance.Updates().GetFile(listIndexFilePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the version in the cache is current.
|
// Check if the version in the cache is current.
|
||||||
index, err := getListIndexFromCache()
|
_, err = getListIndexFromCache()
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, database.ErrNotFound):
|
case errors.Is(err, database.ErrNotFound):
|
||||||
log.Info("filterlists: index not in cache, starting update")
|
log.Info("filterlists: index not in cache, starting update")
|
||||||
case err != nil:
|
case err != nil:
|
||||||
log.Warningf("filterlists: failed to load index from cache, starting update: %s", err)
|
log.Warningf("filterlists: failed to load index from cache, starting update: %s", err)
|
||||||
case !listIndexUpdate.EqualsVersion(strings.TrimPrefix(index.Version, "v")):
|
// case !listIndexUpdate.EqualsVersion(strings.TrimPrefix(index.Version, "v")):
|
||||||
log.Infof(
|
// log.Infof(
|
||||||
"filterlists: index from cache is outdated, starting update (%s != %s)",
|
// "filterlists: index from cache is outdated, starting update (%s != %s)",
|
||||||
strings.TrimPrefix(index.Version, "v"),
|
// strings.TrimPrefix(index.Version, "v"),
|
||||||
listIndexUpdate.Version(),
|
// listIndexUpdate.Version(),
|
||||||
)
|
// )
|
||||||
default:
|
default:
|
||||||
// List is in cache and current, there is nothing to do.
|
// List is in cache and current, there is nothing to do.
|
||||||
log.Debug("filterlists: index is up to date")
|
log.Debug("filterlists: index is up to date")
|
||||||
@@ -204,8 +202,8 @@ func updateListIndex() error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
case listIndexUpdate.UpgradeAvailable():
|
// case listIndexUpdate.UpgradeAvailable():
|
||||||
log.Info("filterlists: index update available, starting update")
|
// log.Info("filterlists: index update available, starting update")
|
||||||
default:
|
default:
|
||||||
// Index is loaded and no update is available, there is nothing to do.
|
// Index is loaded and no update is available, there is nothing to do.
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import (
|
|||||||
"github.com/safing/portmaster/base/database"
|
"github.com/safing/portmaster/base/database"
|
||||||
"github.com/safing/portmaster/base/database/query"
|
"github.com/safing/portmaster/base/database/query"
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/updater"
|
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
|
"github.com/safing/portmaster/service/updates/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
var updateInProgress = abool.New()
|
var updateInProgress = abool.New()
|
||||||
@@ -174,51 +174,51 @@ func removeAllObsoleteFilterEntries(wc *mgr.WorkerCtx) error {
|
|||||||
// getUpgradableFiles returns a slice of filterlists files
|
// getUpgradableFiles returns a slice of filterlists files
|
||||||
// that should be updated. The files MUST be updated and
|
// that should be updated. The files MUST be updated and
|
||||||
// processed in the returned order!
|
// processed in the returned order!
|
||||||
func getUpgradableFiles() ([]*updater.File, error) {
|
func getUpgradableFiles() ([]*registry.File, error) {
|
||||||
var updateOrder []*updater.File
|
var updateOrder []*registry.File
|
||||||
|
|
||||||
cacheDBInUse := isLoaded()
|
// cacheDBInUse := isLoaded()
|
||||||
|
|
||||||
if baseFile == nil || baseFile.UpgradeAvailable() || !cacheDBInUse {
|
// if baseFile == nil || !cacheDBInUse { // TODO(vladimir): || baseFile.UpgradeAvailable()
|
||||||
var err error
|
// var err error
|
||||||
baseFile, err = getFile(baseListFilePath)
|
// baseFile, err = module.instance.Updates().GetFile(baseListFilePath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
log.Tracef("intel/filterlists: base file needs update, selected version %s", baseFile.Version())
|
// log.Tracef("intel/filterlists: base file needs update, selected version %s", baseFile.Version())
|
||||||
updateOrder = append(updateOrder, baseFile)
|
// updateOrder = append(updateOrder, baseFile)
|
||||||
}
|
// }
|
||||||
|
|
||||||
if intermediateFile == nil || intermediateFile.UpgradeAvailable() || !cacheDBInUse {
|
// if intermediateFile == nil || intermediateFile.UpgradeAvailable() || !cacheDBInUse {
|
||||||
var err error
|
// var err error
|
||||||
intermediateFile, err = getFile(intermediateListFilePath)
|
// intermediateFile, err = getFile(intermediateListFilePath)
|
||||||
if err != nil && !errors.Is(err, updater.ErrNotFound) {
|
// if err != nil && !errors.Is(err, updater.ErrNotFound) {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
log.Tracef("intel/filterlists: intermediate file needs update, selected version %s", intermediateFile.Version())
|
// log.Tracef("intel/filterlists: intermediate file needs update, selected version %s", intermediateFile.Version())
|
||||||
updateOrder = append(updateOrder, intermediateFile)
|
// updateOrder = append(updateOrder, intermediateFile)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if urgentFile == nil || urgentFile.UpgradeAvailable() || !cacheDBInUse {
|
// if urgentFile == nil || urgentFile.UpgradeAvailable() || !cacheDBInUse {
|
||||||
var err error
|
// var err error
|
||||||
urgentFile, err = getFile(urgentListFilePath)
|
// urgentFile, err = getFile(urgentListFilePath)
|
||||||
if err != nil && !errors.Is(err, updater.ErrNotFound) {
|
// if err != nil && !errors.Is(err, updater.ErrNotFound) {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
log.Tracef("intel/filterlists: urgent file needs update, selected version %s", urgentFile.Version())
|
// log.Tracef("intel/filterlists: urgent file needs update, selected version %s", urgentFile.Version())
|
||||||
updateOrder = append(updateOrder, urgentFile)
|
// updateOrder = append(updateOrder, urgentFile)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return resolveUpdateOrder(updateOrder)
|
return resolveUpdateOrder(updateOrder)
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveUpdateOrder(updateOrder []*updater.File) ([]*updater.File, error) {
|
func resolveUpdateOrder(updateOrder []*registry.File) ([]*registry.File, error) {
|
||||||
// sort the update order by ascending version
|
// sort the update order by ascending version
|
||||||
sort.Sort(byAscVersion(updateOrder))
|
sort.Sort(byAscVersion(updateOrder))
|
||||||
log.Tracef("intel/filterlists: order of updates: %v", updateOrder)
|
log.Tracef("intel/filterlists: order of updates: %v", updateOrder)
|
||||||
@@ -258,7 +258,7 @@ func resolveUpdateOrder(updateOrder []*updater.File) ([]*updater.File, error) {
|
|||||||
return updateOrder[startAtIdx:], nil
|
return updateOrder[startAtIdx:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type byAscVersion []*updater.File
|
type byAscVersion []*registry.File
|
||||||
|
|
||||||
func (fs byAscVersion) Len() int { return len(fs) }
|
func (fs byAscVersion) Len() int { return len(fs) }
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ import (
|
|||||||
maxminddb "github.com/oschwald/maxminddb-golang"
|
maxminddb "github.com/oschwald/maxminddb-golang"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/updater"
|
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/service/updates"
|
"github.com/safing/portmaster/service/updates/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
var worker *updateWorker
|
var worker *updateWorker
|
||||||
@@ -22,13 +21,13 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
v4MMDBResource = "intel/geoip/geoipv4.mmdb.gz"
|
v4MMDBResource = "geoipv4.mmdb"
|
||||||
v6MMDBResource = "intel/geoip/geoipv6.mmdb.gz"
|
v6MMDBResource = "geoipv6.mmdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
type geoIPDB struct {
|
type geoIPDB struct {
|
||||||
*maxminddb.Reader
|
*maxminddb.Reader
|
||||||
file *updater.File
|
file *registry.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateBroadcaster stores a geoIPDB and provides synchronized
|
// updateBroadcaster stores a geoIPDB and provides synchronized
|
||||||
@@ -47,7 +46,7 @@ func (ub *updateBroadcaster) NeedsUpdate() bool {
|
|||||||
ub.rw.RLock()
|
ub.rw.RLock()
|
||||||
defer ub.rw.RUnlock()
|
defer ub.rw.RUnlock()
|
||||||
|
|
||||||
return ub.db == nil || ub.db.file.UpgradeAvailable()
|
return ub.db == nil // TODO(vladimir) is this needed: || ub.db.file.UpgradeAvailable()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplaceDatabase replaces (or initially sets) the mmdb database.
|
// ReplaceDatabase replaces (or initially sets) the mmdb database.
|
||||||
@@ -181,12 +180,12 @@ func (upd *updateWorker) run(ctx *mgr.WorkerCtx) error {
|
|||||||
func getGeoIPDB(resource string) (*geoIPDB, error) {
|
func getGeoIPDB(resource string) (*geoIPDB, error) {
|
||||||
log.Debugf("geoip: opening database %s", resource)
|
log.Debugf("geoip: opening database %s", resource)
|
||||||
|
|
||||||
file, unpackedPath, err := openAndUnpack(resource)
|
file, err := open(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reader, err := maxminddb.Open(unpackedPath)
|
reader, err := maxminddb.Open(file.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to open: %w", err)
|
return nil, fmt.Errorf("failed to open: %w", err)
|
||||||
}
|
}
|
||||||
@@ -198,16 +197,16 @@ func getGeoIPDB(resource string) (*geoIPDB, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func openAndUnpack(resource string) (*updater.File, string, error) {
|
func open(resource string) (*registry.File, error) {
|
||||||
f, err := updates.GetFile(resource)
|
f, err := module.instance.Updates().GetFile(resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", fmt.Errorf("getting file: %w", err)
|
return nil, fmt.Errorf("getting file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
unpacked, err := f.Unpack(".gz", updater.UnpackGZIP)
|
// unpacked, err := f.Unpack(".gz", updater.UnpackGZIP)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, "", fmt.Errorf("unpacking file: %w", err)
|
// return nil, "", fmt.Errorf("unpacking file: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
return f, unpacked, nil
|
return f, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
|
"github.com/safing/portmaster/service/updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Event Names.
|
// Event Names.
|
||||||
@@ -105,4 +106,6 @@ func New(instance instance) (*NetEnv, error) {
|
|||||||
return module, nil
|
return module, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type instance interface{}
|
type instance interface {
|
||||||
|
Updates() *updates.Updates
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/safing/portmaster/base/notifications"
|
"github.com/safing/portmaster/base/notifications"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/service/network/netutils"
|
"github.com/safing/portmaster/service/network/netutils"
|
||||||
"github.com/safing/portmaster/service/updates"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// OnlineStatus represent a state of connectivity to the Internet.
|
// OnlineStatus represent a state of connectivity to the Internet.
|
||||||
@@ -221,7 +220,7 @@ func updateOnlineStatus(status OnlineStatus, portalURL *url.URL, comment string)
|
|||||||
|
|
||||||
// Trigger update check when coming (semi) online.
|
// Trigger update check when coming (semi) online.
|
||||||
if Online() {
|
if Online() {
|
||||||
_ = updates.TriggerUpdate(false, false)
|
module.instance.Updates().EventResourcesUpdated.Submit(struct{}{})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import (
|
|||||||
"github.com/safing/portmaster/service/process"
|
"github.com/safing/portmaster/service/process"
|
||||||
"github.com/safing/portmaster/service/resolver"
|
"github.com/safing/portmaster/service/resolver"
|
||||||
"github.com/safing/portmaster/service/status"
|
"github.com/safing/portmaster/service/status"
|
||||||
"github.com/safing/portmaster/service/updates"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerAPIEndpoints() error {
|
func registerAPIEndpoints() error {
|
||||||
@@ -94,7 +93,7 @@ func debugInfo(ar *api.Request) (data []byte, err error) {
|
|||||||
config.AddToDebugInfo(di)
|
config.AddToDebugInfo(di)
|
||||||
|
|
||||||
// Detailed information.
|
// Detailed information.
|
||||||
updates.AddToDebugInfo(di)
|
// TODO(vladimir): updates.AddToDebugInfo(di)
|
||||||
// compat.AddToDebugInfo(di) // TODO: Cannot use due to interception import requirement which we don't want for SPN Hubs.
|
// compat.AddToDebugInfo(di) // TODO: Cannot use due to interception import requirement which we don't want for SPN Hubs.
|
||||||
di.AddGoroutineStack()
|
di.AddGoroutineStack()
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/safing/portmaster/base/dataroot"
|
"github.com/safing/portmaster/base/dataroot"
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
|
"github.com/safing/portmaster/service/updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
@@ -56,7 +57,10 @@ func (ui *UI) Stop() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var shimLoaded atomic.Bool
|
var (
|
||||||
|
shimLoaded atomic.Bool
|
||||||
|
module *UI
|
||||||
|
)
|
||||||
|
|
||||||
// New returns a new UI module.
|
// New returns a new UI module.
|
||||||
func New(instance instance) (*UI, error) {
|
func New(instance instance) (*UI, error) {
|
||||||
@@ -64,7 +68,7 @@ func New(instance instance) (*UI, error) {
|
|||||||
return nil, errors.New("only one instance allowed")
|
return nil, errors.New("only one instance allowed")
|
||||||
}
|
}
|
||||||
m := mgr.New("UI")
|
m := mgr.New("UI")
|
||||||
module := &UI{
|
module = &UI{
|
||||||
mgr: m,
|
mgr: m,
|
||||||
instance: instance,
|
instance: instance,
|
||||||
}
|
}
|
||||||
@@ -78,4 +82,5 @@ func New(instance instance) (*UI, error) {
|
|||||||
|
|
||||||
type instance interface {
|
type instance interface {
|
||||||
API() *api.API
|
API() *api.API
|
||||||
|
Updates() *updates.Updates
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ import (
|
|||||||
|
|
||||||
"github.com/safing/portmaster/base/api"
|
"github.com/safing/portmaster/base/api"
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/updater"
|
|
||||||
"github.com/safing/portmaster/base/utils"
|
"github.com/safing/portmaster/base/utils"
|
||||||
"github.com/safing/portmaster/service/updates"
|
"github.com/safing/portmaster/service/updates/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -92,9 +91,9 @@ func (bs *archiveServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get file from update system
|
// get file from update system
|
||||||
zipFile, err := updates.GetFile(fmt.Sprintf("ui/modules/%s.zip", moduleName))
|
zipFile, err := module.instance.Updates().GetFile(fmt.Sprintf("%s.zip", moduleName))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, updater.ErrNotFound) {
|
if errors.Is(err, registry.ErrNotFound) {
|
||||||
log.Tracef("ui: requested module %s does not exist", moduleName)
|
log.Tracef("ui: requested module %s does not exist", moduleName)
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,161 +1,161 @@
|
|||||||
package updates
|
package updates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
// "bytes"
|
||||||
"io"
|
// "io"
|
||||||
"net/http"
|
// "net/http"
|
||||||
"os"
|
// "os"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"github.com/ghodss/yaml"
|
// "github.com/ghodss/yaml"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/api"
|
// "github.com/safing/portmaster/base/api"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/utils"
|
// "github.com/safing/portmaster/base/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
apiPathCheckForUpdates = "updates/check"
|
apiPathCheckForUpdates = "updates/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerAPIEndpoints() error {
|
// func registerAPIEndpoints() error {
|
||||||
if err := api.RegisterEndpoint(api.Endpoint{
|
// if err := api.RegisterEndpoint(api.Endpoint{
|
||||||
Name: "Check for Updates",
|
// Name: "Check for Updates",
|
||||||
Description: "Checks if new versions are available. If automatic updates are enabled, they are also downloaded and applied.",
|
// Description: "Checks if new versions are available. If automatic updates are enabled, they are also downloaded and applied.",
|
||||||
Parameters: []api.Parameter{{
|
// Parameters: []api.Parameter{{
|
||||||
Method: http.MethodPost,
|
// Method: http.MethodPost,
|
||||||
Field: "download",
|
// Field: "download",
|
||||||
Value: "",
|
// Value: "",
|
||||||
Description: "Force downloading and applying of all updates, regardless of auto-update settings.",
|
// Description: "Force downloading and applying of all updates, regardless of auto-update settings.",
|
||||||
}},
|
// }},
|
||||||
Path: apiPathCheckForUpdates,
|
// Path: apiPathCheckForUpdates,
|
||||||
Write: api.PermitUser,
|
// Write: api.PermitUser,
|
||||||
ActionFunc: func(r *api.Request) (msg string, err error) {
|
// ActionFunc: func(r *api.Request) (msg string, err error) {
|
||||||
// Check if we should also download regardless of settings.
|
// // Check if we should also download regardless of settings.
|
||||||
downloadAll := r.URL.Query().Has("download")
|
// downloadAll := r.URL.Query().Has("download")
|
||||||
|
|
||||||
// Trigger update task.
|
// // Trigger update task.
|
||||||
err = TriggerUpdate(true, downloadAll)
|
// err = TriggerUpdate(true, downloadAll)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return "", err
|
// return "", err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Report how we triggered.
|
// // Report how we triggered.
|
||||||
if downloadAll {
|
// if downloadAll {
|
||||||
return "downloading all updates...", nil
|
// return "downloading all updates...", nil
|
||||||
}
|
// }
|
||||||
return "checking for updates...", nil
|
// return "checking for updates...", nil
|
||||||
},
|
// },
|
||||||
}); err != nil {
|
// }); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
if err := api.RegisterEndpoint(api.Endpoint{
|
// if err := api.RegisterEndpoint(api.Endpoint{
|
||||||
Name: "Get Resource",
|
// Name: "Get Resource",
|
||||||
Description: "Returns the requested resource from the udpate system",
|
// Description: "Returns the requested resource from the udpate system",
|
||||||
Path: `updates/get/{identifier:[A-Za-z0-9/\.\-_]{1,255}}`,
|
// Path: `updates/get/{identifier:[A-Za-z0-9/\.\-_]{1,255}}`,
|
||||||
Read: api.PermitUser,
|
// Read: api.PermitUser,
|
||||||
ReadMethod: http.MethodGet,
|
// ReadMethod: http.MethodGet,
|
||||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
// HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||||
// Get identifier from URL.
|
// // Get identifier from URL.
|
||||||
var identifier string
|
// var identifier string
|
||||||
if ar := api.GetAPIRequest(r); ar != nil {
|
// if ar := api.GetAPIRequest(r); ar != nil {
|
||||||
identifier = ar.URLVars["identifier"]
|
// identifier = ar.URLVars["identifier"]
|
||||||
}
|
// }
|
||||||
if identifier == "" {
|
// if identifier == "" {
|
||||||
http.Error(w, "no resource speicified", http.StatusBadRequest)
|
// http.Error(w, "no resource speicified", http.StatusBadRequest)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get resource.
|
// // Get resource.
|
||||||
resource, err := registry.GetFile(identifier)
|
// resource, err := registry.GetFile(identifier)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
// http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Open file for reading.
|
// // Open file for reading.
|
||||||
file, err := os.Open(resource.Path())
|
// file, err := os.Open(resource.Path())
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
defer file.Close() //nolint:errcheck,gosec
|
// defer file.Close() //nolint:errcheck,gosec
|
||||||
|
|
||||||
// Assign file to reader
|
// // Assign file to reader
|
||||||
var reader io.Reader = file
|
// var reader io.Reader = file
|
||||||
|
|
||||||
// Add version to header.
|
// // Add version to header.
|
||||||
w.Header().Set("Resource-Version", resource.Version())
|
// w.Header().Set("Resource-Version", resource.Version())
|
||||||
|
|
||||||
// Set Content-Type.
|
// // Set Content-Type.
|
||||||
contentType, _ := utils.MimeTypeByExtension(filepath.Ext(resource.Path()))
|
// contentType, _ := utils.MimeTypeByExtension(filepath.Ext(resource.Path()))
|
||||||
w.Header().Set("Content-Type", contentType)
|
// w.Header().Set("Content-Type", contentType)
|
||||||
|
|
||||||
// Check if the content type may be returned.
|
// // Check if the content type may be returned.
|
||||||
accept := r.Header.Get("Accept")
|
// accept := r.Header.Get("Accept")
|
||||||
if accept != "" {
|
// if accept != "" {
|
||||||
mimeTypes := strings.Split(accept, ",")
|
// mimeTypes := strings.Split(accept, ",")
|
||||||
// First, clean mime types.
|
// // First, clean mime types.
|
||||||
for i, mimeType := range mimeTypes {
|
// for i, mimeType := range mimeTypes {
|
||||||
mimeType = strings.TrimSpace(mimeType)
|
// mimeType = strings.TrimSpace(mimeType)
|
||||||
mimeType, _, _ = strings.Cut(mimeType, ";")
|
// mimeType, _, _ = strings.Cut(mimeType, ";")
|
||||||
mimeTypes[i] = mimeType
|
// mimeTypes[i] = mimeType
|
||||||
}
|
// }
|
||||||
// Second, check if we may return anything.
|
// // Second, check if we may return anything.
|
||||||
var acceptsAny bool
|
// var acceptsAny bool
|
||||||
for _, mimeType := range mimeTypes {
|
// for _, mimeType := range mimeTypes {
|
||||||
switch mimeType {
|
// switch mimeType {
|
||||||
case "*", "*/*":
|
// case "*", "*/*":
|
||||||
acceptsAny = true
|
// acceptsAny = true
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
// Third, check if we can convert.
|
// // Third, check if we can convert.
|
||||||
if !acceptsAny {
|
// if !acceptsAny {
|
||||||
var converted bool
|
// var converted bool
|
||||||
sourceType, _, _ := strings.Cut(contentType, ";")
|
// sourceType, _, _ := strings.Cut(contentType, ";")
|
||||||
findConvertiblePair:
|
// findConvertiblePair:
|
||||||
for _, mimeType := range mimeTypes {
|
// for _, mimeType := range mimeTypes {
|
||||||
switch {
|
// switch {
|
||||||
case sourceType == "application/yaml" && mimeType == "application/json":
|
// case sourceType == "application/yaml" && mimeType == "application/json":
|
||||||
yamlData, err := io.ReadAll(reader)
|
// yamlData, err := io.ReadAll(reader)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
jsonData, err := yaml.YAMLToJSON(yamlData)
|
// jsonData, err := yaml.YAMLToJSON(yamlData)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
reader = bytes.NewReader(jsonData)
|
// reader = bytes.NewReader(jsonData)
|
||||||
converted = true
|
// converted = true
|
||||||
break findConvertiblePair
|
// break findConvertiblePair
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// If we could not convert to acceptable format, return an error.
|
// // If we could not convert to acceptable format, return an error.
|
||||||
if !converted {
|
// if !converted {
|
||||||
http.Error(w, "conversion to requested format not supported", http.StatusNotAcceptable)
|
// http.Error(w, "conversion to requested format not supported", http.StatusNotAcceptable)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Write file.
|
// // Write file.
|
||||||
w.WriteHeader(http.StatusOK)
|
// w.WriteHeader(http.StatusOK)
|
||||||
if r.Method != http.MethodHead {
|
// if r.Method != http.MethodHead {
|
||||||
_, err = io.Copy(w, reader)
|
// _, err = io.Copy(w, reader)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Errorf("updates: failed to serve resource file: %s", err)
|
// log.Errorf("updates: failed to serve resource file: %s", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
}); err != nil {
|
// }); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"github.com/tevino/abool"
|
"github.com/tevino/abool"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/config"
|
"github.com/safing/portmaster/base/config"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
// "github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/service/updates/helper"
|
// "github.com/safing/portmaster/service/updates/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const cfgDevModeKey = "core/devMode"
|
const cfgDevModeKey = "core/devMode"
|
||||||
@@ -27,152 +27,152 @@ var (
|
|||||||
forceDownload = abool.New()
|
forceDownload = abool.New()
|
||||||
)
|
)
|
||||||
|
|
||||||
func registerConfig() error {
|
// func registerConfig() error {
|
||||||
err := config.Register(&config.Option{
|
// err := config.Register(&config.Option{
|
||||||
Name: "Release Channel",
|
// Name: "Release Channel",
|
||||||
Key: helper.ReleaseChannelKey,
|
// 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.`,
|
// 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,
|
// OptType: config.OptTypeString,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
// ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
// ReleaseLevel: config.ReleaseLevelStable,
|
||||||
RequiresRestart: true,
|
// RequiresRestart: true,
|
||||||
DefaultValue: helper.ReleaseChannelStable,
|
// DefaultValue: helper.ReleaseChannelStable,
|
||||||
PossibleValues: []config.PossibleValue{
|
// PossibleValues: []config.PossibleValue{
|
||||||
{
|
// {
|
||||||
Name: "Stable",
|
// Name: "Stable",
|
||||||
Description: "Production releases.",
|
// Description: "Production releases.",
|
||||||
Value: helper.ReleaseChannelStable,
|
// Value: helper.ReleaseChannelStable,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
Name: "Beta",
|
// Name: "Beta",
|
||||||
Description: "Production releases for testing new features that may break and cause interruption.",
|
// Description: "Production releases for testing new features that may break and cause interruption.",
|
||||||
Value: helper.ReleaseChannelBeta,
|
// Value: helper.ReleaseChannelBeta,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
Name: "Support",
|
// Name: "Support",
|
||||||
Description: "Support releases or version changes for troubleshooting. Only use temporarily and when instructed.",
|
// Description: "Support releases or version changes for troubleshooting. Only use temporarily and when instructed.",
|
||||||
Value: helper.ReleaseChannelSupport,
|
// Value: helper.ReleaseChannelSupport,
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
Name: "Staging",
|
// Name: "Staging",
|
||||||
Description: "Dangerous development releases for testing random things and experimenting. Only use temporarily and when instructed.",
|
// Description: "Dangerous development releases for testing random things and experimenting. Only use temporarily and when instructed.",
|
||||||
Value: helper.ReleaseChannelStaging,
|
// Value: helper.ReleaseChannelStaging,
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
Annotations: config.Annotations{
|
// Annotations: config.Annotations{
|
||||||
config.DisplayOrderAnnotation: -4,
|
// config.DisplayOrderAnnotation: -4,
|
||||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
// config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||||
config.CategoryAnnotation: "Updates",
|
// config.CategoryAnnotation: "Updates",
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = config.Register(&config.Option{
|
// err = config.Register(&config.Option{
|
||||||
Name: "Automatic Software Updates",
|
// Name: "Automatic Software Updates",
|
||||||
Key: enableSoftwareUpdatesKey,
|
// Key: enableSoftwareUpdatesKey,
|
||||||
Description: "Automatically check for and download software updates. This does not include intelligence data updates.",
|
// Description: "Automatically check for and download software updates. This does not include intelligence data updates.",
|
||||||
OptType: config.OptTypeBool,
|
// OptType: config.OptTypeBool,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
// ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
// ReleaseLevel: config.ReleaseLevelStable,
|
||||||
RequiresRestart: false,
|
// RequiresRestart: false,
|
||||||
DefaultValue: true,
|
// DefaultValue: true,
|
||||||
Annotations: config.Annotations{
|
// Annotations: config.Annotations{
|
||||||
config.DisplayOrderAnnotation: -12,
|
// config.DisplayOrderAnnotation: -12,
|
||||||
config.CategoryAnnotation: "Updates",
|
// config.CategoryAnnotation: "Updates",
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
err = config.Register(&config.Option{
|
// err = config.Register(&config.Option{
|
||||||
Name: "Automatic Intelligence Data Updates",
|
// Name: "Automatic Intelligence Data Updates",
|
||||||
Key: enableIntelUpdatesKey,
|
// 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.",
|
// 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,
|
// OptType: config.OptTypeBool,
|
||||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
// ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||||
ReleaseLevel: config.ReleaseLevelStable,
|
// ReleaseLevel: config.ReleaseLevelStable,
|
||||||
RequiresRestart: false,
|
// RequiresRestart: false,
|
||||||
DefaultValue: true,
|
// DefaultValue: true,
|
||||||
Annotations: config.Annotations{
|
// Annotations: config.Annotations{
|
||||||
config.DisplayOrderAnnotation: -11,
|
// config.DisplayOrderAnnotation: -11,
|
||||||
config.CategoryAnnotation: "Updates",
|
// config.CategoryAnnotation: "Updates",
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func initConfig() {
|
// func initConfig() {
|
||||||
releaseChannel = config.Concurrent.GetAsString(helper.ReleaseChannelKey, helper.ReleaseChannelStable)
|
// releaseChannel = config.Concurrent.GetAsString(helper.ReleaseChannelKey, helper.ReleaseChannelStable)
|
||||||
initialReleaseChannel = releaseChannel()
|
// initialReleaseChannel = releaseChannel()
|
||||||
previousReleaseChannel = releaseChannel()
|
// previousReleaseChannel = releaseChannel()
|
||||||
|
|
||||||
enableSoftwareUpdates = config.Concurrent.GetAsBool(enableSoftwareUpdatesKey, true)
|
// enableSoftwareUpdates = config.Concurrent.GetAsBool(enableSoftwareUpdatesKey, true)
|
||||||
enableIntelUpdates = config.Concurrent.GetAsBool(enableIntelUpdatesKey, true)
|
// enableIntelUpdates = config.Concurrent.GetAsBool(enableIntelUpdatesKey, true)
|
||||||
softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates()
|
// softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates()
|
||||||
intelUpdatesCurrentlyEnabled = enableIntelUpdates()
|
// intelUpdatesCurrentlyEnabled = enableIntelUpdates()
|
||||||
|
|
||||||
devMode = config.Concurrent.GetAsBool(cfgDevModeKey, false)
|
// devMode = config.Concurrent.GetAsBool(cfgDevModeKey, false)
|
||||||
previousDevMode = devMode()
|
// previousDevMode = devMode()
|
||||||
}
|
// }
|
||||||
|
|
||||||
func updateRegistryConfig(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
|
// func updateRegistryConfig(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
|
||||||
changed := false
|
// changed := false
|
||||||
|
|
||||||
if enableSoftwareUpdates() != softwareUpdatesCurrentlyEnabled {
|
// if enableSoftwareUpdates() != softwareUpdatesCurrentlyEnabled {
|
||||||
softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates()
|
// softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates()
|
||||||
changed = true
|
// changed = true
|
||||||
}
|
// }
|
||||||
|
|
||||||
if enableIntelUpdates() != intelUpdatesCurrentlyEnabled {
|
// if enableIntelUpdates() != intelUpdatesCurrentlyEnabled {
|
||||||
intelUpdatesCurrentlyEnabled = enableIntelUpdates()
|
// intelUpdatesCurrentlyEnabled = enableIntelUpdates()
|
||||||
changed = true
|
// changed = true
|
||||||
}
|
// }
|
||||||
|
|
||||||
if devMode() != previousDevMode {
|
// if devMode() != previousDevMode {
|
||||||
registry.SetDevMode(devMode())
|
// registry.SetDevMode(devMode())
|
||||||
previousDevMode = devMode()
|
// previousDevMode = devMode()
|
||||||
changed = true
|
// changed = true
|
||||||
}
|
// }
|
||||||
|
|
||||||
if releaseChannel() != previousReleaseChannel {
|
// if releaseChannel() != previousReleaseChannel {
|
||||||
previousReleaseChannel = releaseChannel()
|
// previousReleaseChannel = releaseChannel()
|
||||||
changed = true
|
// changed = true
|
||||||
}
|
// }
|
||||||
|
|
||||||
if changed {
|
// if changed {
|
||||||
// Update indexes based on new settings.
|
// // Update indexes based on new settings.
|
||||||
warning := helper.SetIndexes(
|
// warning := helper.SetIndexes(
|
||||||
registry,
|
// registry,
|
||||||
releaseChannel(),
|
// releaseChannel(),
|
||||||
true,
|
// true,
|
||||||
softwareUpdatesCurrentlyEnabled,
|
// softwareUpdatesCurrentlyEnabled,
|
||||||
intelUpdatesCurrentlyEnabled,
|
// intelUpdatesCurrentlyEnabled,
|
||||||
)
|
// )
|
||||||
if warning != nil {
|
// if warning != nil {
|
||||||
log.Warningf("updates: %s", warning)
|
// log.Warningf("updates: %s", warning)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Select versions depending on new indexes and modes.
|
// // Select versions depending on new indexes and modes.
|
||||||
registry.SelectVersions()
|
// registry.SelectVersions()
|
||||||
module.EventVersionsUpdated.Submit(struct{}{})
|
// module.EventVersionsUpdated.Submit(struct{}{})
|
||||||
|
|
||||||
if softwareUpdatesCurrentlyEnabled || intelUpdatesCurrentlyEnabled {
|
// if softwareUpdatesCurrentlyEnabled || intelUpdatesCurrentlyEnabled {
|
||||||
module.states.Clear()
|
// module.states.Clear()
|
||||||
if err := TriggerUpdate(true, false); err != nil {
|
// if err := TriggerUpdate(true, false); err != nil {
|
||||||
log.Warningf("updates: failed to trigger update: %s", err)
|
// log.Warningf("updates: failed to trigger update: %s", err)
|
||||||
}
|
// }
|
||||||
log.Infof("updates: automatic updates are now enabled")
|
// log.Infof("updates: automatic updates are now enabled")
|
||||||
} else {
|
// } else {
|
||||||
log.Warningf("updates: automatic updates are now completely disabled")
|
// log.Warningf("updates: automatic updates are now completely disabled")
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return false, nil
|
// return false, nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,238 +1,237 @@
|
|||||||
package updates
|
package updates
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"sort"
|
// "sort"
|
||||||
"strings"
|
// "sync"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/database/record"
|
// "github.com/safing/portmaster/base/database/record"
|
||||||
"github.com/safing/portmaster/base/info"
|
// "github.com/safing/portmaster/base/info"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/updater"
|
// "github.com/safing/portmaster/base/updater"
|
||||||
"github.com/safing/portmaster/base/utils/debug"
|
// "github.com/safing/portmaster/base/utils/debug"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
// "github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/service/updates/helper"
|
// "github.com/safing/portmaster/service/updates/helper"
|
||||||
)
|
// )
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
// versionsDBKey is the database key for update version information.
|
// // versionsDBKey is the database key for update version information.
|
||||||
versionsDBKey = "core:status/versions"
|
// versionsDBKey = "core:status/versions"
|
||||||
|
|
||||||
// versionsDBKey is the database key for simple update version information.
|
// // versionsDBKey is the database key for simple update version information.
|
||||||
simpleVersionsDBKey = "core:status/simple-versions"
|
// simpleVersionsDBKey = "core:status/simple-versions"
|
||||||
|
|
||||||
// updateStatusDBKey is the database key for update status information.
|
// // updateStatusDBKey is the database key for update status information.
|
||||||
updateStatusDBKey = "core:status/updates"
|
// updateStatusDBKey = "core:status/updates"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Versions holds update versions and status information.
|
// // Versions holds update versions and status information.
|
||||||
type Versions struct {
|
// type Versions struct {
|
||||||
record.Base
|
// record.Base
|
||||||
sync.Mutex
|
// sync.Mutex
|
||||||
|
|
||||||
Core *info.Info
|
// Core *info.Info
|
||||||
Resources map[string]*updater.Resource
|
// Resources map[string]*updater.Resource
|
||||||
Channel string
|
// Channel string
|
||||||
Beta bool
|
// Beta bool
|
||||||
Staging bool
|
// Staging bool
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SimpleVersions holds simplified update versions and status information.
|
// // SimpleVersions holds simplified update versions and status information.
|
||||||
type SimpleVersions struct {
|
// type SimpleVersions struct {
|
||||||
record.Base
|
// record.Base
|
||||||
sync.Mutex
|
// sync.Mutex
|
||||||
|
|
||||||
Build *info.Info
|
// Build *info.Info
|
||||||
Resources map[string]*SimplifiedResourceVersion
|
// Resources map[string]*SimplifiedResourceVersion
|
||||||
Channel string
|
// Channel string
|
||||||
}
|
// }
|
||||||
|
|
||||||
// SimplifiedResourceVersion holds version information about one resource.
|
// // SimplifiedResourceVersion holds version information about one resource.
|
||||||
type SimplifiedResourceVersion struct {
|
// type SimplifiedResourceVersion struct {
|
||||||
Version string
|
// Version string
|
||||||
}
|
// }
|
||||||
|
|
||||||
// UpdateStateExport is a wrapper to export the updates state.
|
// // UpdateStateExport is a wrapper to export the updates state.
|
||||||
type UpdateStateExport struct {
|
// type UpdateStateExport struct {
|
||||||
record.Base
|
// record.Base
|
||||||
sync.Mutex
|
// sync.Mutex
|
||||||
|
|
||||||
*updater.UpdateState
|
// *updater.UpdateState
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetVersions returns the update versions and status information.
|
// // GetVersions returns the update versions and status information.
|
||||||
// Resources must be locked when accessed.
|
// // Resources must be locked when accessed.
|
||||||
func GetVersions() *Versions {
|
// func GetVersions() *Versions {
|
||||||
return &Versions{
|
// return &Versions{
|
||||||
Core: info.GetInfo(),
|
// Core: info.GetInfo(),
|
||||||
Resources: registry.Export(),
|
// Resources: nil,
|
||||||
Channel: initialReleaseChannel,
|
// Channel: initialReleaseChannel,
|
||||||
Beta: initialReleaseChannel == helper.ReleaseChannelBeta,
|
// Beta: initialReleaseChannel == helper.ReleaseChannelBeta,
|
||||||
Staging: initialReleaseChannel == helper.ReleaseChannelStaging,
|
// Staging: initialReleaseChannel == helper.ReleaseChannelStaging,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetSimpleVersions returns the simplified update versions and status information.
|
// // GetSimpleVersions returns the simplified update versions and status information.
|
||||||
func GetSimpleVersions() *SimpleVersions {
|
// func GetSimpleVersions() *SimpleVersions {
|
||||||
// Fill base info.
|
// // Fill base info.
|
||||||
v := &SimpleVersions{
|
// v := &SimpleVersions{
|
||||||
Build: info.GetInfo(),
|
// Build: info.GetInfo(),
|
||||||
Resources: make(map[string]*SimplifiedResourceVersion),
|
// Resources: make(map[string]*SimplifiedResourceVersion),
|
||||||
Channel: initialReleaseChannel,
|
// Channel: initialReleaseChannel,
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Iterate through all versions and add version info.
|
// // Iterate through all versions and add version info.
|
||||||
for id, resource := range registry.Export() {
|
// // for id, resource := range registry.Export() {
|
||||||
func() {
|
// // func() {
|
||||||
resource.Lock()
|
// // resource.Lock()
|
||||||
defer resource.Unlock()
|
// // defer resource.Unlock()
|
||||||
|
|
||||||
// Get current in-used or selected version.
|
// // // Get current in-used or selected version.
|
||||||
var rv *updater.ResourceVersion
|
// // var rv *updater.ResourceVersion
|
||||||
switch {
|
// // switch {
|
||||||
case resource.ActiveVersion != nil:
|
// // case resource.ActiveVersion != nil:
|
||||||
rv = resource.ActiveVersion
|
// // rv = resource.ActiveVersion
|
||||||
case resource.SelectedVersion != nil:
|
// // case resource.SelectedVersion != nil:
|
||||||
rv = resource.SelectedVersion
|
// // rv = resource.SelectedVersion
|
||||||
}
|
// // }
|
||||||
|
|
||||||
// Get information from resource.
|
// // // Get information from resource.
|
||||||
if rv != nil {
|
// // if rv != nil {
|
||||||
v.Resources[id] = &SimplifiedResourceVersion{
|
// // v.Resources[id] = &SimplifiedResourceVersion{
|
||||||
Version: rv.VersionNumber,
|
// // Version: rv.VersionNumber,
|
||||||
}
|
// // }
|
||||||
}
|
// // }
|
||||||
}()
|
// // }()
|
||||||
}
|
// // }
|
||||||
|
|
||||||
return v
|
// return v
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetStateExport gets the update state from the registry and returns it in an
|
// // GetStateExport gets the update state from the registry and returns it in an
|
||||||
// exportable struct.
|
// // exportable struct.
|
||||||
func GetStateExport() *UpdateStateExport {
|
// func GetStateExport() *UpdateStateExport {
|
||||||
export := registry.GetState()
|
// // export := registry.GetState()
|
||||||
return &UpdateStateExport{
|
// return &UpdateStateExport{
|
||||||
UpdateState: &export.Updates,
|
// // UpdateState: &export.Updates,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// LoadStateExport loads the exported update state from the database.
|
// // LoadStateExport loads the exported update state from the database.
|
||||||
func LoadStateExport() (*UpdateStateExport, error) {
|
// func LoadStateExport() (*UpdateStateExport, error) {
|
||||||
r, err := db.Get(updateStatusDBKey)
|
// r, err := db.Get(updateStatusDBKey)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// unwrap
|
// // unwrap
|
||||||
if r.IsWrapped() {
|
// if r.IsWrapped() {
|
||||||
// only allocate a new struct, if we need it
|
// // only allocate a new struct, if we need it
|
||||||
newRecord := &UpdateStateExport{}
|
// newRecord := &UpdateStateExport{}
|
||||||
err = record.Unwrap(r, newRecord)
|
// err = record.Unwrap(r, newRecord)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
return newRecord, nil
|
// return newRecord, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// or adjust type
|
// // or adjust type
|
||||||
newRecord, ok := r.(*UpdateStateExport)
|
// newRecord, ok := r.(*UpdateStateExport)
|
||||||
if !ok {
|
// if !ok {
|
||||||
return nil, fmt.Errorf("record not of type *UpdateStateExport, but %T", r)
|
// return nil, fmt.Errorf("record not of type *UpdateStateExport, but %T", r)
|
||||||
}
|
// }
|
||||||
return newRecord, nil
|
// return newRecord, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func initVersionExport() (err error) {
|
// func initVersionExport() (err error) {
|
||||||
if err := GetVersions().save(); err != nil {
|
// if err := GetVersions().save(); err != nil {
|
||||||
log.Warningf("updates: failed to export version information: %s", err)
|
// log.Warningf("updates: failed to export version information: %s", err)
|
||||||
}
|
// }
|
||||||
if err := GetSimpleVersions().save(); err != nil {
|
// if err := GetSimpleVersions().save(); err != nil {
|
||||||
log.Warningf("updates: failed to export version information: %s", err)
|
// log.Warningf("updates: failed to export version information: %s", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
module.EventVersionsUpdated.AddCallback("export version status", export)
|
// // module.EventVersionsUpdated.AddCallback("export version status", export)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (v *Versions) save() error {
|
// func (v *Versions) save() error {
|
||||||
if !v.KeyIsSet() {
|
// if !v.KeyIsSet() {
|
||||||
v.SetKey(versionsDBKey)
|
// v.SetKey(versionsDBKey)
|
||||||
}
|
// }
|
||||||
return db.Put(v)
|
// return db.Put(v)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (v *SimpleVersions) save() error {
|
// func (v *SimpleVersions) save() error {
|
||||||
if !v.KeyIsSet() {
|
// if !v.KeyIsSet() {
|
||||||
v.SetKey(simpleVersionsDBKey)
|
// v.SetKey(simpleVersionsDBKey)
|
||||||
}
|
// }
|
||||||
return db.Put(v)
|
// return db.Put(v)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func (s *UpdateStateExport) save() error {
|
// func (s *UpdateStateExport) save() error {
|
||||||
if !s.KeyIsSet() {
|
// if !s.KeyIsSet() {
|
||||||
s.SetKey(updateStatusDBKey)
|
// s.SetKey(updateStatusDBKey)
|
||||||
}
|
// }
|
||||||
return db.Put(s)
|
// return db.Put(s)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// export is an event hook.
|
// // export is an event hook.
|
||||||
func export(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
|
// func export(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
|
||||||
// Export versions.
|
// // Export versions.
|
||||||
if err := GetVersions().save(); err != nil {
|
// if err := GetVersions().save(); err != nil {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
if err := GetSimpleVersions().save(); err != nil {
|
// if err := GetSimpleVersions().save(); err != nil {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
// Export udpate state.
|
// // Export udpate state.
|
||||||
if err := GetStateExport().save(); err != nil {
|
// if err := GetStateExport().save(); err != nil {
|
||||||
return false, err
|
// return false, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return false, nil
|
// return false, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// AddToDebugInfo adds the update system status to the given debug.Info.
|
// // AddToDebugInfo adds the update system status to the given debug.Info.
|
||||||
func AddToDebugInfo(di *debug.Info) {
|
// func AddToDebugInfo(di *debug.Info) {
|
||||||
// Get resources from registry.
|
// // Get resources from registry.
|
||||||
resources := registry.Export()
|
// // resources := registry.Export()
|
||||||
platformPrefix := helper.PlatformIdentifier("")
|
// // platformPrefix := helper.PlatformIdentifier("")
|
||||||
|
|
||||||
// Collect data for debug info.
|
// // Collect data for debug info.
|
||||||
var active, selected []string
|
// var active, selected []string
|
||||||
var activeCnt, totalCnt int
|
// var activeCnt, totalCnt int
|
||||||
for id, r := range resources {
|
// // for id, r := range resources {
|
||||||
// Ignore resources for other platforms.
|
// // // Ignore resources for other platforms.
|
||||||
if !strings.HasPrefix(id, "all/") && !strings.HasPrefix(id, platformPrefix) {
|
// // if !strings.HasPrefix(id, "all/") && !strings.HasPrefix(id, platformPrefix) {
|
||||||
continue
|
// // continue
|
||||||
}
|
// // }
|
||||||
|
|
||||||
totalCnt++
|
// // totalCnt++
|
||||||
if r.ActiveVersion != nil {
|
// // if r.ActiveVersion != nil {
|
||||||
activeCnt++
|
// // activeCnt++
|
||||||
active = append(active, fmt.Sprintf("%s: %s", id, r.ActiveVersion.VersionNumber))
|
// // active = append(active, fmt.Sprintf("%s: %s", id, r.ActiveVersion.VersionNumber))
|
||||||
}
|
// // }
|
||||||
if r.SelectedVersion != nil {
|
// // if r.SelectedVersion != nil {
|
||||||
selected = append(selected, fmt.Sprintf("%s: %s", id, r.SelectedVersion.VersionNumber))
|
// // selected = append(selected, fmt.Sprintf("%s: %s", id, r.SelectedVersion.VersionNumber))
|
||||||
}
|
// // }
|
||||||
}
|
// // }
|
||||||
sort.Strings(active)
|
// sort.Strings(active)
|
||||||
sort.Strings(selected)
|
// sort.Strings(selected)
|
||||||
|
|
||||||
// Compile to one list.
|
// // Compile to one list.
|
||||||
lines := make([]string, 0, len(active)+len(selected)+3)
|
// lines := make([]string, 0, len(active)+len(selected)+3)
|
||||||
lines = append(lines, "Active:")
|
// lines = append(lines, "Active:")
|
||||||
lines = append(lines, active...)
|
// lines = append(lines, active...)
|
||||||
lines = append(lines, "")
|
// lines = append(lines, "")
|
||||||
lines = append(lines, "Selected:")
|
// lines = append(lines, "Selected:")
|
||||||
lines = append(lines, selected...)
|
// lines = append(lines, selected...)
|
||||||
|
|
||||||
// Add section.
|
// // Add section.
|
||||||
di.AddSection(
|
// di.AddSection(
|
||||||
fmt.Sprintf("Updates: %s (%d/%d)", initialReleaseChannel, activeCnt, totalCnt),
|
// fmt.Sprintf("Updates: %s (%d/%d)", initialReleaseChannel, activeCnt, totalCnt),
|
||||||
debug.UseCodeSection|debug.AddContentLineBreaks,
|
// debug.UseCodeSection|debug.AddContentLineBreaks,
|
||||||
lines...,
|
// lines...,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,72 +1,65 @@
|
|||||||
package updates
|
package updates
|
||||||
|
|
||||||
import (
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/updater"
|
|
||||||
"github.com/safing/portmaster/service/updates/helper"
|
|
||||||
)
|
|
||||||
|
|
||||||
// GetPlatformFile returns the latest platform specific file identified by the given identifier.
|
// GetPlatformFile returns the latest platform specific file identified by the given identifier.
|
||||||
func GetPlatformFile(identifier string) (*updater.File, error) {
|
// func GetPlatformFile(identifier string) (*updater.File, error) {
|
||||||
identifier = helper.PlatformIdentifier(identifier)
|
// identifier = helper.PlatformIdentifier(identifier)
|
||||||
|
|
||||||
file, err := registry.GetFile(identifier)
|
// file, err := registry.GetFile(identifier)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
module.EventVersionsUpdated.Submit(struct{}{})
|
// module.EventVersionsUpdated.Submit(struct{}{})
|
||||||
return file, nil
|
// return file, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetFile returns the latest generic file identified by the given identifier.
|
// GetFile returns the latest generic file identified by the given identifier.
|
||||||
func GetFile(identifier string) (*updater.File, error) {
|
// func GetFile(identifier string) (*updater.File, error) {
|
||||||
identifier = path.Join("all", identifier)
|
// identifier = path.Join("all", identifier)
|
||||||
|
|
||||||
file, err := registry.GetFile(identifier)
|
// file, err := registry.GetFile(identifier)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
module.EventVersionsUpdated.Submit(struct{}{})
|
// module.EventVersionsUpdated.Submit(struct{}{})
|
||||||
return file, nil
|
// return file, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetPlatformVersion returns the selected platform specific version of the
|
// GetPlatformVersion returns the selected platform specific version of the
|
||||||
// given identifier.
|
// given identifier.
|
||||||
// The returned resource version may not be modified.
|
// The returned resource version may not be modified.
|
||||||
func GetPlatformVersion(identifier string) (*updater.ResourceVersion, error) {
|
// func GetPlatformVersion(identifier string) (*updater.ResourceVersion, error) {
|
||||||
identifier = helper.PlatformIdentifier(identifier)
|
// identifier = helper.PlatformIdentifier(identifier)
|
||||||
|
|
||||||
rv, err := registry.GetVersion(identifier)
|
// rv, err := registry.GetVersion(identifier)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return rv, nil
|
// return rv, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetVersion returns the selected generic version of the given identifier.
|
// GetVersion returns the selected generic version of the given identifier.
|
||||||
// The returned resource version may not be modified.
|
// The returned resource version may not be modified.
|
||||||
func GetVersion(identifier string) (*updater.ResourceVersion, error) {
|
// func GetVersion(identifier string) (*updater.ResourceVersion, error) {
|
||||||
identifier = path.Join("all", identifier)
|
// identifier = path.Join("all", identifier)
|
||||||
|
|
||||||
rv, err := registry.GetVersion(identifier)
|
// rv, err := registry.GetVersion(identifier)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return rv, nil
|
// return rv, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// GetVersionWithFullID returns the selected generic version of the given full identifier.
|
// GetVersionWithFullID returns the selected generic version of the given full identifier.
|
||||||
// The returned resource version may not be modified.
|
// The returned resource version may not be modified.
|
||||||
func GetVersionWithFullID(identifier string) (*updater.ResourceVersion, error) {
|
// func GetVersionWithFullID(identifier string) (*updater.ResourceVersion, error) {
|
||||||
rv, err := registry.GetVersion(identifier)
|
// rv, err := registry.GetVersion(identifier)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return rv, nil
|
// return rv, nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,57 +1,58 @@
|
|||||||
package helper
|
package helper
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"errors"
|
// "errors"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"os"
|
// "os"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
"runtime"
|
// "runtime"
|
||||||
"strings"
|
// "strings"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/updater"
|
// "github.com/safing/portmaster/base/updater"
|
||||||
)
|
// "github.com/safing/portmaster/service/updates/registry"
|
||||||
|
// )
|
||||||
|
|
||||||
var pmElectronUpdate *updater.File
|
// 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`
|
// 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
|
// // EnsureChromeSandboxPermissions makes sure the chrome-sandbox distributed
|
||||||
// by our app-electron package has the SUID bit set on systems that do not
|
// // by our app-electron package has the SUID bit set on systems that do not
|
||||||
// allow unprivileged CLONE_NEWUSER (clone(3)).
|
// // allow unprivileged CLONE_NEWUSER (clone(3)).
|
||||||
// On non-linux systems or systems that have kernel.unprivileged_userns_clone
|
// // On non-linux systems or systems that have kernel.unprivileged_userns_clone
|
||||||
// set to 1 EnsureChromeSandboPermissions is a NO-OP.
|
// // set to 1 EnsureChromeSandboPermissions is a NO-OP.
|
||||||
func EnsureChromeSandboxPermissions(reg *updater.ResourceRegistry) error {
|
// func EnsureChromeSandboxPermissions(reg *updater.ResourceRegistry) error {
|
||||||
if runtime.GOOS != "linux" {
|
// if runtime.GOOS != "linux" {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
if pmElectronUpdate != nil && !pmElectronUpdate.UpgradeAvailable() {
|
// if pmElectronUpdate != nil && !pmElectronUpdate.UpgradeAvailable() {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
identifier := PlatformIdentifier("app/portmaster-app.zip")
|
// identifier := PlatformIdentifier("app/portmaster-app.zip")
|
||||||
|
|
||||||
var err error
|
// var err error
|
||||||
pmElectronUpdate, err = reg.GetFile(identifier)
|
// pmElectronUpdate, err = reg.GetFile(identifier)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
if errors.Is(err, updater.ErrNotAvailableLocally) {
|
// if errors.Is(err, updater.ErrNotAvailableLocally) {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return fmt.Errorf("failed to get file: %w", err)
|
// return fmt.Errorf("failed to get file: %w", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
unpackedPath := strings.TrimSuffix(
|
// unpackedPath := strings.TrimSuffix(
|
||||||
pmElectronUpdate.Path(),
|
// pmElectronUpdate.Path(),
|
||||||
filepath.Ext(pmElectronUpdate.Path()),
|
// filepath.Ext(pmElectronUpdate.Path()),
|
||||||
)
|
// )
|
||||||
sandboxFile := filepath.Join(unpackedPath, "chrome-sandbox")
|
// sandboxFile := filepath.Join(unpackedPath, "chrome-sandbox")
|
||||||
if err := os.Chmod(sandboxFile, 0o0755|os.ModeSetuid); err != nil {
|
// if err := os.Chmod(sandboxFile, 0o0755|os.ModeSetuid); err != nil {
|
||||||
log.Errorf(suidBitWarning, 0o0755|os.ModeSetuid, sandboxFile)
|
// log.Errorf(suidBitWarning, 0o0755|os.ModeSetuid, sandboxFile)
|
||||||
|
|
||||||
return fmt.Errorf("failed to chmod: %w", err)
|
// return fmt.Errorf("failed to chmod: %w", err)
|
||||||
}
|
// }
|
||||||
log.Debugf("updates: fixed SUID permission for chrome-sandbox")
|
// log.Debugf("updates: fixed SUID permission for chrome-sandbox")
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,136 +1,136 @@
|
|||||||
package helper
|
package helper
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"errors"
|
// "errors"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"io/fs"
|
// "io/fs"
|
||||||
"os"
|
// "os"
|
||||||
"path/filepath"
|
// "path/filepath"
|
||||||
|
|
||||||
"github.com/safing/jess/filesig"
|
// "github.com/safing/jess/filesig"
|
||||||
"github.com/safing/portmaster/base/updater"
|
// "github.com/safing/portmaster/base/updater"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Release Channel Configuration Keys.
|
// // Release Channel Configuration Keys.
|
||||||
const (
|
// const (
|
||||||
ReleaseChannelKey = "core/releaseChannel"
|
// ReleaseChannelKey = "core/releaseChannel"
|
||||||
ReleaseChannelJSONKey = "core.releaseChannel"
|
// ReleaseChannelJSONKey = "core.releaseChannel"
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Release Channels.
|
// // Release Channels.
|
||||||
const (
|
// const (
|
||||||
ReleaseChannelStable = "stable"
|
// ReleaseChannelStable = "stable"
|
||||||
ReleaseChannelBeta = "beta"
|
// ReleaseChannelBeta = "beta"
|
||||||
ReleaseChannelStaging = "staging"
|
// ReleaseChannelStaging = "staging"
|
||||||
ReleaseChannelSupport = "support"
|
// ReleaseChannelSupport = "support"
|
||||||
)
|
// )
|
||||||
|
|
||||||
const jsonSuffix = ".json"
|
// const jsonSuffix = ".json"
|
||||||
|
|
||||||
// SetIndexes sets the update registry indexes and also configures the registry
|
// // SetIndexes sets the update registry indexes and also configures the registry
|
||||||
// to use pre-releases based on the channel.
|
// // to use pre-releases based on the channel.
|
||||||
func SetIndexes(
|
// func SetIndexes(
|
||||||
registry *updater.ResourceRegistry,
|
// registry *updater.ResourceRegistry,
|
||||||
releaseChannel string,
|
// releaseChannel string,
|
||||||
deleteUnusedIndexes bool,
|
// deleteUnusedIndexes bool,
|
||||||
autoDownload bool,
|
// autoDownload bool,
|
||||||
autoDownloadIntel bool,
|
// autoDownloadIntel bool,
|
||||||
) (warning error) {
|
// ) (warning error) {
|
||||||
usePreReleases := false
|
// usePreReleases := false
|
||||||
|
|
||||||
// Be reminded that the order is important, as indexes added later will
|
// // Be reminded that the order is important, as indexes added later will
|
||||||
// override the current release from earlier indexes.
|
// // override the current release from earlier indexes.
|
||||||
|
|
||||||
// Reset indexes before adding them (again).
|
// // Reset indexes before adding them (again).
|
||||||
registry.ResetIndexes()
|
// registry.ResetIndexes()
|
||||||
|
|
||||||
// Add the intel index first, in order to be able to override it with the
|
// // Add the intel index first, in order to be able to override it with the
|
||||||
// other indexes when needed.
|
// // other indexes when needed.
|
||||||
registry.AddIndex(updater.Index{
|
// registry.AddIndex(updater.Index{
|
||||||
Path: "all/intel/intel.json",
|
// Path: "all/intel/intel.json",
|
||||||
AutoDownload: autoDownloadIntel,
|
// AutoDownload: autoDownloadIntel,
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Always add the stable index as a base.
|
// // Always add the stable index as a base.
|
||||||
registry.AddIndex(updater.Index{
|
// registry.AddIndex(updater.Index{
|
||||||
Path: ReleaseChannelStable + jsonSuffix,
|
// Path: ReleaseChannelStable + jsonSuffix,
|
||||||
AutoDownload: autoDownload,
|
// AutoDownload: autoDownload,
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Add beta index if in beta or staging channel.
|
// // Add beta index if in beta or staging channel.
|
||||||
indexPath := ReleaseChannelBeta + jsonSuffix
|
// indexPath := ReleaseChannelBeta + jsonSuffix
|
||||||
if releaseChannel == ReleaseChannelBeta ||
|
// if releaseChannel == ReleaseChannelBeta ||
|
||||||
releaseChannel == ReleaseChannelStaging ||
|
// releaseChannel == ReleaseChannelStaging ||
|
||||||
(releaseChannel == "" && indexExists(registry, indexPath)) {
|
// (releaseChannel == "" && indexExists(registry, indexPath)) {
|
||||||
registry.AddIndex(updater.Index{
|
// registry.AddIndex(updater.Index{
|
||||||
Path: indexPath,
|
// Path: indexPath,
|
||||||
PreRelease: true,
|
// PreRelease: true,
|
||||||
AutoDownload: autoDownload,
|
// AutoDownload: autoDownload,
|
||||||
})
|
// })
|
||||||
usePreReleases = true
|
// usePreReleases = true
|
||||||
} else if deleteUnusedIndexes {
|
// } else if deleteUnusedIndexes {
|
||||||
err := deleteIndex(registry, indexPath)
|
// err := deleteIndex(registry, indexPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
|
// warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Add staging index if in staging channel.
|
// // Add staging index if in staging channel.
|
||||||
indexPath = ReleaseChannelStaging + jsonSuffix
|
// indexPath = ReleaseChannelStaging + jsonSuffix
|
||||||
if releaseChannel == ReleaseChannelStaging ||
|
// if releaseChannel == ReleaseChannelStaging ||
|
||||||
(releaseChannel == "" && indexExists(registry, indexPath)) {
|
// (releaseChannel == "" && indexExists(registry, indexPath)) {
|
||||||
registry.AddIndex(updater.Index{
|
// registry.AddIndex(updater.Index{
|
||||||
Path: indexPath,
|
// Path: indexPath,
|
||||||
PreRelease: true,
|
// PreRelease: true,
|
||||||
AutoDownload: autoDownload,
|
// AutoDownload: autoDownload,
|
||||||
})
|
// })
|
||||||
usePreReleases = true
|
// usePreReleases = true
|
||||||
} else if deleteUnusedIndexes {
|
// } else if deleteUnusedIndexes {
|
||||||
err := deleteIndex(registry, indexPath)
|
// err := deleteIndex(registry, indexPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
|
// warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Add support index if in support channel.
|
// // Add support index if in support channel.
|
||||||
indexPath = ReleaseChannelSupport + jsonSuffix
|
// indexPath = ReleaseChannelSupport + jsonSuffix
|
||||||
if releaseChannel == ReleaseChannelSupport ||
|
// if releaseChannel == ReleaseChannelSupport ||
|
||||||
(releaseChannel == "" && indexExists(registry, indexPath)) {
|
// (releaseChannel == "" && indexExists(registry, indexPath)) {
|
||||||
registry.AddIndex(updater.Index{
|
// registry.AddIndex(updater.Index{
|
||||||
Path: indexPath,
|
// Path: indexPath,
|
||||||
AutoDownload: autoDownload,
|
// AutoDownload: autoDownload,
|
||||||
})
|
// })
|
||||||
usePreReleases = true
|
// usePreReleases = true
|
||||||
} else if deleteUnusedIndexes {
|
// } else if deleteUnusedIndexes {
|
||||||
err := deleteIndex(registry, indexPath)
|
// err := deleteIndex(registry, indexPath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
|
// warning = fmt.Errorf("failed to delete unused index %s: %w", indexPath, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Set pre-release usage.
|
// // Set pre-release usage.
|
||||||
registry.SetUsePreReleases(usePreReleases)
|
// registry.SetUsePreReleases(usePreReleases)
|
||||||
|
|
||||||
return warning
|
// return warning
|
||||||
}
|
// }
|
||||||
|
|
||||||
func indexExists(registry *updater.ResourceRegistry, indexPath string) bool {
|
// func indexExists(registry *updater.ResourceRegistry, indexPath string) bool {
|
||||||
_, err := os.Stat(filepath.Join(registry.StorageDir().Path, indexPath))
|
// _, err := os.Stat(filepath.Join(registry.StorageDir().Path, indexPath))
|
||||||
return err == nil
|
// return err == nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func deleteIndex(registry *updater.ResourceRegistry, indexPath string) error {
|
// func deleteIndex(registry *updater.ResourceRegistry, indexPath string) error {
|
||||||
// Remove index itself.
|
// // Remove index itself.
|
||||||
err := os.Remove(filepath.Join(registry.StorageDir().Path, indexPath))
|
// err := os.Remove(filepath.Join(registry.StorageDir().Path, indexPath))
|
||||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
// if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Remove any accompanying signature.
|
// // Remove any accompanying signature.
|
||||||
err = os.Remove(filepath.Join(registry.StorageDir().Path, indexPath+filesig.Extension))
|
// err = os.Remove(filepath.Join(registry.StorageDir().Path, indexPath+filesig.Extension))
|
||||||
if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
// if err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
package helper
|
package helper
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/safing/jess"
|
// "github.com/safing/jess"
|
||||||
"github.com/safing/portmaster/base/updater"
|
// "github.com/safing/portmaster/base/updater"
|
||||||
)
|
// )
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
// VerificationConfig holds the complete verification configuration for the registry.
|
// // VerificationConfig holds the complete verification configuration for the registry.
|
||||||
VerificationConfig = map[string]*updater.VerificationOptions{
|
// VerificationConfig = map[string]*updater.VerificationOptions{
|
||||||
"": { // Default.
|
// "": { // Default.
|
||||||
TrustStore: BinarySigningTrustStore,
|
// TrustStore: BinarySigningTrustStore,
|
||||||
DownloadPolicy: updater.SignaturePolicyRequire,
|
// DownloadPolicy: updater.SignaturePolicyRequire,
|
||||||
DiskLoadPolicy: updater.SignaturePolicyWarn,
|
// DiskLoadPolicy: updater.SignaturePolicyWarn,
|
||||||
},
|
// },
|
||||||
"all/intel/": nil, // Disable until IntelHub supports signing.
|
// "all/intel/": nil, // Disable until IntelHub supports signing.
|
||||||
}
|
// }
|
||||||
|
|
||||||
// BinarySigningKeys holds the signing keys in text format.
|
// // BinarySigningKeys holds the signing keys in text format.
|
||||||
BinarySigningKeys = []string{
|
// BinarySigningKeys = []string{
|
||||||
// Safing Code Signing Key #1
|
// // Safing Code Signing Key #1
|
||||||
"recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD",
|
// "recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD",
|
||||||
// Safing Code Signing Key #2
|
// // Safing Code Signing Key #2
|
||||||
"recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3",
|
// "recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3",
|
||||||
}
|
// }
|
||||||
|
|
||||||
// BinarySigningTrustStore is an in-memory trust store with the signing keys.
|
// // BinarySigningTrustStore is an in-memory trust store with the signing keys.
|
||||||
BinarySigningTrustStore = jess.NewMemTrustStore()
|
// BinarySigningTrustStore = jess.NewMemTrustStore()
|
||||||
)
|
// )
|
||||||
|
|
||||||
func init() {
|
// func init() {
|
||||||
for _, signingKey := range BinarySigningKeys {
|
// for _, signingKey := range BinarySigningKeys {
|
||||||
rcpt, err := jess.RecipientFromTextFormat(signingKey)
|
// rcpt, err := jess.RecipientFromTextFormat(signingKey)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
panic(err)
|
// panic(err)
|
||||||
}
|
// }
|
||||||
err = BinarySigningTrustStore.StoreSignet(rcpt)
|
// err = BinarySigningTrustStore.StoreSignet(rcpt)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
panic(err)
|
// panic(err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,95 +1,95 @@
|
|||||||
package helper
|
package helper
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"fmt"
|
// "fmt"
|
||||||
"runtime"
|
// "runtime"
|
||||||
|
|
||||||
"github.com/tevino/abool"
|
// "github.com/tevino/abool"
|
||||||
)
|
// )
|
||||||
|
|
||||||
const onWindows = runtime.GOOS == "windows"
|
// const onWindows = runtime.GOOS == "windows"
|
||||||
|
|
||||||
var intelOnly = abool.New()
|
// var intelOnly = abool.New()
|
||||||
|
|
||||||
// IntelOnly specifies that only intel data is mandatory.
|
// // IntelOnly specifies that only intel data is mandatory.
|
||||||
func IntelOnly() {
|
// func IntelOnly() {
|
||||||
intelOnly.Set()
|
// intelOnly.Set()
|
||||||
}
|
// }
|
||||||
|
|
||||||
// PlatformIdentifier converts identifier for the current platform.
|
// // PlatformIdentifier converts identifier for the current platform.
|
||||||
func PlatformIdentifier(identifier string) string {
|
// func PlatformIdentifier(identifier string) string {
|
||||||
// From https://golang.org/pkg/runtime/#GOARCH
|
// // From https://golang.org/pkg/runtime/#GOARCH
|
||||||
// GOOS is the running program's operating system target: one of darwin, freebsd, linux, and so on.
|
// // 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.
|
// // 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)
|
// return fmt.Sprintf("%s_%s/%s", runtime.GOOS, runtime.GOARCH, identifier)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// MandatoryUpdates returns mandatory updates that should be loaded on install
|
// // MandatoryUpdates returns mandatory updates that should be loaded on install
|
||||||
// or reset.
|
// // or reset.
|
||||||
func MandatoryUpdates() (identifiers []string) {
|
// func MandatoryUpdates() (identifiers []string) {
|
||||||
// Intel
|
// // Intel
|
||||||
identifiers = append(
|
// identifiers = append(
|
||||||
identifiers,
|
// identifiers,
|
||||||
|
|
||||||
// Filter lists data
|
// // Filter lists data
|
||||||
"all/intel/lists/index.dsd",
|
// "all/intel/lists/index.dsd",
|
||||||
"all/intel/lists/base.dsdl",
|
// "all/intel/lists/base.dsdl",
|
||||||
"all/intel/lists/intermediate.dsdl",
|
// "all/intel/lists/intermediate.dsdl",
|
||||||
"all/intel/lists/urgent.dsdl",
|
// "all/intel/lists/urgent.dsdl",
|
||||||
|
|
||||||
// Geo IP data
|
// // Geo IP data
|
||||||
"all/intel/geoip/geoipv4.mmdb.gz",
|
// "all/intel/geoip/geoipv4.mmdb.gz",
|
||||||
"all/intel/geoip/geoipv6.mmdb.gz",
|
// "all/intel/geoip/geoipv6.mmdb.gz",
|
||||||
)
|
// )
|
||||||
|
|
||||||
// Stop here if we only want intel data.
|
// // Stop here if we only want intel data.
|
||||||
if intelOnly.IsSet() {
|
// if intelOnly.IsSet() {
|
||||||
return identifiers
|
// return identifiers
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Binaries
|
// // Binaries
|
||||||
if onWindows {
|
// if onWindows {
|
||||||
identifiers = append(
|
// identifiers = append(
|
||||||
identifiers,
|
// identifiers,
|
||||||
PlatformIdentifier("core/portmaster-core.exe"),
|
// PlatformIdentifier("core/portmaster-core.exe"),
|
||||||
PlatformIdentifier("kext/portmaster-kext.sys"),
|
// PlatformIdentifier("kext/portmaster-kext.sys"),
|
||||||
PlatformIdentifier("kext/portmaster-kext.pdb"),
|
// PlatformIdentifier("kext/portmaster-kext.pdb"),
|
||||||
PlatformIdentifier("start/portmaster-start.exe"),
|
// PlatformIdentifier("start/portmaster-start.exe"),
|
||||||
PlatformIdentifier("notifier/portmaster-notifier.exe"),
|
// PlatformIdentifier("notifier/portmaster-notifier.exe"),
|
||||||
PlatformIdentifier("notifier/portmaster-wintoast.dll"),
|
// PlatformIdentifier("notifier/portmaster-wintoast.dll"),
|
||||||
PlatformIdentifier("app2/portmaster-app.zip"),
|
// PlatformIdentifier("app2/portmaster-app.zip"),
|
||||||
)
|
// )
|
||||||
} else {
|
// } else {
|
||||||
identifiers = append(
|
// identifiers = append(
|
||||||
identifiers,
|
// identifiers,
|
||||||
PlatformIdentifier("core/portmaster-core"),
|
// PlatformIdentifier("core/portmaster-core"),
|
||||||
PlatformIdentifier("start/portmaster-start"),
|
// PlatformIdentifier("start/portmaster-start"),
|
||||||
PlatformIdentifier("notifier/portmaster-notifier"),
|
// PlatformIdentifier("notifier/portmaster-notifier"),
|
||||||
PlatformIdentifier("app2/portmaster-app"),
|
// PlatformIdentifier("app2/portmaster-app"),
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Components, Assets and Data
|
// // Components, Assets and Data
|
||||||
identifiers = append(
|
// identifiers = append(
|
||||||
identifiers,
|
// identifiers,
|
||||||
|
|
||||||
// User interface components
|
// // User interface components
|
||||||
PlatformIdentifier("app/portmaster-app.zip"),
|
// PlatformIdentifier("app/portmaster-app.zip"),
|
||||||
"all/ui/modules/portmaster.zip",
|
// "all/ui/modules/portmaster.zip",
|
||||||
"all/ui/modules/assets.zip",
|
// "all/ui/modules/assets.zip",
|
||||||
)
|
// )
|
||||||
|
|
||||||
return identifiers
|
// return identifiers
|
||||||
}
|
// }
|
||||||
|
|
||||||
// AutoUnpackUpdates returns assets that need unpacking.
|
// // AutoUnpackUpdates returns assets that need unpacking.
|
||||||
func AutoUnpackUpdates() []string {
|
// func AutoUnpackUpdates() []string {
|
||||||
if intelOnly.IsSet() {
|
// if intelOnly.IsSet() {
|
||||||
return []string{}
|
// return []string{}
|
||||||
}
|
// }
|
||||||
|
|
||||||
return []string{
|
// return []string{
|
||||||
PlatformIdentifier("app/portmaster-app.zip"),
|
// PlatformIdentifier("app/portmaster-app.zip"),
|
||||||
PlatformIdentifier("app2/portmaster-app.zip"),
|
// PlatformIdentifier("app2/portmaster-app.zip"),
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
package updates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UpdateIndex struct {
|
|
||||||
Directory string
|
|
||||||
DownloadDirectory string
|
|
||||||
Ignore []string
|
|
||||||
IndexURLs []string
|
|
||||||
IndexFile string
|
|
||||||
AutoApply bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UpdateIndex) downloadIndexFile() (err error) {
|
|
||||||
_ = os.MkdirAll(ui.Directory, defaultDirMode)
|
|
||||||
_ = os.MkdirAll(ui.DownloadDirectory, defaultDirMode)
|
|
||||||
for _, url := range ui.IndexURLs {
|
|
||||||
err = ui.downloadIndexFileFromURL(url)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("updates: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Downloading was successful.
|
|
||||||
err = nil
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UpdateIndex) checkForUpdates() (bool, error) {
|
|
||||||
err := ui.downloadIndexFile()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
currentBundle, err := ui.GetInstallBundle()
|
|
||||||
if err != nil {
|
|
||||||
return true, err // Current installed bundle not found, act as there is update.
|
|
||||||
}
|
|
||||||
updateBundle, err := ui.GetUpdateBundle()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return currentBundle.Version != updateBundle.Version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UpdateIndex) downloadIndexFileFromURL(url string) error {
|
|
||||||
client := http.Client{}
|
|
||||||
resp, err := client.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed a get request to %s: %w", url, err)
|
|
||||||
}
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
filePath := fmt.Sprintf("%s/%s", ui.DownloadDirectory, ui.IndexFile)
|
|
||||||
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, defaultFileMode)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer func() { _ = file.Close() }()
|
|
||||||
|
|
||||||
_, err = io.Copy(file, resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UpdateIndex) GetInstallBundle() (*Bundle, error) {
|
|
||||||
indexFile := fmt.Sprintf("%s/%s", ui.Directory, ui.IndexFile)
|
|
||||||
return ui.GetBundle(indexFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UpdateIndex) GetUpdateBundle() (*Bundle, error) {
|
|
||||||
indexFile := fmt.Sprintf("%s/%s", ui.DownloadDirectory, ui.IndexFile)
|
|
||||||
return ui.GetBundle(indexFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ui *UpdateIndex) GetBundle(indexFile string) (*Bundle, error) {
|
|
||||||
// Check if the file exists.
|
|
||||||
file, err := os.Open(indexFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open index file: %w", err)
|
|
||||||
}
|
|
||||||
defer func() { _ = file.Close() }()
|
|
||||||
|
|
||||||
// Read
|
|
||||||
content, err := io.ReadAll(file)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse
|
|
||||||
var bundle Bundle
|
|
||||||
err = json.Unmarshal(content, &bundle)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &bundle, nil
|
|
||||||
}
|
|
||||||
@@ -6,9 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/database"
|
"github.com/safing/portmaster/base/database"
|
||||||
"github.com/safing/portmaster/base/log"
|
|
||||||
"github.com/safing/portmaster/base/updater"
|
|
||||||
"github.com/safing/portmaster/service/mgr"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,10 +14,6 @@ const (
|
|||||||
enableSoftwareUpdatesKey = "core/automaticUpdates"
|
enableSoftwareUpdatesKey = "core/automaticUpdates"
|
||||||
enableIntelUpdatesKey = "core/automaticIntelUpdates"
|
enableIntelUpdatesKey = "core/automaticIntelUpdates"
|
||||||
|
|
||||||
// ModuleName is the name of the update module
|
|
||||||
// and can be used when declaring module dependencies.
|
|
||||||
ModuleName = "updates"
|
|
||||||
|
|
||||||
// VersionUpdateEvent is emitted every time a new
|
// VersionUpdateEvent is emitted every time a new
|
||||||
// version of a monitored resource is selected.
|
// version of a monitored resource is selected.
|
||||||
// During module initialization VersionUpdateEvent
|
// During module initialization VersionUpdateEvent
|
||||||
@@ -37,8 +30,6 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
registry *updater.ResourceRegistry
|
|
||||||
|
|
||||||
userAgentFromFlag string
|
userAgentFromFlag string
|
||||||
updateServerFromFlag string
|
updateServerFromFlag string
|
||||||
|
|
||||||
@@ -57,205 +48,13 @@ const (
|
|||||||
updateTaskRepeatDuration = 1 * time.Hour
|
updateTaskRepeatDuration = 1 * time.Hour
|
||||||
)
|
)
|
||||||
|
|
||||||
func start() error {
|
|
||||||
// module.restartWorkerMgr.Repeat(10 * time.Minute)
|
|
||||||
// module.instance.Config().EventConfigChange.AddCallback("update registry config", updateRegistryConfig)
|
|
||||||
|
|
||||||
// // create registry
|
|
||||||
// registry = &updater.ResourceRegistry{
|
|
||||||
// Name: ModuleName,
|
|
||||||
// UpdateURLs: DefaultUpdateURLs,
|
|
||||||
// UserAgent: UserAgent,
|
|
||||||
// MandatoryUpdates: helper.MandatoryUpdates(),
|
|
||||||
// AutoUnpack: helper.AutoUnpackUpdates(),
|
|
||||||
// Verification: helper.VerificationConfig,
|
|
||||||
// DevMode: devMode(),
|
|
||||||
// Online: true,
|
|
||||||
// }
|
|
||||||
// // Override values from flags.
|
|
||||||
// if userAgentFromFlag != "" {
|
|
||||||
// registry.UserAgent = userAgentFromFlag
|
|
||||||
// }
|
|
||||||
// if updateServerFromFlag != "" {
|
|
||||||
// registry.UpdateURLs = []string{updateServerFromFlag}
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // pre-init state
|
|
||||||
// updateStateExport, err := LoadStateExport()
|
|
||||||
// if err != nil {
|
|
||||||
// log.Debugf("updates: failed to load exported update state: %s", err)
|
|
||||||
// } else if updateStateExport.UpdateState != nil {
|
|
||||||
// err := registry.PreInitUpdateState(*updateStateExport.UpdateState)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// initialize
|
|
||||||
// err := registry.Initialize(dataroot.Root().ChildDir(updatesDirName, 0o0755))
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // register state provider
|
|
||||||
// err = registerRegistryStateProvider()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
// registry.StateNotifyFunc = pushRegistryState
|
|
||||||
|
|
||||||
// // Set indexes based on the release channel.
|
|
||||||
// warning := helper.SetIndexes(
|
|
||||||
// registry,
|
|
||||||
// initialReleaseChannel,
|
|
||||||
// true,
|
|
||||||
// enableSoftwareUpdates() && !DisableSoftwareAutoUpdate,
|
|
||||||
// enableIntelUpdates(),
|
|
||||||
// )
|
|
||||||
// if warning != nil {
|
|
||||||
// log.Warningf("updates: %s", warning)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// err = registry.LoadIndexes(module.m.Ctx())
|
|
||||||
// if err != nil {
|
|
||||||
// log.Warningf("updates: failed to load indexes: %s", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// err = registry.ScanStorage("")
|
|
||||||
// if err != nil {
|
|
||||||
// log.Warningf("updates: error during storage scan: %s", err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// registry.SelectVersions()
|
|
||||||
// module.EventVersionsUpdated.Submit(struct{}{})
|
|
||||||
|
|
||||||
// // Initialize the version export - this requires the registry to be set up.
|
|
||||||
// err = initVersionExport()
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // start updater task
|
|
||||||
// if !disableTaskSchedule {
|
|
||||||
// _ = module.updateWorkerMgr.Repeat(30 * time.Minute)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if updateASAP {
|
|
||||||
// module.updateWorkerMgr.Go()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // react to upgrades
|
|
||||||
// if err := initUpgrader(); err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// warnOnIncorrectParentPath()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TriggerUpdate queues the update task to execute ASAP.
|
|
||||||
func TriggerUpdate(forceIndexCheck, downloadAll bool) error {
|
|
||||||
// switch {
|
|
||||||
// case !forceIndexCheck && !enableSoftwareUpdates() && !enableIntelUpdates():
|
|
||||||
// return errors.New("automatic updating is disabled")
|
|
||||||
|
|
||||||
// default:
|
|
||||||
// if forceIndexCheck {
|
|
||||||
// forceCheck.Set()
|
|
||||||
// }
|
|
||||||
// if downloadAll {
|
|
||||||
// forceDownload.Set()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // If index check if forced, start quicker.
|
|
||||||
// module.updateWorkerMgr.Go()
|
|
||||||
// }
|
|
||||||
|
|
||||||
log.Debugf("updates: triggering update to run as soon as possible")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisableUpdateSchedule disables the update schedule.
|
|
||||||
// If called, updates are only checked when TriggerUpdate()
|
|
||||||
// is called.
|
|
||||||
func DisableUpdateSchedule() error {
|
|
||||||
// TODO: Updater state should be always on
|
|
||||||
// switch module.Status() {
|
|
||||||
// case modules.StatusStarting, modules.StatusOnline, modules.StatusStopping:
|
|
||||||
// return errors.New("module already online")
|
|
||||||
// }
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkForUpdates(ctx *mgr.WorkerCtx) (err error) {
|
|
||||||
// Set correct error if context was canceled.
|
|
||||||
// defer func() {
|
|
||||||
// select {
|
|
||||||
// case <-ctx.Done():
|
|
||||||
// err = context.Canceled
|
|
||||||
// default:
|
|
||||||
// }
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// // Get flags.
|
|
||||||
// forceIndexCheck := forceCheck.SetToIf(true, false)
|
|
||||||
// downloadAll := forceDownload.SetToIf(true, false)
|
|
||||||
|
|
||||||
// // Check again if downloading updates is enabled, or forced.
|
|
||||||
// if !forceIndexCheck && !enableSoftwareUpdates() && !enableIntelUpdates() {
|
|
||||||
// log.Warningf("updates: automatic updates are disabled")
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// defer func() {
|
|
||||||
// // Resolve any error and send success notification.
|
|
||||||
// if err == nil {
|
|
||||||
// log.Infof("updates: successfully checked for updates")
|
|
||||||
// notifyUpdateSuccess(forceIndexCheck)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Log and notify error.
|
|
||||||
// log.Errorf("updates: check failed: %s", err)
|
|
||||||
// notifyUpdateCheckFailed(forceIndexCheck, err)
|
|
||||||
// }()
|
|
||||||
|
|
||||||
// if err = registry.UpdateIndexes(ctx.Ctx()); err != nil {
|
|
||||||
// err = fmt.Errorf("failed to update indexes: %w", err)
|
|
||||||
// return //nolint:nakedret // TODO: Would "return err" work with the defer?
|
|
||||||
// }
|
|
||||||
|
|
||||||
// err = registry.DownloadUpdates(ctx.Ctx(), downloadAll)
|
|
||||||
// if err != nil {
|
|
||||||
// err = fmt.Errorf("failed to download updates: %w", err)
|
|
||||||
// return //nolint:nakedret // TODO: Would "return err" work with the defer?
|
|
||||||
// }
|
|
||||||
|
|
||||||
// registry.SelectVersions()
|
|
||||||
|
|
||||||
// // Unpack selected resources.
|
|
||||||
// err = registry.UnpackResources()
|
|
||||||
// if err != nil {
|
|
||||||
// err = fmt.Errorf("failed to unpack updates: %w", err)
|
|
||||||
// return //nolint:nakedret // TODO: Would "return err" work with the defer?
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // Purge old resources
|
|
||||||
// registry.Purge(2)
|
|
||||||
|
|
||||||
// module.EventResourcesUpdated.Submit(struct{}{})
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop() error {
|
func stop() error {
|
||||||
if registry != nil {
|
// if registry != nil {
|
||||||
err := registry.Cleanup()
|
// err := registry.Cleanup()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("updates: failed to clean up registry: %s", err)
|
// log.Warningf("updates: failed to clean up registry: %s", err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,8 @@ package updates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/api"
|
"github.com/safing/portmaster/base/api"
|
||||||
@@ -13,34 +11,33 @@ import (
|
|||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/notifications"
|
"github.com/safing/portmaster/base/notifications"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
|
"github.com/safing/portmaster/service/updates/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
var applyUpdates bool
|
||||||
defaultFileMode = os.FileMode(0o0644)
|
|
||||||
defaultDirMode = os.FileMode(0o0755)
|
func init() {
|
||||||
)
|
flag.BoolVar(&applyUpdates, "update", false, "apply downloaded updates")
|
||||||
|
}
|
||||||
|
|
||||||
// Updates provides access to released artifacts.
|
// Updates provides access to released artifacts.
|
||||||
type Updates struct {
|
type Updates struct {
|
||||||
m *mgr.Manager
|
m *mgr.Manager
|
||||||
states *mgr.StateMgr
|
states *mgr.StateMgr
|
||||||
|
|
||||||
updateWorkerMgr *mgr.WorkerMgr
|
updateBinaryWorkerMgr *mgr.WorkerMgr
|
||||||
restartWorkerMgr *mgr.WorkerMgr
|
updateIntelWorkerMgr *mgr.WorkerMgr
|
||||||
|
restartWorkerMgr *mgr.WorkerMgr
|
||||||
|
|
||||||
EventResourcesUpdated *mgr.EventMgr[struct{}]
|
EventResourcesUpdated *mgr.EventMgr[struct{}]
|
||||||
EventVersionsUpdated *mgr.EventMgr[struct{}]
|
EventVersionsUpdated *mgr.EventMgr[struct{}]
|
||||||
|
|
||||||
binUpdates UpdateIndex
|
registry registry.Registry
|
||||||
intelUpdates UpdateIndex
|
|
||||||
|
|
||||||
instance instance
|
instance instance
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var shimLoaded atomic.Bool
|
||||||
module *Updates
|
|
||||||
shimLoaded atomic.Bool
|
|
||||||
)
|
|
||||||
|
|
||||||
// New returns a new UI module.
|
// New returns a new UI module.
|
||||||
func New(instance instance) (*Updates, error) {
|
func New(instance instance) (*Updates, error) {
|
||||||
@@ -49,20 +46,22 @@ func New(instance instance) (*Updates, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
m := mgr.New("Updates")
|
m := mgr.New("Updates")
|
||||||
module = &Updates{
|
module := &Updates{
|
||||||
m: m,
|
m: m,
|
||||||
states: m.NewStateMgr(),
|
states: m.NewStateMgr(),
|
||||||
|
|
||||||
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
|
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
|
||||||
EventVersionsUpdated: mgr.NewEventMgr[struct{}](VersionUpdateEvent, m),
|
EventVersionsUpdated: mgr.NewEventMgr[struct{}](VersionUpdateEvent, m),
|
||||||
instance: instance,
|
|
||||||
|
instance: instance,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Events
|
// Events
|
||||||
module.updateWorkerMgr = m.NewWorkerMgr("updater", module.checkForUpdates, nil)
|
module.updateBinaryWorkerMgr = m.NewWorkerMgr("binary updater", module.checkForBinaryUpdates, nil)
|
||||||
|
module.updateIntelWorkerMgr = m.NewWorkerMgr("intel updater", module.checkForIntelUpdates, nil)
|
||||||
module.restartWorkerMgr = m.NewWorkerMgr("automatic restart", automaticRestart, nil)
|
module.restartWorkerMgr = m.NewWorkerMgr("automatic restart", automaticRestart, nil)
|
||||||
|
|
||||||
module.binUpdates = UpdateIndex{
|
binIndex := registry.UpdateIndex{
|
||||||
Directory: "/usr/lib/portmaster",
|
Directory: "/usr/lib/portmaster",
|
||||||
DownloadDirectory: "/var/portmaster/new_bin",
|
DownloadDirectory: "/var/portmaster/new_bin",
|
||||||
Ignore: []string{"databases", "intel", "config.json"},
|
Ignore: []string{"databases", "intel", "config.json"},
|
||||||
@@ -71,62 +70,48 @@ func New(instance instance) (*Updates, error) {
|
|||||||
AutoApply: false,
|
AutoApply: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
module.intelUpdates = UpdateIndex{
|
intelIndex := registry.UpdateIndex{
|
||||||
Directory: "/var/portmaster/intel",
|
Directory: "/var/portmaster/intel",
|
||||||
DownloadDirectory: "/var/portmaster/new_intel",
|
DownloadDirectory: "/var/portmaster/new_intel",
|
||||||
IndexURLs: []string{"http://localhost:8000/test-intel.json"},
|
IndexURLs: []string{"http://localhost:8000/test-intel.json"},
|
||||||
IndexFile: "intel-index.json",
|
IndexFile: "intel-index.json",
|
||||||
AutoApply: true,
|
AutoApply: true,
|
||||||
}
|
}
|
||||||
|
module.registry = registry.New(binIndex, intelIndex)
|
||||||
|
|
||||||
return module, nil
|
return module, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteUnfinishedDownloads(rootDir string) error {
|
func (u *Updates) checkForBinaryUpdates(_ *mgr.WorkerCtx) error {
|
||||||
return filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
hasUpdates, err := u.registry.CheckForBinaryUpdates()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("updates: failed to check for binary updates: %s", err)
|
||||||
|
}
|
||||||
|
if hasUpdates {
|
||||||
|
log.Infof("updates: there is updates available in the binary bundle")
|
||||||
|
err = u.registry.DownloadBinaryUpdates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
log.Errorf("updates: failed to download bundle: %s", err)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Check if the current file has the specified extension
|
log.Infof("updates: no new binary updates")
|
||||||
if !info.IsDir() && strings.HasSuffix(info.Name(), ".download") {
|
}
|
||||||
log.Warningf("updates deleting unfinished: %s\n", path)
|
return nil
|
||||||
err := os.Remove(path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to delete file %s: %w", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updates) checkForUpdates(_ *mgr.WorkerCtx) error {
|
func (u *Updates) checkForIntelUpdates(_ *mgr.WorkerCtx) error {
|
||||||
_ = deleteUnfinishedDownloads(u.binUpdates.DownloadDirectory)
|
hasUpdates, err := u.registry.CheckForIntelUpdates()
|
||||||
hasUpdate, err := u.binUpdates.checkForUpdates()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("failed to get binary index file: %s", err)
|
log.Errorf("updates: failed to check for intel updates: %s", err)
|
||||||
}
|
}
|
||||||
if hasUpdate {
|
if hasUpdates {
|
||||||
binBundle, err := u.binUpdates.GetUpdateBundle()
|
log.Infof("updates: there is updates available in the intel bundle")
|
||||||
if err == nil {
|
err = u.registry.DownloadIntelUpdates()
|
||||||
log.Debugf("Bin Bundle: %+v", binBundle)
|
if err != nil {
|
||||||
_ = os.MkdirAll(u.binUpdates.DownloadDirectory, defaultDirMode)
|
log.Errorf("updates: failed to download bundle: %s", err)
|
||||||
binBundle.downloadAndVerify(u.binUpdates.DownloadDirectory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ = deleteUnfinishedDownloads(u.intelUpdates.DownloadDirectory)
|
|
||||||
hasUpdate, err = u.intelUpdates.checkForUpdates()
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("failed to get intel index file: %s", err)
|
|
||||||
}
|
|
||||||
if hasUpdate {
|
|
||||||
intelBundle, err := u.intelUpdates.GetUpdateBundle()
|
|
||||||
if err == nil {
|
|
||||||
log.Debugf("Intel Bundle: %+v", intelBundle)
|
|
||||||
_ = os.MkdirAll(u.intelUpdates.DownloadDirectory, defaultDirMode)
|
|
||||||
intelBundle.downloadAndVerify(u.intelUpdates.DownloadDirectory)
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
log.Infof("updates: no new intel data updates")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -143,38 +128,36 @@ func (u *Updates) Manager() *mgr.Manager {
|
|||||||
|
|
||||||
// Start starts the module.
|
// Start starts the module.
|
||||||
func (u *Updates) Start() error {
|
func (u *Updates) Start() error {
|
||||||
initConfig()
|
// initConfig()
|
||||||
u.m.Go("check for updates", func(w *mgr.WorkerCtx) error {
|
|
||||||
binBundle, err := u.binUpdates.GetInstallBundle()
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("failed to get binary bundle: %s", err)
|
|
||||||
} else {
|
|
||||||
err = binBundle.Verify(u.binUpdates.Directory)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("binary bundle is not valid: %s", err)
|
|
||||||
} else {
|
|
||||||
log.Infof("binary bundle is valid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
intelBundle, err := u.intelUpdates.GetInstallBundle()
|
if applyUpdates {
|
||||||
|
err := u.registry.ApplyBinaryUpdates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("failed to get intel bundle: %s", err)
|
log.Errorf("updates: failed to apply binary updates: %s", err)
|
||||||
} else {
|
|
||||||
err = intelBundle.Verify(u.intelUpdates.Directory)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("intel bundle is not valid: %s", err)
|
|
||||||
} else {
|
|
||||||
log.Infof("intel bundle is valid")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
err = u.registry.ApplyIntelUpdates()
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("updates: failed to apply intel updates: %s", err)
|
||||||
|
}
|
||||||
|
u.instance.Restart()
|
||||||
return nil
|
return nil
|
||||||
})
|
}
|
||||||
u.updateWorkerMgr.Go()
|
|
||||||
|
err := u.registry.Initialize()
|
||||||
|
if err != nil {
|
||||||
|
// TODO(vladimir): Find a better way to handle this error. The service will stop if parsing of the bundle files fails.
|
||||||
|
return fmt.Errorf("failed to initialize registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
u.updateBinaryWorkerMgr.Go()
|
||||||
|
u.updateIntelWorkerMgr.Go()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *Updates) GetFile(id string) (*registry.File, error) {
|
||||||
|
return u.registry.GetFile(id)
|
||||||
|
}
|
||||||
|
|
||||||
// Stop stops the module.
|
// Stop stops the module.
|
||||||
func (u *Updates) Stop() error {
|
func (u *Updates) Stop() error {
|
||||||
return stop()
|
return stop()
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
package updates
|
package updates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/notifications"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -25,109 +21,109 @@ func (u *Updates) notificationsEnabled() bool {
|
|||||||
return u.instance.Notifications() != nil
|
return u.instance.Notifications() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyUpdateSuccess(force bool) {
|
// func notifyUpdateSuccess(force bool) {
|
||||||
if !module.notificationsEnabled() {
|
// if !module.notificationsEnabled() {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
updateFailedCnt.Store(0)
|
// updateFailedCnt.Store(0)
|
||||||
module.states.Clear()
|
// module.states.Clear()
|
||||||
updateState := registry.GetState().Updates
|
// updateState := registry.GetState().Updates
|
||||||
|
|
||||||
flavor := updateSuccess
|
// flavor := updateSuccess
|
||||||
switch {
|
// switch {
|
||||||
case len(updateState.PendingDownload) > 0:
|
// case len(updateState.PendingDownload) > 0:
|
||||||
// Show notification if there are pending downloads.
|
// // Show notification if there are pending downloads.
|
||||||
flavor = updateSuccessPending
|
// flavor = updateSuccessPending
|
||||||
case updateState.LastDownloadAt != nil &&
|
// case updateState.LastDownloadAt != nil &&
|
||||||
time.Since(*updateState.LastDownloadAt) < 5*time.Second:
|
// time.Since(*updateState.LastDownloadAt) < 5*time.Second:
|
||||||
// Show notification if we downloaded something within the last minute.
|
// // Show notification if we downloaded something within the last minute.
|
||||||
flavor = updateSuccessDownloaded
|
// flavor = updateSuccessDownloaded
|
||||||
case force:
|
// case force:
|
||||||
// Always show notification if update was manually triggered.
|
// // Always show notification if update was manually triggered.
|
||||||
default:
|
// default:
|
||||||
// Otherwise, the update was uneventful. Do not show notification.
|
// // Otherwise, the update was uneventful. Do not show notification.
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
switch flavor {
|
// switch flavor {
|
||||||
case updateSuccess:
|
// case updateSuccess:
|
||||||
notifications.Notify(¬ifications.Notification{
|
// notifications.Notify(¬ifications.Notification{
|
||||||
EventID: updateSuccess,
|
// EventID: updateSuccess,
|
||||||
Type: notifications.Info,
|
// Type: notifications.Info,
|
||||||
Title: "Portmaster Is Up-To-Date",
|
// Title: "Portmaster Is Up-To-Date",
|
||||||
Message: "Portmaster successfully checked for updates. Everything is up to date.\n\n" + getUpdatingInfoMsg(),
|
// Message: "Portmaster successfully checked for updates. Everything is up to date.\n\n" + getUpdatingInfoMsg(),
|
||||||
Expires: time.Now().Add(1 * time.Minute).Unix(),
|
// Expires: time.Now().Add(1 * time.Minute).Unix(),
|
||||||
AvailableActions: []*notifications.Action{
|
// AvailableActions: []*notifications.Action{
|
||||||
{
|
// {
|
||||||
ID: "ack",
|
// ID: "ack",
|
||||||
Text: "OK",
|
// Text: "OK",
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
|
|
||||||
case updateSuccessPending:
|
// case updateSuccessPending:
|
||||||
msg := fmt.Sprintf(
|
// msg := fmt.Sprintf(
|
||||||
`%d updates are available for download:
|
// `%d updates are available for download:
|
||||||
|
|
||||||
- %s
|
// - %s
|
||||||
|
|
||||||
Press "Download Now" to download and automatically apply all pending updates. You will be notified of important updates that need restarting.`,
|
// Press "Download Now" to download and automatically apply all pending updates. You will be notified of important updates that need restarting.`,
|
||||||
len(updateState.PendingDownload),
|
// len(updateState.PendingDownload),
|
||||||
strings.Join(updateState.PendingDownload, "\n- "),
|
// strings.Join(updateState.PendingDownload, "\n- "),
|
||||||
)
|
// )
|
||||||
|
|
||||||
notifications.Notify(¬ifications.Notification{
|
// notifications.Notify(¬ifications.Notification{
|
||||||
EventID: updateSuccess,
|
// EventID: updateSuccess,
|
||||||
Type: notifications.Info,
|
// Type: notifications.Info,
|
||||||
Title: fmt.Sprintf("%d Updates Available", len(updateState.PendingDownload)),
|
// Title: fmt.Sprintf("%d Updates Available", len(updateState.PendingDownload)),
|
||||||
Message: msg,
|
// Message: msg,
|
||||||
AvailableActions: []*notifications.Action{
|
// AvailableActions: []*notifications.Action{
|
||||||
{
|
// {
|
||||||
ID: "ack",
|
// ID: "ack",
|
||||||
Text: "OK",
|
// Text: "OK",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
ID: "download",
|
// ID: "download",
|
||||||
Text: "Download Now",
|
// Text: "Download Now",
|
||||||
Type: notifications.ActionTypeWebhook,
|
// Type: notifications.ActionTypeWebhook,
|
||||||
Payload: ¬ifications.ActionTypeWebhookPayload{
|
// Payload: ¬ifications.ActionTypeWebhookPayload{
|
||||||
URL: apiPathCheckForUpdates + "?download",
|
// URL: apiPathCheckForUpdates + "?download",
|
||||||
ResultAction: "display",
|
// ResultAction: "display",
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
|
|
||||||
case updateSuccessDownloaded:
|
// case updateSuccessDownloaded:
|
||||||
msg := fmt.Sprintf(
|
// msg := fmt.Sprintf(
|
||||||
`%d updates were downloaded and applied:
|
// `%d updates were downloaded and applied:
|
||||||
|
|
||||||
- %s
|
// - %s
|
||||||
|
|
||||||
%s
|
// %s
|
||||||
`,
|
// `,
|
||||||
len(updateState.LastDownload),
|
// len(updateState.LastDownload),
|
||||||
strings.Join(updateState.LastDownload, "\n- "),
|
// strings.Join(updateState.LastDownload, "\n- "),
|
||||||
getUpdatingInfoMsg(),
|
// getUpdatingInfoMsg(),
|
||||||
)
|
// )
|
||||||
|
|
||||||
notifications.Notify(¬ifications.Notification{
|
// notifications.Notify(¬ifications.Notification{
|
||||||
EventID: updateSuccess,
|
// EventID: updateSuccess,
|
||||||
Type: notifications.Info,
|
// Type: notifications.Info,
|
||||||
Title: fmt.Sprintf("%d Updates Applied", len(updateState.LastDownload)),
|
// Title: fmt.Sprintf("%d Updates Applied", len(updateState.LastDownload)),
|
||||||
Message: msg,
|
// Message: msg,
|
||||||
Expires: time.Now().Add(1 * time.Minute).Unix(),
|
// Expires: time.Now().Add(1 * time.Minute).Unix(),
|
||||||
AvailableActions: []*notifications.Action{
|
// AvailableActions: []*notifications.Action{
|
||||||
{
|
// {
|
||||||
ID: "ack",
|
// ID: "ack",
|
||||||
Text: "OK",
|
// Text: "OK",
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
|
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func getUpdatingInfoMsg() string {
|
func getUpdatingInfoMsg() string {
|
||||||
switch {
|
switch {
|
||||||
@@ -140,41 +136,41 @@ func getUpdatingInfoMsg() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func notifyUpdateCheckFailed(force bool, err error) {
|
// func notifyUpdateCheckFailed(force bool, err error) {
|
||||||
if !module.notificationsEnabled() {
|
// if !module.notificationsEnabled() {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
failedCnt := updateFailedCnt.Add(1)
|
// failedCnt := updateFailedCnt.Add(1)
|
||||||
lastSuccess := registry.GetState().Updates.LastSuccessAt
|
// lastSuccess := registry.GetState().Updates.LastSuccessAt
|
||||||
|
|
||||||
switch {
|
// switch {
|
||||||
case force:
|
// case force:
|
||||||
// Always show notification if update was manually triggered.
|
// // Always show notification if update was manually triggered.
|
||||||
case failedCnt < failedUpdateNotifyCountThreshold:
|
// case failedCnt < failedUpdateNotifyCountThreshold:
|
||||||
// Not failed often enough for notification.
|
// // Not failed often enough for notification.
|
||||||
return
|
// return
|
||||||
case lastSuccess == nil:
|
// case lastSuccess == nil:
|
||||||
// No recorded successful update.
|
// // No recorded successful update.
|
||||||
case time.Now().Add(-failedUpdateNotifyDurationThreshold).Before(*lastSuccess):
|
// case time.Now().Add(-failedUpdateNotifyDurationThreshold).Before(*lastSuccess):
|
||||||
// Failed too recently for notification.
|
// // Failed too recently for notification.
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
notifications.NotifyWarn(
|
// notifications.NotifyWarn(
|
||||||
updateFailed,
|
// updateFailed,
|
||||||
"Update Check Failed",
|
// "Update Check Failed",
|
||||||
fmt.Sprintf(
|
// 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",
|
// "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,
|
// err,
|
||||||
),
|
// ),
|
||||||
notifications.Action{
|
// notifications.Action{
|
||||||
Text: "Try Again Now",
|
// Text: "Try Again Now",
|
||||||
Type: notifications.ActionTypeWebhook,
|
// Type: notifications.ActionTypeWebhook,
|
||||||
Payload: ¬ifications.ActionTypeWebhookPayload{
|
// Payload: ¬ifications.ActionTypeWebhookPayload{
|
||||||
URL: apiPathCheckForUpdates,
|
// URL: apiPathCheckForUpdates,
|
||||||
ResultAction: "display",
|
// ResultAction: "display",
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
).SyncWithState(module.states)
|
// ).SyncWithState(module.states)
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,204 +1,201 @@
|
|||||||
package updates
|
package updates
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"bytes"
|
// "crypto/sha256"
|
||||||
"crypto/sha256"
|
// _ "embed"
|
||||||
_ "embed"
|
// "encoding/hex"
|
||||||
"encoding/hex"
|
// "errors"
|
||||||
"errors"
|
// "fmt"
|
||||||
"fmt"
|
// "io/fs"
|
||||||
"io"
|
// "os"
|
||||||
"io/fs"
|
// "path/filepath"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/tevino/abool"
|
// "github.com/tevino/abool"
|
||||||
"golang.org/x/exp/slices"
|
// "golang.org/x/exp/slices"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/dataroot"
|
// "github.com/safing/portmaster/base/dataroot"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/utils/renameio"
|
// )
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
portmasterCoreServiceFilePath = "portmaster.service"
|
// portmasterCoreServiceFilePath = "portmaster.service"
|
||||||
portmasterNotifierServiceFilePath = "portmaster_notifier.desktop"
|
// portmasterNotifierServiceFilePath = "portmaster_notifier.desktop"
|
||||||
backupExtension = ".backup"
|
// backupExtension = ".backup"
|
||||||
|
|
||||||
//go:embed assets/portmaster.service
|
// //go:embed assets/portmaster.service
|
||||||
currentPortmasterCoreServiceFile []byte
|
// currentPortmasterCoreServiceFile []byte
|
||||||
|
|
||||||
checkedSystemIntegration = abool.New()
|
// checkedSystemIntegration = abool.New()
|
||||||
|
|
||||||
// ErrRequiresManualUpgrade is returned when a system integration file requires a manual upgrade.
|
// // ErrRequiresManualUpgrade is returned when a system integration file requires a manual upgrade.
|
||||||
ErrRequiresManualUpgrade = errors.New("requires a manual upgrade")
|
// ErrRequiresManualUpgrade = errors.New("requires a manual upgrade")
|
||||||
)
|
// )
|
||||||
|
|
||||||
func upgradeSystemIntegration() {
|
// func upgradeSystemIntegration() {
|
||||||
// Check if we already checked the system integration.
|
// // Check if we already checked the system integration.
|
||||||
if !checkedSystemIntegration.SetToIf(false, true) {
|
// if !checkedSystemIntegration.SetToIf(false, true) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Upgrade portmaster core systemd service.
|
// // Upgrade portmaster core systemd service.
|
||||||
err := upgradeSystemIntegrationFile(
|
// err := upgradeSystemIntegrationFile(
|
||||||
"portmaster core systemd service",
|
// "portmaster core systemd service",
|
||||||
filepath.Join(dataroot.Root().Path, portmasterCoreServiceFilePath),
|
// filepath.Join(dataroot.Root().Path, portmasterCoreServiceFilePath),
|
||||||
0o0600,
|
// 0o0600,
|
||||||
currentPortmasterCoreServiceFile,
|
// currentPortmasterCoreServiceFile,
|
||||||
[]string{
|
// []string{
|
||||||
"bc26dd37e6953af018ad3676ee77570070e075f2b9f5df6fa59d65651a481468", // Commit 19c76c7 on 2022-01-25
|
// "bc26dd37e6953af018ad3676ee77570070e075f2b9f5df6fa59d65651a481468", // Commit 19c76c7 on 2022-01-25
|
||||||
"cc0cb49324dfe11577e8c066dd95cc03d745b50b2153f32f74ca35234c3e8cb5", // Commit ef479e5 on 2022-01-24
|
// "cc0cb49324dfe11577e8c066dd95cc03d745b50b2153f32f74ca35234c3e8cb5", // Commit ef479e5 on 2022-01-24
|
||||||
"d08a3b5f3aee351f8e120e6e2e0a089964b94c9e9d0a9e5fa822e60880e315fd", // Commit b64735e on 2021-12-07
|
// "d08a3b5f3aee351f8e120e6e2e0a089964b94c9e9d0a9e5fa822e60880e315fd", // Commit b64735e on 2021-12-07
|
||||||
},
|
// },
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("updates: %s", err)
|
// log.Warningf("updates: %s", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Upgrade portmaster notifier systemd user service.
|
// // Upgrade portmaster notifier systemd user service.
|
||||||
// Permissions only!
|
// // Permissions only!
|
||||||
err = upgradeSystemIntegrationFile(
|
// err = upgradeSystemIntegrationFile(
|
||||||
"portmaster notifier systemd user service",
|
// "portmaster notifier systemd user service",
|
||||||
filepath.Join(dataroot.Root().Path, portmasterNotifierServiceFilePath),
|
// filepath.Join(dataroot.Root().Path, portmasterNotifierServiceFilePath),
|
||||||
0o0644,
|
// 0o0644,
|
||||||
nil, // Do not update contents.
|
// nil, // Do not update contents.
|
||||||
nil, // Do not update contents.
|
// nil, // Do not update contents.
|
||||||
)
|
// )
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("updates: %s", err)
|
// log.Warningf("updates: %s", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// upgradeSystemIntegrationFile upgrades the file contents and permissions.
|
// // upgradeSystemIntegrationFile upgrades the file contents and permissions.
|
||||||
// System integration files are not necessarily present and may also be
|
// // System integration files are not necessarily present and may also be
|
||||||
// edited by third parties, such as the OS itself or other installers.
|
// // edited by third parties, such as the OS itself or other installers.
|
||||||
// The supplied hashes must be sha256 hex-encoded.
|
// // The supplied hashes must be sha256 hex-encoded.
|
||||||
func upgradeSystemIntegrationFile(
|
// func upgradeSystemIntegrationFile(
|
||||||
name string,
|
// name string,
|
||||||
filePath string,
|
// filePath string,
|
||||||
fileMode fs.FileMode,
|
// fileMode fs.FileMode,
|
||||||
fileData []byte,
|
// fileData []byte,
|
||||||
permittedUpgradeHashes []string,
|
// permittedUpgradeHashes []string,
|
||||||
) error {
|
// ) error {
|
||||||
// Upgrade file contents.
|
// // Upgrade file contents.
|
||||||
if len(fileData) > 0 {
|
// if len(fileData) > 0 {
|
||||||
if err := upgradeSystemIntegrationFileContents(name, filePath, fileData, permittedUpgradeHashes); err != nil {
|
// if err := upgradeSystemIntegrationFileContents(name, filePath, fileData, permittedUpgradeHashes); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Upgrade file permissions.
|
// // Upgrade file permissions.
|
||||||
if fileMode != 0 {
|
// if fileMode != 0 {
|
||||||
if err := upgradeSystemIntegrationFilePermissions(name, filePath, fileMode); err != nil {
|
// if err := upgradeSystemIntegrationFilePermissions(name, filePath, fileMode); err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// upgradeSystemIntegrationFileContents upgrades the file contents.
|
// // upgradeSystemIntegrationFileContents upgrades the file contents.
|
||||||
// System integration files are not necessarily present and may also be
|
// // System integration files are not necessarily present and may also be
|
||||||
// edited by third parties, such as the OS itself or other installers.
|
// // edited by third parties, such as the OS itself or other installers.
|
||||||
// The supplied hashes must be sha256 hex-encoded.
|
// // The supplied hashes must be sha256 hex-encoded.
|
||||||
func upgradeSystemIntegrationFileContents(
|
// func upgradeSystemIntegrationFileContents(
|
||||||
name string,
|
// name string,
|
||||||
filePath string,
|
// filePath string,
|
||||||
fileData []byte,
|
// fileData []byte,
|
||||||
permittedUpgradeHashes []string,
|
// permittedUpgradeHashes []string,
|
||||||
) error {
|
// ) error {
|
||||||
// Read existing file.
|
// // Read existing file.
|
||||||
existingFileData, err := os.ReadFile(filePath)
|
// existingFileData, err := os.ReadFile(filePath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
// if errors.Is(err, os.ErrNotExist) {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return fmt.Errorf("failed to read %s at %s: %w", name, filePath, err)
|
// return fmt.Errorf("failed to read %s at %s: %w", name, filePath, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if file is already the current version.
|
// // Check if file is already the current version.
|
||||||
existingSum := sha256.Sum256(existingFileData)
|
// existingSum := sha256.Sum256(existingFileData)
|
||||||
existingHexSum := hex.EncodeToString(existingSum[:])
|
// existingHexSum := hex.EncodeToString(existingSum[:])
|
||||||
currentSum := sha256.Sum256(fileData)
|
// currentSum := sha256.Sum256(fileData)
|
||||||
currentHexSum := hex.EncodeToString(currentSum[:])
|
// currentHexSum := hex.EncodeToString(currentSum[:])
|
||||||
if existingHexSum == currentHexSum {
|
// if existingHexSum == currentHexSum {
|
||||||
log.Debugf("updates: %s at %s is up to date", name, filePath)
|
// log.Debugf("updates: %s at %s is up to date", name, filePath)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check if we are allowed to upgrade from the existing file.
|
// // Check if we are allowed to upgrade from the existing file.
|
||||||
if !slices.Contains[[]string, string](permittedUpgradeHashes, existingHexSum) {
|
// 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)
|
// 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!
|
// // Start with upgrade!
|
||||||
|
|
||||||
// Make backup of existing file.
|
// // Make backup of existing file.
|
||||||
err = CopyFile(filePath, filePath+backupExtension)
|
// err = CopyFile(filePath, filePath+backupExtension)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf(
|
// return fmt.Errorf(
|
||||||
"failed to create backup of %s from %s to %s: %w",
|
// "failed to create backup of %s from %s to %s: %w",
|
||||||
name,
|
// name,
|
||||||
filePath,
|
// filePath,
|
||||||
filePath+backupExtension,
|
// filePath+backupExtension,
|
||||||
err,
|
// err,
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Open destination file for writing.
|
// // Open destination file for writing.
|
||||||
atomicDstFile, err := renameio.TempFile(registry.TmpDir().Path, filePath)
|
// // atomicDstFile, err := renameio.TempFile(registry.TmpDir().Path, filePath)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return fmt.Errorf("failed to create tmp file to update %s at %s: %w", name, filePath, err)
|
// // 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
|
// // defer atomicDstFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway
|
||||||
|
|
||||||
// Write file.
|
// // // Write file.
|
||||||
_, err = io.Copy(atomicDstFile, bytes.NewReader(fileData))
|
// // _, err = io.Copy(atomicDstFile, bytes.NewReader(fileData))
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return err
|
// // return err
|
||||||
}
|
// // }
|
||||||
|
|
||||||
// Finalize file.
|
// // // Finalize file.
|
||||||
err = atomicDstFile.CloseAtomicallyReplace()
|
// // err = atomicDstFile.CloseAtomicallyReplace()
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return fmt.Errorf("failed to finalize update of %s at %s: %w", name, filePath, err)
|
// // 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)
|
// log.Warningf("updates: %s at %s was upgraded to %s - a reboot may be required", name, filePath, currentHexSum)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// upgradeSystemIntegrationFilePermissions upgrades the file permissions.
|
// // upgradeSystemIntegrationFilePermissions upgrades the file permissions.
|
||||||
// System integration files are not necessarily present and may also be
|
// // System integration files are not necessarily present and may also be
|
||||||
// edited by third parties, such as the OS itself or other installers.
|
// // edited by third parties, such as the OS itself or other installers.
|
||||||
func upgradeSystemIntegrationFilePermissions(
|
// func upgradeSystemIntegrationFilePermissions(
|
||||||
name string,
|
// name string,
|
||||||
filePath string,
|
// filePath string,
|
||||||
fileMode fs.FileMode,
|
// fileMode fs.FileMode,
|
||||||
) error {
|
// ) error {
|
||||||
// Get current file permissions.
|
// // Get current file permissions.
|
||||||
stat, err := os.Stat(filePath)
|
// stat, err := os.Stat(filePath)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
// if errors.Is(err, os.ErrNotExist) {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return fmt.Errorf("failed to read %s file metadata at %s: %w", name, filePath, err)
|
// return fmt.Errorf("failed to read %s file metadata at %s: %w", name, filePath, err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// If permissions are as expected, do nothing.
|
// // If permissions are as expected, do nothing.
|
||||||
if stat.Mode().Perm() == fileMode {
|
// if stat.Mode().Perm() == fileMode {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Otherwise, set correct permissions.
|
// // Otherwise, set correct permissions.
|
||||||
err = os.Chmod(filePath, fileMode)
|
// err = os.Chmod(filePath, fileMode)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to update %s file permissions at %s: %w", name, filePath, err)
|
// 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)
|
// log.Warningf("updates: %s file permissions at %s updated to %v", name, filePath, fileMode)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
package updates
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package updates
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
@@ -17,6 +17,12 @@ import (
|
|||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultFileMode = os.FileMode(0o0644)
|
||||||
|
executableFileMode = os.FileMode(0o0744)
|
||||||
|
defaultDirMode = os.FileMode(0o0755)
|
||||||
|
)
|
||||||
|
|
||||||
const MaxUnpackSize = 1 << 30 // 2^30 == 1GB
|
const MaxUnpackSize = 1 << 30 // 2^30 == 1GB
|
||||||
|
|
||||||
type Artifact struct {
|
type Artifact struct {
|
||||||
@@ -29,40 +35,40 @@ type Artifact struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Bundle struct {
|
type Bundle struct {
|
||||||
|
dir string
|
||||||
Name string `json:"Bundle"`
|
Name string `json:"Bundle"`
|
||||||
Version string `json:"Version"`
|
Version string `json:"Version"`
|
||||||
Published time.Time `json:"Published"`
|
Published time.Time `json:"Published"`
|
||||||
Artifacts []Artifact `json:"Artifacts"`
|
Artifacts []Artifact `json:"Artifacts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bundle Bundle) downloadAndVerify(dataDir string) {
|
func (bundle Bundle) downloadAndVerify() {
|
||||||
client := http.Client{}
|
client := http.Client{}
|
||||||
for _, artifact := range bundle.Artifacts {
|
for _, artifact := range bundle.Artifacts {
|
||||||
|
|
||||||
filePath := fmt.Sprintf("%s/%s", dataDir, artifact.Filename)
|
filePath := fmt.Sprintf("%s/%s", bundle.dir, artifact.Filename)
|
||||||
// TODO(vladimir): is this needed?
|
// TODO(vladimir): is this needed?
|
||||||
_ = os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
|
_ = os.MkdirAll(filepath.Dir(filePath), defaultDirMode)
|
||||||
|
|
||||||
// Check file is already downloaded and valid.
|
// Check file is already downloaded and valid.
|
||||||
exists, err := checkIfFileIsValid(filePath, artifact)
|
exists, _ := checkIfFileIsValid(filePath, artifact)
|
||||||
if exists {
|
if exists {
|
||||||
log.Debugf("file already download: %s", filePath)
|
log.Debugf("updates: file already downloaded: %s", filePath)
|
||||||
continue
|
continue
|
||||||
} else if err != nil {
|
|
||||||
log.Errorf("error while checking old download: %s", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download artifact
|
// Download artifact
|
||||||
err = processArtifact(&client, artifact, filePath)
|
err := processArtifact(&client, artifact, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("updates: %s", err)
|
log.Errorf("updates: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bundle Bundle) Verify(dataDir string) error {
|
// Verify checks if the files are present int the dataDir and have the correct hash.
|
||||||
|
func (bundle Bundle) Verify() error {
|
||||||
for _, artifact := range bundle.Artifacts {
|
for _, artifact := range bundle.Artifacts {
|
||||||
artifactPath := fmt.Sprintf("%s/%s", dataDir, artifact.Filename)
|
artifactPath := fmt.Sprintf("%s/%s", bundle.dir, artifact.Filename)
|
||||||
file, err := os.Open(artifactPath)
|
file, err := os.Open(artifactPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to open file %s: %w", artifactPath, err)
|
return fmt.Errorf("failed to open file %s: %w", artifactPath, err)
|
||||||
@@ -86,8 +92,7 @@ func checkIfFileIsValid(filename string, artifact Artifact) (bool, error) {
|
|||||||
// Check if file already exists
|
// Check if file already exists
|
||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
//nolint:nilerr
|
return false, err
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
defer func() { _ = file.Close() }()
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
@@ -131,7 +136,7 @@ func processArtifact(client *http.Client, artifact Artifact, filePath string) er
|
|||||||
// Verify
|
// Verify
|
||||||
hash := sha256.Sum256(content)
|
hash := sha256.Sum256(content)
|
||||||
if !bytes.Equal(providedHash, hash[:]) {
|
if !bytes.Equal(providedHash, hash[:]) {
|
||||||
// FIXME(vladimir): just for testing. Make it an error before commit.
|
// FIXME(vladimir): just for testing. Make it an error.
|
||||||
err = fmt.Errorf("failed to verify artifact: %s", artifact.Filename)
|
err = fmt.Errorf("failed to verify artifact: %s", artifact.Filename)
|
||||||
log.Debugf("updates: %s", err)
|
log.Debugf("updates: %s", err)
|
||||||
}
|
}
|
||||||
@@ -142,6 +147,11 @@ func processArtifact(client *http.Client, artifact Artifact, filePath string) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create file: %w", err)
|
return fmt.Errorf("failed to create file: %w", err)
|
||||||
}
|
}
|
||||||
|
if artifact.Platform == "" {
|
||||||
|
_ = file.Chmod(defaultFileMode)
|
||||||
|
} else {
|
||||||
|
_ = file.Chmod(executableFileMode)
|
||||||
|
}
|
||||||
_, err = file.Write(content)
|
_, err = file.Write(content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to write to file: %w", err)
|
return fmt.Errorf("failed to write to file: %w", err)
|
||||||
56
service/updates/registry/index.go
Normal file
56
service/updates/registry/index.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/safing/portmaster/base/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateIndex struct {
|
||||||
|
Directory string
|
||||||
|
DownloadDirectory string
|
||||||
|
Ignore []string
|
||||||
|
IndexURLs []string
|
||||||
|
IndexFile string
|
||||||
|
AutoApply bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui *UpdateIndex) downloadIndexFile() (err error) {
|
||||||
|
_ = os.MkdirAll(ui.DownloadDirectory, defaultDirMode)
|
||||||
|
for _, url := range ui.IndexURLs {
|
||||||
|
err = ui.downloadIndexFileFromURL(url)
|
||||||
|
if err != nil {
|
||||||
|
log.Warningf("updates: %s", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Downloading was successful.
|
||||||
|
err = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ui *UpdateIndex) downloadIndexFileFromURL(url string) error {
|
||||||
|
client := http.Client{}
|
||||||
|
resp, err := client.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed a get request to %s: %w", url, err)
|
||||||
|
}
|
||||||
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
filePath := fmt.Sprintf("%s/%s", ui.DownloadDirectory, ui.IndexFile)
|
||||||
|
file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, defaultFileMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
245
service/updates/registry/registry.go
Normal file
245
service/updates/registry/registry.go
Normal file
@@ -0,0 +1,245 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/safing/portmaster/base/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotFound error = errors.New("file not found")
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
id string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Identifier() string {
|
||||||
|
return f.id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Path() string {
|
||||||
|
return f.path
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *File) Version() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type Registry struct {
|
||||||
|
binaryUpdateIndex UpdateIndex
|
||||||
|
intelUpdateIndex UpdateIndex
|
||||||
|
|
||||||
|
binaryBundle *Bundle
|
||||||
|
intelBundle *Bundle
|
||||||
|
|
||||||
|
binaryUpdateBundle *Bundle
|
||||||
|
intelUpdateBundle *Bundle
|
||||||
|
|
||||||
|
files map[string]File
|
||||||
|
}
|
||||||
|
|
||||||
|
// New create new Registry.
|
||||||
|
func New(binIndex UpdateIndex, intelIndex UpdateIndex) Registry {
|
||||||
|
return Registry{
|
||||||
|
binaryUpdateIndex: binIndex,
|
||||||
|
intelUpdateIndex: intelIndex,
|
||||||
|
files: make(map[string]File),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize parses and initializes currently installed bundles.
|
||||||
|
func (reg *Registry) Initialize() error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Parse current installed binary bundle.
|
||||||
|
reg.binaryBundle, err = parseBundle(reg.binaryUpdateIndex.Directory, reg.binaryUpdateIndex.IndexFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse binary bundle: %w", err)
|
||||||
|
}
|
||||||
|
// Parse current installed intel bundle.
|
||||||
|
reg.intelBundle, err = parseBundle(reg.intelUpdateIndex.Directory, reg.intelUpdateIndex.IndexFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse intel bundle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add bundle artifacts to registry.
|
||||||
|
reg.processBundle(reg.binaryBundle)
|
||||||
|
reg.processBundle(reg.intelBundle)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (reg *Registry) processBundle(bundle *Bundle) {
|
||||||
|
for _, artifact := range bundle.Artifacts {
|
||||||
|
artifactPath := fmt.Sprintf("%s/%s", bundle.dir, artifact.Filename)
|
||||||
|
reg.files[artifact.Filename] = File{id: artifact.Filename, path: artifactPath}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFile returns the object of a artifact by id.
|
||||||
|
func (reg *Registry) GetFile(id string) (*File, error) {
|
||||||
|
file, ok := reg.files[id]
|
||||||
|
if ok {
|
||||||
|
return &file, nil
|
||||||
|
} else {
|
||||||
|
log.Errorf("updates: requested file id not found: %s", id)
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckForBinaryUpdates checks if there is a new binary bundle updates.
|
||||||
|
func (reg *Registry) CheckForBinaryUpdates() (bool, error) {
|
||||||
|
err := reg.binaryUpdateIndex.downloadIndexFile()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reg.binaryUpdateBundle, err = parseBundle(reg.binaryUpdateIndex.DownloadDirectory, reg.binaryUpdateIndex.IndexFile)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to parse bundle file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(vladimir): Make a better check.
|
||||||
|
if reg.binaryBundle.Version != reg.binaryUpdateBundle.Version {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadBinaryUpdates downloads available binary updates.
|
||||||
|
func (reg *Registry) DownloadBinaryUpdates() error {
|
||||||
|
if reg.binaryUpdateBundle == nil {
|
||||||
|
// CheckForBinaryUpdates needs to be called before this.
|
||||||
|
return fmt.Errorf("no valid update bundle found")
|
||||||
|
}
|
||||||
|
_ = deleteUnfinishedDownloads(reg.binaryBundle.dir)
|
||||||
|
reg.binaryUpdateBundle.downloadAndVerify()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckForIntelUpdates checks if there is a new intel data bundle updates.
|
||||||
|
func (reg *Registry) CheckForIntelUpdates() (bool, error) {
|
||||||
|
err := reg.intelUpdateIndex.downloadIndexFile()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reg.intelUpdateBundle, err = parseBundle(reg.intelUpdateIndex.DownloadDirectory, reg.intelUpdateIndex.IndexFile)
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("failed to parse bundle file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(vladimir): Make a better check.
|
||||||
|
if reg.intelBundle.Version != reg.intelUpdateBundle.Version {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadIntelUpdates downloads available intel data updates.
|
||||||
|
func (reg *Registry) DownloadIntelUpdates() error {
|
||||||
|
if reg.intelUpdateBundle == nil {
|
||||||
|
// CheckForIntelUpdates needs to be called before this.
|
||||||
|
return fmt.Errorf("no valid update bundle found")
|
||||||
|
}
|
||||||
|
_ = deleteUnfinishedDownloads(reg.intelBundle.dir)
|
||||||
|
reg.intelUpdateBundle.downloadAndVerify()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyBinaryUpdates removes the current binary folder and replaces it with the downloaded one.
|
||||||
|
func (reg *Registry) ApplyBinaryUpdates() error {
|
||||||
|
bundle, err := parseBundle(reg.binaryUpdateIndex.DownloadDirectory, reg.binaryUpdateIndex.IndexFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse index file: %w", err)
|
||||||
|
}
|
||||||
|
err = bundle.Verify()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("binary bundle is not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(reg.binaryUpdateIndex.Directory)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remove dir: %w", err)
|
||||||
|
}
|
||||||
|
err = os.Rename(reg.binaryUpdateIndex.DownloadDirectory, reg.binaryUpdateIndex.Directory)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to move dir: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApplyIntelUpdates removes the current intel folder and replaces it with the downloaded one.
|
||||||
|
func (reg *Registry) ApplyIntelUpdates() error {
|
||||||
|
bundle, err := parseBundle(reg.intelUpdateIndex.DownloadDirectory, reg.intelUpdateIndex.IndexFile)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to parse index file: %w", err)
|
||||||
|
}
|
||||||
|
err = bundle.Verify()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("binary bundle is not valid: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.RemoveAll(reg.intelUpdateIndex.Directory)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to remove dir: %w", err)
|
||||||
|
}
|
||||||
|
err = os.Rename(reg.intelUpdateIndex.DownloadDirectory, reg.intelUpdateIndex.Directory)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to move dir: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseBundle(dir string, indexFile string) (*Bundle, error) {
|
||||||
|
filepath := fmt.Sprintf("%s/%s", dir, indexFile)
|
||||||
|
// Check if the file exists.
|
||||||
|
file, err := os.Open(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open index file: %w", err)
|
||||||
|
}
|
||||||
|
defer func() { _ = file.Close() }()
|
||||||
|
|
||||||
|
// Read
|
||||||
|
content, err := io.ReadAll(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
var bundle Bundle
|
||||||
|
err = json.Unmarshal(content, &bundle)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bundle.dir = dir
|
||||||
|
|
||||||
|
return &bundle, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteUnfinishedDownloads(rootDir string) error {
|
||||||
|
return filepath.Walk(rootDir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the current file has the specified extension
|
||||||
|
if !info.IsDir() && strings.HasSuffix(info.Name(), ".download") {
|
||||||
|
log.Warningf("updates deleting unfinished: %s\n", path)
|
||||||
|
err := os.Remove(path)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to delete file %s: %w", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -54,7 +54,7 @@ func DelayedRestart(delay time.Duration) {
|
|||||||
// Schedule the restart task.
|
// Schedule the restart task.
|
||||||
log.Warningf("updates: restart triggered, will execute in %s", delay)
|
log.Warningf("updates: restart triggered, will execute in %s", delay)
|
||||||
restartAt := time.Now().Add(delay)
|
restartAt := time.Now().Add(delay)
|
||||||
module.restartWorkerMgr.Delay(delay)
|
// module.restartWorkerMgr.Delay(delay)
|
||||||
|
|
||||||
// Set restartTime.
|
// Set restartTime.
|
||||||
restartTimeLock.Lock()
|
restartTimeLock.Lock()
|
||||||
@@ -68,23 +68,23 @@ func AbortRestart() {
|
|||||||
log.Warningf("updates: restart aborted")
|
log.Warningf("updates: restart aborted")
|
||||||
|
|
||||||
// Cancel schedule.
|
// Cancel schedule.
|
||||||
module.restartWorkerMgr.Delay(0)
|
// module.restartWorkerMgr.Delay(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TriggerRestartIfPending triggers an automatic restart, if one is pending.
|
// TriggerRestartIfPending triggers an automatic restart, if one is pending.
|
||||||
// This can be used to prepone a scheduled restart if the conditions are preferable.
|
// This can be used to prepone a scheduled restart if the conditions are preferable.
|
||||||
func TriggerRestartIfPending() {
|
func TriggerRestartIfPending() {
|
||||||
if restartPending.IsSet() {
|
// if restartPending.IsSet() {
|
||||||
module.restartWorkerMgr.Go()
|
// module.restartWorkerMgr.Go()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestartNow immediately executes a restart.
|
// RestartNow immediately executes a restart.
|
||||||
// This only works if the process is managed by portmaster-start.
|
// This only works if the process is managed by portmaster-start.
|
||||||
func RestartNow() {
|
func RestartNow() {
|
||||||
restartPending.Set()
|
restartPending.Set()
|
||||||
module.restartWorkerMgr.Go()
|
// module.restartWorkerMgr.Go()
|
||||||
}
|
}
|
||||||
|
|
||||||
func automaticRestart(w *mgr.WorkerCtx) error {
|
func automaticRestart(w *mgr.WorkerCtx) error {
|
||||||
@@ -108,11 +108,11 @@ func automaticRestart(w *mgr.WorkerCtx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set restart exit code.
|
// Set restart exit code.
|
||||||
if !rebooting {
|
// if !rebooting {
|
||||||
module.instance.Restart()
|
// module.instance.Restart()
|
||||||
} else {
|
// } else {
|
||||||
module.instance.Shutdown()
|
// module.instance.Shutdown()
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -1,49 +1,49 @@
|
|||||||
package updates
|
package updates
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"github.com/safing/portmaster/base/database/record"
|
// "github.com/safing/portmaster/base/database/record"
|
||||||
"github.com/safing/portmaster/base/runtime"
|
// "github.com/safing/portmaster/base/runtime"
|
||||||
"github.com/safing/portmaster/base/updater"
|
// "github.com/safing/portmaster/base/updater"
|
||||||
)
|
// )
|
||||||
|
|
||||||
var pushRegistryStatusUpdate runtime.PushFunc
|
// var pushRegistryStatusUpdate runtime.PushFunc
|
||||||
|
|
||||||
// RegistryStateExport is a wrapper to export the registry state.
|
// // RegistryStateExport is a wrapper to export the registry state.
|
||||||
type RegistryStateExport struct {
|
// type RegistryStateExport struct {
|
||||||
record.Base
|
// record.Base
|
||||||
*updater.RegistryState
|
// *updater.RegistryState
|
||||||
}
|
// }
|
||||||
|
|
||||||
func exportRegistryState(s *updater.RegistryState) *RegistryStateExport {
|
// func exportRegistryState(s *updater.RegistryState) *RegistryStateExport {
|
||||||
if s == nil {
|
// // if s == nil {
|
||||||
state := registry.GetState()
|
// // state := registry.GetState()
|
||||||
s = &state
|
// // s = &state
|
||||||
}
|
// // }
|
||||||
|
|
||||||
export := &RegistryStateExport{
|
// export := &RegistryStateExport{
|
||||||
RegistryState: s,
|
// RegistryState: s,
|
||||||
}
|
// }
|
||||||
|
|
||||||
export.CreateMeta()
|
// export.CreateMeta()
|
||||||
export.SetKey("runtime:core/updates/state")
|
// export.SetKey("runtime:core/updates/state")
|
||||||
|
|
||||||
return export
|
// return export
|
||||||
}
|
// }
|
||||||
|
|
||||||
func pushRegistryState(s *updater.RegistryState) {
|
// func pushRegistryState(s *updater.RegistryState) {
|
||||||
export := exportRegistryState(s)
|
// export := exportRegistryState(s)
|
||||||
pushRegistryStatusUpdate(export)
|
// pushRegistryStatusUpdate(export)
|
||||||
}
|
// }
|
||||||
|
|
||||||
func registerRegistryStateProvider() (err error) {
|
// func registerRegistryStateProvider() (err error) {
|
||||||
registryStateProvider := runtime.SimpleValueGetterFunc(func(_ string) ([]record.Record, error) {
|
// registryStateProvider := runtime.SimpleValueGetterFunc(func(_ string) ([]record.Record, error) {
|
||||||
return []record.Record{exportRegistryState(nil)}, nil
|
// return []record.Record{exportRegistryState(nil)}, nil
|
||||||
})
|
// })
|
||||||
|
|
||||||
pushRegistryStatusUpdate, err = runtime.Register("core/updates/state", registryStateProvider)
|
// pushRegistryStatusUpdate, err = runtime.Register("core/updates/state", registryStateProvider)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,406 +1,403 @@
|
|||||||
package updates
|
package updates
|
||||||
|
|
||||||
import (
|
// import (
|
||||||
"context"
|
// "context"
|
||||||
"fmt"
|
// "fmt"
|
||||||
"io"
|
// "os"
|
||||||
"os"
|
// "os/exec"
|
||||||
"os/exec"
|
// "path/filepath"
|
||||||
"path/filepath"
|
// "regexp"
|
||||||
"regexp"
|
// "strings"
|
||||||
"strings"
|
// "time"
|
||||||
"time"
|
|
||||||
|
|
||||||
processInfo "github.com/shirou/gopsutil/process"
|
// processInfo "github.com/shirou/gopsutil/process"
|
||||||
"github.com/tevino/abool"
|
// "github.com/tevino/abool"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/dataroot"
|
// "github.com/safing/portmaster/base/dataroot"
|
||||||
"github.com/safing/portmaster/base/info"
|
// "github.com/safing/portmaster/base/info"
|
||||||
"github.com/safing/portmaster/base/log"
|
// "github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/notifications"
|
// "github.com/safing/portmaster/base/notifications"
|
||||||
"github.com/safing/portmaster/base/rng"
|
// "github.com/safing/portmaster/base/rng"
|
||||||
"github.com/safing/portmaster/base/updater"
|
// "github.com/safing/portmaster/base/updater"
|
||||||
"github.com/safing/portmaster/base/utils/renameio"
|
// "github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
// )
|
||||||
"github.com/safing/portmaster/service/updates/helper"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
// const (
|
||||||
upgradedSuffix = "-upgraded"
|
// upgradedSuffix = "-upgraded"
|
||||||
exeExt = ".exe"
|
// exeExt = ".exe"
|
||||||
)
|
// )
|
||||||
|
|
||||||
var (
|
// var (
|
||||||
upgraderActive = abool.NewBool(false)
|
// upgraderActive = abool.NewBool(false)
|
||||||
|
|
||||||
pmCtrlUpdate *updater.File
|
// pmCtrlUpdate *updater.File
|
||||||
pmCoreUpdate *updater.File
|
// pmCoreUpdate *updater.File
|
||||||
|
|
||||||
spnHubUpdate *updater.File
|
// spnHubUpdate *updater.File
|
||||||
|
|
||||||
rawVersionRegex = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+b?\*?$`)
|
// rawVersionRegex = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+b?\*?$`)
|
||||||
)
|
// )
|
||||||
|
|
||||||
func initUpgrader() error {
|
// func initUpgrader() error {
|
||||||
module.EventResourcesUpdated.AddCallback("run upgrades", upgrader)
|
// // module.EventResourcesUpdated.AddCallback("run upgrades", upgrader)
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func upgrader(m *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
|
// func upgrader(m *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
|
||||||
// Lock runs, but discard additional runs.
|
// // Lock runs, but discard additional runs.
|
||||||
if !upgraderActive.SetToIf(false, true) {
|
// if !upgraderActive.SetToIf(false, true) {
|
||||||
return false, nil
|
// return false, nil
|
||||||
}
|
// }
|
||||||
defer upgraderActive.SetTo(false)
|
// defer upgraderActive.SetTo(false)
|
||||||
|
|
||||||
// Upgrade portmaster-start.
|
// // Upgrade portmaster-start.
|
||||||
err = upgradePortmasterStart()
|
// err = upgradePortmasterStart()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Warningf("updates: failed to upgrade portmaster-start: %s", err)
|
// log.Warningf("updates: failed to upgrade portmaster-start: %s", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Upgrade based on binary.
|
// // Upgrade based on binary.
|
||||||
binBaseName := strings.Split(filepath.Base(os.Args[0]), "_")[0]
|
// binBaseName := strings.Split(filepath.Base(os.Args[0]), "_")[0]
|
||||||
switch binBaseName {
|
// switch binBaseName {
|
||||||
case "portmaster-core":
|
// case "portmaster-core":
|
||||||
// Notify about upgrade.
|
// // Notify about upgrade.
|
||||||
if err := upgradeCoreNotify(); err != nil {
|
// if err := upgradeCoreNotify(); err != nil {
|
||||||
log.Warningf("updates: failed to notify about core upgrade: %s", err)
|
// log.Warningf("updates: failed to notify about core upgrade: %s", err)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Fix chrome sandbox permissions.
|
// // Fix chrome sandbox permissions.
|
||||||
if err := helper.EnsureChromeSandboxPermissions(registry); err != nil {
|
// // if err := helper.EnsureChromeSandboxPermissions(registry); err != nil {
|
||||||
log.Warningf("updates: failed to handle electron upgrade: %s", err)
|
// // log.Warningf("updates: failed to handle electron upgrade: %s", err)
|
||||||
}
|
// // }
|
||||||
|
|
||||||
// Upgrade system integration.
|
// // Upgrade system integration.
|
||||||
upgradeSystemIntegration()
|
// upgradeSystemIntegration()
|
||||||
|
|
||||||
case "spn-hub":
|
// case "spn-hub":
|
||||||
// Trigger upgrade procedure.
|
// // Trigger upgrade procedure.
|
||||||
if err := upgradeHub(); err != nil {
|
// if err := upgradeHub(); err != nil {
|
||||||
log.Warningf("updates: failed to initiate hub upgrade: %s", err)
|
// log.Warningf("updates: failed to initiate hub upgrade: %s", err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return false, nil
|
// return false, nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func upgradeCoreNotify() error {
|
// func upgradeCoreNotify() error {
|
||||||
if pmCoreUpdate != nil && !pmCoreUpdate.UpgradeAvailable() {
|
// if pmCoreUpdate != nil && !pmCoreUpdate.UpgradeAvailable() {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// make identifier
|
// // make identifier
|
||||||
identifier := "core/portmaster-core" // identifier, use forward slash!
|
// identifier := "core/portmaster-core" // identifier, use forward slash!
|
||||||
if onWindows {
|
// if onWindows {
|
||||||
identifier += exeExt
|
// identifier += exeExt
|
||||||
}
|
// }
|
||||||
|
|
||||||
// get newest portmaster-core
|
// // get newest portmaster-core
|
||||||
newFile, err := GetPlatformFile(identifier)
|
// // newFile, err := GetPlatformFile(identifier)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return err
|
// // return err
|
||||||
}
|
// // }
|
||||||
pmCoreUpdate = newFile
|
// // pmCoreUpdate = newFile
|
||||||
|
|
||||||
// check for new version
|
// // check for new version
|
||||||
if info.VersionNumber() != pmCoreUpdate.Version() {
|
// if info.VersionNumber() != pmCoreUpdate.Version() {
|
||||||
n := notifications.Notify(¬ifications.Notification{
|
// n := notifications.Notify(¬ifications.Notification{
|
||||||
EventID: "updates:core-update-available",
|
// EventID: "updates:core-update-available",
|
||||||
Type: notifications.Info,
|
// Type: notifications.Info,
|
||||||
Title: fmt.Sprintf(
|
// Title: fmt.Sprintf(
|
||||||
"Portmaster Update v%s Is Ready!",
|
// "Portmaster Update v%s Is Ready!",
|
||||||
pmCoreUpdate.Version(),
|
// pmCoreUpdate.Version(),
|
||||||
),
|
// ),
|
||||||
Category: "Core",
|
// Category: "Core",
|
||||||
Message: fmt.Sprintf(
|
// Message: fmt.Sprintf(
|
||||||
`A new Portmaster version is ready to go! Restart the Portmaster to upgrade to %s.`,
|
// `A new Portmaster version is ready to go! Restart the Portmaster to upgrade to %s.`,
|
||||||
pmCoreUpdate.Version(),
|
// pmCoreUpdate.Version(),
|
||||||
),
|
// ),
|
||||||
ShowOnSystem: true,
|
// ShowOnSystem: true,
|
||||||
AvailableActions: []*notifications.Action{
|
// AvailableActions: []*notifications.Action{
|
||||||
// TODO: Use special UI action in order to reload UI on restart.
|
// // TODO: Use special UI action in order to reload UI on restart.
|
||||||
{
|
// {
|
||||||
ID: "restart",
|
// ID: "restart",
|
||||||
Text: "Restart",
|
// Text: "Restart",
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
ID: "later",
|
// ID: "later",
|
||||||
Text: "Not now",
|
// Text: "Not now",
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
})
|
// })
|
||||||
n.SetActionFunction(upgradeCoreNotifyActionHandler)
|
// n.SetActionFunction(upgradeCoreNotifyActionHandler)
|
||||||
|
|
||||||
log.Debugf("updates: new portmaster version available, sending notification to user")
|
// log.Debugf("updates: new portmaster version available, sending notification to user")
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func upgradeCoreNotifyActionHandler(_ context.Context, n *notifications.Notification) error {
|
// func upgradeCoreNotifyActionHandler(_ context.Context, n *notifications.Notification) error {
|
||||||
switch n.SelectedActionID {
|
// switch n.SelectedActionID {
|
||||||
case "restart":
|
// case "restart":
|
||||||
log.Infof("updates: user triggered restart via core update notification")
|
// log.Infof("updates: user triggered restart via core update notification")
|
||||||
RestartNow()
|
// RestartNow()
|
||||||
case "later":
|
// case "later":
|
||||||
n.Delete()
|
// n.Delete()
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func upgradeHub() error {
|
// func upgradeHub() error {
|
||||||
if spnHubUpdate != nil && !spnHubUpdate.UpgradeAvailable() {
|
// if spnHubUpdate != nil && !spnHubUpdate.UpgradeAvailable() {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Make identifier for getting file from updater.
|
// // Make identifier for getting file from updater.
|
||||||
identifier := "hub/spn-hub" // identifier, use forward slash!
|
// identifier := "hub/spn-hub" // identifier, use forward slash!
|
||||||
if onWindows {
|
// if onWindows {
|
||||||
identifier += exeExt
|
// identifier += exeExt
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get newest spn-hub file.
|
// // Get newest spn-hub file.
|
||||||
newFile, err := GetPlatformFile(identifier)
|
// // newFile, err := GetPlatformFile(identifier)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return err
|
// // return err
|
||||||
}
|
// // }
|
||||||
spnHubUpdate = newFile
|
// // spnHubUpdate = newFile
|
||||||
|
|
||||||
// Check if the new version is different.
|
// // Check if the new version is different.
|
||||||
if info.GetInfo().Version != spnHubUpdate.Version() {
|
// if info.GetInfo().Version != spnHubUpdate.Version() {
|
||||||
// Get random delay with up to three hours.
|
// // Get random delay with up to three hours.
|
||||||
delayMinutes, err := rng.Number(3 * 60)
|
// delayMinutes, err := rng.Number(3 * 60)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Delay restart for at least one hour for preparations.
|
// // Delay restart for at least one hour for preparations.
|
||||||
DelayedRestart(time.Duration(delayMinutes+60) * time.Minute)
|
// DelayedRestart(time.Duration(delayMinutes+60) * time.Minute)
|
||||||
|
|
||||||
// Increase update checks in order to detect aborts better.
|
// // Increase update checks in order to detect aborts better.
|
||||||
// if !disableTaskSchedule {
|
// // if !disableTaskSchedule {
|
||||||
module.updateWorkerMgr.Repeat(10 * time.Minute)
|
// // module.updateBinaryWorkerMgr.Repeat(10 * time.Minute)
|
||||||
// }
|
// // }
|
||||||
} else {
|
// } else {
|
||||||
AbortRestart()
|
// AbortRestart()
|
||||||
|
|
||||||
// Set update task schedule back to normal.
|
// // Set update task schedule back to normal.
|
||||||
// if !disableTaskSchedule {
|
// // if !disableTaskSchedule {
|
||||||
module.updateWorkerMgr.Repeat(updateTaskRepeatDuration)
|
// // module.updateBinaryWorkerMgr.Repeat(updateTaskRepeatDuration)
|
||||||
// }
|
// // }
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func upgradePortmasterStart() error {
|
// func upgradePortmasterStart() error {
|
||||||
filename := "portmaster-start"
|
// filename := "portmaster-start"
|
||||||
if onWindows {
|
// if onWindows {
|
||||||
filename += exeExt
|
// filename += exeExt
|
||||||
}
|
// }
|
||||||
|
|
||||||
// check if we can upgrade
|
// // check if we can upgrade
|
||||||
if pmCtrlUpdate == nil || pmCtrlUpdate.UpgradeAvailable() {
|
// if pmCtrlUpdate == nil || pmCtrlUpdate.UpgradeAvailable() {
|
||||||
// get newest portmaster-start
|
// // get newest portmaster-start
|
||||||
newFile, err := GetPlatformFile("start/" + filename) // identifier, use forward slash!
|
// // newFile, err := GetPlatformFile("start/" + filename) // identifier, use forward slash!
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return err
|
// // return err
|
||||||
}
|
// // }
|
||||||
pmCtrlUpdate = newFile
|
// // pmCtrlUpdate = newFile
|
||||||
} else {
|
// } else {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// update portmaster-start in data root
|
// // update portmaster-start in data root
|
||||||
rootPmStartPath := filepath.Join(dataroot.Root().Path, filename)
|
// rootPmStartPath := filepath.Join(dataroot.Root().Path, filename)
|
||||||
err := upgradeBinary(rootPmStartPath, pmCtrlUpdate)
|
// err := upgradeBinary(rootPmStartPath, pmCtrlUpdate)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
func warnOnIncorrectParentPath() {
|
// func warnOnIncorrectParentPath() {
|
||||||
expectedFileName := "portmaster-start"
|
// expectedFileName := "portmaster-start"
|
||||||
if onWindows {
|
// if onWindows {
|
||||||
expectedFileName += exeExt
|
// expectedFileName += exeExt
|
||||||
}
|
// }
|
||||||
|
|
||||||
// upgrade parent process, if it's portmaster-start
|
// // upgrade parent process, if it's portmaster-start
|
||||||
parent, err := processInfo.NewProcess(int32(os.Getppid()))
|
// parent, err := processInfo.NewProcess(int32(os.Getppid()))
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Tracef("could not get parent process: %s", err)
|
// log.Tracef("could not get parent process: %s", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
parentName, err := parent.Name()
|
// parentName, err := parent.Name()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Tracef("could not get parent process name: %s", err)
|
// log.Tracef("could not get parent process name: %s", err)
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if parentName != expectedFileName {
|
// if parentName != expectedFileName {
|
||||||
// Only warn about this if not in dev mode.
|
// // Only warn about this if not in dev mode.
|
||||||
if !devMode() {
|
// if !devMode() {
|
||||||
log.Warningf("updates: parent process does not seem to be portmaster-start, name is %s", parentName)
|
// 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
|
// // TODO(ppacher): once we released a new installer and folks had time
|
||||||
// to update we should send a module warning/hint to the
|
// // to update we should send a module warning/hint to the
|
||||||
// UI notifying the user that he's still using portmaster-control.
|
// // UI notifying the user that he's still using portmaster-control.
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
parentPath, err := parent.Exe()
|
// // parentPath, err := parent.Exe()
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
log.Tracef("could not get parent process path: %s", err)
|
// // log.Tracef("could not get parent process path: %s", err)
|
||||||
return
|
// // return
|
||||||
}
|
// // }
|
||||||
|
|
||||||
absPath, err := filepath.Abs(parentPath)
|
// // absPath, err := filepath.Abs(parentPath)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
log.Tracef("could not get absolut parent process path: %s", err)
|
// // log.Tracef("could not get absolut parent process path: %s", err)
|
||||||
return
|
// // return
|
||||||
}
|
// // }
|
||||||
|
|
||||||
root := filepath.Dir(registry.StorageDir().Path)
|
// // root := filepath.Dir(registry.StorageDir().Path)
|
||||||
if !strings.HasPrefix(absPath, root) {
|
// // if !strings.HasPrefix(absPath, root) {
|
||||||
log.Warningf("detected unexpected path %s for portmaster-start", absPath)
|
// // log.Warningf("detected unexpected path %s for portmaster-start", absPath)
|
||||||
notifications.NotifyWarn(
|
// // notifications.NotifyWarn(
|
||||||
"updates:unsupported-parent",
|
// // "updates:unsupported-parent",
|
||||||
"Unsupported Launcher",
|
// // "Unsupported Launcher",
|
||||||
fmt.Sprintf(
|
// // 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.",
|
// // "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,
|
// // expectedFileName,
|
||||||
absPath,
|
// // absPath,
|
||||||
filepath.Join(root, expectedFileName),
|
// // filepath.Join(root, expectedFileName),
|
||||||
),
|
// // ),
|
||||||
)
|
// // )
|
||||||
}
|
// // }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func upgradeBinary(fileToUpgrade string, file *updater.File) error {
|
// func upgradeBinary(fileToUpgrade string, file *updater.File) error {
|
||||||
fileExists := false
|
// fileExists := false
|
||||||
_, err := os.Stat(fileToUpgrade)
|
// _, err := os.Stat(fileToUpgrade)
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
// file exists and is accessible
|
// // file exists and is accessible
|
||||||
fileExists = true
|
// fileExists = true
|
||||||
}
|
// }
|
||||||
|
|
||||||
if fileExists {
|
// if fileExists {
|
||||||
// get current version
|
// // get current version
|
||||||
var currentVersion string
|
// var currentVersion string
|
||||||
cmd := exec.Command(fileToUpgrade, "version", "--short")
|
// cmd := exec.Command(fileToUpgrade, "version", "--short")
|
||||||
out, err := cmd.Output()
|
// out, err := cmd.Output()
|
||||||
if err == nil {
|
// if err == nil {
|
||||||
// abort if version matches
|
// // abort if version matches
|
||||||
currentVersion = strings.Trim(strings.TrimSpace(string(out)), "*")
|
// currentVersion = strings.Trim(strings.TrimSpace(string(out)), "*")
|
||||||
if currentVersion == file.Version() {
|
// if currentVersion == file.Version() {
|
||||||
log.Debugf("updates: %s is already v%s", fileToUpgrade, file.Version())
|
// log.Debugf("updates: %s is already v%s", fileToUpgrade, file.Version())
|
||||||
// already up to date!
|
// // already up to date!
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
log.Warningf("updates: failed to run %s to get version for upgrade check: %s", fileToUpgrade, err)
|
// log.Warningf("updates: failed to run %s to get version for upgrade check: %s", fileToUpgrade, err)
|
||||||
currentVersion = "0.0.0"
|
// currentVersion = "0.0.0"
|
||||||
}
|
// }
|
||||||
|
|
||||||
// test currentVersion for sanity
|
// // test currentVersion for sanity
|
||||||
if !rawVersionRegex.MatchString(currentVersion) {
|
// if !rawVersionRegex.MatchString(currentVersion) {
|
||||||
log.Debugf("updates: version string returned by %s is invalid: %s", fileToUpgrade, currentVersion)
|
// log.Debugf("updates: version string returned by %s is invalid: %s", fileToUpgrade, currentVersion)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// try removing old version
|
// // try removing old version
|
||||||
err = os.Remove(fileToUpgrade)
|
// err = os.Remove(fileToUpgrade)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
// ensure tmp dir is here
|
// // ensure tmp dir is here
|
||||||
err = registry.TmpDir().Ensure()
|
// // err = registry.TmpDir().Ensure()
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return fmt.Errorf("could not prepare tmp directory for moving file that needs upgrade: %w", err)
|
// // 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
|
// // maybe we're on windows and it's in use, try moving
|
||||||
err = os.Rename(fileToUpgrade, filepath.Join(
|
// // err = os.Rename(fileToUpgrade, filepath.Join(
|
||||||
registry.TmpDir().Path,
|
// // registry.TmpDir().Path,
|
||||||
fmt.Sprintf(
|
// // fmt.Sprintf(
|
||||||
"%s-%d%s",
|
// // "%s-%d%s",
|
||||||
filepath.Base(fileToUpgrade),
|
// // filepath.Base(fileToUpgrade),
|
||||||
time.Now().UTC().Unix(),
|
// // time.Now().UTC().Unix(),
|
||||||
upgradedSuffix,
|
// // upgradedSuffix,
|
||||||
),
|
// // ),
|
||||||
))
|
// // ))
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return fmt.Errorf("unable to move file that needs upgrade: %w", err)
|
// // return fmt.Errorf("unable to move file that needs upgrade: %w", err)
|
||||||
}
|
// // }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// copy upgrade
|
// // copy upgrade
|
||||||
err = CopyFile(file.Path(), fileToUpgrade)
|
// err = CopyFile(file.Path(), fileToUpgrade)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
// try again
|
// // try again
|
||||||
time.Sleep(1 * time.Second)
|
// time.Sleep(1 * time.Second)
|
||||||
err = CopyFile(file.Path(), fileToUpgrade)
|
// err = CopyFile(file.Path(), fileToUpgrade)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return err
|
// return err
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// check permissions
|
// // check permissions
|
||||||
if !onWindows {
|
// if !onWindows {
|
||||||
info, err := os.Stat(fileToUpgrade)
|
// info, err := os.Stat(fileToUpgrade)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to get file info on %s: %w", fileToUpgrade, err)
|
// return fmt.Errorf("failed to get file info on %s: %w", fileToUpgrade, err)
|
||||||
}
|
// }
|
||||||
if info.Mode() != 0o0755 {
|
// if info.Mode() != 0o0755 {
|
||||||
err := os.Chmod(fileToUpgrade, 0o0755) //nolint:gosec // Set execute permissions.
|
// err := os.Chmod(fileToUpgrade, 0o0755) //nolint:gosec // Set execute permissions.
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
return fmt.Errorf("failed to set permissions on %s: %w", fileToUpgrade, err)
|
// return fmt.Errorf("failed to set permissions on %s: %w", fileToUpgrade, err)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
log.Infof("updates: upgraded %s to v%s", fileToUpgrade, file.Version())
|
// log.Infof("updates: upgraded %s to v%s", fileToUpgrade, file.Version())
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|
||||||
// CopyFile atomically copies a file using the update registry's tmp dir.
|
// // CopyFile atomically copies a file using the update registry's tmp dir.
|
||||||
func CopyFile(srcPath, dstPath string) error {
|
// func CopyFile(srcPath, dstPath string) error {
|
||||||
// check tmp dir
|
// // check tmp dir
|
||||||
err := registry.TmpDir().Ensure()
|
// // err := registry.TmpDir().Ensure()
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return fmt.Errorf("could not prepare tmp directory for copying file: %w", err)
|
// // return fmt.Errorf("could not prepare tmp directory for copying file: %w", err)
|
||||||
}
|
// // }
|
||||||
|
|
||||||
// open file for writing
|
// // open file for writing
|
||||||
atomicDstFile, err := renameio.TempFile(registry.TmpDir().Path, dstPath)
|
// // atomicDstFile, err := renameio.TempFile(registry.TmpDir().Path, dstPath)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return fmt.Errorf("could not create temp file for atomic copy: %w", err)
|
// // 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
|
// // defer atomicDstFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway
|
||||||
|
|
||||||
// open source
|
// // // open source
|
||||||
srcFile, err := os.Open(srcPath)
|
// // srcFile, err := os.Open(srcPath)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return err
|
// // return err
|
||||||
}
|
// // }
|
||||||
defer func() {
|
// // defer func() {
|
||||||
_ = srcFile.Close()
|
// // _ = srcFile.Close()
|
||||||
}()
|
// // }()
|
||||||
|
|
||||||
// copy data
|
// // // copy data
|
||||||
_, err = io.Copy(atomicDstFile, srcFile)
|
// // _, err = io.Copy(atomicDstFile, srcFile)
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return err
|
// // return err
|
||||||
}
|
// // }
|
||||||
|
|
||||||
// finalize file
|
// // // finalize file
|
||||||
err = atomicDstFile.CloseAtomicallyReplace()
|
// // err = atomicDstFile.CloseAtomicallyReplace()
|
||||||
if err != nil {
|
// // if err != nil {
|
||||||
return fmt.Errorf("updates: failed to finalize copy to file %s: %w", dstPath, err)
|
// // return fmt.Errorf("updates: failed to finalize copy to file %s: %w", dstPath, err)
|
||||||
}
|
// // }
|
||||||
|
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -6,9 +6,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/updater"
|
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/service/updates"
|
"github.com/safing/portmaster/service/updates/registry"
|
||||||
"github.com/safing/portmaster/spn/conf"
|
"github.com/safing/portmaster/spn/conf"
|
||||||
"github.com/safing/portmaster/spn/hub"
|
"github.com/safing/portmaster/spn/hub"
|
||||||
"github.com/safing/portmaster/spn/navigator"
|
"github.com/safing/portmaster/spn/navigator"
|
||||||
@@ -16,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
intelResource *updater.File
|
intelResource *registry.File
|
||||||
intelResourcePath = "intel/spn/main-intel.yaml"
|
intelResourcePath = "intel/spn/main-intel.yaml"
|
||||||
intelResourceMapName = "main"
|
intelResourceMapName = "main"
|
||||||
intelResourceUpdateLock sync.Mutex
|
intelResourceUpdateLock sync.Mutex
|
||||||
@@ -44,12 +43,13 @@ func updateSPNIntel(_ context.Context, _ interface{}) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if there is something to do.
|
// Check if there is something to do.
|
||||||
if intelResource != nil && !intelResource.UpgradeAvailable() {
|
// TODO(vladimir): is update check needed
|
||||||
|
if intelResource != nil { //&& !intelResource.UpgradeAvailable() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get intel file and load it from disk.
|
// Get intel file and load it from disk.
|
||||||
intelResource, err = updates.GetFile(intelResourcePath)
|
intelResource, err = module.instance.Updates().GetFile(intelResourcePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get SPN intel update: %w", err)
|
return fmt.Errorf("failed to get SPN intel update: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user