WIP
This commit is contained in:
@@ -1,12 +1,14 @@
|
||||
package main
|
||||
package cmdbase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"time"
|
||||
|
||||
@@ -15,14 +17,12 @@ import (
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/spn/conf"
|
||||
)
|
||||
|
||||
var printStackOnExit bool
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&printStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down")
|
||||
}
|
||||
var (
|
||||
RebootOnRestart bool
|
||||
PrintStackOnExit bool
|
||||
)
|
||||
|
||||
type SystemService interface {
|
||||
Run()
|
||||
@@ -30,21 +30,47 @@ type SystemService interface {
|
||||
RestartService() error
|
||||
}
|
||||
|
||||
func cmdRun(cmd *cobra.Command, args []string) {
|
||||
// Run platform specific setup or switches.
|
||||
runPlatformSpecifics(cmd, args)
|
||||
type ServiceInstance interface {
|
||||
Ready() bool
|
||||
Start() error
|
||||
Stop() error
|
||||
Restart()
|
||||
Shutdown()
|
||||
Ctx() context.Context
|
||||
IsShuttingDown() bool
|
||||
ShuttingDown() <-chan struct{}
|
||||
ShutdownCtx() context.Context
|
||||
IsShutDown() bool
|
||||
ShutdownComplete() <-chan struct{}
|
||||
ExitCode() int
|
||||
ShouldRestartIsSet() bool
|
||||
CommandLineOperationIsSet() bool
|
||||
CommandLineOperationExecute() error
|
||||
}
|
||||
|
||||
// SETUP
|
||||
var (
|
||||
SvcFactory func(*service.ServiceConfig) (ServiceInstance, error)
|
||||
SvcConfig *service.ServiceConfig
|
||||
)
|
||||
|
||||
// Enable SPN client mode.
|
||||
// TODO: Move this to service config.
|
||||
conf.EnableClient(true)
|
||||
conf.EnableIntegration(true)
|
||||
func RunService(cmd *cobra.Command, args []string) {
|
||||
if SvcFactory == nil || SvcConfig == nil {
|
||||
fmt.Fprintln(os.Stderr, "internal error: service not set up in cmdbase")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Start logging.
|
||||
// Note: Must be created before the service instance, so that they use the right logger.
|
||||
err := log.Start(SvcConfig.LogLevel, SvcConfig.LogToStdout, SvcConfig.LogDir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(4)
|
||||
}
|
||||
|
||||
// Create instance.
|
||||
// Instance modules might request a cmdline execution of a function.
|
||||
var execCmdLine bool
|
||||
instance, err := service.New(svcCfg)
|
||||
instance, err := SvcFactory(SvcConfig)
|
||||
switch {
|
||||
case err == nil:
|
||||
// Continue
|
||||
@@ -59,13 +85,13 @@ func cmdRun(cmd *cobra.Command, args []string) {
|
||||
switch {
|
||||
case !execCmdLine:
|
||||
// Run service.
|
||||
case instance.CommandLineOperation == nil:
|
||||
case !instance.CommandLineOperationIsSet():
|
||||
fmt.Println("command line operation execution requested, but not set")
|
||||
os.Exit(3)
|
||||
default:
|
||||
// Run the function and exit.
|
||||
fmt.Println("executing cmdline op")
|
||||
err = instance.CommandLineOperation()
|
||||
err = instance.CommandLineOperationExecute()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err)
|
||||
os.Exit(3)
|
||||
@@ -75,16 +101,6 @@ func cmdRun(cmd *cobra.Command, args []string) {
|
||||
|
||||
// START
|
||||
|
||||
// FIXME: fix color and duplicate level when logging with slog
|
||||
// FIXME: check for tty for color enabling
|
||||
|
||||
// Start logging.
|
||||
err = log.Start(svcCfg.LogLevel, svcCfg.LogToStdout, svcCfg.LogDir)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(4)
|
||||
}
|
||||
|
||||
// Create system service.
|
||||
service := NewSystemService(instance)
|
||||
|
||||
@@ -102,7 +118,7 @@ func cmdRun(cmd *cobra.Command, args []string) {
|
||||
select {
|
||||
case <-instance.ShutdownComplete():
|
||||
// Print stack on shutdown, if enabled.
|
||||
if printStackOnExit {
|
||||
if PrintStackOnExit {
|
||||
printStackTo(log.GlobalWriter, "PRINTING STACK ON EXIT")
|
||||
}
|
||||
case <-time.After(3 * time.Minute):
|
||||
@@ -110,9 +126,22 @@ func cmdRun(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
// Check if restart was triggered and send start service command if true.
|
||||
if instance.ShouldRestart && service.IsService() {
|
||||
if err := service.RestartService(); err != nil {
|
||||
slog.Error("failed to restart service", "err", err)
|
||||
if instance.ShouldRestartIsSet() && service.IsService() {
|
||||
// Check if we should reboot instead.
|
||||
var rebooting bool
|
||||
if RebootOnRestart {
|
||||
// Trigger system reboot and record success.
|
||||
rebooting = triggerSystemReboot()
|
||||
if !rebooting {
|
||||
log.Warningf("updates: rebooting failed, only restarting service instead")
|
||||
}
|
||||
}
|
||||
|
||||
// Restart service if not rebooting.
|
||||
if !rebooting {
|
||||
if err := service.RestartService(); err != nil {
|
||||
slog.Error("failed to restart service", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,3 +167,19 @@ func printStackTo(writer io.Writer, msg string) {
|
||||
slog.Error("failed to write stack trace", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
func triggerSystemReboot() (success bool) {
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
err := exec.Command("systemctl", "reboot").Run()
|
||||
if err != nil {
|
||||
log.Errorf("updates: triggering reboot with systemctl failed: %s", err)
|
||||
return false
|
||||
}
|
||||
default:
|
||||
log.Warningf("updates: rebooting is not support on %s", runtime.GOOS)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package cmdbase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -9,17 +9,15 @@ import (
|
||||
"syscall"
|
||||
|
||||
processInfo "github.com/shirou/gopsutil/process"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service"
|
||||
)
|
||||
|
||||
type LinuxSystemService struct {
|
||||
instance *service.Instance
|
||||
instance ServiceInstance
|
||||
}
|
||||
|
||||
func NewSystemService(instance *service.Instance) *LinuxSystemService {
|
||||
func NewSystemService(instance ServiceInstance) *LinuxSystemService {
|
||||
return &LinuxSystemService{instance: instance}
|
||||
}
|
||||
|
||||
@@ -30,7 +28,7 @@ func (s *LinuxSystemService) Run() {
|
||||
slog.Error("failed to start", "err", err)
|
||||
|
||||
// Print stack on start failure, if enabled.
|
||||
if printStackOnExit {
|
||||
if PrintStackOnExit {
|
||||
printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE")
|
||||
}
|
||||
|
||||
@@ -62,7 +60,7 @@ wait:
|
||||
continue wait
|
||||
} else {
|
||||
// Trigger shutdown.
|
||||
fmt.Printf(" <SIGNAL: %v>", sig) // CLI output.
|
||||
fmt.Printf(" <SIGNAL: %v>\n", sig) // CLI output.
|
||||
slog.Warn("received stop signal", "signal", sig)
|
||||
s.instance.Shutdown()
|
||||
break wait
|
||||
@@ -128,18 +126,3 @@ func (s *LinuxSystemService) IsService() bool {
|
||||
// Check if the parent process ID is 1 == init system
|
||||
return ppid == 1
|
||||
}
|
||||
|
||||
func runPlatformSpecifics(cmd *cobra.Command, args []string) {
|
||||
// If recover-iptables flag is set, run the recover-iptables command.
|
||||
// This is for backwards compatibility
|
||||
if recoverIPTables {
|
||||
exitCode := 0
|
||||
err := recover(cmd, args)
|
||||
if err != nil {
|
||||
fmt.Printf("failed: %s", err)
|
||||
exitCode = 1
|
||||
}
|
||||
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package cmdbase
|
||||
|
||||
// Based on the official Go examples from
|
||||
// https://github.com/golang/sys/blob/master/windows/svc/example
|
||||
@@ -13,21 +13,19 @@ import (
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sys/windows/svc"
|
||||
"golang.org/x/sys/windows/svc/debug"
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service"
|
||||
)
|
||||
|
||||
const serviceName = "PortmasterCore"
|
||||
|
||||
type WindowsSystemService struct {
|
||||
instance *service.Instance
|
||||
instance ServiceInstance
|
||||
}
|
||||
|
||||
func NewSystemService(instance *service.Instance) *WindowsSystemService {
|
||||
func NewSystemService(instance ServiceInstance) *WindowsSystemService {
|
||||
return &WindowsSystemService{instance: instance}
|
||||
}
|
||||
|
||||
@@ -67,7 +65,7 @@ func (s *WindowsSystemService) Execute(args []string, changeRequests <-chan svc.
|
||||
fmt.Printf("failed to start: %s\n", err)
|
||||
|
||||
// Print stack on start failure, if enabled.
|
||||
if printStackOnExit {
|
||||
if PrintStackOnExit {
|
||||
printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE")
|
||||
}
|
||||
|
||||
@@ -102,7 +100,7 @@ waitSignal:
|
||||
select {
|
||||
case sig := <-signalCh:
|
||||
// Trigger shutdown.
|
||||
fmt.Printf(" <SIGNAL: %v>", sig) // CLI output.
|
||||
fmt.Printf(" <SIGNAL: %v>\n", sig) // CLI output.
|
||||
slog.Warn("received stop signal", "signal", sig)
|
||||
break waitSignal
|
||||
|
||||
@@ -112,7 +110,7 @@ waitSignal:
|
||||
changes <- c.CurrentStatus
|
||||
|
||||
case svc.Stop, svc.Shutdown:
|
||||
fmt.Printf(" <SERVICE CMD: %v>", serviceCmdName(c.Cmd)) // CLI output.
|
||||
fmt.Printf(" <SERVICE CMD: %v>\n", serviceCmdName(c.Cmd)) // CLI output.
|
||||
slog.Warn("received service shutdown command", "cmd", c.Cmd)
|
||||
break waitSignal
|
||||
|
||||
@@ -201,8 +199,6 @@ sc.exe start $serviceName`
|
||||
return nil
|
||||
}
|
||||
|
||||
func runPlatformSpecifics(cmd *cobra.Command, args []string)
|
||||
|
||||
func serviceCmdName(cmd svc.Cmd) string {
|
||||
switch cmd {
|
||||
case svc.Stop:
|
||||
@@ -1,4 +1,4 @@
|
||||
package main
|
||||
package cmdbase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -12,32 +12,28 @@ import (
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
var UpdateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Force an update of all components.",
|
||||
RunE: update,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
||||
func update(cmd *cobra.Command, args []string) error {
|
||||
// Finalize config.
|
||||
err := svcCfg.Init()
|
||||
err := SvcConfig.Init()
|
||||
if err != nil {
|
||||
return fmt.Errorf("internal configuration error: %w", err)
|
||||
}
|
||||
// Force logging to stdout.
|
||||
svcCfg.LogToStdout = true
|
||||
SvcConfig.LogToStdout = true
|
||||
|
||||
// Start logging.
|
||||
_ = log.Start(svcCfg.LogLevel, svcCfg.LogToStdout, svcCfg.LogDir)
|
||||
_ = log.Start(SvcConfig.LogLevel, SvcConfig.LogToStdout, SvcConfig.LogDir)
|
||||
defer log.Shutdown()
|
||||
|
||||
// Create updaters.
|
||||
instance := &updateDummyInstance{}
|
||||
binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(svcCfg)
|
||||
binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(SvcConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("init updater config: %w", err)
|
||||
}
|
||||
20
cmds/cmdbase/version.go
Normal file
20
cmds/cmdbase/version.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package cmdbase
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/portmaster/base/info"
|
||||
)
|
||||
|
||||
var VersionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Show version and related metadata.",
|
||||
RunE: Version,
|
||||
}
|
||||
|
||||
func Version(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println(info.FullVersion())
|
||||
return nil
|
||||
}
|
||||
184
cmds/hub/main.go
184
cmds/hub/main.go
@@ -1,158 +1,94 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/safing/portmaster/base/info"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/metrics"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/cmds/cmdbase"
|
||||
"github.com/safing/portmaster/service"
|
||||
"github.com/safing/portmaster/service/configure"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
"github.com/safing/portmaster/spn"
|
||||
"github.com/safing/portmaster/spn/conf"
|
||||
)
|
||||
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "spn-hub",
|
||||
PersistentPreRun: initializeGlobals,
|
||||
Run: cmdbase.RunService,
|
||||
}
|
||||
|
||||
binDir string
|
||||
dataDir string
|
||||
|
||||
logToStdout bool
|
||||
logDir string
|
||||
logLevel string
|
||||
)
|
||||
|
||||
func init() {
|
||||
// flag.BoolVar(&updates.RebootOnRestart, "reboot-on-restart", false, "reboot server on auto-upgrade")
|
||||
// FIXME
|
||||
// Add persisent flags for all commands.
|
||||
rootCmd.PersistentFlags().StringVar(&binDir, "bin-dir", "", "set directory for executable binaries (rw/ro)")
|
||||
rootCmd.PersistentFlags().StringVar(&dataDir, "data-dir", "", "set directory for variable data (rw)")
|
||||
|
||||
// Add flags for service only.
|
||||
rootCmd.Flags().BoolVar(&logToStdout, "log-stdout", false, "log to stdout instead of file")
|
||||
rootCmd.Flags().StringVar(&logDir, "log-dir", "", "set directory for logs")
|
||||
rootCmd.Flags().StringVar(&logLevel, "log", "", "set log level to [trace|debug|info|warning|error|critical]")
|
||||
rootCmd.Flags().BoolVar(&cmdbase.PrintStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down")
|
||||
rootCmd.Flags().BoolVar(&cmdbase.RebootOnRestart, "reboot-on-restart", false, "reboot server instead of service restart")
|
||||
|
||||
// Add other commands.
|
||||
rootCmd.AddCommand(cmdbase.VersionCmd)
|
||||
rootCmd.AddCommand(cmdbase.UpdateCmd)
|
||||
}
|
||||
|
||||
var sigUSR1 = syscall.Signal(0xa)
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
// Add Go's default flag set.
|
||||
// TODO: Move flags throughout Portmaster to here and add their values to the service config.
|
||||
rootCmd.Flags().AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
// Set name and license.
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||
// Set version info.
|
||||
info.Set("SPN Hub", "", "GPLv3")
|
||||
|
||||
// Configure metrics.
|
||||
_ = metrics.SetNamespace("hub")
|
||||
|
||||
// Configure user agent and updates.
|
||||
// Configure user agent.
|
||||
updates.UserAgent = fmt.Sprintf("SPN Hub (%s %s)", runtime.GOOS, runtime.GOARCH)
|
||||
// helper.IntelOnly()
|
||||
|
||||
// Set SPN public hub mode.
|
||||
conf.EnablePublicHub(true)
|
||||
|
||||
// Start logger with default log level.
|
||||
_ = log.Start(log.WarningLevel)
|
||||
|
||||
// FIXME: Use service?
|
||||
|
||||
// Create instance.
|
||||
var execCmdLine bool
|
||||
instance, err := spn.New()
|
||||
switch {
|
||||
case err == nil:
|
||||
// Continue
|
||||
case errors.Is(err, mgr.ErrExecuteCmdLineOp):
|
||||
execCmdLine = true
|
||||
default:
|
||||
fmt.Printf("error creating an instance: %s\n", err)
|
||||
os.Exit(2)
|
||||
// Configure service.
|
||||
cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) {
|
||||
svc, err := service.New(svcCfg)
|
||||
return svc, err
|
||||
}
|
||||
cmdbase.SvcConfig = &service.ServiceConfig{
|
||||
BinDir: binDir,
|
||||
DataDir: dataDir,
|
||||
|
||||
// Execute command line operation, if requested or available.
|
||||
switch {
|
||||
case !execCmdLine:
|
||||
// Run service.
|
||||
case instance.CommandLineOperation == nil:
|
||||
fmt.Println("command line operation execution requested, but not set")
|
||||
os.Exit(3)
|
||||
default:
|
||||
// Run the function and exit.
|
||||
err = instance.CommandLineOperation()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
LogToStdout: logToStdout,
|
||||
LogDir: logDir,
|
||||
LogLevel: logLevel,
|
||||
|
||||
// Start
|
||||
go func() {
|
||||
err = instance.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("instance start failed: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for signal.
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
signal.Notify(
|
||||
signalCh,
|
||||
os.Interrupt,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT,
|
||||
sigUSR1,
|
||||
)
|
||||
|
||||
select {
|
||||
case sig := <-signalCh:
|
||||
// Only print and continue to wait if SIGUSR1
|
||||
if sig == sigUSR1 {
|
||||
printStackTo(os.Stderr, "PRINTING STACK ON REQUEST")
|
||||
} else {
|
||||
fmt.Println(" <INTERRUPT>") // CLI output.
|
||||
slog.Warn("program was interrupted, stopping")
|
||||
}
|
||||
|
||||
case <-instance.ShutdownComplete():
|
||||
log.Shutdown()
|
||||
os.Exit(instance.ExitCode())
|
||||
}
|
||||
|
||||
// Catch signals during shutdown.
|
||||
// Rapid unplanned disassembly after 5 interrupts.
|
||||
go func() {
|
||||
forceCnt := 5
|
||||
for {
|
||||
<-signalCh
|
||||
forceCnt--
|
||||
if forceCnt > 0 {
|
||||
fmt.Printf(" <INTERRUPT> again, but already shutting down - %d more to force\n", forceCnt)
|
||||
} else {
|
||||
printStackTo(os.Stderr, "PRINTING STACK ON FORCED EXIT")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Rapid unplanned disassembly after 3 minutes.
|
||||
go func() {
|
||||
time.Sleep(3 * time.Minute)
|
||||
printStackTo(os.Stderr, "PRINTING STACK - TAKING TOO LONG FOR SHUTDOWN")
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
// Stop instance.
|
||||
if err := instance.Stop(); err != nil {
|
||||
slog.Error("failed to stop", "err", err)
|
||||
}
|
||||
log.Shutdown()
|
||||
os.Exit(instance.ExitCode())
|
||||
}
|
||||
|
||||
func printStackTo(writer io.Writer, msg string) {
|
||||
_, err := fmt.Fprintf(writer, "===== %s =====\n", msg)
|
||||
if err == nil {
|
||||
err = pprof.Lookup("goroutine").WriteTo(writer, 1)
|
||||
}
|
||||
if err != nil {
|
||||
slog.Error("failed to write stack trace", "err", err)
|
||||
BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs,
|
||||
IntelIndexURLs: configure.DefaultIntelIndexURLs,
|
||||
VerifyBinaryUpdates: configure.BinarySigningTrustStore,
|
||||
VerifyIntelUpdates: configure.BinarySigningTrustStore,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,41 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/info"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/metrics"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/cmds/cmdbase"
|
||||
"github.com/safing/portmaster/service"
|
||||
"github.com/safing/portmaster/service/configure"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
"github.com/safing/portmaster/spn"
|
||||
"github.com/safing/portmaster/spn/captain"
|
||||
"github.com/safing/portmaster/spn/conf"
|
||||
"github.com/safing/portmaster/spn/sluice"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var sigUSR1 = syscall.Signal(0xa)
|
||||
var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "observation-hub",
|
||||
PersistentPreRun: initializeGlobals,
|
||||
Run: cmdbase.RunService,
|
||||
}
|
||||
|
||||
binDir string
|
||||
dataDir string
|
||||
|
||||
logToStdout bool
|
||||
logDir string
|
||||
logLevel string
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Add persisent flags for all commands.
|
||||
rootCmd.PersistentFlags().StringVar(&binDir, "bin-dir", "", "set directory for executable binaries (rw/ro)")
|
||||
rootCmd.PersistentFlags().StringVar(&dataDir, "data-dir", "", "set directory for variable data (rw)")
|
||||
|
||||
// Add flags for service only.
|
||||
rootCmd.Flags().BoolVar(&logToStdout, "log-stdout", false, "log to stdout instead of file")
|
||||
rootCmd.Flags().StringVar(&logDir, "log-dir", "", "set directory for logs")
|
||||
rootCmd.Flags().StringVar(&logLevel, "log", "", "set log level to [trace|debug|info|warning|error|critical]")
|
||||
rootCmd.Flags().BoolVar(&cmdbase.PrintStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down")
|
||||
rootCmd.Flags().BoolVar(&cmdbase.RebootOnRestart, "reboot-on-restart", false, "reboot server instead of service restart")
|
||||
|
||||
// Add other commands.
|
||||
rootCmd.AddCommand(cmdbase.VersionCmd)
|
||||
rootCmd.AddCommand(cmdbase.UpdateCmd)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
// Add Go's default flag set.
|
||||
// TODO: Move flags throughout Portmaster to here and add their values to the service config.
|
||||
rootCmd.Flags().AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||
// Set version info.
|
||||
info.Set("SPN Observation Hub", "", "GPLv3")
|
||||
|
||||
// Configure metrics.
|
||||
_ = metrics.SetNamespace("observer")
|
||||
|
||||
// Configure user agent and updates.
|
||||
// Configure user agent.
|
||||
updates.UserAgent = fmt.Sprintf("SPN Observation Hub (%s %s)", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
// Configure SPN mode.
|
||||
@@ -46,129 +80,37 @@ func main() {
|
||||
sluice.EnableListener = false
|
||||
api.EnableServer = false
|
||||
|
||||
// Start logger with default log level.
|
||||
_ = log.Start(log.WarningLevel)
|
||||
// Configure service.
|
||||
cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) {
|
||||
svc, err := service.New(svcCfg)
|
||||
|
||||
// Create instance.
|
||||
var execCmdLine bool
|
||||
instance, err := spn.New()
|
||||
switch {
|
||||
case err == nil:
|
||||
// Continue
|
||||
case errors.Is(err, mgr.ErrExecuteCmdLineOp):
|
||||
execCmdLine = true
|
||||
default:
|
||||
fmt.Printf("error creating an instance: %s\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Add additional modules.
|
||||
observer, err := New(instance)
|
||||
if err != nil {
|
||||
fmt.Printf("error creating an instance: create observer module: %s\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
instance.AddModule(observer)
|
||||
|
||||
_, err = NewApprise(instance)
|
||||
if err != nil {
|
||||
fmt.Printf("error creating an instance: create apprise module: %s\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
instance.AddModule(observer)
|
||||
|
||||
// FIXME: Use service?
|
||||
|
||||
// Execute command line operation, if requested or available.
|
||||
switch {
|
||||
case !execCmdLine:
|
||||
// Run service.
|
||||
case instance.CommandLineOperation == nil:
|
||||
fmt.Println("command line operation execution requested, but not set")
|
||||
os.Exit(3)
|
||||
default:
|
||||
// Run the function and exit.
|
||||
err = instance.CommandLineOperation()
|
||||
// Add additional modules.
|
||||
observer, err := New(svc)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err)
|
||||
os.Exit(3)
|
||||
fmt.Printf("error creating an instance: create observer module: %s\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Start
|
||||
go func() {
|
||||
err = instance.Start()
|
||||
svc.AddModule(observer)
|
||||
_, err = NewApprise(svc)
|
||||
if err != nil {
|
||||
fmt.Printf("instance start failed: %s\n", err)
|
||||
os.Exit(1)
|
||||
fmt.Printf("error creating an instance: create apprise module: %s\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
}()
|
||||
svc.AddModule(observer)
|
||||
|
||||
// Wait for signal.
|
||||
signalCh := make(chan os.Signal, 1)
|
||||
signal.Notify(
|
||||
signalCh,
|
||||
os.Interrupt,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT,
|
||||
sigUSR1,
|
||||
)
|
||||
|
||||
select {
|
||||
case sig := <-signalCh:
|
||||
// Only print and continue to wait if SIGUSR1
|
||||
if sig == sigUSR1 {
|
||||
printStackTo(os.Stderr, "PRINTING STACK ON REQUEST")
|
||||
} else {
|
||||
fmt.Println(" <INTERRUPT>") // CLI output.
|
||||
slog.Warn("program was interrupted, stopping")
|
||||
}
|
||||
|
||||
case <-instance.ShuttingDown():
|
||||
log.Shutdown()
|
||||
os.Exit(instance.ExitCode())
|
||||
return svc, err
|
||||
}
|
||||
cmdbase.SvcConfig = &service.ServiceConfig{
|
||||
BinDir: binDir,
|
||||
DataDir: dataDir,
|
||||
|
||||
// Catch signals during shutdown.
|
||||
// Rapid unplanned disassembly after 5 interrupts.
|
||||
go func() {
|
||||
forceCnt := 5
|
||||
for {
|
||||
<-signalCh
|
||||
forceCnt--
|
||||
if forceCnt > 0 {
|
||||
fmt.Printf(" <INTERRUPT> again, but already shutting down - %d more to force\n", forceCnt)
|
||||
} else {
|
||||
printStackTo(os.Stderr, "PRINTING STACK ON FORCED EXIT")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
LogToStdout: logToStdout,
|
||||
LogDir: logDir,
|
||||
LogLevel: logLevel,
|
||||
|
||||
// Rapid unplanned disassembly after 3 minutes.
|
||||
go func() {
|
||||
time.Sleep(3 * time.Minute)
|
||||
printStackTo(os.Stderr, "PRINTING STACK - TAKING TOO LONG FOR SHUTDOWN")
|
||||
os.Exit(1)
|
||||
}()
|
||||
|
||||
// Stop instance.
|
||||
if err := instance.Stop(); err != nil {
|
||||
slog.Error("failed to stop", "err", err)
|
||||
}
|
||||
log.Shutdown()
|
||||
os.Exit(instance.ExitCode())
|
||||
}
|
||||
|
||||
func printStackTo(writer io.Writer, msg string) {
|
||||
_, err := fmt.Fprintf(writer, "===== %s =====\n", msg)
|
||||
if err == nil {
|
||||
err = pprof.Lookup("goroutine").WriteTo(writer, 1)
|
||||
}
|
||||
if err != nil {
|
||||
slog.Error("failed to write stack trace", "err", err)
|
||||
BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs,
|
||||
IntelIndexURLs: configure.DefaultIntelIndexURLs,
|
||||
VerifyBinaryUpdates: configure.BinarySigningTrustStore,
|
||||
VerifyIntelUpdates: configure.BinarySigningTrustStore,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ import (
|
||||
|
||||
"github.com/safing/portmaster/base/info"
|
||||
"github.com/safing/portmaster/base/metrics"
|
||||
"github.com/safing/portmaster/cmds/cmdbase"
|
||||
"github.com/safing/portmaster/service"
|
||||
"github.com/safing/portmaster/service/configure"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
@@ -18,7 +20,7 @@ var (
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "portmaster-core",
|
||||
PersistentPreRun: initializeGlobals,
|
||||
Run: cmdRun,
|
||||
Run: mainRun,
|
||||
}
|
||||
|
||||
binDir string
|
||||
@@ -28,14 +30,10 @@ var (
|
||||
logDir string
|
||||
logLevel string
|
||||
|
||||
svcCfg *service.ServiceConfig
|
||||
printVersion bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Add Go's default flag set.
|
||||
// TODO: Move flags throughout Portmaster to here and add their values to the service config.
|
||||
rootCmd.Flags().AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
// Add persisent flags for all commands.
|
||||
rootCmd.PersistentFlags().StringVar(&binDir, "bin-dir", "", "set directory for executable binaries (rw/ro)")
|
||||
rootCmd.PersistentFlags().StringVar(&dataDir, "data-dir", "", "set directory for variable data (rw)")
|
||||
@@ -44,17 +42,32 @@ func init() {
|
||||
rootCmd.Flags().BoolVar(&logToStdout, "log-stdout", false, "log to stdout instead of file")
|
||||
rootCmd.Flags().StringVar(&logDir, "log-dir", "", "set directory for logs")
|
||||
rootCmd.Flags().StringVar(&logLevel, "log", "", "set log level to [trace|debug|info|warning|error|critical]")
|
||||
rootCmd.Flags().BoolVar(&printVersion, "version", false, "print version (backward compatibility; use command instead)")
|
||||
rootCmd.Flags().BoolVar(&cmdbase.PrintStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down")
|
||||
|
||||
// Add other commands.
|
||||
rootCmd.AddCommand(cmdbase.VersionCmd)
|
||||
rootCmd.AddCommand(cmdbase.UpdateCmd)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Add Go's default flag set.
|
||||
// TODO: Move flags throughout Portmaster to here and add their values to the service config.
|
||||
rootCmd.Flags().AddGoFlagSet(flag.CommandLine)
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func mainRun(cmd *cobra.Command, args []string) {
|
||||
runPlatformSpecifics(cmd, args)
|
||||
cmdbase.RunService(cmd, args)
|
||||
}
|
||||
|
||||
func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||
// set information
|
||||
// Set version info.
|
||||
info.Set("Portmaster", "", "GPLv3")
|
||||
|
||||
// Configure metrics.
|
||||
@@ -63,8 +76,12 @@ func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||
// Configure user agent.
|
||||
updates.UserAgent = fmt.Sprintf("Portmaster Core (%s %s)", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
// Create service config.
|
||||
svcCfg = &service.ServiceConfig{
|
||||
// Configure service.
|
||||
cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) {
|
||||
svc, err := service.New(svcCfg)
|
||||
return svc, err
|
||||
}
|
||||
cmdbase.SvcConfig = &service.ServiceConfig{
|
||||
BinDir: binDir,
|
||||
DataDir: dataDir,
|
||||
|
||||
@@ -72,9 +89,18 @@ func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||
LogDir: logDir,
|
||||
LogLevel: logLevel,
|
||||
|
||||
BinariesIndexURLs: service.DefaultStableBinaryIndexURLs,
|
||||
IntelIndexURLs: service.DefaultIntelIndexURLs,
|
||||
VerifyBinaryUpdates: service.BinarySigningTrustStore,
|
||||
VerifyIntelUpdates: service.BinarySigningTrustStore,
|
||||
BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs,
|
||||
IntelIndexURLs: configure.DefaultIntelIndexURLs,
|
||||
VerifyBinaryUpdates: configure.BinarySigningTrustStore,
|
||||
VerifyIntelUpdates: configure.BinarySigningTrustStore,
|
||||
}
|
||||
}
|
||||
|
||||
func runFlagCmd(fn func(cmd *cobra.Command, args []string) error, cmd *cobra.Command, args []string) {
|
||||
if err := fn(cmd, args); err != nil {
|
||||
fmt.Printf("failed: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
21
cmds/portmaster-core/main_linux.go
Normal file
21
cmds/portmaster-core/main_linux.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/safing/portmaster/cmds/cmdbase"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var recoverIPTablesFlag bool
|
||||
|
||||
func init() {
|
||||
rootCmd.Flags().BoolVar(&recoverIPTablesFlag, "recover-iptables", false, "recovers ip table rules (backward compatibility; use command instead)")
|
||||
}
|
||||
|
||||
func runPlatformSpecifics(cmd *cobra.Command, args []string) {
|
||||
switch {
|
||||
case printVersion:
|
||||
runFlagCmd(cmdbase.Version, cmd, args)
|
||||
case recoverIPTablesFlag:
|
||||
runFlagCmd(recoverIPTables, cmd, args)
|
||||
}
|
||||
}
|
||||
13
cmds/portmaster-core/main_windows.go
Normal file
13
cmds/portmaster-core/main_windows.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/safing/portmaster/cmds/cmdbase"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func runPlatformSpecifics(cmd *cobra.Command, args []string) {
|
||||
switch {
|
||||
case printVersion:
|
||||
runFlagCmd(cmdbase.Version, cmd, args)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -13,23 +12,17 @@ import (
|
||||
"github.com/safing/portmaster/service/firewall/interception"
|
||||
)
|
||||
|
||||
var (
|
||||
recoverCmd = &cobra.Command{
|
||||
Use: "recover-iptables",
|
||||
Short: "Force an update of all components.",
|
||||
RunE: update,
|
||||
}
|
||||
|
||||
recoverIPTables bool
|
||||
)
|
||||
var recoverCmd = &cobra.Command{
|
||||
Use: "recover-iptables",
|
||||
Short: "Clean up Portmaster rules in iptables",
|
||||
RunE: recoverIPTables,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(recoverCmd)
|
||||
|
||||
flag.BoolVar(&recoverIPTables, "recover-iptables", false, "recovers ip table rules (backward compatibility; use command instead)")
|
||||
}
|
||||
|
||||
func recover(cmd *cobra.Command, args []string) error {
|
||||
func recoverIPTables(cmd *cobra.Command, args []string) error {
|
||||
// interception.DeactiveNfqueueFirewall uses coreos/go-iptables
|
||||
// which shells out to the /sbin/iptables binary. As a result,
|
||||
// we don't get the errno of the actual error and need to parse the
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func setupDatabases(path string) error {
|
||||
err := database.InitializeWithPath(path)
|
||||
err := database.Initialize(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -37,13 +37,12 @@ func main() {
|
||||
}
|
||||
|
||||
// Start logging.
|
||||
err := log.Start()
|
||||
err := log.Start("trace", true, "")
|
||||
if err != nil {
|
||||
fmt.Printf("failed to start logging: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer log.Shutdown()
|
||||
log.SetLogLevel(log.TraceLevel)
|
||||
log.Info("starting traffic generator")
|
||||
|
||||
// Execute requests
|
||||
|
||||
Reference in New Issue
Block a user