[WIP] updater rafactoring, minor improvments
This commit is contained in:
@@ -35,20 +35,12 @@ type windowsService struct {
|
||||
func (ws *windowsService) Execute(args []string, changeRequests <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
|
||||
startupComplete := make(chan struct{})
|
||||
go func() {
|
||||
for !ws.instance.Ready() {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
startupComplete <- struct{}{}
|
||||
}()
|
||||
ws.instance.Start()
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
|
||||
service:
|
||||
for {
|
||||
select {
|
||||
case <-startupComplete:
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
case <-ws.instance.Stopped():
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
break service
|
||||
@@ -59,13 +51,13 @@ service:
|
||||
case svc.Stop, svc.Shutdown:
|
||||
ws.instance.Shutdown()
|
||||
default:
|
||||
log.Errorf("unexpected control request: #%d\n", c)
|
||||
log.Errorf("unexpected control request: #%d", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wait until everything else is finished
|
||||
finishWg.Wait()
|
||||
// finishWg.Wait()
|
||||
|
||||
log.Shutdown()
|
||||
|
||||
@@ -88,12 +80,12 @@ func run(instance *service.Instance) error {
|
||||
// select service run type
|
||||
svcRun := svc.Run
|
||||
if !isService {
|
||||
log.Warningf("running interactively, switching to debug execution (no real service).\n")
|
||||
log.Warningf("running interactively, switching to debug execution (no real service).")
|
||||
svcRun = debug.Run
|
||||
go registerSignalHandler(instance)
|
||||
}
|
||||
|
||||
runWg.Add(2)
|
||||
runWg.Add(1)
|
||||
|
||||
// run service client
|
||||
go func() {
|
||||
@@ -105,29 +97,27 @@ func run(instance *service.Instance) error {
|
||||
} else {
|
||||
log.Infof("shuting down service")
|
||||
}
|
||||
instance.Shutdown()
|
||||
runWg.Done()
|
||||
}()
|
||||
|
||||
finishWg.Add(1)
|
||||
// finishWg.Add(1)
|
||||
// run service
|
||||
go func() {
|
||||
// run slightly delayed
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
instance.Start()
|
||||
// go func() {
|
||||
// // run slightly delayed
|
||||
// time.Sleep(250 * time.Millisecond)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("instance start failed: %s\n", err)
|
||||
// if err != nil {
|
||||
// fmt.Printf("instance start failed: %s\n", err)
|
||||
|
||||
// Print stack on start failure, if enabled.
|
||||
if printStackOnExit {
|
||||
printStackTo(os.Stdout, "PRINTING STACK ON START FAILURE")
|
||||
}
|
||||
// // Print stack on start failure, if enabled.
|
||||
// if printStackOnExit {
|
||||
// printStackTo(os.Stdout, "PRINTING STACK ON START FAILURE")
|
||||
// }
|
||||
|
||||
}
|
||||
runWg.Done()
|
||||
finishWg.Done()
|
||||
}()
|
||||
// }
|
||||
// runWg.Done()
|
||||
// finishWg.Done()
|
||||
// }()
|
||||
|
||||
runWg.Wait()
|
||||
|
||||
|
||||
@@ -33,7 +33,9 @@ Update WIX installer template:
|
||||
2. Replace the contents of `templates/main_original.wxs` with the repository version.
|
||||
3. Replace the contents of `templates/main.wsx` and add the fallowing lines at the end of the file, inside the `Product` tag.
|
||||
```xml
|
||||
<!-- Service fragments -->
|
||||
<CustomActionRef Id='InstallPortmasterService' />
|
||||
<CustomActionRef Id='StopPortmasterService' />
|
||||
<CustomActionRef Id='DeletePortmasterService' />
|
||||
<!-- End Service fragments -->
|
||||
```
|
||||
|
||||
@@ -109,8 +109,10 @@
|
||||
},
|
||||
"wix": {
|
||||
"fragmentPaths": [
|
||||
"templates/service.wxs"
|
||||
"templates/service.wxs",
|
||||
"templates/files.wxs"
|
||||
],
|
||||
"componentGroupRefs": ["BinaryAndIntelFiles"],
|
||||
"template": "templates/main.wxs"
|
||||
}
|
||||
},
|
||||
|
||||
36
desktop/tauri/src-tauri/templates/files.wxs
Normal file
36
desktop/tauri/src-tauri/templates/files.wxs
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||
<Fragment>
|
||||
<DirectoryRef Id="INSTALLDIR">
|
||||
<Directory Id="BinaryDir" Name="binary" />
|
||||
<Directory Id="IntelDir" Name="intel" />
|
||||
</DirectoryRef>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<Component Id="BinaryFiles" Directory="BinaryDir" Guid="850cdd31-424d-45f5-b8f0-95df950ebd0d">
|
||||
<File Id="BinIndexJson" Source="..\..\..\..\binaries\bin-index.json" />
|
||||
<File Id="PortmasterCoreExe" Source="..\..\..\..\binaries\portmaster-core.exe" />
|
||||
<File Id="PortmasterKextSys" Source="..\..\..\..\binaries\portmaster-kext.sys" />
|
||||
<File Id="PortmasterZip" Source="..\..\..\..\binaries\portmaster.zip" />
|
||||
<File Id="AssetsZip" Source="..\..\..\..\binaries\assets.zip" />
|
||||
</Component>
|
||||
|
||||
<Component Id="IntelFiles" Directory="IntelDir" Guid="0bb439f1-2075-45b0-95bf-78ed3dffeb69">
|
||||
<File Id="IntelIndexJson" Source="..\..\..\..\binaries\intel-index.json" />
|
||||
<File Id="BaseDsdl" Source="..\..\..\..\binaries\base.dsdl" />
|
||||
<File Id="Geoipv4Mmdb" Source="..\..\..\..\binaries\geoipv4.mmdb" />
|
||||
<File Id="Geoipv6Mmdb" Source="..\..\..\..\binaries\geoipv6.mmdb" />
|
||||
<File Id="IndexDsd" Source="..\..\..\..\binaries\index.dsd" />
|
||||
<File Id="IntermediateDsdl" Source="..\..\..\..\binaries\intermediate.dsdl" />
|
||||
<File Id="UrgentDsdl" Source="..\..\..\..\binaries\urgent.dsdl" />
|
||||
</Component>
|
||||
</Fragment>
|
||||
|
||||
<Fragment>
|
||||
<ComponentGroup Id="BinaryAndIntelFiles">
|
||||
<ComponentRef Id="BinaryFiles" />
|
||||
<ComponentRef Id="IntelFiles" />
|
||||
</ComponentGroup>
|
||||
</Fragment>
|
||||
</Wix>
|
||||
@@ -341,14 +341,16 @@
|
||||
</InstallExecuteSequence>
|
||||
{{/if}}
|
||||
|
||||
<CustomActionRef Id='InstallPortmasterService' />
|
||||
<CustomActionRef Id='StopPortmasterService' />
|
||||
<CustomActionRef Id='DeletePortmasterService' />
|
||||
|
||||
<InstallExecuteSequence>
|
||||
<Custom Action="LaunchApplication" After="InstallFinalize">AUTOLAUNCHAPP AND NOT Installed</Custom>
|
||||
</InstallExecuteSequence>
|
||||
|
||||
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||
|
||||
<!-- Service fragments -->
|
||||
<CustomActionRef Id='InstallPortmasterService' />
|
||||
<CustomActionRef Id='StopPortmasterService' />
|
||||
<CustomActionRef Id='DeletePortmasterService' />
|
||||
<!-- End Service fragments -->
|
||||
</Product>
|
||||
</Wix>
|
||||
@@ -3,7 +3,7 @@
|
||||
<Fragment>
|
||||
<CustomAction Id="InstallPortmasterService"
|
||||
Directory="INSTALLDIR"
|
||||
ExeCommand=""[INSTALLDIR]portmaster-start.exe" install core-service --data="[INSTALLDIR]data""
|
||||
ExeCommand="sc.exe create PortmasterCore binPath= "[INSTALLDIR]binary\portmaster-core.exe --data [INSTALLDIR]data""
|
||||
Execute="commit"
|
||||
Return="check"
|
||||
Impersonate="no"
|
||||
|
||||
@@ -3,6 +3,8 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
go_runtime "runtime"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@@ -35,7 +37,6 @@ import (
|
||||
"github.com/safing/portmaster/service/sync"
|
||||
"github.com/safing/portmaster/service/ui"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
"github.com/safing/portmaster/spn/access"
|
||||
"github.com/safing/portmaster/spn/cabin"
|
||||
"github.com/safing/portmaster/spn/captain"
|
||||
@@ -103,31 +104,54 @@ type Instance struct {
|
||||
CommandLineOperation func() error
|
||||
}
|
||||
|
||||
func getCurrentBinaryFolder() (string, error) {
|
||||
// Get the path of the currently running executable
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get executable path: %w", err)
|
||||
}
|
||||
|
||||
// Get the absolute path
|
||||
absPath, err := filepath.Abs(exePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get absolute path: %w", err)
|
||||
}
|
||||
|
||||
// Get the directory of the executable
|
||||
installDir := filepath.Dir(absPath)
|
||||
|
||||
return installDir, nil
|
||||
}
|
||||
|
||||
// New returns a new Portmaster service instance.
|
||||
func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
||||
var binaryUpdateIndex registry.UpdateIndex
|
||||
var intelUpdateIndex registry.UpdateIndex
|
||||
var binaryUpdateIndex updates.UpdateIndex
|
||||
var intelUpdateIndex updates.UpdateIndex
|
||||
if go_runtime.GOOS == "windows" {
|
||||
binaryUpdateIndex = registry.UpdateIndex{
|
||||
Directory: "C:/Program Files/Portmaster/binary",
|
||||
DownloadDirectory: "C:/Program Files/Portmaster/new_binary",
|
||||
PurgeDirectory: "C:/Program Files/Portmaster/old_binary",
|
||||
binaryFolder, err := getCurrentBinaryFolder()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
binaryUpdateIndex = updates.UpdateIndex{
|
||||
Directory: binaryFolder, // Default: C:/Program Files/Portmaster/binary
|
||||
DownloadDirectory: os.ExpandEnv("%ProgramData%/Portmaster/new_binary"),
|
||||
PurgeDirectory: os.ExpandEnv("%ProgramData%/Portmaster/old_binary"),
|
||||
Ignore: []string{"databases", "intel", "config.json"},
|
||||
IndexURLs: []string{"http://192.168.88.11:8000/test-binary.json"},
|
||||
IndexFile: "bin-index.json",
|
||||
AutoApply: false,
|
||||
}
|
||||
|
||||
intelUpdateIndex = registry.UpdateIndex{
|
||||
Directory: "C:/Program Files/Portmaster/intel",
|
||||
DownloadDirectory: "C:/Program Files/Portmaster/new_intel",
|
||||
PurgeDirectory: "C:/Program Files/Portmaster/old_intel",
|
||||
intelUpdateIndex = updates.UpdateIndex{
|
||||
Directory: os.ExpandEnv("%ProgramData%/Portmaster/intel"),
|
||||
DownloadDirectory: os.ExpandEnv("%ProgramData%/Portmaster/new_intel"),
|
||||
PurgeDirectory: os.ExpandEnv("%ProgramData%/Portmaster/old_intel"),
|
||||
IndexURLs: []string{"http://192.168.88.11:8000/test-intel.json"},
|
||||
IndexFile: "intel-index.json",
|
||||
AutoApply: true,
|
||||
}
|
||||
} else if go_runtime.GOOS == "linux" {
|
||||
binaryUpdateIndex = registry.UpdateIndex{
|
||||
binaryUpdateIndex = updates.UpdateIndex{
|
||||
Directory: "/usr/lib/portmaster",
|
||||
DownloadDirectory: "/var/lib/portmaster/new_bin",
|
||||
PurgeDirectory: "/var/lib/portmaster/old_bin",
|
||||
@@ -137,7 +161,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
||||
AutoApply: false,
|
||||
}
|
||||
|
||||
intelUpdateIndex = registry.UpdateIndex{
|
||||
intelUpdateIndex = updates.UpdateIndex{
|
||||
Directory: "/var/lib/portmaster/intel",
|
||||
DownloadDirectory: "/var/lib/portmaster/new_intel",
|
||||
PurgeDirectory: "/var/lib/portmaster/intel_bin",
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/safing/portmaster/base/database"
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -39,9 +39,9 @@ var (
|
||||
filterListLock sync.RWMutex
|
||||
|
||||
// Updater files for tracking upgrades.
|
||||
baseFile *registry.File
|
||||
intermediateFile *registry.File
|
||||
urgentFile *registry.File
|
||||
baseFile *updates.File
|
||||
intermediateFile *updates.File
|
||||
urgentFile *updates.File
|
||||
|
||||
filterListsLoaded chan struct{}
|
||||
)
|
||||
@@ -77,7 +77,7 @@ func isLoaded() bool {
|
||||
|
||||
// processListFile opens the latest version of file and decodes it's DSDL
|
||||
// content. It calls processEntry for each decoded filterlists entry.
|
||||
func processListFile(ctx context.Context, filter *scopedBloom, file *registry.File) error {
|
||||
func processListFile(ctx context.Context, filter *scopedBloom, file *updates.File) error {
|
||||
f, err := os.Open(file.Path())
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/safing/portmaster/base/database"
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
"github.com/safing/structures/dsd"
|
||||
)
|
||||
|
||||
@@ -162,7 +162,7 @@ func getListIndexFromCache() (*ListIndexFile, error) {
|
||||
|
||||
var (
|
||||
// listIndexUpdate must only be used by updateListIndex.
|
||||
listIndexUpdate *registry.File
|
||||
listIndexUpdate *updates.File
|
||||
listIndexUpdateLock sync.Mutex
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/safing/portmaster/base/database/query"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var updateInProgress = abool.New()
|
||||
@@ -174,8 +174,8 @@ func removeAllObsoleteFilterEntries(wc *mgr.WorkerCtx) error {
|
||||
// getUpgradableFiles returns a slice of filterlists files
|
||||
// that should be updated. The files MUST be updated and
|
||||
// processed in the returned order!
|
||||
func getUpgradableFiles() ([]*registry.File, error) {
|
||||
var updateOrder []*registry.File
|
||||
func getUpgradableFiles() ([]*updates.File, error) {
|
||||
var updateOrder []*updates.File
|
||||
|
||||
// cacheDBInUse := isLoaded()
|
||||
|
||||
@@ -218,7 +218,7 @@ func getUpgradableFiles() ([]*registry.File, error) {
|
||||
return resolveUpdateOrder(updateOrder)
|
||||
}
|
||||
|
||||
func resolveUpdateOrder(updateOrder []*registry.File) ([]*registry.File, error) {
|
||||
func resolveUpdateOrder(updateOrder []*updates.File) ([]*updates.File, error) {
|
||||
// sort the update order by ascending version
|
||||
sort.Sort(byAscVersion(updateOrder))
|
||||
log.Tracef("intel/filterlists: order of updates: %v", updateOrder)
|
||||
@@ -258,7 +258,7 @@ func resolveUpdateOrder(updateOrder []*registry.File) ([]*registry.File, error)
|
||||
return updateOrder[startAtIdx:], nil
|
||||
}
|
||||
|
||||
type byAscVersion []*registry.File
|
||||
type byAscVersion []*updates.File
|
||||
|
||||
func (fs byAscVersion) Len() int { return len(fs) }
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var worker *updateWorker
|
||||
@@ -27,7 +27,7 @@ const (
|
||||
|
||||
type geoIPDB struct {
|
||||
*maxminddb.Reader
|
||||
file *registry.File
|
||||
file *updates.File
|
||||
}
|
||||
|
||||
// updateBroadcaster stores a geoIPDB and provides synchronized
|
||||
@@ -197,7 +197,7 @@ func getGeoIPDB(resource string) (*geoIPDB, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
func open(resource string) (*registry.File, error) {
|
||||
func open(resource string) (*updates.File, error) {
|
||||
f, err := module.instance.IntelUpdates().GetFile(resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getting file: %w", err)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -93,7 +93,7 @@ func (bs *archiveServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// get file from update system
|
||||
zipFile, err := module.instance.BinaryUpdates().GetFile(fmt.Sprintf("%s.zip", moduleName))
|
||||
if err != nil {
|
||||
if errors.Is(err, registry.ErrNotFound) {
|
||||
if errors.Is(err, updates.ErrNotFound) {
|
||||
log.Tracef("ui: requested module %s does not exist", moduleName)
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package registry
|
||||
package updates
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -20,7 +21,7 @@ import (
|
||||
|
||||
const MaxUnpackSize = 1 << 30 // 2^30 == 1GB
|
||||
|
||||
const current_platform = runtime.GOOS + "_" + runtime.GOARCH
|
||||
const currentPlatform = runtime.GOOS + "_" + runtime.GOARCH
|
||||
|
||||
type Artifact struct {
|
||||
Filename string `json:"Filename"`
|
||||
@@ -38,7 +39,73 @@ type Bundle struct {
|
||||
Artifacts []Artifact `json:"Artifacts"`
|
||||
}
|
||||
|
||||
func (bundle Bundle) downloadAndVerify(dir string) {
|
||||
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
|
||||
}
|
||||
return &bundle, nil
|
||||
}
|
||||
|
||||
// CopyMatchingFilesFromCurrent check if there the current bundle files has matching files with the new bundle and copies them if they match.
|
||||
func (bundle Bundle) CopyMatchingFilesFromCurrent(current Bundle, currentDir, newDir string) error {
|
||||
for _, currentArtifact := range current.Artifacts {
|
||||
new:
|
||||
for _, newArtifact := range bundle.Artifacts {
|
||||
if currentArtifact.Filename == newArtifact.Filename {
|
||||
if currentArtifact.SHA256 == newArtifact.SHA256 {
|
||||
// Files match, make sure new dir exists
|
||||
_ = os.MkdirAll(newDir, defaultDirMode)
|
||||
|
||||
// Open the current file
|
||||
sourceFilePath := fmt.Sprintf("%s/%s", currentDir, newArtifact.Filename)
|
||||
sourceFile, err := os.Open(sourceFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open %s file: %w", sourceFilePath, err)
|
||||
}
|
||||
defer sourceFile.Close()
|
||||
|
||||
// Create new file
|
||||
destFilePath := fmt.Sprintf("%s/%s", newDir, newArtifact.Filename)
|
||||
destFile, err := os.Create(destFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create %s file: %w", destFilePath, err)
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
// Copy
|
||||
_, err = io.Copy(destFile, sourceFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to copy contents: %w", err)
|
||||
}
|
||||
// Flush
|
||||
_ = destFile.Sync()
|
||||
|
||||
}
|
||||
break new
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bundle Bundle) DownloadAndVerify(dir string) {
|
||||
client := http.Client{}
|
||||
for _, artifact := range bundle.Artifacts {
|
||||
|
||||
@@ -111,7 +178,7 @@ func checkIfFileIsValid(filename string, artifact Artifact) (bool, error) {
|
||||
|
||||
func processArtifact(client *http.Client, artifact Artifact, filePath string) error {
|
||||
// Skip artifacts not meant for this machine.
|
||||
if artifact.Platform != "" && artifact.Platform != current_platform {
|
||||
if artifact.Platform != "" && artifact.Platform != currentPlatform {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package registry
|
||||
package updates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -19,7 +19,7 @@ type UpdateIndex struct {
|
||||
AutoApply bool
|
||||
}
|
||||
|
||||
func (ui *UpdateIndex) downloadIndexFile() (err error) {
|
||||
func (ui *UpdateIndex) DownloadIndexFile() (err error) {
|
||||
_ = os.MkdirAll(ui.DownloadDirectory, defaultDirMode)
|
||||
for _, url := range ui.IndexURLs {
|
||||
err = ui.downloadIndexFileFromURL(url)
|
||||
@@ -1,23 +1,38 @@
|
||||
package updates
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
semver "github.com/hashicorp/go-version"
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/config"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/notifications"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
)
|
||||
|
||||
var autoUpdate bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&autoUpdate, "auto-update", false, "auto apply downloaded updates")
|
||||
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 ""
|
||||
}
|
||||
|
||||
var ErrNotFound error = errors.New("file not found")
|
||||
|
||||
// Updates provides access to released artifacts.
|
||||
type Updates struct {
|
||||
m *mgr.Manager
|
||||
@@ -29,13 +44,18 @@ type Updates struct {
|
||||
EventResourcesUpdated *mgr.EventMgr[struct{}]
|
||||
EventVersionsUpdated *mgr.EventMgr[struct{}]
|
||||
|
||||
registry registry.Registry
|
||||
updateIndex UpdateIndex
|
||||
|
||||
bundle *Bundle
|
||||
updateBundle *Bundle
|
||||
|
||||
files map[string]File
|
||||
|
||||
instance instance
|
||||
}
|
||||
|
||||
// New returns a new Updates module.
|
||||
func New(instance instance, name string, index registry.UpdateIndex) (*Updates, error) {
|
||||
func New(instance instance, name string, index UpdateIndex) (*Updates, error) {
|
||||
m := mgr.New(name)
|
||||
module := &Updates{
|
||||
m: m,
|
||||
@@ -44,6 +64,8 @@ func New(instance instance, name string, index registry.UpdateIndex) (*Updates,
|
||||
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
|
||||
EventVersionsUpdated: mgr.NewEventMgr[struct{}](VersionUpdateEvent, m),
|
||||
|
||||
updateIndex: index,
|
||||
|
||||
instance: instance,
|
||||
}
|
||||
|
||||
@@ -51,39 +73,87 @@ func New(instance instance, name string, index registry.UpdateIndex) (*Updates,
|
||||
module.updateCheckWorkerMgr = m.NewWorkerMgr("update checker", module.checkForUpdates, nil)
|
||||
module.updateCheckWorkerMgr.Repeat(30 * time.Second)
|
||||
module.upgraderWorkerMgr = m.NewWorkerMgr("upgrader", func(w *mgr.WorkerCtx) error {
|
||||
err := module.registry.ApplyUpdates()
|
||||
err := applyUpdates(module.updateIndex, *module.updateBundle)
|
||||
if err != nil {
|
||||
// TODO(vladimir): Send notification to UI
|
||||
log.Errorf("updates: failed to apply updates: %s", err)
|
||||
} else {
|
||||
// TODO(vladimir): Prompt user to restart?
|
||||
module.instance.Restart()
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
|
||||
module.registry = registry.New(index)
|
||||
_ = module.registry.Initialize()
|
||||
var err error
|
||||
module.bundle, err = ParseBundle(module.updateIndex.Directory, module.updateIndex.IndexFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse binary bundle: %s", err)
|
||||
}
|
||||
|
||||
// Add bundle artifacts to registry.
|
||||
module.processBundle(module.bundle)
|
||||
|
||||
// Remove old files
|
||||
m.Go("old files cleaner", func(ctx *mgr.WorkerCtx) error {
|
||||
err := os.RemoveAll(module.updateIndex.PurgeDirectory)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete folder: %w", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return module, nil
|
||||
}
|
||||
|
||||
func (reg *Updates) processBundle(bundle *Bundle) {
|
||||
for _, artifact := range bundle.Artifacts {
|
||||
artifactPath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, artifact.Filename)
|
||||
reg.files[artifact.Filename] = File{id: artifact.Filename, path: artifactPath}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updates) checkForUpdates(_ *mgr.WorkerCtx) error {
|
||||
hasUpdates, err := u.registry.CheckForUpdates()
|
||||
err := u.updateIndex.DownloadIndexFile()
|
||||
if err != nil {
|
||||
log.Errorf("updates: failed to check for updates: %s", err)
|
||||
return fmt.Errorf("failed to download index file: %s", err)
|
||||
}
|
||||
if hasUpdates {
|
||||
log.Infof("updates: there is updates available")
|
||||
err = u.registry.DownloadUpdates()
|
||||
if err != nil {
|
||||
log.Errorf("updates: failed to download bundle: %s", err)
|
||||
} else if autoUpdate {
|
||||
u.ApplyUpdates()
|
||||
}
|
||||
} else {
|
||||
log.Infof("updates: no new updates")
|
||||
u.EventResourcesUpdated.Submit(struct{}{})
|
||||
|
||||
u.updateBundle, err = ParseBundle(u.updateIndex.DownloadDirectory, u.updateIndex.IndexFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed parse bundle: %s", err)
|
||||
}
|
||||
defer u.EventResourcesUpdated.Submit(struct{}{})
|
||||
|
||||
// Compare current and downloaded index version.
|
||||
currentVersion, err := semver.NewVersion(u.bundle.Version)
|
||||
downloadVersion, err := semver.NewVersion(u.updateBundle.Version)
|
||||
if currentVersion.Compare(downloadVersion) <= 0 {
|
||||
// no updates
|
||||
log.Info("updates: check complete: no new updates")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("updates: check complete: downloading new version: %s %s", u.updateBundle.Name, u.updateBundle.Version)
|
||||
err = u.DownloadUpdates()
|
||||
if err != nil {
|
||||
log.Errorf("updates: failed to download bundle: %s", err)
|
||||
} else if u.updateIndex.AutoApply {
|
||||
u.ApplyUpdates()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadUpdates downloads available binary updates.
|
||||
func (u *Updates) DownloadUpdates() error {
|
||||
if u.updateBundle == nil {
|
||||
// CheckForBinaryUpdates needs to be called before this.
|
||||
return fmt.Errorf("no valid update bundle found")
|
||||
}
|
||||
_ = deleteUnfinishedDownloads(u.updateIndex.DownloadDirectory)
|
||||
err := u.updateBundle.CopyMatchingFilesFromCurrent(*u.bundle, u.updateIndex.Directory, u.updateIndex.DownloadDirectory)
|
||||
if err != nil {
|
||||
log.Warningf("updates: error while coping file from current to update: %s", err)
|
||||
}
|
||||
u.updateBundle.DownloadAndVerify(u.updateIndex.DownloadDirectory)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -107,8 +177,17 @@ func (u *Updates) Start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *Updates) GetFile(id string) (*registry.File, error) {
|
||||
return u.registry.GetFile(id)
|
||||
func (u *Updates) GetFile(id string) (*File, error) {
|
||||
file, ok := u.files[id]
|
||||
if ok {
|
||||
return &file, nil
|
||||
} else {
|
||||
log.Errorf("updates: requested file id not found: %s", id)
|
||||
for _, file := range u.files {
|
||||
log.Debugf("File: %s", file)
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the module.
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
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")
|
||||
|
||||
const (
|
||||
defaultFileMode = os.FileMode(0o0644)
|
||||
executableFileMode = os.FileMode(0o0744)
|
||||
defaultDirMode = os.FileMode(0o0755)
|
||||
)
|
||||
|
||||
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 {
|
||||
updateIndex UpdateIndex
|
||||
|
||||
bundle *Bundle
|
||||
updateBundle *Bundle
|
||||
|
||||
files map[string]File
|
||||
}
|
||||
|
||||
// New create new Registry.
|
||||
func New(index UpdateIndex) Registry {
|
||||
return Registry{
|
||||
updateIndex: index,
|
||||
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.bundle, err = parseBundle(reg.updateIndex.Directory, reg.updateIndex.IndexFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse binary bundle: %w", err)
|
||||
}
|
||||
|
||||
// Add bundle artifacts to registry.
|
||||
reg.processBundle(reg.bundle)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (reg *Registry) processBundle(bundle *Bundle) {
|
||||
for _, artifact := range bundle.Artifacts {
|
||||
artifactPath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, 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)
|
||||
for _, file := range reg.files {
|
||||
log.Debugf("File: %s", file)
|
||||
}
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
}
|
||||
|
||||
// CheckForUpdates checks if there is a new binary bundle updates.
|
||||
func (reg *Registry) CheckForUpdates() (bool, error) {
|
||||
err := reg.updateIndex.downloadIndexFile()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
reg.updateBundle, err = parseBundle(reg.updateIndex.DownloadDirectory, reg.updateIndex.IndexFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// TODO(vladimir): Make a better check.
|
||||
if reg.bundle.Version != reg.updateBundle.Version {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DownloadUpdates downloads available binary updates.
|
||||
func (reg *Registry) DownloadUpdates() error {
|
||||
if reg.updateBundle == nil {
|
||||
// CheckForBinaryUpdates needs to be called before this.
|
||||
return fmt.Errorf("no valid update bundle found")
|
||||
}
|
||||
_ = deleteUnfinishedDownloads(reg.updateIndex.DownloadDirectory)
|
||||
reg.updateBundle.downloadAndVerify(reg.updateIndex.DownloadDirectory)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyUpdates removes the current binary folder and replaces it with the downloaded one.
|
||||
func (reg *Registry) ApplyUpdates() error {
|
||||
// Create purge dir.
|
||||
err := os.MkdirAll(reg.updateIndex.PurgeDirectory, defaultDirMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
// Read all files in the current version folder.
|
||||
files, err := os.ReadDir(reg.updateIndex.Directory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move current version files into purge folder.
|
||||
for _, file := range files {
|
||||
filepath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, file.Name())
|
||||
purgePath := fmt.Sprintf("%s/%s", reg.updateIndex.PurgeDirectory, file.Name())
|
||||
err := os.Rename(filepath, purgePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move file %s: %w", filepath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Move the new index file
|
||||
indexFile := fmt.Sprintf("%s/%s", reg.updateIndex.DownloadDirectory, reg.updateIndex.IndexFile)
|
||||
newIndexFile := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, reg.updateIndex.IndexFile)
|
||||
err = os.Rename(indexFile, newIndexFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move index file %s: %w", indexFile, err)
|
||||
}
|
||||
|
||||
// Move downloaded files to the current version folder.
|
||||
for _, artifact := range reg.bundle.Artifacts {
|
||||
fromFilepath := fmt.Sprintf("%s/%s", reg.updateIndex.DownloadDirectory, artifact.Filename)
|
||||
toFilepath := fmt.Sprintf("%s/%s", reg.updateIndex.Directory, artifact.Filename)
|
||||
err = os.Rename(fromFilepath, toFilepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move file %s: %w", fromFilepath, 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
|
||||
}
|
||||
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
|
||||
})
|
||||
}
|
||||
78
service/updates/updater.go
Normal file
78
service/updates/updater.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package updates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFileMode = os.FileMode(0o0644)
|
||||
executableFileMode = os.FileMode(0o0744)
|
||||
defaultDirMode = os.FileMode(0o0755)
|
||||
)
|
||||
|
||||
func applyUpdates(updateIndex UpdateIndex, newBundle Bundle) error {
|
||||
// Create purge dir.
|
||||
err := os.MkdirAll(updateIndex.PurgeDirectory, defaultDirMode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create directory: %w", err)
|
||||
}
|
||||
|
||||
// Read all files in the current version folder.
|
||||
files, err := os.ReadDir(updateIndex.Directory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Move current version files into purge folder.
|
||||
for _, file := range files {
|
||||
filepath := fmt.Sprintf("%s/%s", updateIndex.Directory, file.Name())
|
||||
purgePath := fmt.Sprintf("%s/%s", updateIndex.PurgeDirectory, file.Name())
|
||||
err := os.Rename(filepath, purgePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move file %s: %w", filepath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Move the new index file
|
||||
indexFile := fmt.Sprintf("%s/%s", updateIndex.DownloadDirectory, updateIndex.IndexFile)
|
||||
newIndexFile := fmt.Sprintf("%s/%s", updateIndex.Directory, updateIndex.IndexFile)
|
||||
err = os.Rename(indexFile, newIndexFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move index file %s: %w", indexFile, err)
|
||||
}
|
||||
|
||||
// Move downloaded files to the current version folder.
|
||||
for _, artifact := range newBundle.Artifacts {
|
||||
fromFilepath := fmt.Sprintf("%s/%s", updateIndex.DownloadDirectory, artifact.Filename)
|
||||
toFilepath := fmt.Sprintf("%s/%s", updateIndex.Directory, artifact.Filename)
|
||||
err = os.Rename(fromFilepath, toFilepath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to move file %s: %w", fromFilepath, err)
|
||||
}
|
||||
}
|
||||
return 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
|
||||
})
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates/registry"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
"github.com/safing/portmaster/spn/conf"
|
||||
"github.com/safing/portmaster/spn/hub"
|
||||
"github.com/safing/portmaster/spn/navigator"
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
intelResource *registry.File
|
||||
intelResource *updates.File
|
||||
intelResourcePath = "intel/spn/main-intel.yaml"
|
||||
intelResourceMapName = "main"
|
||||
intelResourceUpdateLock sync.Mutex
|
||||
|
||||
Reference in New Issue
Block a user