mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
Merge pull request #709 from safing/feature/broadcast-notifications
Add Broadcast Notifications
This commit is contained in:
commit
0e5e3dcab3
13 changed files with 959 additions and 124 deletions
116
broadcasts/api.go
Normal file
116
broadcasts/api.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package broadcasts
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/safing/portbase/api"
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/accessor"
|
||||
)
|
||||
|
||||
func registerAPIEndpoints() error {
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: `broadcasts/matching-data`,
|
||||
Read: api.PermitAdmin,
|
||||
BelongsTo: module,
|
||||
StructFunc: handleMatchingData,
|
||||
Name: "Get Broadcast Notifications Matching Data",
|
||||
Description: "Returns the data used by the broadcast notifications to match the instance.",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: `broadcasts/reset-state`,
|
||||
Write: api.PermitAdmin,
|
||||
WriteMethod: http.MethodPost,
|
||||
BelongsTo: module,
|
||||
ActionFunc: handleResetState,
|
||||
Name: "Resets the Broadcast Notification States",
|
||||
Description: "Delete the cache of Broadcast Notifications, making them appear again.",
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Path: `broadcasts/simulate`,
|
||||
Write: api.PermitAdmin,
|
||||
WriteMethod: http.MethodPost,
|
||||
BelongsTo: module,
|
||||
ActionFunc: handleSimulate,
|
||||
Name: "Simulate Broadcast Notifications",
|
||||
Description: "Test broadcast notifications by sending a valid source file in the body.",
|
||||
Parameters: []api.Parameter{
|
||||
{
|
||||
Method: http.MethodPost,
|
||||
Field: "state",
|
||||
Value: "true",
|
||||
Description: "Check against state when deciding to display a broadcast notification. Acknowledgements are always saved.",
|
||||
},
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleMatchingData(ar *api.Request) (i interface{}, err error) {
|
||||
return collectData(), nil
|
||||
}
|
||||
|
||||
func handleResetState(ar *api.Request) (msg string, err error) {
|
||||
err = db.Delete(broadcastStatesDBKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "Reset complete.", nil
|
||||
}
|
||||
|
||||
func handleSimulate(ar *api.Request) (msg string, err error) {
|
||||
// Parse broadcast notification data.
|
||||
broadcasts, err := parseBroadcastSource(ar.InputData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse broadcast notifications update: %w", err)
|
||||
}
|
||||
|
||||
// Get and marshal matching data.
|
||||
matchingData := collectData()
|
||||
matchingJSON, err := json.Marshal(matchingData)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal broadcast notifications matching data: %w", err)
|
||||
}
|
||||
matchingDataAccessor := accessor.NewJSONBytesAccessor(&matchingJSON)
|
||||
|
||||
var bss *BroadcastStates
|
||||
if ar.URL.Query().Get("state") == "true" {
|
||||
// Get broadcast notification states.
|
||||
bss, err = getBroadcastStates()
|
||||
if err != nil {
|
||||
if !errors.Is(err, database.ErrNotFound) {
|
||||
return "", fmt.Errorf("failed to get broadcast notifications states: %w", err)
|
||||
}
|
||||
bss = newBroadcastStates()
|
||||
}
|
||||
}
|
||||
|
||||
// Go through all broadcast nofications and check if they match.
|
||||
var results []string
|
||||
for _, bn := range broadcasts.Notifications {
|
||||
err := handleBroadcast(bn, matchingDataAccessor, bss)
|
||||
switch {
|
||||
case err == nil:
|
||||
results = append(results, fmt.Sprintf("%30s: displayed", bn.id))
|
||||
case errors.Is(err, ErrSkip):
|
||||
results = append(results, fmt.Sprintf("%30s: %s", bn.id, err))
|
||||
default:
|
||||
results = append(results, fmt.Sprintf("FAILED %23s: %s", bn.id, err))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(results, "\n"), nil
|
||||
}
|
102
broadcasts/data.go
Normal file
102
broadcasts/data.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package broadcasts
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/config"
|
||||
"github.com/safing/portmaster/intel/geoip"
|
||||
"github.com/safing/portmaster/netenv"
|
||||
"github.com/safing/portmaster/updates"
|
||||
"github.com/safing/spn/access"
|
||||
"github.com/safing/spn/captain"
|
||||
)
|
||||
|
||||
var portmasterStarted = time.Now()
|
||||
|
||||
func collectData() interface{} {
|
||||
data := make(map[string]interface{})
|
||||
|
||||
// Get data about versions.
|
||||
versions := updates.GetSimpleVersions()
|
||||
data["Updates"] = versions
|
||||
data["Version"] = versions.Build.Version
|
||||
numericVersion, err := MakeNumericVersion(versions.Build.Version)
|
||||
if err != nil {
|
||||
data["NumericVersion"] = &DataError{
|
||||
Error: err,
|
||||
}
|
||||
} else {
|
||||
data["NumericVersion"] = numericVersion
|
||||
}
|
||||
|
||||
// Get data about install.
|
||||
installInfo, err := GetInstallInfo()
|
||||
if err != nil {
|
||||
data["Install"] = &DataError{
|
||||
Error: err,
|
||||
}
|
||||
} else {
|
||||
data["Install"] = installInfo
|
||||
}
|
||||
|
||||
// Get global configuration.
|
||||
data["Config"] = config.GetActiveConfigValues()
|
||||
|
||||
// Get data about device location.
|
||||
locs, ok := netenv.GetInternetLocation()
|
||||
if ok && locs.Best().LocationOrNil() != nil {
|
||||
loc := locs.Best()
|
||||
data["Location"] = &Location{
|
||||
Country: loc.Location.Country.ISOCode,
|
||||
Coordinates: loc.Location.Coordinates,
|
||||
ASN: loc.Location.AutonomousSystemNumber,
|
||||
ASOrg: loc.Location.AutonomousSystemOrganization,
|
||||
Source: loc.Source,
|
||||
SourceAccuracy: loc.SourceAccuracy,
|
||||
}
|
||||
}
|
||||
|
||||
// Get data about SPN status.
|
||||
data["SPN"] = captain.GetSPNStatus()
|
||||
|
||||
// Get data about account.
|
||||
userRecord, err := access.GetUser()
|
||||
if err != nil {
|
||||
data["Account"] = &DataError{
|
||||
Error: err,
|
||||
}
|
||||
} else {
|
||||
data["Account"] = &Account{
|
||||
UserRecord: userRecord,
|
||||
UpToDate: userRecord.Meta().Modified > time.Now().Add(-7*24*time.Hour).Unix(),
|
||||
MayUseUSP: userRecord.MayUseSPN(),
|
||||
}
|
||||
}
|
||||
|
||||
// Time running.
|
||||
data["UptimeHours"] = int(time.Since(portmasterStarted).Hours())
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// Location holds location matching data.
|
||||
type Location struct {
|
||||
Country string
|
||||
Coordinates geoip.Coordinates
|
||||
ASN uint
|
||||
ASOrg string
|
||||
Source netenv.DeviceLocationSource
|
||||
SourceAccuracy int
|
||||
}
|
||||
|
||||
// Account holds SPN account matching data.
|
||||
type Account struct {
|
||||
*access.UserRecord
|
||||
UpToDate bool
|
||||
MayUseUSP bool
|
||||
}
|
||||
|
||||
// DataError represents an error getting some matching data.
|
||||
type DataError struct {
|
||||
Error error
|
||||
}
|
175
broadcasts/install_info.go
Normal file
175
broadcasts/install_info.go
Normal file
|
@ -0,0 +1,175 @@
|
|||
package broadcasts
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
semver "github.com/hashicorp/go-version"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/query"
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/info"
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
const installInfoDBKey = "core:status/install-info"
|
||||
|
||||
// InstallInfo holds generic info about the install.
|
||||
type InstallInfo struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
Version string
|
||||
NumericVersion int64
|
||||
|
||||
Time time.Time
|
||||
NumericDate int64
|
||||
DaysSinceInstall int64
|
||||
UnixTimestamp int64
|
||||
}
|
||||
|
||||
// GetInstallInfo returns the install info from the database.
|
||||
func GetInstallInfo() (*InstallInfo, error) {
|
||||
r, err := db.Get(installInfoDBKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unwrap.
|
||||
if r.IsWrapped() {
|
||||
// Only allocate a new struct, if we need it.
|
||||
newRecord := &InstallInfo{}
|
||||
err = record.Unwrap(r, newRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newRecord, nil
|
||||
}
|
||||
|
||||
// or adjust type
|
||||
newRecord, ok := r.(*InstallInfo)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("record not of type *InstallInfo, but %T", r)
|
||||
}
|
||||
return newRecord, nil
|
||||
}
|
||||
|
||||
func ensureInstallInfo() {
|
||||
// Get current install info from database.
|
||||
installInfo, err := GetInstallInfo()
|
||||
if err != nil {
|
||||
installInfo = &InstallInfo{}
|
||||
if !errors.Is(err, database.ErrNotFound) {
|
||||
log.Warningf("updates: failed to load install info: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fill in missing data and save.
|
||||
installInfo.checkAll()
|
||||
if err := installInfo.save(); err != nil {
|
||||
log.Warningf("updates: failed to save install info: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (ii *InstallInfo) save() error {
|
||||
if !ii.KeyIsSet() {
|
||||
ii.SetKey(installInfoDBKey)
|
||||
}
|
||||
return db.Put(ii)
|
||||
}
|
||||
|
||||
func (ii *InstallInfo) checkAll() {
|
||||
ii.checkVersion()
|
||||
ii.checkInstallDate()
|
||||
}
|
||||
|
||||
func (ii *InstallInfo) checkVersion() {
|
||||
// Check if everything is present.
|
||||
if ii.Version != "" && ii.NumericVersion > 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Update version information.
|
||||
versionInfo := info.GetInfo()
|
||||
ii.Version = versionInfo.Version
|
||||
|
||||
// Update numeric version.
|
||||
if versionInfo.Version != "" {
|
||||
numericVersion, err := MakeNumericVersion(versionInfo.Version)
|
||||
if err != nil {
|
||||
log.Warningf("updates: failed to make numeric version: %s", err)
|
||||
} else {
|
||||
ii.NumericVersion = numericVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MakeNumericVersion makes a numeric version with the first three version
|
||||
// segment always using three digits.
|
||||
func MakeNumericVersion(version string) (numericVersion int64, err error) {
|
||||
// Parse version string.
|
||||
ver, err := semver.NewVersion(version)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to parse core version: %w", err)
|
||||
}
|
||||
|
||||
// Transform version for numeric representation.
|
||||
segments := ver.Segments()
|
||||
for i := 0; i < 3 && i < len(segments); i++ {
|
||||
segmentNumber := int64(segments[i])
|
||||
if segmentNumber > 999 {
|
||||
segmentNumber = 999
|
||||
}
|
||||
switch i {
|
||||
case 0:
|
||||
numericVersion += segmentNumber * 1000000
|
||||
case 1:
|
||||
numericVersion += segmentNumber * 1000
|
||||
case 2:
|
||||
numericVersion += segmentNumber
|
||||
}
|
||||
}
|
||||
|
||||
return numericVersion, nil
|
||||
}
|
||||
|
||||
func (ii *InstallInfo) checkInstallDate() {
|
||||
// Check if everything is present.
|
||||
if ii.UnixTimestamp > 0 &&
|
||||
ii.NumericDate > 0 &&
|
||||
ii.DaysSinceInstall > 0 &&
|
||||
!ii.Time.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
// Find oldest created database entry and use it as install time.
|
||||
oldest := time.Now().Unix()
|
||||
it, err := db.Query(query.New("core"))
|
||||
if err != nil {
|
||||
log.Warningf("updates: failed to create iterator for searching DB for install time: %s", err)
|
||||
return
|
||||
}
|
||||
defer it.Cancel()
|
||||
for r := range it.Next {
|
||||
if oldest > r.Meta().Created {
|
||||
oldest = r.Meta().Created
|
||||
}
|
||||
}
|
||||
|
||||
// Set data.
|
||||
ii.UnixTimestamp = oldest
|
||||
ii.Time = time.Unix(oldest, 0)
|
||||
ii.DaysSinceInstall = int64(time.Since(ii.Time).Hours()) / 24
|
||||
|
||||
// Transform date for numeric representation.
|
||||
numericDate, err := strconv.ParseInt(ii.Time.Format("20060102"), 10, 64)
|
||||
if err != nil {
|
||||
log.Warningf("updates: failed to make numeric date from %s: %s", ii.Time, err)
|
||||
} else {
|
||||
ii.NumericDate = numericDate
|
||||
}
|
||||
}
|
46
broadcasts/module.go
Normal file
46
broadcasts/module.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package broadcasts
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/modules"
|
||||
)
|
||||
|
||||
var (
|
||||
module *modules.Module
|
||||
|
||||
db = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
Internal: true,
|
||||
})
|
||||
|
||||
startOnce sync.Once
|
||||
)
|
||||
|
||||
func init() {
|
||||
module = modules.Register("broadcasts", prep, start, nil, "updates", "netenv", "notifications")
|
||||
}
|
||||
|
||||
func prep() error {
|
||||
// Register API endpoints.
|
||||
if err := registerAPIEndpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func start() error {
|
||||
// Ensure the install info is up to date.
|
||||
ensureInstallInfo()
|
||||
|
||||
// Start broadcast notifier task.
|
||||
startOnce.Do(func() {
|
||||
module.NewTask("broadcast notifier", broadcastNotify).
|
||||
Repeat(10 * time.Minute).Queue()
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
285
broadcasts/notify.go
Normal file
285
broadcasts/notify.go
Normal file
|
@ -0,0 +1,285 @@
|
|||
package broadcasts
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/accessor"
|
||||
"github.com/safing/portbase/database/query"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portbase/notifications"
|
||||
"github.com/safing/portmaster/updates"
|
||||
)
|
||||
|
||||
const (
|
||||
broadcastsResourcePath = "intel/portmaster/notifications.yaml"
|
||||
|
||||
broadcastNotificationIDPrefix = "broadcasts:"
|
||||
|
||||
minRepeatDuration = 1 * time.Hour
|
||||
)
|
||||
|
||||
// Errors.
|
||||
var (
|
||||
ErrSkip = errors.New("broadcast skipped")
|
||||
ErrSkipDoesNotMatch = fmt.Errorf("%w: does not match", ErrSkip)
|
||||
ErrSkipAlreadyActive = fmt.Errorf("%w: already active", ErrSkip)
|
||||
ErrSkipAlreadyShown = fmt.Errorf("%w: already shown", ErrSkip)
|
||||
ErrSkipRemovedByMismatch = fmt.Errorf("%w: removed due to mismatch", ErrSkip)
|
||||
ErrSkipRemovedBySource = fmt.Errorf("%w: removed by source", ErrSkip)
|
||||
)
|
||||
|
||||
// BroadcastNotifications holds the data structure of the broadcast
|
||||
// notifications update file.
|
||||
type BroadcastNotifications struct {
|
||||
Notifications map[string]*BroadcastNotification
|
||||
}
|
||||
|
||||
// BroadcastNotification is a single broadcast notification.
|
||||
type BroadcastNotification struct {
|
||||
*notifications.Notification
|
||||
id string
|
||||
|
||||
// Match holds a query string that needs to match the local matching data in
|
||||
// order for the broadcast to be displayed.
|
||||
Match string
|
||||
matchingQuery *query.Query
|
||||
// AttachToModule signifies if the broadcast notification should be attached to the module.
|
||||
AttachToModule bool
|
||||
// Remove signifies that the broadcast should be canceled and its state removed.
|
||||
Remove bool
|
||||
// Permanent signifies that the broadcast cannot be acknowledge by the user
|
||||
// and remains in the UI indefinitely.
|
||||
Permanent bool
|
||||
// Repeat specifies a duration after which the broadcast should be shown again.
|
||||
Repeat string
|
||||
repeatDuration time.Duration
|
||||
}
|
||||
|
||||
func broadcastNotify(ctx context.Context, t *modules.Task) error {
|
||||
// Get broadcast notifications file, load it from disk and parse it.
|
||||
broadcastsResource, err := updates.GetFile(broadcastsResourcePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get broadcast notifications update: %w", err)
|
||||
}
|
||||
broadcastsData, err := ioutil.ReadFile(broadcastsResource.Path())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to load broadcast notifications update: %w", err)
|
||||
}
|
||||
broadcasts, err := parseBroadcastSource(broadcastsData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse broadcast notifications update: %w", err)
|
||||
}
|
||||
|
||||
// Get and marshal matching data.
|
||||
matchingData := collectData()
|
||||
matchingJSON, err := json.Marshal(matchingData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal broadcast notifications matching data: %w", err)
|
||||
}
|
||||
matchingDataAccessor := accessor.NewJSONBytesAccessor(&matchingJSON)
|
||||
|
||||
// Get broadcast notification states.
|
||||
bss, err := getBroadcastStates()
|
||||
if err != nil {
|
||||
if !errors.Is(err, database.ErrNotFound) {
|
||||
return fmt.Errorf("failed to get broadcast notifications states: %w", err)
|
||||
}
|
||||
bss = newBroadcastStates()
|
||||
}
|
||||
|
||||
// Go through all broadcast nofications and check if they match.
|
||||
for _, bn := range broadcasts.Notifications {
|
||||
err := handleBroadcast(bn, matchingDataAccessor, bss)
|
||||
switch {
|
||||
case err == nil:
|
||||
log.Infof("broadcasts: displaying broadcast %s", bn.id)
|
||||
case errors.Is(err, ErrSkip):
|
||||
log.Tracef("broadcasts: skipped displaying broadcast %s: %s", bn.id, err)
|
||||
default:
|
||||
log.Warningf("broadcasts: failed to handle broadcast %s: %s", bn.id, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBroadcastSource(yamlData []byte) (*BroadcastNotifications, error) {
|
||||
// Parse data.
|
||||
broadcasts := &BroadcastNotifications{}
|
||||
err := yaml.Unmarshal(yamlData, broadcasts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add IDs to struct for easier handling.
|
||||
for id, bn := range broadcasts.Notifications {
|
||||
bn.id = id
|
||||
|
||||
// Parse matching query.
|
||||
if bn.Match != "" {
|
||||
q, err := query.ParseQuery("query / where " + bn.Match)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse query of broadcast notification %s: %w", bn.id, err)
|
||||
}
|
||||
bn.matchingQuery = q
|
||||
}
|
||||
|
||||
// Parse the repeat duration.
|
||||
if bn.Repeat != "" {
|
||||
duration, err := time.ParseDuration(bn.Repeat)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse repeat duration of broadcast notification %s: %w", bn.id, err)
|
||||
}
|
||||
bn.repeatDuration = duration
|
||||
// Raise duration to minimum.
|
||||
if bn.repeatDuration < minRepeatDuration {
|
||||
bn.repeatDuration = minRepeatDuration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return broadcasts, nil
|
||||
}
|
||||
|
||||
func handleBroadcast(bn *BroadcastNotification, matchingDataAccessor accessor.Accessor, bss *BroadcastStates) error {
|
||||
// Check if broadcast was already shown.
|
||||
if bss != nil {
|
||||
state, ok := bss.States[bn.id]
|
||||
switch {
|
||||
case !ok || state.Read.IsZero():
|
||||
// Was never shown, continue.
|
||||
case bn.repeatDuration == 0 && !state.Read.IsZero():
|
||||
// Was already shown and is not repeated, skip.
|
||||
return ErrSkipAlreadyShown
|
||||
case bn.repeatDuration > 0 && time.Now().Add(-bn.repeatDuration).After(state.Read):
|
||||
// Was already shown, but should be repeated now, continue.
|
||||
}
|
||||
}
|
||||
|
||||
// Check if broadcast should be removed.
|
||||
if bn.Remove {
|
||||
removeBroadcast(bn, bss)
|
||||
return ErrSkipRemovedBySource
|
||||
}
|
||||
|
||||
// Skip if broadcast does not match.
|
||||
if bn.matchingQuery != nil && !bn.matchingQuery.MatchesAccessor(matchingDataAccessor) {
|
||||
removed := removeBroadcast(bn, bss)
|
||||
if removed {
|
||||
return ErrSkipRemovedByMismatch
|
||||
}
|
||||
return ErrSkipDoesNotMatch
|
||||
}
|
||||
|
||||
// Check if there is already an active notification for this.
|
||||
eventID := broadcastNotificationIDPrefix + bn.id
|
||||
n := notifications.Get(eventID)
|
||||
if n != nil {
|
||||
// Already active!
|
||||
return ErrSkipAlreadyActive
|
||||
}
|
||||
|
||||
// Prepare notification for displaying.
|
||||
n = bn.Notification
|
||||
n.EventID = eventID
|
||||
n.GUID = ""
|
||||
n.State = ""
|
||||
n.SelectedActionID = ""
|
||||
|
||||
// It is okay to edit the notification, as they are loaded from the file every time.
|
||||
// Add dismiss button if the notification is not permanent.
|
||||
if !bn.Permanent {
|
||||
n.AvailableActions = append(n.AvailableActions, ¬ifications.Action{
|
||||
ID: "ack",
|
||||
Text: "Got it!",
|
||||
})
|
||||
}
|
||||
n.SetActionFunction(markBroadcastAsRead)
|
||||
|
||||
// Display notification.
|
||||
n.Save()
|
||||
|
||||
// Attach to module to raise more awareness.
|
||||
if bn.AttachToModule {
|
||||
n.AttachToModule(module)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeBroadcast(bn *BroadcastNotification, bss *BroadcastStates) (removed bool) {
|
||||
// Remove any active notification.
|
||||
n := notifications.Get(broadcastNotificationIDPrefix + bn.id)
|
||||
if n != nil {
|
||||
removed = true
|
||||
n.Delete()
|
||||
}
|
||||
|
||||
// Remove any state.
|
||||
if bss != nil {
|
||||
delete(bss.States, bn.id)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var savingBroadcastStateLock sync.Mutex
|
||||
|
||||
func markBroadcastAsRead(ctx context.Context, n *notifications.Notification) error {
|
||||
// Lock persisting broadcast state.
|
||||
savingBroadcastStateLock.Lock()
|
||||
defer savingBroadcastStateLock.Unlock()
|
||||
|
||||
// Get notification data.
|
||||
var broadcastID, actionID string
|
||||
func() {
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
broadcastID = strings.TrimPrefix(n.EventID, broadcastNotificationIDPrefix)
|
||||
actionID = n.SelectedActionID
|
||||
}()
|
||||
|
||||
// Check response.
|
||||
switch actionID {
|
||||
case "ack":
|
||||
case "":
|
||||
return fmt.Errorf("no action ID for %s", broadcastID)
|
||||
default:
|
||||
return fmt.Errorf("unexpected action ID for %s: %s", broadcastID, actionID)
|
||||
}
|
||||
|
||||
// Get broadcast notification states.
|
||||
bss, err := getBroadcastStates()
|
||||
if err != nil {
|
||||
if !errors.Is(err, database.ErrNotFound) {
|
||||
return fmt.Errorf("failed to get broadcast notifications states: %w", err)
|
||||
}
|
||||
bss = newBroadcastStates()
|
||||
}
|
||||
|
||||
// Get state for this notification.
|
||||
bs, ok := bss.States[broadcastID]
|
||||
if !ok {
|
||||
bs = &BroadcastState{}
|
||||
bss.States[broadcastID] = bs
|
||||
}
|
||||
|
||||
// Delete to allow for timely repeats.
|
||||
n.Delete()
|
||||
|
||||
// Mark as read and save to DB.
|
||||
log.Infof("broadcasts: user acknowledged broadcast %s", broadcastID)
|
||||
bs.Read = time.Now()
|
||||
return bss.save()
|
||||
}
|
64
broadcasts/state.go
Normal file
64
broadcasts/state.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package broadcasts
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
)
|
||||
|
||||
const broadcastStatesDBKey = "core:broadcasts/state"
|
||||
|
||||
// BroadcastStates holds states for broadcast notifications.
|
||||
type BroadcastStates struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
States map[string]*BroadcastState
|
||||
}
|
||||
|
||||
// BroadcastState holds state for a single broadcast notifications.
|
||||
type BroadcastState struct {
|
||||
Read time.Time
|
||||
}
|
||||
|
||||
func (bss *BroadcastStates) save() error {
|
||||
return db.Put(bss)
|
||||
}
|
||||
|
||||
// getbroadcastStates returns the broadcast states from the database.
|
||||
func getBroadcastStates() (*BroadcastStates, error) {
|
||||
r, err := db.Get(broadcastStatesDBKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Unwrap.
|
||||
if r.IsWrapped() {
|
||||
// Only allocate a new struct, if we need it.
|
||||
newRecord := &BroadcastStates{}
|
||||
err = record.Unwrap(r, newRecord)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newRecord, nil
|
||||
}
|
||||
|
||||
// or adjust type
|
||||
newRecord, ok := r.(*BroadcastStates)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("record not of type *BroadcastStates, but %T", r)
|
||||
}
|
||||
return newRecord, nil
|
||||
}
|
||||
|
||||
// newBroadcastStates returns a new BroadcastStates.
|
||||
func newBroadcastStates() *BroadcastStates {
|
||||
bss := &BroadcastStates{
|
||||
States: make(map[string]*BroadcastState),
|
||||
}
|
||||
bss.SetKey(broadcastStatesDBKey)
|
||||
|
||||
return bss
|
||||
}
|
9
broadcasts/testdata/README.md
vendored
Normal file
9
broadcasts/testdata/README.md
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Testing Broadcast Notifications
|
||||
|
||||
```
|
||||
# Reset state
|
||||
curl -X POST http://127.0.0.1:817/api/v1/broadcasts/reset-state
|
||||
|
||||
# Simulate notifications
|
||||
curl --upload-file notifications.yaml http://127.0.0.1:817/api/v1/broadcasts/simulate
|
||||
```
|
22
broadcasts/testdata/notifications.yaml
vendored
Normal file
22
broadcasts/testdata/notifications.yaml
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
notifications:
|
||||
test1:
|
||||
title: "[TEST] Normal Broadcast"
|
||||
message: "This is a normal broadcast without matching. (#1)"
|
||||
test2:
|
||||
title: "[TEST] Permanent Broadcast"
|
||||
message: "This is a permanent broadcast without matching. (#2)"
|
||||
type: 1 # Warning
|
||||
permanent: true
|
||||
test3:
|
||||
title: "[TEST] Repeating Broadcast"
|
||||
message: "This is a repeating broadcast without matching. (#3)"
|
||||
repeat: "1m"
|
||||
test4:
|
||||
title: "[TEST] Matching Broadcast: PM version"
|
||||
message: "This is a normal broadcast that matches the PM version. (#4)"
|
||||
match: "NumericVersion > 8000"
|
||||
test5:
|
||||
title: "[TEST] Important Update"
|
||||
message: "A criticial update has been released, please update immediately. (#5)"
|
||||
type: 3 # Error
|
||||
attachToModule: true
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portbase/modules/subsystems"
|
||||
_ "github.com/safing/portmaster/broadcasts"
|
||||
_ "github.com/safing/portmaster/netenv"
|
||||
_ "github.com/safing/portmaster/status"
|
||||
_ "github.com/safing/portmaster/ui"
|
||||
|
@ -25,7 +26,7 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
module = modules.Register("core", prep, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui", "netenv", "network", "interception", "compat")
|
||||
module = modules.Register("core", prep, start, nil, "base", "subsystems", "status", "updates", "api", "notifications", "ui", "netenv", "network", "interception", "compat", "broadcasts")
|
||||
subsystems.Register(
|
||||
"core",
|
||||
"Core",
|
||||
|
|
25
go.mod
25
go.mod
|
@ -7,23 +7,24 @@ require (
|
|||
github.com/cookieo9/resources-go v0.0.0-20150225115733-d27c04069d0d
|
||||
github.com/coreos/go-iptables v0.6.0
|
||||
github.com/florianl/go-nfqueue v1.3.1
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/godbus/dbus/v5 v5.1.0
|
||||
github.com/google/gopacket v1.1.19
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/hashicorp/go-version v1.4.0
|
||||
github.com/miekg/dns v1.1.49
|
||||
github.com/hashicorp/go-version v1.5.0
|
||||
github.com/miekg/dns v1.1.50
|
||||
github.com/oschwald/maxminddb-golang v1.9.0
|
||||
github.com/safing/portbase v0.14.4
|
||||
github.com/safing/spn v0.4.11
|
||||
github.com/safing/portbase v0.14.5
|
||||
github.com/safing/spn v0.4.12
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/cobra v1.4.0
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/stretchr/testify v1.7.1
|
||||
github.com/tannerryan/ring v1.1.2
|
||||
github.com/tevino/abool v1.2.0
|
||||
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
|
||||
golang.org/x/net v0.0.0-20220513224357-95641704303c
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -35,7 +36,6 @@ require (
|
|||
github.com/bluele/gcache v0.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/gofrs/uuid v4.2.0+incompatible // indirect
|
||||
github.com/google/go-cmp v0.5.8 // indirect
|
||||
|
@ -71,10 +71,9 @@ require (
|
|||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.2 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
|
||||
)
|
||||
|
|
45
go.sum
45
go.sum
|
@ -250,6 +250,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
|
@ -530,8 +531,9 @@ github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
|
|||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.4.0 h1:aAQzgqIrRKRa7w75CKpbBxYsmUoPjzVm1W59ca1L0J4=
|
||||
github.com/hashicorp/go-version v1.4.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=
|
||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
|
@ -688,8 +690,9 @@ github.com/miekg/dns v1.1.45/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7Xn
|
|||
github.com/miekg/dns v1.1.46/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.49 h1:qe0mQU3Z/XpFeE+AEBo2rqaS1IPBJ3anmqZ4XiZJVG8=
|
||||
github.com/miekg/dns v1.1.49/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
|
@ -818,8 +821,9 @@ github.com/safing/portbase v0.14.0/go.mod h1:z9sRR/vqohAGdYSSx2B+o8tND4WVvcxPL6X
|
|||
github.com/safing/portbase v0.14.1/go.mod h1:z9sRR/vqohAGdYSSx2B+o8tND4WVvcxPL6XBBtN3bDI=
|
||||
github.com/safing/portbase v0.14.2/go.mod h1:z9sRR/vqohAGdYSSx2B+o8tND4WVvcxPL6XBBtN3bDI=
|
||||
github.com/safing/portbase v0.14.3/go.mod h1:z9sRR/vqohAGdYSSx2B+o8tND4WVvcxPL6XBBtN3bDI=
|
||||
github.com/safing/portbase v0.14.4 h1:yEWlvh2w0gVdjrA9oo5QPFV5oluJ6YDoKRPtmFb9B4c=
|
||||
github.com/safing/portbase v0.14.4/go.mod h1:z9sRR/vqohAGdYSSx2B+o8tND4WVvcxPL6XBBtN3bDI=
|
||||
github.com/safing/portbase v0.14.5 h1:+8H+mQ7AFjA04M7UPq0490pj3/+nvJj3pEUP1PYTMYc=
|
||||
github.com/safing/portbase v0.14.5/go.mod h1:z9sRR/vqohAGdYSSx2B+o8tND4WVvcxPL6XBBtN3bDI=
|
||||
github.com/safing/portmaster v0.7.3/go.mod h1:o//kZ8eE+5vT1V22mgnxHIAdlEz42sArsK5OF2Lf/+s=
|
||||
github.com/safing/portmaster v0.7.4/go.mod h1:Q93BWdF1oAL0oUMukshl8W1aPZhmrlTGi6tFTFc3pTw=
|
||||
github.com/safing/portmaster v0.7.6/go.mod h1:qOs9hQtvAzTVICRbwLg3vddqOaqJHeWBjWQ0C+TJ/Bw=
|
||||
|
@ -837,6 +841,7 @@ github.com/safing/portmaster v0.8.5/go.mod h1:MqOlFwHcIx/109Ugutz/CG23znuuXCRVHc
|
|||
github.com/safing/portmaster v0.8.7/go.mod h1:RUgCWt5v22jDUOtJfOwApi//Kt8RTZQhlREcBc+L4z8=
|
||||
github.com/safing/portmaster v0.8.9-interdep/go.mod h1:1hK7QpvFVlb/sglkc3SKj+RXMGBuk0wqO2s3pvMg1Xs=
|
||||
github.com/safing/portmaster v0.8.9/go.mod h1:tv0rxO76hrpBLdArN7YTypOaseH6zgQ2gLI2zCknk9Q=
|
||||
github.com/safing/portmaster v0.8.14-interdep/go.mod h1:HIkaE8wCXr8ULyZSWFkQNNY9obpMufxizXZugnjHLK0=
|
||||
github.com/safing/spn v0.3.4/go.mod h1:TfzNsZCbnlWv0UFDILFOUSudVKJZlnBVoR1fDXrjOK0=
|
||||
github.com/safing/spn v0.3.5/go.mod h1:jHkFF2Yu1fnjFu4KXjVA+iagMr/z4eB4p3jiwikvKj8=
|
||||
github.com/safing/spn v0.3.6/go.mod h1:RSeFb/h5Wt3yDVezXj3lhXJ/Iwd7FbtsGf5E+p5J2YQ=
|
||||
|
@ -854,14 +859,10 @@ github.com/safing/spn v0.4.3/go.mod h1:YHtg3FkZviN8T7db4BdRffbYO1pO7w9SydQatLmvW
|
|||
github.com/safing/spn v0.4.5/go.mod h1:mkQA5pYM1SUd4JkTyuwXFycFMGQXLTd9RUJuY2vqccM=
|
||||
github.com/safing/spn v0.4.6/go.mod h1:AmZ+rore+6DQp0GSchIAXPn8ij0Knyw7uy4PbMLljXg=
|
||||
github.com/safing/spn v0.4.7/go.mod h1:NoSG9K0OK9hrPC76yqWFS6RtvbqZdIc/KGOsC4T3hV8=
|
||||
github.com/safing/spn v0.4.8 h1:C5QOl03hypDaC4qvFtWjAKmSJIOjfqYVGT+sTnUIO/E=
|
||||
github.com/safing/spn v0.4.8/go.mod h1:nro/I6b2JnafeeqoMsQRqf6TaQeL9uLLZkUREtxLVDE=
|
||||
github.com/safing/spn v0.4.9 h1:p+TRS2y1yAY4CYyHiVuy+V3NcA9p826NtYksuBaSl64=
|
||||
github.com/safing/spn v0.4.9/go.mod h1:nro/I6b2JnafeeqoMsQRqf6TaQeL9uLLZkUREtxLVDE=
|
||||
github.com/safing/spn v0.4.10 h1:nZuWPBn1z0aBUDnnpG9pKZCmPAH2hgNE4cqiSB7JGU8=
|
||||
github.com/safing/spn v0.4.10/go.mod h1:nro/I6b2JnafeeqoMsQRqf6TaQeL9uLLZkUREtxLVDE=
|
||||
github.com/safing/spn v0.4.11 h1:Er3ZtBCqKaf0G6lmRKpm1LenlrWeaWUp8612bpeG+aM=
|
||||
github.com/safing/spn v0.4.11/go.mod h1:nro/I6b2JnafeeqoMsQRqf6TaQeL9uLLZkUREtxLVDE=
|
||||
github.com/safing/spn v0.4.12 h1:Tw7TUZEZR4yZy7L+ICRCketDk5L5x0s0pvrSUHFaKs4=
|
||||
github.com/safing/spn v0.4.12/go.mod h1:AUNgBrRwCcspC98ljptDnrPuHLn/BHSG+rSprV/5Wlc=
|
||||
github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
|
@ -905,8 +906,9 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL
|
|||
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4=
|
||||
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
|
||||
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
|
||||
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
|
@ -1094,8 +1096,10 @@ golang.org/x/crypto v0.0.0-20220321153916-2c7772ba3064/go.mod h1:IxCIyHEi3zRg3s0
|
|||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -1133,8 +1137,9 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
|
@ -1215,8 +1220,10 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
|
|||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220513224357-95641704303c h1:nF9mHSvoKBLkQNQhJZNsc66z2UzAMUbLGjC95CF3pU0=
|
||||
golang.org/x/net v0.0.0-20220513224357-95641704303c/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220621193019-9d032be2e588/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305 h1:dAgbJ2SP4jD6XYfMNLVj0BF21jo2PjChrtGaAvF5M3I=
|
||||
golang.org/x/net v0.0.0-20220622184535-263ec571b305/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -1248,8 +1255,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29 h1:w8s32wxx3sY+OjLlv9qltkLU5yvJzxjjgiHWLjdIcw4=
|
||||
golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -1374,8 +1382,11 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220325203850-36772127a21f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
|
||||
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU=
|
||||
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -1471,13 +1482,13 @@ golang.org/x/tools v0.1.6/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
|||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||
golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20=
|
||||
golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
|
||||
golang.org/x/tools v0.1.11 h1:loJ25fNOEhSXfHrpoGj91eCUThwdNX6u24rO1xnNteY=
|
||||
golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U=
|
||||
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
|
|
|
@ -2,11 +2,8 @@ package updates
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/query"
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/info"
|
||||
"github.com/safing/portbase/log"
|
||||
|
@ -14,48 +11,95 @@ import (
|
|||
"github.com/safing/portmaster/updates/helper"
|
||||
)
|
||||
|
||||
// Database key for update information.
|
||||
const (
|
||||
// versionsDBKey is the database key for update version information.
|
||||
versionsDBKey = "core:status/versions"
|
||||
|
||||
// versionsDBKey is the database key for simple update version information.
|
||||
simpleVersionsDBKey = "core:status/simple-versions"
|
||||
)
|
||||
|
||||
var (
|
||||
versionExport *versions
|
||||
versionExportDB = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
Internal: true,
|
||||
})
|
||||
versionExportHook *database.RegisteredHook
|
||||
)
|
||||
|
||||
// versions holds updates status information.
|
||||
type versions struct {
|
||||
// Versions holds update versions and status information.
|
||||
type Versions struct {
|
||||
record.Base
|
||||
lock sync.Mutex
|
||||
sync.Mutex
|
||||
|
||||
Core *info.Info
|
||||
Resources map[string]*updater.Resource
|
||||
Channel string
|
||||
Beta bool
|
||||
Staging bool
|
||||
}
|
||||
|
||||
internalSave bool
|
||||
// SimpleVersions holds simplified update versions and status information.
|
||||
type SimpleVersions struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
Build *info.Info
|
||||
Resources map[string]*SimplifiedResourceVersion
|
||||
Channel string
|
||||
}
|
||||
|
||||
// SimplifiedResourceVersion holds version information about one resource.
|
||||
type SimplifiedResourceVersion struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
// GetVersions returns the update versions and status information.
|
||||
// Resources must be locked when accessed.
|
||||
func GetVersions() *Versions {
|
||||
return &Versions{
|
||||
Core: info.GetInfo(),
|
||||
Resources: registry.Export(),
|
||||
Channel: initialReleaseChannel,
|
||||
Beta: initialReleaseChannel == helper.ReleaseChannelBeta,
|
||||
Staging: initialReleaseChannel == helper.ReleaseChannelStaging,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSimpleVersions returns the simplified update versions and status information.
|
||||
func GetSimpleVersions() *SimpleVersions {
|
||||
// Fill base info.
|
||||
v := &SimpleVersions{
|
||||
Build: info.GetInfo(),
|
||||
Resources: make(map[string]*SimplifiedResourceVersion),
|
||||
Channel: initialReleaseChannel,
|
||||
}
|
||||
|
||||
// Iterate through all versions and add version info.
|
||||
for id, resource := range registry.Export() {
|
||||
func() {
|
||||
resource.Lock()
|
||||
defer resource.Unlock()
|
||||
|
||||
// Get current in-used or selected version.
|
||||
var rv *updater.ResourceVersion
|
||||
switch {
|
||||
case resource.ActiveVersion != nil:
|
||||
rv = resource.ActiveVersion
|
||||
case resource.SelectedVersion != nil:
|
||||
rv = resource.SelectedVersion
|
||||
}
|
||||
|
||||
// Get information from resource.
|
||||
if rv != nil {
|
||||
v.Resources[id] = &SimplifiedResourceVersion{
|
||||
Version: rv.VersionNumber,
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return v
|
||||
}
|
||||
|
||||
func initVersionExport() (err error) {
|
||||
// init export struct
|
||||
versionExport = &versions{
|
||||
internalSave: true,
|
||||
Channel: initialReleaseChannel,
|
||||
Beta: initialReleaseChannel == helper.ReleaseChannelBeta,
|
||||
Staging: initialReleaseChannel == helper.ReleaseChannelStaging,
|
||||
if err := GetVersions().save(); err != nil {
|
||||
log.Warningf("updates: failed to export version information: %s", err)
|
||||
}
|
||||
versionExport.SetKey(versionsDBKey)
|
||||
|
||||
// attach hook to database
|
||||
versionExportHook, err = database.RegisterHook(query.New(versionsDBKey), &exportHook{})
|
||||
if err != nil {
|
||||
return err
|
||||
if err := GetSimpleVersions().save(); err != nil {
|
||||
log.Warningf("updates: failed to export version information: %s", err)
|
||||
}
|
||||
|
||||
return module.RegisterEventHook(
|
||||
|
@ -66,71 +110,24 @@ func initVersionExport() (err error) {
|
|||
)
|
||||
}
|
||||
|
||||
func stopVersionExport() error {
|
||||
return versionExportHook.Cancel()
|
||||
func (v *Versions) save() error {
|
||||
if !v.KeyIsSet() {
|
||||
v.SetKey(versionsDBKey)
|
||||
}
|
||||
return db.Put(v)
|
||||
}
|
||||
|
||||
func (v *SimpleVersions) save() error {
|
||||
if !v.KeyIsSet() {
|
||||
v.SetKey(simpleVersionsDBKey)
|
||||
}
|
||||
return db.Put(v)
|
||||
}
|
||||
|
||||
// export is an event hook.
|
||||
func export(_ context.Context, _ interface{}) error {
|
||||
// populate
|
||||
versionExport.lock.Lock()
|
||||
versionExport.Core = info.GetInfo()
|
||||
versionExport.Resources = registry.Export()
|
||||
versionExport.lock.Unlock()
|
||||
|
||||
// save
|
||||
err := versionExportDB.Put(versionExport)
|
||||
if err != nil {
|
||||
log.Warningf("updates: failed to export versions: %s", err)
|
||||
if err := GetVersions().save(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock locks the versionExport and all associated resources.
|
||||
func (v *versions) Lock() {
|
||||
// lock self
|
||||
v.lock.Lock()
|
||||
|
||||
// lock all resources
|
||||
for _, res := range v.Resources {
|
||||
res.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Lock unlocks the versionExport and all associated resources.
|
||||
func (v *versions) Unlock() {
|
||||
// unlock all resources
|
||||
for _, res := range v.Resources {
|
||||
res.Unlock()
|
||||
}
|
||||
|
||||
// unlock self
|
||||
v.lock.Unlock()
|
||||
}
|
||||
|
||||
type exportHook struct {
|
||||
database.HookBase
|
||||
}
|
||||
|
||||
// UsesPrePut implements the Hook interface.
|
||||
func (eh *exportHook) UsesPrePut() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
var errInternalRecord = errors.New("may not modify internal record")
|
||||
|
||||
// PrePut implements the Hook interface.
|
||||
func (eh *exportHook) PrePut(r record.Record) (record.Record, error) {
|
||||
if r.IsWrapped() {
|
||||
return nil, errInternalRecord
|
||||
}
|
||||
ve, ok := r.(*versions)
|
||||
if !ok {
|
||||
return nil, errInternalRecord
|
||||
}
|
||||
if !ve.internalSave {
|
||||
return nil, errInternalRecord
|
||||
}
|
||||
return r, nil
|
||||
return GetSimpleVersions().save()
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/dataroot"
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/modules"
|
||||
|
@ -48,6 +49,11 @@ var (
|
|||
updateASAP bool
|
||||
disableTaskSchedule bool
|
||||
|
||||
db = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
Internal: true,
|
||||
})
|
||||
|
||||
// UserAgent is an HTTP User-Agent that is used to add
|
||||
// more context to requests made by the registry when
|
||||
// fetching resources from the update server.
|
||||
|
@ -55,6 +61,8 @@ var (
|
|||
)
|
||||
|
||||
const (
|
||||
updatesDirName = "updates"
|
||||
|
||||
updateFailed = "updates:failed"
|
||||
updateSuccess = "updates:success"
|
||||
)
|
||||
|
@ -108,7 +116,7 @@ func start() error {
|
|||
registry.UserAgent = userAgentFromFlag
|
||||
}
|
||||
// initialize
|
||||
err := registry.Initialize(dataroot.Root().ChildDir("updates", 0o0755))
|
||||
err := registry.Initialize(dataroot.Root().ChildDir(updatesDirName, 0o0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -275,7 +283,7 @@ func stop() error {
|
|||
}
|
||||
}
|
||||
|
||||
return stopVersionExport()
|
||||
return nil
|
||||
}
|
||||
|
||||
// RootPath returns the root path used for storing updates.
|
||||
|
|
Loading…
Add table
Reference in a new issue