diff --git a/config/basic_config.go b/config/basic_config.go new file mode 100644 index 0000000..ce4a716 --- /dev/null +++ b/config/basic_config.go @@ -0,0 +1,113 @@ +package config + +import ( + "context" + "flag" + + "github.com/safing/portbase/log" +) + +// Configuration Keys. +var ( + CfgDevModeKey = "core/devMode" + defaultDevMode bool + + CfgLogLevel = "core/log/level" + defaultLogLevel = log.InfoLevel.String() + logLevel StringOption +) + +func init() { + flag.BoolVar(&defaultDevMode, "devmode", false, "enable development mode") +} + +func registerBasicOptions() error { + // Get the default log level from the log package. + defaultLogLevel = log.GetLogLevel().Name() + + // Register logging setting. + // The log package cannot do that, as it would trigger and import loop. + if err := Register(&Option{ + Name: "Log Level", + Key: CfgLogLevel, + Description: "Configure the logging level.", + OptType: OptTypeString, + ExpertiseLevel: ExpertiseLevelDeveloper, + ReleaseLevel: ReleaseLevelStable, + DefaultValue: defaultLogLevel, + Annotations: Annotations{ + DisplayOrderAnnotation: 513, + DisplayHintAnnotation: DisplayHintOneOf, + CategoryAnnotation: "Development", + }, + PossibleValues: []PossibleValue{ + { + Name: "Critical", + Value: "critical", + Description: "The critical level only logs errors that lead to a partial, but imminent failure.", + }, + { + Name: "Error", + Value: "error", + Description: "The error level logs errors that potentially break functionality. Everything logged by the critical level is included here too.", + }, + { + Name: "Warning", + Value: "warning", + Description: "The warning level logs minor errors and worse. Everything logged by the error level is included here too.", + }, + { + Name: "Info", + Value: "info", + Description: "The info level logs the main events that are going on and are interesting to the user. Everything logged by the warning level is included here too.", + }, + { + Name: "Debug", + Value: "debug", + Description: "The debug level logs some additional debugging details. Everything logged by the info level is included here too.", + }, + { + Name: "Trace", + Value: "trace", + Description: "The trace level logs loads of detailed information as well as operation and request traces. Everything logged by the debug level is included here too.", + }, + }, + }); err != nil { + return err + } + logLevel = GetAsString(CfgLogLevel, defaultLogLevel) + + // Register to hook to update the log level. + if err := module.RegisterEventHook( + "config", + configChangeEvent, + "update log level", + setLogLevel, + ); err != nil { + return err + } + + return Register(&Option{ + Name: "Development Mode", + Key: CfgDevModeKey, + Description: "In Development Mode, security restrictions are lifted/softened to enable unrestricted access for debugging and testing purposes.", + OptType: OptTypeBool, + ExpertiseLevel: ExpertiseLevelDeveloper, + ReleaseLevel: ReleaseLevelStable, + DefaultValue: defaultDevMode, + Annotations: Annotations{ + DisplayOrderAnnotation: 512, + CategoryAnnotation: "Development", + }, + }) +} + +func loadLogLevel() { + setDefaultConfigOption(CfgLogLevel, log.GetLogLevel().Name(), false) +} + +func setLogLevel(ctx context.Context, data interface{}) error { + log.SetLogLevel(log.ParseLevel(logLevel())) + + return nil +} diff --git a/config/devmode.go b/config/devmode.go deleted file mode 100644 index 7dbd190..0000000 --- a/config/devmode.go +++ /dev/null @@ -1,39 +0,0 @@ -package config - -import ( - "flag" - - "github.com/safing/portbase/log" -) - -// Configuration Keys. -var ( - CfgDevModeKey = "core/devMode" - defaultDevMode bool -) - -func init() { - flag.BoolVar(&defaultDevMode, "devmode", false, "enable development mode") -} - -func logDevModeOverride() { - if defaultDevMode { - log.Warning("config: development mode is enabled by default by the -devmode flag") - } -} - -func registerDevModeOption() error { - return Register(&Option{ - Name: "Development Mode", - Key: CfgDevModeKey, - Description: "In Development Mode, security restrictions are lifted/softened to enable unrestricted access for debugging and testing purposes.", - OptType: OptTypeBool, - ExpertiseLevel: ExpertiseLevelDeveloper, - ReleaseLevel: ReleaseLevelStable, - DefaultValue: defaultDevMode, - Annotations: Annotations{ - DisplayOrderAnnotation: 512, - CategoryAnnotation: "Development", - }, - }) -} diff --git a/config/main.go b/config/main.go index 2857ce8..b064b95 100644 --- a/config/main.go +++ b/config/main.go @@ -47,18 +47,15 @@ func prep() error { modules.SetCmdLineOperation(exportConfigCmd) } - logDevModeOverride() - err := registerDevModeOption() - if err != nil { - return err - } - - return nil + return registerBasicOptions() } func start() error { configFilePath = filepath.Join(dataRoot.Path, "config.json") + // Load log level from log package after it started. + loadLogLevel() + err := registerAsDatabase() if err != nil && !os.IsNotExist(err) { return err diff --git a/config/set.go b/config/set.go index 6e1ef4b..b9b11ff 100644 --- a/config/set.go +++ b/config/set.go @@ -192,5 +192,7 @@ func setDefaultConfigOption(key string, value interface{}, push bool) (err error // finalize change, activate triggers signalChanges() - return saveConfig() + // Do not save the configuration, as it only saves the active values, not the + // active default value. + return nil } diff --git a/log/logging.go b/log/logging.go index 2af4390..27439dd 100644 --- a/log/logging.go +++ b/log/logging.go @@ -102,7 +102,7 @@ var ( logBuffer chan *logLine forceEmptyingOfBuffer = make(chan struct{}) - logLevelInt = uint32(3) + logLevelInt = uint32(InfoLevel) logLevel = &logLevelInt pkgLevelsActive = abool.NewBool(false) @@ -134,11 +134,36 @@ func UnSetPkgLevels() { pkgLevelsActive.UnSet() } +// GetLogLevel returns the current log level. +func GetLogLevel() Severity { + return Severity(atomic.LoadUint32(logLevel)) +} + // SetLogLevel sets a new log level. Only effective after Start(). func SetLogLevel(level Severity) { atomic.StoreUint32(logLevel, uint32(level)) } +// Name returns the name of the log level. +func (s Severity) Name() string { + switch s { + case TraceLevel: + return "trace" + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarningLevel: + return "warning" + case ErrorLevel: + return "error" + case CriticalLevel: + return "critical" + default: + return "none" + } +} + // ParseLevel returns the level severity of a log level name. func ParseLevel(level string) Severity { switch strings.ToLower(level) { diff --git a/utils/broadcastflag.go b/utils/broadcastflag.go new file mode 100644 index 0000000..51c79f2 --- /dev/null +++ b/utils/broadcastflag.go @@ -0,0 +1,84 @@ +package utils + +import ( + "sync" + + "github.com/tevino/abool" +) + +// BroadcastFlag is a simple system to broadcast a flag value. +type BroadcastFlag struct { + flag *abool.AtomicBool + signal chan struct{} + lock sync.Mutex +} + +// Flag receives changes from its broadcasting flag. +// A Flag must only be used in one goroutine and is not concurrency safe, +// but fast. +type Flag struct { + flag *abool.AtomicBool + signal chan struct{} + broadcaster *BroadcastFlag +} + +// NewBroadcastFlag returns a new BroadcastFlag. +// In the initial state, the flag is not set and the singal does not trigger. +func NewBroadcastFlag() *BroadcastFlag { + return &BroadcastFlag{ + flag: abool.New(), + signal: make(chan struct{}), + lock: sync.Mutex{}, + } +} + +// NewFlag returns a new Flag that listens to this broadcasting flag. +// In the initial state, the flag is set and the singal triggers. +// You can call Refresh immediately to get the current state from the +// broadcasting flag. +func (bf *BroadcastFlag) NewFlag() *Flag { + newFlag := &Flag{ + flag: abool.NewBool(true), + signal: make(chan struct{}), + broadcaster: bf, + } + close(newFlag.signal) + return newFlag +} + +// NotifyAndReset notifies all flags of this broadcasting flag and resets the +// internal broadcast flag state. +func (bf *BroadcastFlag) NotifyAndReset() { + bf.lock.Lock() + defer bf.lock.Unlock() + + // Notify all flags of the change. + bf.flag.Set() + close(bf.signal) + + // Reset + bf.flag = abool.New() + bf.signal = make(chan struct{}) +} + +// Signal returns a channel that waits for the flag to be set. This does not +// reset the Flag itself, you'll need to call Refresh for that. +func (f *Flag) Signal() <-chan struct{} { + return f.signal +} + +// IsSet returns whether the flag was set since the last Refresh. +// This does not reset the Flag itself, you'll need to call Refresh for that. +func (f *Flag) IsSet() bool { + return f.flag.IsSet() +} + +// Refresh fetches the current state from the broadcasting flag. +func (f *Flag) Refresh() { + f.broadcaster.lock.Lock() + defer f.broadcaster.lock.Unlock() + + // Copy current flag and signal from the broadcasting flag. + f.flag = f.broadcaster.flag + f.signal = f.broadcaster.signal +} diff --git a/utils/osdetail/powershell_windows.go b/utils/osdetail/powershell_windows.go index 7478abb..52ce5c4 100644 --- a/utils/osdetail/powershell_windows.go +++ b/utils/osdetail/powershell_windows.go @@ -12,6 +12,7 @@ func RunPowershellCmd(script string) (output string, err error) { // Create command to execute. cmd := exec.Command( "powershell.exe", + "-ExecutionPolicy", "Bypass", "-NoProfile", "-NonInteractive", "[System.Console]::OutputEncoding = [System.Text.Encoding]::UTF8\n"+script,