diff --git a/cmds/portmaster-start/main.go b/cmds/portmaster-start/main.go index 90261a96..51a5e8fd 100644 --- a/cmds/portmaster-start/main.go +++ b/cmds/portmaster-start/main.go @@ -127,7 +127,7 @@ func initCobra() { // set up logging log.SetFlags(log.Ldate | log.Ltime | log.LUTC) - log.SetPrefix("[control] ") + log.SetPrefix("[pmstart] ") log.SetOutput(os.Stdout) // not using portbase logger @@ -186,7 +186,7 @@ func ensureLoggingDir() error { func updateRegistryIndex(mustLoadIndex bool) error { // Set indexes based on the release channel. - warning := helper.SetIndexes(registry, "", false) + warning := helper.SetIndexes(registry, "", false, false, false) if warning != nil { log.Printf("WARNING: %s\n", warning) } diff --git a/cmds/portmaster-start/update.go b/cmds/portmaster-start/update.go index 68627c4f..e04508c3 100644 --- a/cmds/portmaster-start/update.go +++ b/cmds/portmaster-start/update.go @@ -3,11 +3,13 @@ package main import ( "context" "fmt" + "log" "os" "github.com/spf13/cobra" - "github.com/safing/portbase/log" + portlog "github.com/safing/portbase/log" + "github.com/safing/portbase/updater" "github.com/safing/portmaster/updates/helper" ) @@ -58,23 +60,16 @@ func downloadUpdates() error { helper.IntelOnly() } + // Set registry state notify callback. + registry.StateNotifyFunc = logProgress + // Set required updates. registry.MandatoryUpdates = helper.MandatoryUpdates() registry.AutoUnpack = helper.AutoUnpackUpdates() - // logging is configured as a persistent pre-run method inherited from - // the root command but since we don't use run.Run() we need to start - // logging ourself. - log.SetLogLevel(log.InfoLevel) - err := log.Start() - if err != nil { - fmt.Printf("failed to start logging: %s\n", err) - } - defer log.Shutdown() - if reset { // Delete storage. - err = os.RemoveAll(registry.StorageDir().Path) + err := os.RemoveAll(registry.StorageDir().Path) if err != nil { return fmt.Errorf("failed to reset update dir: %w", err) } @@ -88,13 +83,19 @@ func downloadUpdates() error { } // Update all indexes. - err = registry.UpdateIndexes(context.TODO()) + err := registry.UpdateIndexes(context.TODO()) if err != nil { return err } + // Check if updates are available. + if len(registry.GetState().Updates.PendingDownload) == 0 { + log.Println("all resources are up to date") + return nil + } + // Download all required updates. - err = registry.DownloadUpdates(context.TODO()) + err = registry.DownloadUpdates(context.TODO(), false) if err != nil { return err } @@ -116,17 +117,43 @@ func downloadUpdates() error { return nil } +func logProgress(state *updater.RegistryState) { + switch state.ID { + case updater.StateChecking: + if state.Updates.LastCheckAt == nil { + log.Println("checking for new versions") + } + case updater.StateDownloading: + if state.Details == nil { + log.Printf("downloading %d updates\n", len(state.Updates.PendingDownload)) + } else if downloadDetails, ok := state.Details.(*updater.StateDownloadingDetails); ok { + if downloadDetails.FinishedUpTo < len(downloadDetails.Resources) { + log.Printf( + "[%d/%d] downloading %s", + downloadDetails.FinishedUpTo+1, + len(downloadDetails.Resources), + downloadDetails.Resources[downloadDetails.FinishedUpTo], + ) + } else { + if state.Updates.LastDownloadAt == nil { + log.Println("finalizing downloads") + } + } + } + } +} + func purge() error { - log.SetLogLevel(log.TraceLevel) + portlog.SetLogLevel(portlog.TraceLevel) // logging is configured as a persistent pre-run method inherited from // the root command but since we don't use run.Run() we need to start // logging ourself. - err := log.Start() + err := portlog.Start() if err != nil { fmt.Printf("failed to start logging: %s\n", err) } - defer log.Shutdown() + defer portlog.Shutdown() registry.Purge(3) return nil diff --git a/cmds/portmaster-start/verify.go b/cmds/portmaster-start/verify.go index dd7feeae..f863ddcf 100644 --- a/cmds/portmaster-start/verify.go +++ b/cmds/portmaster-start/verify.go @@ -154,7 +154,7 @@ func verifyUpdates(ctx context.Context) error { // Re-download broken files. registry.MandatoryUpdates = helper.MandatoryUpdates() registry.AutoUnpack = helper.AutoUnpackUpdates() - err = registry.DownloadUpdates(ctx) + err = registry.DownloadUpdates(ctx, false) if err != nil { return fmt.Errorf("failed to re-download files: %w", err) } diff --git a/go.mod b/go.mod index 601ad4d2..0464b29a 100644 --- a/go.mod +++ b/go.mod @@ -17,11 +17,11 @@ require ( github.com/miekg/dns v1.1.53 github.com/oschwald/maxminddb-golang v1.10.0 github.com/safing/jess v0.3.1 - github.com/safing/portbase v0.16.4 + github.com/safing/portbase v0.16.5 github.com/safing/portmaster-android/go v0.0.0-20230329101752-28296331340b - github.com/safing/spn v0.6.6 + github.com/safing/spn v0.6.7 github.com/shirou/gopsutil v3.21.11+incompatible - github.com/spf13/cobra v1.6.1 + github.com/spf13/cobra v1.7.0 github.com/spkg/zipfs v0.7.1 github.com/stretchr/testify v1.8.1 github.com/tannerryan/ring v1.1.2 @@ -29,7 +29,7 @@ require ( github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 golang.org/x/net v0.8.0 golang.org/x/sync v0.1.0 - golang.org/x/sys v0.6.0 + golang.org/x/sys v0.7.0 zombiezen.com/go/sqlite v0.13.0 ) @@ -85,7 +85,8 @@ require ( github.com/zalando/go-keyring v0.2.2 // indirect go.etcd.io/bbolt v1.3.7 // indirect golang.org/x/crypto v0.7.0 // indirect - golang.org/x/mod v0.9.0 // indirect + golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect + golang.org/x/mod v0.10.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.7.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 8f5430e5..0f0a8646 100644 --- a/go.sum +++ b/go.sum @@ -27,8 +27,8 @@ github.com/brianvoe/gofakeit v3.18.0+incompatible h1:wDOmHc9DLG4nRjUVVaxA+CEglKO github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -48,8 +48,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8= github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE= github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= -github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI= github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -76,12 +76,12 @@ github.com/gofrs/uuid v4.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -201,12 +201,12 @@ github.com/safing/jess v0.3.1 h1:cMZVhi2whW/YdD98MPLeLIWJndQ7o2QVt2HefQ/ByFA= github.com/safing/jess v0.3.1/go.mod h1:aj73Eot1zm2ETkJuw9hJlIO8bRom52uBbsCHemvlZmA= github.com/safing/portbase v0.15.2/go.mod h1:5bHi99fz7Hh/wOsZUOI631WF9ePSHk57c4fdlOMS91Y= github.com/safing/portbase v0.16.2/go.mod h1:mzNCWqPbO7vIYbbK5PElGbudwd2vx4YPNawymL8Aro8= -github.com/safing/portbase v0.16.4 h1:3w4Sc0TlSDvUS4Uk5TrsFXf98cwMbCf+BG5Bw6coxNk= -github.com/safing/portbase v0.16.4/go.mod h1:mzNCWqPbO7vIYbbK5PElGbudwd2vx4YPNawymL8Aro8= +github.com/safing/portbase v0.16.5 h1:vn6jGQW+ACnPVV57XtlNX+A/LESFh8/YmKn9rwAdy90= +github.com/safing/portbase v0.16.5/go.mod h1:CJSY+0pgbgh00UcM9myS/l+Bcgqgc6afLJX2BqzD+Ek= github.com/safing/portmaster-android/go v0.0.0-20230329101752-28296331340b h1:b+GCsl+dTnR22JY3N2aRDVnac2ocjFqqUhn8rPxywGM= github.com/safing/portmaster-android/go v0.0.0-20230329101752-28296331340b/go.mod h1:E3MFiTwsHxsPfzI+CGhpE8BOGQYS/V7tkXttMTFeyuc= -github.com/safing/spn v0.6.6 h1:hKSe0TmDRkyFKcSB/UejvWO+21BBzhbyEpjdw3Biq3w= -github.com/safing/spn v0.6.6/go.mod h1:AnjcIlkmsMDzCf4APp5h45TsCjMxL5cEwlN5Pj+cMAs= +github.com/safing/spn v0.6.7 h1:WNxzf6eEI/FyRtEjUwXK0GTwHD9VPxrKhlupLqQlz+E= +github.com/safing/spn v0.6.7/go.mod h1:PsKnBKxCIYZncVk+rEWv67/PXcTfZJAUWbgj62hOYbw= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/seehuhn/fortuna v1.0.1 h1:lu9+CHsmR0bZnx5Ay646XvCSRJ8PJTi5UYJwDBX68H0= @@ -221,8 +221,8 @@ github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= -github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= +github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= +github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -301,13 +301,15 @@ golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -381,8 +383,9 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU= +golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -408,7 +411,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.2-0.20230118093459-a9481185b34d h1:qp0AnQCvRCMlu9jBjtdbTaaEmThIgZOrbVyDEOcmKhQ= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/netenv/network-change.go b/netenv/network-change.go index 0d83d704..503b2fe9 100644 --- a/netenv/network-change.go +++ b/netenv/network-change.go @@ -26,7 +26,8 @@ func notifyOfNetworkChange() { module.TriggerEvent(NetworkChangedEvent, nil) } -func triggerNetworkChangeCheck() { +// TriggerNetworkChangeCheck triggers a network change check. +func TriggerNetworkChangeCheck() { select { case networkChangeCheckTrigger <- struct{}{}: default: diff --git a/netenv/online-status.go b/netenv/online-status.go index cc9cb376..ce782724 100644 --- a/netenv/online-status.go +++ b/netenv/online-status.go @@ -209,7 +209,7 @@ func updateOnlineStatus(status OnlineStatus, portalURL *url.URL, comment string) } else { log.Infof("netenv: setting online status to %s (%s)", status, comment) } - triggerNetworkChangeCheck() + TriggerNetworkChangeCheck() // Notify user. notifyOnlineStatus(status) diff --git a/process/process.go b/process/process.go index 95ca1daf..3d386b37 100644 --- a/process/process.go +++ b/process/process.go @@ -170,19 +170,18 @@ func GetOrFindProcess(ctx context.Context, pid int) (*Process, error) { return GetSystemProcess(ctx), nil } - // Get pid and created time for identification. + // Get pid and creation time for identification. pInfo, err := processInfo.NewProcessWithContext(ctx, int32(pid)) if err != nil { return nil, err } - - createdTime, err := pInfo.CreateTimeWithContext(ctx) + createdAt, err := pInfo.CreateTimeWithContext(ctx) if err != nil { return nil, err } + key := getProcessKey(int32(pid), createdAt) - key := getProcessKey(int32(pid), createdTime) - + // Load process and make sure it is only loaded once. p, err, _ := getProcessSingleInflight.Do(key, func() (interface{}, error) { return loadProcess(ctx, key, pInfo) }) @@ -197,9 +196,7 @@ func GetOrFindProcess(ctx context.Context, pid int) (*Process, error) { } func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (*Process, error) { - // Get created time of process. The value should be cached. - createdAt, _ := pInfo.CreateTimeWithContext(ctx) - + // Check if we already have the process. process, ok := GetProcessFromStorage(key) if ok { return process, nil @@ -208,13 +205,13 @@ func loadProcess(ctx context.Context, key string, pInfo *processInfo.Process) (* // Create new a process object. process = &Process{ Pid: int(pInfo.Pid), - CreatedAt: createdAt, FirstSeen: time.Now().Unix(), processKey: key, } - // Get process information from the system. - pInfo, err := processInfo.NewProcessWithContext(ctx, pInfo.Pid) + // Get creation time of process. (The value should be cached by the library.) + var err error + process.CreatedAt, err = pInfo.CreateTimeWithContext(ctx) if err != nil { return nil, err } diff --git a/process/special.go b/process/special.go index 93f17c9a..aa35160a 100644 --- a/process/special.go +++ b/process/special.go @@ -38,35 +38,32 @@ func init() { var ( // unidentifiedProcess is used for non-attributed outgoing connections. unidentifiedProcess = &Process{ - UserID: UnidentifiedProcessID, - UserName: "Unknown", - Pid: UnidentifiedProcessID, - CreatedAt: 1, - ParentPid: UnidentifiedProcessID, - ParentCreatedAt: 1, - Name: profile.UnidentifiedProfileName, + UserID: UnidentifiedProcessID, + UserName: "Unknown", + Pid: UnidentifiedProcessID, + ParentPid: UnidentifiedProcessID, + Name: profile.UnidentifiedProfileName, + processKey: getProcessKey(UnidentifiedProcessID, 0), } // unsolicitedProcess is used for non-attributed incoming connections. unsolicitedProcess = &Process{ - UserID: UnsolicitedProcessID, - UserName: "Unknown", - Pid: UnsolicitedProcessID, - CreatedAt: 1, - ParentPid: UnsolicitedProcessID, - ParentCreatedAt: 1, - Name: profile.UnsolicitedProfileName, + UserID: UnsolicitedProcessID, + UserName: "Unknown", + Pid: UnsolicitedProcessID, + ParentPid: UnsolicitedProcessID, + Name: profile.UnsolicitedProfileName, + processKey: getProcessKey(UnsolicitedProcessID, 0), } // systemProcess is used to represent the Kernel. systemProcess = &Process{ - UserID: SystemProcessID, - UserName: "Kernel", - Pid: SystemProcessID, - CreatedAt: 1, - ParentPid: SystemProcessID, - ParentCreatedAt: 1, - Name: profile.SystemProfileName, + UserID: SystemProcessID, + UserName: "Kernel", + Pid: SystemProcessID, + ParentPid: SystemProcessID, + Name: profile.SystemProfileName, + processKey: getProcessKey(SystemProcessID, 0), } getSpecialProcessSingleInflight singleflight.Group diff --git a/updates/config.go b/updates/config.go index e2d12da2..18d41ded 100644 --- a/updates/config.go +++ b/updates/config.go @@ -7,25 +7,24 @@ import ( "github.com/safing/portbase/config" "github.com/safing/portbase/log" - "github.com/safing/portbase/notifications" "github.com/safing/portmaster/updates/helper" ) -const ( - cfgDevModeKey = "core/devMode" - updatesDisabledNotificationID = "updates:disabled" -) +const cfgDevModeKey = "core/devMode" var ( - releaseChannel config.StringOption - devMode config.BoolOption - enableUpdates config.BoolOption + releaseChannel config.StringOption + devMode config.BoolOption + enableSoftwareUpdates config.BoolOption + enableIntelUpdates config.BoolOption - initialReleaseChannel string - previousReleaseChannel string - updatesCurrentlyEnabled bool - previousDevMode bool - forceUpdate = abool.New() + initialReleaseChannel string + previousReleaseChannel string + + softwareUpdatesCurrentlyEnabled bool + intelUpdatesCurrentlyEnabled bool + previousDevMode bool + forceUpdate = abool.New() ) func registerConfig() error { @@ -71,9 +70,9 @@ func registerConfig() error { } err = config.Register(&config.Option{ - Name: "Automatic Updates", - Key: enableUpdatesKey, - Description: "Enable automatic checking, downloading and applying of updates. This affects all kinds of updates, including intelligence feeds and broadcast notifications.", + Name: "Automatic Software Updates", + Key: enableSoftwareUpdatesKey, + Description: "Automatically check for and download software updates. This does not include intelligence data updates.", OptType: config.OptTypeBool, ExpertiseLevel: config.ExpertiseLevelExpert, ReleaseLevel: config.ReleaseLevelStable, @@ -88,6 +87,24 @@ func registerConfig() error { return err } + err = config.Register(&config.Option{ + Name: "Automatic Intelligence Data Updates", + Key: enableIntelUpdatesKey, + Description: "Automatically check for and download intelligence data updates. This includes filter lists, geo-ip data, and more. Does not include software updates.", + OptType: config.OptTypeBool, + ExpertiseLevel: config.ExpertiseLevelExpert, + ReleaseLevel: config.ReleaseLevelStable, + RequiresRestart: false, + DefaultValue: true, + Annotations: config.Annotations{ + config.DisplayOrderAnnotation: -11, + config.CategoryAnnotation: "Updates", + }, + }) + if err != nil { + return err + } + return nil } @@ -96,37 +113,25 @@ func initConfig() { initialReleaseChannel = releaseChannel() previousReleaseChannel = releaseChannel() - enableUpdates = config.Concurrent.GetAsBool(enableUpdatesKey, true) - updatesCurrentlyEnabled = enableUpdates() + enableSoftwareUpdates = config.Concurrent.GetAsBool(enableSoftwareUpdatesKey, true) + enableIntelUpdates = config.Concurrent.GetAsBool(enableIntelUpdatesKey, true) + softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates() + intelUpdatesCurrentlyEnabled = enableIntelUpdates() devMode = config.Concurrent.GetAsBool(cfgDevModeKey, false) previousDevMode = devMode() } -func createWarningNotification() { - notifications.NotifyWarn( - updatesDisabledNotificationID, - "Automatic Updates Disabled", - "Automatic updates are disabled through configuration. Please note that this is potentially dangerous, as this also affects security updates as well as the filter lists and threat intelligence feeds.", - notifications.Action{ - Text: "Change", - Type: notifications.ActionTypeOpenSetting, - Payload: ¬ifications.ActionTypeOpenSettingPayload{ - Key: enableUpdatesKey, - }, - }, - ).AttachToModule(module) -} - func updateRegistryConfig(_ context.Context, _ interface{}) error { changed := false - if releaseChannel() != previousReleaseChannel { - previousReleaseChannel = releaseChannel() - warning := helper.SetIndexes(registry, releaseChannel(), true) - if warning != nil { - log.Warningf("updates: %s", warning) - } + if enableSoftwareUpdates() != softwareUpdatesCurrentlyEnabled { + softwareUpdatesCurrentlyEnabled = enableSoftwareUpdates() + changed = true + } + + if enableIntelUpdates() != intelUpdatesCurrentlyEnabled { + intelUpdatesCurrentlyEnabled = enableIntelUpdates() changed = true } @@ -136,24 +141,36 @@ func updateRegistryConfig(_ context.Context, _ interface{}) error { changed = true } - if enableUpdates() != updatesCurrentlyEnabled { - updatesCurrentlyEnabled = enableUpdates() + if releaseChannel() != previousReleaseChannel { + previousReleaseChannel = releaseChannel() changed = true } if changed { + // Update indexes based on new settings. + warning := helper.SetIndexes( + registry, + releaseChannel(), + true, + softwareUpdatesCurrentlyEnabled, + intelUpdatesCurrentlyEnabled, + ) + if warning != nil { + log.Warningf("updates: %s", warning) + } + + // Select versions depending on new indexes and modes. registry.SelectVersions() module.TriggerEvent(VersionUpdateEvent, nil) - if updatesCurrentlyEnabled { + if softwareUpdatesCurrentlyEnabled || intelUpdatesCurrentlyEnabled { module.Resolve("") if err := TriggerUpdate(false); err != nil { log.Warningf("updates: failed to trigger update: %s", err) } log.Infof("updates: automatic updates are now enabled") } else { - createWarningNotification() - log.Warningf("updates: automatic updates are now disabled") + log.Warningf("updates: automatic updates are now completely disabled") } } diff --git a/updates/export.go b/updates/export.go index fe29f464..e17113d1 100644 --- a/updates/export.go +++ b/updates/export.go @@ -21,6 +21,9 @@ const ( // versionsDBKey is the database key for simple update version information. simpleVersionsDBKey = "core:status/simple-versions" + + // updateStatusDBKey is the database key for update status information. + updateStatusDBKey = "core:status/updates" ) // Versions holds update versions and status information. @@ -50,6 +53,14 @@ type SimplifiedResourceVersion struct { Version string } +// UpdateStateExport is a wrapper to export the updates state. +type UpdateStateExport struct { + record.Base + sync.Mutex + + *updater.UpdateState +} + // GetVersions returns the update versions and status information. // Resources must be locked when accessed. func GetVersions() *Versions { @@ -98,6 +109,41 @@ func GetSimpleVersions() *SimpleVersions { return v } +// GetStateExport gets the update state from the registry and returns it in an +// exportable struct. +func GetStateExport() *UpdateStateExport { + export := registry.GetState() + return &UpdateStateExport{ + UpdateState: &export.Updates, + } +} + +// LoadStateExport loads the exported update state from the database. +func LoadStateExport() (*UpdateStateExport, error) { + r, err := db.Get(updateStatusDBKey) + if err != nil { + return nil, err + } + + // unwrap + if r.IsWrapped() { + // only allocate a new struct, if we need it + newRecord := &UpdateStateExport{} + err = record.Unwrap(r, newRecord) + if err != nil { + return nil, err + } + return newRecord, nil + } + + // or adjust type + newRecord, ok := r.(*UpdateStateExport) + if !ok { + return nil, fmt.Errorf("record not of type *UpdateStateExport, but %T", r) + } + return newRecord, nil +} + func initVersionExport() (err error) { if err := GetVersions().save(); err != nil { log.Warningf("updates: failed to export version information: %s", err) @@ -128,12 +174,28 @@ func (v *SimpleVersions) save() error { return db.Put(v) } +func (s *UpdateStateExport) save() error { + if !s.KeyIsSet() { + s.SetKey(updateStatusDBKey) + } + return db.Put(s) +} + // export is an event hook. func export(_ context.Context, _ interface{}) error { + // Export versions. if err := GetVersions().save(); err != nil { return err } - return GetSimpleVersions().save() + if err := GetSimpleVersions().save(); err != nil { + return err + } + // Export udpate state. + if err := GetStateExport().save(); err != nil { + return err + } + + return nil } // AddToDebugInfo adds the update system status to the given debug.Info. diff --git a/updates/get.go b/updates/get.go index cc16e700..68868f0d 100644 --- a/updates/get.go +++ b/updates/get.go @@ -32,3 +32,30 @@ func GetFile(identifier string) (*updater.File, error) { module.TriggerEvent(VersionUpdateEvent, nil) return file, nil } + +// GetPlatformVersion returns the selected platform specific version of the +// given identifier. +// The returned resource version may not be modified. +func GetPlatformVersion(identifier string) (*updater.ResourceVersion, error) { + identifier = helper.PlatformIdentifier(identifier) + + rv, err := registry.GetVersion(identifier) + if err != nil { + return nil, err + } + + return rv, nil +} + +// GetVersion returns the selected generic version of the given identifier. +// The returned resource version may not be modified. +func GetVersion(identifier string) (*updater.ResourceVersion, error) { + identifier = path.Join("all", identifier) + + rv, err := registry.GetVersion(identifier) + if err != nil { + return nil, err + } + + return rv, nil +} diff --git a/updates/helper/indexes.go b/updates/helper/indexes.go index 2995e168..948ab33a 100644 --- a/updates/helper/indexes.go +++ b/updates/helper/indexes.go @@ -27,7 +27,13 @@ const ( // SetIndexes sets the update registry indexes and also configures the registry // to use pre-releases based on the channel. -func SetIndexes(registry *updater.ResourceRegistry, releaseChannel string, deleteUnusedIndexes bool) (warning error) { +func SetIndexes( + registry *updater.ResourceRegistry, + releaseChannel string, + deleteUnusedIndexes bool, + autoDownload bool, + autoDownloadIntel bool, +) (warning error) { usePreReleases := false // Be reminded that the order is important, as indexes added later will @@ -39,12 +45,14 @@ func SetIndexes(registry *updater.ResourceRegistry, releaseChannel string, delet // Add the intel index first, in order to be able to override it with the // other indexes when needed. registry.AddIndex(updater.Index{ - Path: "all/intel/intel.json", + Path: "all/intel/intel.json", + AutoDownload: autoDownloadIntel, }) // Always add the stable index as a base. registry.AddIndex(updater.Index{ - Path: ReleaseChannelStable + ".json", + Path: ReleaseChannelStable + ".json", + AutoDownload: autoDownload, }) // Add beta index if in beta or staging channel. @@ -53,8 +61,9 @@ func SetIndexes(registry *updater.ResourceRegistry, releaseChannel string, delet releaseChannel == ReleaseChannelStaging || (releaseChannel == "" && indexExists(registry, indexPath)) { registry.AddIndex(updater.Index{ - Path: indexPath, - PreRelease: true, + Path: indexPath, + PreRelease: true, + AutoDownload: autoDownload, }) usePreReleases = true } else if deleteUnusedIndexes { @@ -69,8 +78,9 @@ func SetIndexes(registry *updater.ResourceRegistry, releaseChannel string, delet if releaseChannel == ReleaseChannelStaging || (releaseChannel == "" && indexExists(registry, indexPath)) { registry.AddIndex(updater.Index{ - Path: indexPath, - PreRelease: true, + Path: indexPath, + PreRelease: true, + AutoDownload: autoDownload, }) usePreReleases = true } else if deleteUnusedIndexes { @@ -85,7 +95,8 @@ func SetIndexes(registry *updater.ResourceRegistry, releaseChannel string, delet if releaseChannel == ReleaseChannelSupport || (releaseChannel == "" && indexExists(registry, indexPath)) { registry.AddIndex(updater.Index{ - Path: indexPath, + Path: indexPath, + AutoDownload: autoDownload, }) } else if deleteUnusedIndexes { err := deleteIndex(registry, indexPath) diff --git a/updates/main.go b/updates/main.go index 3819f99b..db9951bb 100644 --- a/updates/main.go +++ b/updates/main.go @@ -5,14 +5,12 @@ import ( "flag" "fmt" "runtime" - "sync/atomic" "time" "github.com/safing/portbase/database" "github.com/safing/portbase/dataroot" "github.com/safing/portbase/log" "github.com/safing/portbase/modules" - "github.com/safing/portbase/notifications" "github.com/safing/portbase/updater" "github.com/safing/portmaster/updates/helper" ) @@ -20,7 +18,8 @@ import ( const ( onWindows = runtime.GOOS == "windows" - enableUpdatesKey = "core/automaticUpdates" + enableSoftwareUpdatesKey = "core/automaticUpdates" + enableIntelUpdatesKey = "core/automaticIntelUpdates" // ModuleName is the name of the update module // and can be used when declaring module dependencies. @@ -64,9 +63,6 @@ var ( const ( updatesDirName = "updates" - updateFailed = "updates:failed" - updateSuccess = "updates:success" - updateTaskRepeatDuration = 1 * time.Hour ) @@ -119,14 +115,39 @@ func start() error { // override with flag value registry.UserAgent = userAgentFromFlag } + + // 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)) + 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) + warning := helper.SetIndexes( + registry, + initialReleaseChannel, + true, + enableSoftwareUpdates(), + enableIntelUpdates(), + ) if warning != nil { log.Warningf("updates: %s", warning) } @@ -144,10 +165,6 @@ func start() error { registry.SelectVersions() module.TriggerEvent(VersionUpdateEvent, nil) - if !updatesCurrentlyEnabled { - createWarningNotification() - } - // Initialize the version export - this requires the registry to be set up. err = initVersionExport() if err != nil { @@ -185,11 +202,13 @@ func TriggerUpdate(force bool) error { case !module.Online(): updateASAP = true - case !force && !enableUpdates(): + case !force && !enableSoftwareUpdates() && !enableIntelUpdates(): return fmt.Errorf("automatic updating is disabled") default: - forceUpdate.Set() + if force { + forceUpdate.Set() + } updateTask.StartASAP() } @@ -211,8 +230,6 @@ func DisableUpdateSchedule() error { return nil } -var updateFailedCnt = new(atomic.Int32) - func checkForUpdates(ctx context.Context) (err error) { // Set correct error if context was canceled. defer func() { @@ -223,7 +240,8 @@ func checkForUpdates(ctx context.Context) (err error) { } }() - if !forceUpdate.SetToIf(true, false) && !enableUpdates() { + forcedUpdate := forceUpdate.SetToIf(true, false) + if !forcedUpdate && !enableSoftwareUpdates() && !enableIntelUpdates() { log.Warningf("updates: automatic updates are disabled") return nil } @@ -231,45 +249,14 @@ func checkForUpdates(ctx context.Context) (err error) { defer func() { // Resolve any error and and send succes notification. if err == nil { - updateFailedCnt.Store(0) log.Infof("updates: successfully checked for updates") - module.Resolve(updateFailed) - notifications.Notify(¬ifications.Notification{ - EventID: updateSuccess, - Type: notifications.Info, - Title: "Update Check Successful", - Message: "The Portmaster successfully checked for updates and downloaded any available updates. Most updates are applied automatically. You will be notified of important updates that need restarting.", - Expires: time.Now().Add(1 * time.Minute).Unix(), - AvailableActions: []*notifications.Action{ - { - ID: "ack", - Text: "OK", - }, - }, - }) + notifyUpdateSuccess(forcedUpdate) return } - // Log error in any case. + // Log and notify error. log.Errorf("updates: check failed: %s", err) - - // Do not alert user if update failed for only a few times. - if updateFailedCnt.Add(1) > 3 { - notifications.NotifyWarn( - updateFailed, - "Update Check Failed", - "The 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. If you just installed the Portmaster, please try disabling potentially conflicting software, such as other firewalls or VPNs.", - notifications.Action{ - ID: "retry", - Text: "Try Again Now", - Type: notifications.ActionTypeWebhook, - Payload: ¬ifications.ActionTypeWebhookPayload{ - URL: apiPathCheckForUpdates, - ResultAction: "display", - }, - }, - ).AttachToModule(module) - } + notifyUpdateCheckFailed(forcedUpdate, err) }() if err = registry.UpdateIndexes(ctx); err != nil { @@ -277,7 +264,7 @@ func checkForUpdates(ctx context.Context) (err error) { return } - err = registry.DownloadUpdates(ctx) + err = registry.DownloadUpdates(ctx, !forcedUpdate) if err != nil { err = fmt.Errorf("failed to download updates: %w", err) return @@ -293,7 +280,7 @@ func checkForUpdates(ctx context.Context) (err error) { } // Purge old resources - registry.Purge(3) + registry.Purge(2) module.TriggerEvent(ResourceUpdateEvent, nil) return nil diff --git a/updates/notify.go b/updates/notify.go new file mode 100644 index 00000000..6bc34817 --- /dev/null +++ b/updates/notify.go @@ -0,0 +1,140 @@ +package updates + +import ( + "fmt" + "sync/atomic" + "time" + + "github.com/safing/portbase/notifications" +) + +const ( + updateFailed = "updates:failed" + updateSuccess = "updates:success" + updateSuccessPending = "updates:success-pending" + updateSuccessDownloaded = "updates:success-downloaded" + + failedUpdateNotifyDurationThreshold = 24 * time.Hour + failedUpdateNotifyCountThreshold = 3 +) + +var updateFailedCnt = new(atomic.Int32) + +func notifyUpdateSuccess(forced bool) { + updateFailedCnt.Store(0) + module.Resolve(updateFailed) + updateState := registry.GetState().Updates + + flavor := updateSuccess + switch { + case len(updateState.PendingDownload) > 0: + // Show notification if there are pending downloads. + flavor = updateSuccessPending + case updateState.LastDownloadAt != nil && + time.Since(*updateState.LastDownloadAt) < time.Minute: + // Show notification if we downloaded something within the last minute. + flavor = updateSuccessDownloaded + case forced: + // Always show notification if update was manually triggered. + default: + // Otherwise, the update was uneventful. Do not show notification. + return + } + + switch flavor { + case updateSuccess: + notifications.Notify(¬ifications.Notification{ + EventID: updateSuccess, + Type: notifications.Info, + Title: "Portmaster Is Up-To-Date", + Message: "Portmaster successfully checked for updates. Everything is up to date. Most updates are applied automatically. You will be notified of important updates that need restarting.", + Expires: time.Now().Add(1 * time.Minute).Unix(), + AvailableActions: []*notifications.Action{ + { + ID: "ack", + Text: "OK", + }, + }, + }) + + case updateSuccessPending: + notifications.Notify(¬ifications.Notification{ + EventID: updateSuccess, + Type: notifications.Info, + Title: fmt.Sprintf("%d Updates Available", len(updateState.PendingDownload)), + Message: fmt.Sprintf( + `%d updates are available for download. Press "Download Now" or check for updates later to download and automatically apply all pending updates. You will be notified of important updates that need restarting.`, + len(updateState.PendingDownload), + ), + AvailableActions: []*notifications.Action{ + { + ID: "ack", + Text: "OK", + }, + { + ID: "download", + Text: "Download Now", + Type: notifications.ActionTypeWebhook, + Payload: ¬ifications.ActionTypeWebhookPayload{ + URL: apiPathCheckForUpdates, + ResultAction: "display", + }, + }, + }, + }) + + case updateSuccessDownloaded: + notifications.Notify(¬ifications.Notification{ + EventID: updateSuccess, + Type: notifications.Info, + Title: fmt.Sprintf("%d Updates Applied", len(updateState.LastDownload)), + Message: fmt.Sprintf( + `%d updates were downloaded and applied. You will be notified of important updates that need restarting.`, + len(updateState.LastDownload), + ), + Expires: time.Now().Add(1 * time.Minute).Unix(), + AvailableActions: []*notifications.Action{ + { + ID: "ack", + Text: "OK", + }, + }, + }) + + } +} + +func notifyUpdateCheckFailed(forced bool, err error) { + failedCnt := updateFailedCnt.Add(1) + lastSuccess := registry.GetState().Updates.LastSuccessAt + + switch { + case forced: + // Always show notification if update was manually triggered. + case failedCnt < failedUpdateNotifyCountThreshold: + // Not failed often enough for notification. + return + case lastSuccess == nil: + // No recorded successful udpate. + case time.Now().Add(-failedUpdateNotifyDurationThreshold).Before(*lastSuccess): + // Failed too recently for notification. + return + } + + notifications.NotifyWarn( + updateFailed, + "Update Check Failed", + fmt.Sprintf( + "Portmaster failed to check for updates. This might be a temporary issue of your device, your network or the update servers. The Portmaster will automatically try again later. The error was: %s", + err, + ), + notifications.Action{ + Text: "Try Again Now", + Type: notifications.ActionTypeWebhook, + Payload: ¬ifications.ActionTypeWebhookPayload{ + URL: apiPathCheckForUpdates, + ResultAction: "display", + }, + }, + ).AttachToModule(module) +} diff --git a/updates/state.go b/updates/state.go new file mode 100644 index 00000000..2e051057 --- /dev/null +++ b/updates/state.go @@ -0,0 +1,49 @@ +package updates + +import ( + "github.com/safing/portbase/database/record" + "github.com/safing/portbase/runtime" + "github.com/safing/portbase/updater" +) + +var pushRegistryStatusUpdate runtime.PushFunc + +// RegistryStateExport is a wrapper to export the registry state. +type RegistryStateExport struct { + record.Base + *updater.RegistryState +} + +func exportRegistryState(s *updater.RegistryState) *RegistryStateExport { + if s == nil { + state := registry.GetState() + s = &state + } + + export := &RegistryStateExport{ + RegistryState: s, + } + + export.CreateMeta() + export.SetKey("runtime:core/updates/state") + + return export +} + +func pushRegistryState(s *updater.RegistryState) { + export := exportRegistryState(s) + pushRegistryStatusUpdate(export) +} + +func registerRegistryStateProvider() (err error) { + registryStateProvider := runtime.SimpleValueGetterFunc(func(_ string) ([]record.Record, error) { + return []record.Record{exportRegistryState(nil)}, nil + }) + + pushRegistryStatusUpdate, err = runtime.Register("core/updates/state", registryStateProvider) + if err != nil { + return err + } + + return nil +}