[WIP] Add restart command to instance

This commit is contained in:
Vladimir Stoilov 2024-09-26 13:51:42 +03:00
parent c9631daa3e
commit 61babe2822
No known key found for this signature in database
GPG key ID: 2F190B67A43A81AF
15 changed files with 81 additions and 98 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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
}

View file

@ -1,3 +1,8 @@
package service
type ServiceConfig struct{}
import "os/exec"
type ServiceConfig struct {
IsRunningAsService bool
DefaultRestartCommand *exec.Cmd
}

View file

@ -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
}

View file

@ -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

View file

@ -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"
@ -102,6 +105,8 @@ type Instance struct {
terminal *terminal.TerminalModule
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

View file

@ -16,6 +16,7 @@ import (
"strings"
semver "github.com/hashicorp/go-version"
"github.com/safing/portmaster/base/log"
)

View file

@ -1 +0,0 @@
package updates

View file

@ -1 +0,0 @@
package updates

View file

@ -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

View file

@ -8,6 +8,7 @@ import (
"path/filepath"
semver "github.com/hashicorp/go-version"
"github.com/safing/portmaster/base/log"
)

View file

@ -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()
}

View file

@ -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()
}

View file

@ -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
}