[WIP] Add restart service command for windows

This commit is contained in:
Vladimir Stoilov 2024-09-27 17:07:01 +03:00
parent 61babe2822
commit f0272766c1
No known key found for this signature in database
GPG key ID: 2F190B67A43A81AF
5 changed files with 72 additions and 86 deletions

View file

@ -55,10 +55,7 @@ func initialize() *service.Instance {
// Create instance.
var execCmdLine bool
instance, err := service.New(&service.ServiceConfig{
IsRunningAsService: isRunningAsService(),
DefaultRestartCommand: defaultRestartCommand,
})
instance, err := service.New(&service.ServiceConfig{})
switch {
case err == nil:
// Continue

View file

@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"
@ -15,8 +16,6 @@ import (
"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)
@ -101,9 +100,34 @@ func run(instance *service.Instance) {
printStackTo(os.Stdout, "PRINTING STACK ON EXIT")
}
// Check if restart was trigger and send start service command if true.
if isRunningAsService() && instance.ShouldRestart {
_ = runServiceRestart()
}
os.Exit(instance.ExitCode())
}
func runServiceRestart() error {
// Check if user defined custom command for restarting the service.
restartCommand, exists := os.LookupEnv("PORTMASTER_RESTART_COMMAND")
// Run the service restart
if exists && restartCommand != "" {
log.Debugf(`instance: running custom restart command: "%s"`, restartCommand)
commandSplit := strings.Split(restartCommand, " ")
cmd := exec.Command(commandSplit[0], commandSplit[1:]...)
_ = cmd.Run()
} else {
cmd := exec.Command("systemctl", "restart", "portmaster")
if err := cmd.Start(); err != nil {
return fmt.Errorf("failed run restart command: %w", err)
}
}
return nil
}
func isRunningAsService() bool {
// Get the current process ID
pid := os.Getpid()

View file

@ -11,7 +11,6 @@ import (
"os"
"os/exec"
"os/signal"
"sync"
"syscall"
"time"
@ -21,14 +20,6 @@ import (
"golang.org/x/sys/windows/svc/debug"
)
var (
// wait groups
runWg sync.WaitGroup
finishWg sync.WaitGroup
defaultRestartCommand = exec.Command("sc.exe", "restart", "PortmasterCore")
)
const serviceName = "PortmasterCore"
type windowsService struct {
@ -45,13 +36,15 @@ service:
for {
select {
case <-ws.instance.Stopped():
changes <- svc.Status{State: svc.StopPending}
log.Infof("instance stopped")
break service
case c := <-changeRequests:
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
log.Debugf("received shutdown command")
changes <- svc.Status{State: svc.StopPending}
ws.instance.Shutdown()
default:
log.Errorf("unexpected control request: #%d", c)
@ -59,9 +52,6 @@ service:
}
}
// wait until everything else is finished
// finishWg.Wait()
log.Shutdown()
// send stopped status
@ -75,11 +65,13 @@ service:
func run(instance *service.Instance) error {
log.SetLogLevel(log.WarningLevel)
_ = log.Start()
// check if we are running interactively
isService, err := svc.IsWindowsService()
if err != nil {
return fmt.Errorf("could not determine if running interactively: %s", err)
}
// select service run type
svcRun := svc.Run
if !isService {
@ -88,41 +80,20 @@ func run(instance *service.Instance) error {
go registerSignalHandler(instance)
}
runWg.Add(1)
// run service client
go func() {
sErr := svcRun(serviceName, &windowsService{
instance: instance,
})
if sErr != nil {
log.Infof("shuting down service with error: %s", sErr)
} else {
log.Infof("shuting down service")
}
runWg.Done()
}()
sErr := svcRun(serviceName, &windowsService{
instance: instance,
})
if sErr != nil {
fmt.Printf("shuting down service with error: %s", sErr)
} else {
fmt.Printf("shuting down service")
}
// finishWg.Add(1)
// run service
// go func() {
// // run slightly delayed
// time.Sleep(250 * time.Millisecond)
// if err != nil {
// fmt.Printf("instance start failed: %s\n", err)
// // Print stack on start failure, if enabled.
// if printStackOnExit {
// printStackTo(os.Stdout, "PRINTING STACK ON START FAILURE")
// }
// }
// runWg.Done()
// finishWg.Done()
// }()
runWg.Wait()
// Check if restart was trigger and send start service command if true.
if isRunningAsService() && instance.ShouldRestart {
_ = runServiceRestart()
}
return err
}
@ -179,3 +150,24 @@ func isRunningAsService() bool {
}
return isService
}
func runServiceRestart() error {
// Script that wait for portmaster service status to change to stop
// and then sends a start command for the same service.
command := `
$serviceName = "PortmasterCore"
while ((Get-Service -Name $serviceName).Status -ne 'Stopped') {
Start-Sleep -Seconds 1
}
sc.exe start $serviceName`
// Create the command to execute the PowerShell script
cmd := exec.Command("powershell.exe", "-Command", command)
// Start the command. The script will continue even after the parent process exits.
err := cmd.Start()
if err != nil {
return err
}
return nil
}

View file

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

View file

@ -4,17 +4,14 @@ 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"
@ -104,9 +101,8 @@ type Instance struct {
sluice *sluice.SluiceModule
terminal *terminal.TerminalModule
CommandLineOperation func() error
isRunningAsService bool
defaultRestartCommand *exec.Cmd
CommandLineOperation func() error
ShouldRestart bool
}
func getCurrentBinaryFolder() (string, error) {
@ -183,8 +179,6 @@ 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
@ -275,6 +269,7 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
if err != nil {
return instance, fmt.Errorf("create customlist module: %w", err)
}
instance.status, err = status.New(instance)
if err != nil {
return instance, fmt.Errorf("create status module: %w", err)
@ -678,7 +673,9 @@ func (i *Instance) Restart() {
i.core.EventRestart.Submit(struct{}{})
time.Sleep(10 * time.Millisecond)
i.serviceRestart()
// Set the restart flag and shutdown.
i.ShouldRestart = true
i.shutdown(RestartExitCode)
}
// Shutdown asynchronously stops the instance.
@ -707,25 +704,6 @@ 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