diff --git a/cmds/portmaster-core/main.go b/cmds/portmaster-core/main.go index 630f5827..01d71402 100644 --- a/cmds/portmaster-core/main.go +++ b/cmds/portmaster-core/main.go @@ -55,7 +55,10 @@ func initialize() *service.Instance { // Create instance. var execCmdLine bool - instance, err := service.New(&service.ServiceConfig{}) + instance, err := service.New(&service.ServiceConfig{ + IsRunningAsService: isRunningAsService(), + DefaultRestartCommand: defaultRestartCommand, + }) switch { case err == nil: // Continue diff --git a/cmds/portmaster-core/main_linux.go b/cmds/portmaster-core/main_linux.go index aa8d9a60..e8268dd7 100644 --- a/cmds/portmaster-core/main_linux.go +++ b/cmds/portmaster-core/main_linux.go @@ -4,14 +4,19 @@ import ( "fmt" "log/slog" "os" + "os/exec" "os/signal" "syscall" "time" + processInfo "github.com/shirou/gopsutil/process" + "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/service" ) +var defaultRestartCommand = exec.Command("systemctl", "restart", "portmaster") + func run(instance *service.Instance) { // Set default log level. log.SetLogLevel(log.WarningLevel) @@ -98,3 +103,20 @@ func run(instance *service.Instance) { os.Exit(instance.ExitCode()) } + +func isRunningAsService() bool { + // Get the current process ID + pid := os.Getpid() + + currentProcess, err := processInfo.NewProcess(int32(pid)) + if err != nil { + return false + } + + ppid, err := currentProcess.Ppid() + if err != nil { + return false + } + // Check if the parent process ID is 1 == init system + return ppid == 1 +} diff --git a/cmds/portmaster-core/main_windows.go b/cmds/portmaster-core/main_windows.go index 9b3798c3..cf47760e 100644 --- a/cmds/portmaster-core/main_windows.go +++ b/cmds/portmaster-core/main_windows.go @@ -9,6 +9,7 @@ import ( "fmt" "log/slog" "os" + "os/exec" "os/signal" "sync" "syscall" @@ -24,6 +25,8 @@ var ( // wait groups runWg sync.WaitGroup finishWg sync.WaitGroup + + defaultRestartCommand = exec.Command("sc.exe", "restart", "PortmasterCore") ) const serviceName = "PortmasterCore" @@ -168,3 +171,11 @@ func registerSignalHandler(instance *service.Instance) { } }() } + +func isRunningAsService() bool { + isService, err := svc.IsWindowsService() + if err != nil { + return false + } + return isService +} diff --git a/service/config.go b/service/config.go index 85a98603..8823ba5a 100644 --- a/service/config.go +++ b/service/config.go @@ -1,3 +1,8 @@ package service -type ServiceConfig struct{} +import "os/exec" + +type ServiceConfig struct { + IsRunningAsService bool + DefaultRestartCommand *exec.Cmd +} diff --git a/service/core/api.go b/service/core/api.go index 2c465ccc..bb63464b 100644 --- a/service/core/api.go +++ b/service/core/api.go @@ -19,7 +19,6 @@ import ( "github.com/safing/portmaster/service/process" "github.com/safing/portmaster/service/resolver" "github.com/safing/portmaster/service/status" - "github.com/safing/portmaster/service/updates" "github.com/safing/portmaster/spn/captain" ) @@ -149,8 +148,8 @@ func shutdown(_ *api.Request) (msg string, err error) { func restart(_ *api.Request) (msg string, err error) { log.Info("core: user requested restart via action") - // Let the updates module handle restarting. - updates.RestartNow() + // Trigger restart + module.instance.Restart() return "restart initiated", nil } diff --git a/service/core/core.go b/service/core/core.go index 60ad5857..1f41285c 100644 --- a/service/core/core.go +++ b/service/core/core.go @@ -114,6 +114,7 @@ func New(instance instance) (*Core, error) { type instance interface { Shutdown() + Restart() AddWorkerInfoToDebugInfo(di *debug.Info) BinaryUpdates() *updates.Updates IntelUpdates() *updates.Updates diff --git a/service/instance.go b/service/instance.go index 4c522a34..7887b0ef 100644 --- a/service/instance.go +++ b/service/instance.go @@ -4,14 +4,17 @@ import ( "context" "fmt" "os" + "os/exec" "path/filepath" go_runtime "runtime" + "strings" "sync/atomic" "time" "github.com/safing/portmaster/base/api" "github.com/safing/portmaster/base/config" "github.com/safing/portmaster/base/database/dbmodule" + "github.com/safing/portmaster/base/log" "github.com/safing/portmaster/base/metrics" "github.com/safing/portmaster/base/notifications" "github.com/safing/portmaster/base/rng" @@ -101,7 +104,9 @@ type Instance struct { sluice *sluice.SluiceModule terminal *terminal.TerminalModule - CommandLineOperation func() error + CommandLineOperation func() error + isRunningAsService bool + defaultRestartCommand *exec.Cmd } func getCurrentBinaryFolder() (string, error) { @@ -178,6 +183,8 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx // Create instance to pass it to modules. instance := &Instance{} instance.ctx, instance.cancelCtx = context.WithCancel(context.Background()) + instance.isRunningAsService = svcCfg.IsRunningAsService + instance.defaultRestartCommand = svcCfg.DefaultRestartCommand var err error @@ -671,7 +678,7 @@ func (i *Instance) Restart() { i.core.EventRestart.Submit(struct{}{}) time.Sleep(10 * time.Millisecond) - i.shutdown(RestartExitCode) + i.serviceRestart() } // Shutdown asynchronously stops the instance. @@ -700,6 +707,25 @@ func (i *Instance) shutdown(exitCode int) { }) } +func (i *Instance) serviceRestart() { + if !i.isRunningAsService { + i.shutdown(RestartExitCode) + return + } + cmd := i.defaultRestartCommand + + // Check if user defined custom command for restarting the service. + restartCommand, exists := os.LookupEnv("PORTMASTER_RESTART_COMMAND") + + log.Debugf(`instance: running command "%s", %v`, restartCommand, exists) + if exists && restartCommand != "" { + commandSplit := strings.Split(restartCommand, " ") + cmd = exec.Command(commandSplit[0], commandSplit[1:]...) + } + log.Debugf("instance: running command %s", cmd.String()) + _ = cmd.Run() +} + // Stopping returns whether the instance is shutting down. func (i *Instance) Stopping() bool { return i.ctx.Err() != nil diff --git a/service/updates/downloader.go b/service/updates/downloader.go index 3cc35624..e5d1d54d 100644 --- a/service/updates/downloader.go +++ b/service/updates/downloader.go @@ -16,6 +16,7 @@ import ( "strings" semver "github.com/hashicorp/go-version" + "github.com/safing/portmaster/base/log" ) diff --git a/service/updates/index.go b/service/updates/index.go deleted file mode 100644 index de15a98b..00000000 --- a/service/updates/index.go +++ /dev/null @@ -1 +0,0 @@ -package updates diff --git a/service/updates/main.go b/service/updates/main.go deleted file mode 100644 index de15a98b..00000000 --- a/service/updates/main.go +++ /dev/null @@ -1 +0,0 @@ -package updates diff --git a/service/updates/module.go b/service/updates/module.go index 625fe395..cd29c3f7 100644 --- a/service/updates/module.go +++ b/service/updates/module.go @@ -150,7 +150,7 @@ func (u *Updates) applyUpdates(_ *mgr.WorkerCtx) error { // Perform restart. u.instance.Restart() } else { - // Update completed and no restart was needed. Submit an event. + // Update completed and no restart is needed. Submit an event. u.EventResourcesUpdated.Submit(struct{}{}) } return nil diff --git a/service/updates/registry.go b/service/updates/registry.go index 4a797947..8ffc461f 100644 --- a/service/updates/registry.go +++ b/service/updates/registry.go @@ -8,6 +8,7 @@ import ( "path/filepath" semver "github.com/hashicorp/go-version" + "github.com/safing/portmaster/base/log" ) diff --git a/service/updates/restart.go b/service/updates/restart.go deleted file mode 100644 index 66ee82d8..00000000 --- a/service/updates/restart.go +++ /dev/null @@ -1,85 +0,0 @@ -package updates - -import ( - "sync" - "time" - - "github.com/tevino/abool" - - "github.com/safing/portmaster/base/log" -) - -var ( - // RebootOnRestart defines whether the whole system, not just the service, - // should be restarted automatically when triggering a restart internally. - RebootOnRestart bool - - restartPending = abool.New() - restartTriggered = abool.New() - - restartTime time.Time - restartTimeLock sync.Mutex -) - -// IsRestarting returns whether a restart has been triggered. -func IsRestarting() bool { - return restartTriggered.IsSet() -} - -// RestartIsPending returns whether a restart is pending. -func RestartIsPending() (pending bool, restartAt time.Time) { - if restartPending.IsNotSet() { - return false, time.Time{} - } - - restartTimeLock.Lock() - defer restartTimeLock.Unlock() - - return true, restartTime -} - -// DelayedRestart triggers a restart of the application by shutting down the -// module system gracefully and returning with RestartExitCode. The restart -// may be further delayed by up to 10 minutes by the internal task scheduling -// system. This only works if the process is managed by portmaster-start. -func DelayedRestart(delay time.Duration) { - // Check if restart is already pending. - if !restartPending.SetToIf(false, true) { - return - } - - // Schedule the restart task. - log.Warningf("updates: restart triggered, will execute in %s", delay) - restartAt := time.Now().Add(delay) - // module.restartWorkerMgr.Delay(delay) - - // Set restartTime. - restartTimeLock.Lock() - defer restartTimeLock.Unlock() - restartTime = restartAt -} - -// AbortRestart aborts a (delayed) restart. -func AbortRestart() { - if restartPending.SetToIf(true, false) { - log.Warningf("updates: restart aborted") - - // Cancel schedule. - // module.restartWorkerMgr.Delay(0) - } -} - -// TriggerRestartIfPending triggers an automatic restart, if one is pending. -// This can be used to prepone a scheduled restart if the conditions are preferable. -func TriggerRestartIfPending() { - // if restartPending.IsSet() { - // module.restartWorkerMgr.Go() - // } -} - -// RestartNow immediately executes a restart. -// This only works if the process is managed by portmaster-start. -func RestartNow() { - restartPending.Set() - // module.restartWorkerMgr.Go() -} diff --git a/spn/captain/hooks.go b/spn/captain/hooks.go index d21bd67b..6e1b1010 100644 --- a/spn/captain/hooks.go +++ b/spn/captain/hooks.go @@ -1,7 +1,6 @@ package captain import ( - "github.com/safing/portmaster/service/updates" "github.com/safing/portmaster/spn/conf" "github.com/safing/portmaster/spn/docks" ) @@ -41,5 +40,7 @@ func updateConnectionStatus() { return } } - updates.TriggerRestartIfPending() + + // TODO(vladimir): what was this needed for? + // updates.TriggerRestartIfPending() } diff --git a/spn/captain/intel.go b/spn/captain/intel.go index 68b7a74d..ac1145a6 100644 --- a/spn/captain/intel.go +++ b/spn/captain/intel.go @@ -44,7 +44,7 @@ func updateSPNIntel(_ context.Context, _ interface{}) (err error) { // Check if there is something to do. // TODO(vladimir): is update check needed - if intelResource != nil { //&& !intelResource.UpgradeAvailable() { + if intelResource != nil { // && !intelResource.UpgradeAvailable() { return nil }