mirror of
https://github.com/safing/portmaster
synced 2025-09-01 01:59:11 +00:00
WIP
This commit is contained in:
parent
f91003d077
commit
706ce222d0
35 changed files with 1138 additions and 601 deletions
|
@ -10,8 +10,6 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// FIXME: version does not show in portmaster
|
||||
|
||||
var (
|
||||
name string
|
||||
license string
|
||||
|
@ -167,9 +165,9 @@ func CondensedVersion() string {
|
|||
}
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%s %s (%s; built with %s [%s %s] from %s [%s] at %s)",
|
||||
"%s %s (%s/%s; built with %s [%s %s] from %s [%s] at %s)",
|
||||
info.Name, version,
|
||||
runtime.GOOS,
|
||||
runtime.GOOS, runtime.GOARCH,
|
||||
runtime.Version(), runtime.Compiler, cgoInfo,
|
||||
info.Commit, dirtyInfo, info.CommitTime,
|
||||
)
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
package log
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/lmittmann/tint"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
)
|
||||
|
||||
func setupSLog(level Severity) {
|
||||
// TODO: Changes in the log level are not yet reflected onto the slog handlers in the modules.
|
||||
|
||||
// Set highest possible level, so it can be changed in runtime.
|
||||
handlerLogLevel := level.toSLogLevel()
|
||||
|
||||
|
@ -17,21 +22,23 @@ func setupSLog(level Severity) {
|
|||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
logHandler = tint.NewHandler(
|
||||
GlobalWriter,
|
||||
windowsColoring(GlobalWriter), // Enable coloring on Windows.
|
||||
&tint.Options{
|
||||
AddSource: true,
|
||||
Level: handlerLogLevel,
|
||||
TimeFormat: timeFormat,
|
||||
NoColor: !GlobalWriter.IsStdout(), // FIXME: also check for tty.
|
||||
NoColor: !( /* Color: */ GlobalWriter.IsStdout() && isatty.IsTerminal(GlobalWriter.file.Fd())),
|
||||
},
|
||||
)
|
||||
|
||||
case "linux":
|
||||
logHandler = tint.NewHandler(GlobalWriter, &tint.Options{
|
||||
AddSource: true,
|
||||
Level: handlerLogLevel,
|
||||
TimeFormat: timeFormat,
|
||||
NoColor: !GlobalWriter.IsStdout(), // FIXME: also check for tty.
|
||||
NoColor: !( /* Color: */ GlobalWriter.IsStdout() && isatty.IsTerminal(GlobalWriter.file.Fd())),
|
||||
})
|
||||
|
||||
default:
|
||||
logHandler = tint.NewHandler(os.Stdout, &tint.Options{
|
||||
AddSource: true,
|
||||
|
@ -43,6 +50,11 @@ func setupSLog(level Severity) {
|
|||
|
||||
// Set as default logger.
|
||||
slog.SetDefault(slog.New(logHandler))
|
||||
// Set actual log level.
|
||||
slog.SetLogLoggerLevel(handlerLogLevel)
|
||||
}
|
||||
|
||||
func windowsColoring(lw *LogWriter) io.Writer {
|
||||
if lw.IsStdout() {
|
||||
return colorable.NewColorable(lw.file)
|
||||
}
|
||||
return lw
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -238,13 +238,13 @@ export class EditProfileDialog implements OnInit, OnDestroy {
|
|||
this.portapi.delete(icon.Value).subscribe();
|
||||
}
|
||||
|
||||
// FIXME(ppacher): we cannot yet delete API based icons ...
|
||||
// TODO(ppacher): we cannot yet delete API based icons ...
|
||||
});
|
||||
|
||||
if (this.iconData !== '') {
|
||||
// save the new icon in the cache database
|
||||
|
||||
// FIXME(ppacher): we currently need to calls because the icon API in portmaster
|
||||
// TODO(ppacher): we currently need to calls because the icon API in portmaster
|
||||
// does not update the profile but just saves the file and returns the filename.
|
||||
// So we still need to update the profile manually.
|
||||
updateIcon = this.profileService
|
||||
|
@ -261,7 +261,7 @@ export class EditProfileDialog implements OnInit, OnDestroy {
|
|||
})
|
||||
);
|
||||
|
||||
// FIXME(ppacher): reset presentationpath
|
||||
// TODO(ppacher): reset presentationpath
|
||||
} else {
|
||||
// just clear out that there was an icon
|
||||
this.profile.Icons = [];
|
||||
|
|
|
@ -543,7 +543,7 @@ export class SfngNetqueryLineChartComponent<D extends SeriesData = any> implemen
|
|||
.append("title")
|
||||
.text(d => d.text)
|
||||
|
||||
// FIXME(ppacher): somehow d3 does not recognize which data points must be removed
|
||||
// TODO(ppacher): somehow d3 does not recognize which data points must be removed
|
||||
// or re-placed. For now, just remove them all
|
||||
this.svgInner
|
||||
.select('.points')
|
||||
|
|
|
@ -184,7 +184,7 @@ export class SfngNetquerySearchbarComponent implements ControlValueAccessor, OnI
|
|||
const queries: Observable<SfngSearchbarSuggestion<any>>[] = [];
|
||||
const queryKeys: (keyof Partial<NetqueryConnection>)[] = [];
|
||||
|
||||
// FIXME(ppacher): confirm .type is an actually allowed field
|
||||
// TODO(ppacher): confirm .type is an actually allowed field
|
||||
if (!!parser.lastUnterminatedCondition) {
|
||||
fields = [parser.lastUnterminatedCondition.type as keyof NetqueryConnection];
|
||||
limit = 0;
|
||||
|
|
|
@ -21,7 +21,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
broadcastsResourcePath = "intel/portmaster/notifications.yaml"
|
||||
broadcastsResourceName = "notifications.yaml"
|
||||
|
||||
broadcastNotificationIDPrefix = "broadcasts:"
|
||||
|
||||
|
@ -67,7 +67,7 @@ type BroadcastNotification struct {
|
|||
|
||||
func broadcastNotify(ctx *mgr.WorkerCtx) error {
|
||||
// Get broadcast notifications file, load it from disk and parse it.
|
||||
broadcastsResource, err := module.instance.IntelUpdates().GetFile(broadcastsResourcePath)
|
||||
broadcastsResource, err := module.instance.IntelUpdates().GetFile(broadcastsResourceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get broadcast notifications update: %w", err)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ import (
|
|||
|
||||
"github.com/safing/jess"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/service/configure"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
type ServiceConfig struct {
|
||||
|
@ -76,11 +78,10 @@ func (sc *ServiceConfig) Init() error {
|
|||
|
||||
// Apply defaults for required fields.
|
||||
if len(sc.BinariesIndexURLs) == 0 {
|
||||
// FIXME: Select based on setting.
|
||||
sc.BinariesIndexURLs = DefaultStableBinaryIndexURLs
|
||||
sc.BinariesIndexURLs = configure.DefaultStableBinaryIndexURLs
|
||||
}
|
||||
if len(sc.IntelIndexURLs) == 0 {
|
||||
sc.IntelIndexURLs = DefaultIntelIndexURLs
|
||||
sc.IntelIndexURLs = configure.DefaultIntelIndexURLs
|
||||
}
|
||||
|
||||
// Check log level.
|
||||
|
@ -109,3 +110,71 @@ func getCurrentBinaryFolder() (string, error) {
|
|||
|
||||
return installDir, nil
|
||||
}
|
||||
|
||||
func MakeUpdateConfigs(svcCfg *ServiceConfig) (binaryUpdateConfig, intelUpdateConfig *updates.Config, err error) {
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
binaryUpdateConfig = &updates.Config{
|
||||
Name: "binaries",
|
||||
Directory: svcCfg.BinDir,
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.BinDir, "upgrade_obsolete_binaries"),
|
||||
Ignore: []string{"databases", "intel", "config.json"},
|
||||
IndexURLs: svcCfg.BinariesIndexURLs, // May be changed by config during instance startup.
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyBinaryUpdates,
|
||||
AutoCheck: true, // May be changed by config during instance startup.
|
||||
AutoDownload: false,
|
||||
AutoApply: false,
|
||||
NeedsRestart: true,
|
||||
Notify: true,
|
||||
}
|
||||
intelUpdateConfig = &updates.Config{
|
||||
Name: "intel",
|
||||
Directory: filepath.Join(svcCfg.DataDir, "intel"),
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
|
||||
IndexURLs: svcCfg.IntelIndexURLs,
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyIntelUpdates,
|
||||
AutoCheck: true, // May be changed by config during instance startup.
|
||||
AutoDownload: true,
|
||||
AutoApply: true,
|
||||
NeedsRestart: false,
|
||||
Notify: false,
|
||||
}
|
||||
|
||||
case "linux":
|
||||
binaryUpdateConfig = &updates.Config{
|
||||
Name: "binaries",
|
||||
Directory: svcCfg.BinDir,
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_binaries"),
|
||||
Ignore: []string{"databases", "intel", "config.json"},
|
||||
IndexURLs: svcCfg.BinariesIndexURLs, // May be changed by config during instance startup.
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyBinaryUpdates,
|
||||
AutoCheck: true, // May be changed by config during instance startup.
|
||||
AutoDownload: false,
|
||||
AutoApply: false,
|
||||
NeedsRestart: true,
|
||||
Notify: true,
|
||||
}
|
||||
intelUpdateConfig = &updates.Config{
|
||||
Name: "intel",
|
||||
Directory: filepath.Join(svcCfg.DataDir, "intel"),
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
|
||||
IndexURLs: svcCfg.IntelIndexURLs,
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyIntelUpdates,
|
||||
AutoCheck: true, // May be changed by config during instance startup.
|
||||
AutoDownload: true,
|
||||
AutoApply: true,
|
||||
NeedsRestart: false,
|
||||
Notify: false,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
65
service/configure/updates.go
Normal file
65
service/configure/updates.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package configure
|
||||
|
||||
import (
|
||||
"github.com/safing/jess"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultStableBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/stable.v3.json",
|
||||
}
|
||||
DefaultBetaBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/beta.v3.json",
|
||||
}
|
||||
DefaultStagingBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/staging.v3.json",
|
||||
}
|
||||
DefaultSupportBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/support.v3.json",
|
||||
}
|
||||
|
||||
DefaultIntelIndexURLs = []string{
|
||||
"https://updates.safing.io/intel.v3.json",
|
||||
}
|
||||
|
||||
// BinarySigningKeys holds the signing keys in text format.
|
||||
BinarySigningKeys = []string{
|
||||
// Safing Code Signing Key #1
|
||||
"recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD",
|
||||
// Safing Code Signing Key #2
|
||||
"recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3",
|
||||
}
|
||||
|
||||
// BinarySigningTrustStore is an in-memory trust store with the signing keys.
|
||||
BinarySigningTrustStore = jess.NewMemTrustStore()
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, signingKey := range BinarySigningKeys {
|
||||
rcpt, err := jess.RecipientFromTextFormat(signingKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = BinarySigningTrustStore.StoreSignet(rcpt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetBinaryUpdateURLs returns the correct binary update URLs for the given release channel.
|
||||
// Silently falls back to stable if release channel is invalid.
|
||||
func GetBinaryUpdateURLs(releaseChannel string) []string {
|
||||
switch releaseChannel {
|
||||
case "stable":
|
||||
return DefaultStableBinaryIndexURLs
|
||||
case "beta":
|
||||
return DefaultBetaBinaryIndexURLs
|
||||
case "staging":
|
||||
return DefaultStagingBinaryIndexURLs
|
||||
case "support":
|
||||
return DefaultSupportBinaryIndexURLs
|
||||
default:
|
||||
return DefaultStableBinaryIndexURLs
|
||||
}
|
||||
}
|
|
@ -1,19 +1,27 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/config"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/notifications"
|
||||
"github.com/safing/portmaster/base/rng"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/base/utils/debug"
|
||||
"github.com/safing/portmaster/service/compat"
|
||||
"github.com/safing/portmaster/service/process"
|
||||
|
@ -149,6 +157,17 @@ func registerAPIEndpoints() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := api.RegisterEndpoint(api.Endpoint{
|
||||
Name: "Get Resource",
|
||||
Description: "Returns the requested resource from the udpate system",
|
||||
Path: `updates/get/?{artifact_path:[A-Za-z0-9/\.\-_]{1,255}}/{artifact_name:[A-Za-z0-9\.\-_]{1,255}}`,
|
||||
Read: api.PermitUser,
|
||||
ReadMethod: http.MethodGet,
|
||||
HandlerFunc: getUpdateResource,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -170,6 +189,113 @@ func restart(_ *api.Request) (msg string, err error) {
|
|||
return "restart initiated", nil
|
||||
}
|
||||
|
||||
func getUpdateResource(w http.ResponseWriter, r *http.Request) {
|
||||
// Get identifier from URL.
|
||||
var identifier string
|
||||
if ar := api.GetAPIRequest(r); ar != nil {
|
||||
identifier = ar.URLVars["artifact_name"]
|
||||
}
|
||||
if identifier == "" {
|
||||
http.Error(w, "no resource specified", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Get resource.
|
||||
artifact, err := module.instance.BinaryUpdates().GetFile(identifier)
|
||||
if err != nil {
|
||||
intelArtifact, intelErr := module.instance.IntelUpdates().GetFile(identifier)
|
||||
if intelErr == nil {
|
||||
artifact = intelArtifact
|
||||
} else {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Open file for reading.
|
||||
file, err := os.Open(artifact.Path())
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer file.Close() //nolint:errcheck,gosec
|
||||
|
||||
// Assign file to reader
|
||||
var reader io.Reader = file
|
||||
|
||||
// Add version and hash to header.
|
||||
if artifact.Version != "" {
|
||||
w.Header().Set("Resource-Version", artifact.Version)
|
||||
}
|
||||
if artifact.SHA256 != "" {
|
||||
w.Header().Set("Resource-SHA256", artifact.SHA256)
|
||||
}
|
||||
|
||||
// Set Content-Type.
|
||||
contentType, _ := utils.MimeTypeByExtension(filepath.Ext(artifact.Path()))
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
|
||||
// Check if the content type may be returned.
|
||||
accept := r.Header.Get("Accept")
|
||||
if accept != "" {
|
||||
mimeTypes := strings.Split(accept, ",")
|
||||
// First, clean mime types.
|
||||
for i, mimeType := range mimeTypes {
|
||||
mimeType = strings.TrimSpace(mimeType)
|
||||
mimeType, _, _ = strings.Cut(mimeType, ";")
|
||||
mimeTypes[i] = mimeType
|
||||
}
|
||||
// Second, check if we may return anything.
|
||||
var acceptsAny bool
|
||||
for _, mimeType := range mimeTypes {
|
||||
switch mimeType {
|
||||
case "*", "*/*":
|
||||
acceptsAny = true
|
||||
}
|
||||
}
|
||||
// Third, check if we can convert.
|
||||
if !acceptsAny {
|
||||
var converted bool
|
||||
sourceType, _, _ := strings.Cut(contentType, ";")
|
||||
findConvertiblePair:
|
||||
for _, mimeType := range mimeTypes {
|
||||
switch {
|
||||
case sourceType == "application/yaml" && mimeType == "application/json":
|
||||
yamlData, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
jsonData, err := yaml.YAMLToJSON(yamlData)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
reader = bytes.NewReader(jsonData)
|
||||
converted = true
|
||||
break findConvertiblePair
|
||||
}
|
||||
}
|
||||
|
||||
// If we could not convert to acceptable format, return an error.
|
||||
if !converted {
|
||||
http.Error(w, "conversion to requested format not supported", http.StatusNotAcceptable)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write file.
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if r.Method != http.MethodHead {
|
||||
_, err = io.Copy(w, reader)
|
||||
if err != nil {
|
||||
log.Errorf("updates: failed to serve resource file: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// debugInfo returns the debugging information for support requests.
|
||||
func debugInfo(ar *api.Request) (data []byte, err error) {
|
||||
// Create debug information helper.
|
||||
|
@ -192,7 +318,7 @@ func debugInfo(ar *api.Request) (data []byte, err error) {
|
|||
config.AddToDebugInfo(di)
|
||||
|
||||
// Detailed information.
|
||||
// TODO(vladimir): updates.AddToDebugInfo(di)
|
||||
AddVersionsToDebugInfo(di)
|
||||
compat.AddToDebugInfo(di)
|
||||
module.instance.AddWorkerInfoToDebugInfo(di)
|
||||
di.AddGoroutineStack()
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/info"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
)
|
||||
|
||||
// Default Values (changeable for testing).
|
||||
var (
|
||||
DefaultAPIListenAddress = "127.0.0.1:817"
|
||||
|
||||
showVersion bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&showVersion, "version", false, "show version and exit")
|
||||
}
|
||||
|
||||
func prep(instance instance) error {
|
||||
// check if meta info is ok
|
||||
err := info.CheckVersion()
|
||||
if err != nil {
|
||||
return errors.New("compile error: please compile using the provided build script")
|
||||
}
|
||||
|
||||
// print version
|
||||
if showVersion {
|
||||
instance.SetCmdLineOperation(printVersion)
|
||||
return mgr.ErrExecuteCmdLineOp
|
||||
}
|
||||
|
||||
// set api listen address
|
||||
api.SetDefaultAPIListenAddress(DefaultAPIListenAddress)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func printVersion() error {
|
||||
fmt.Println(info.FullVersion())
|
||||
return nil
|
||||
}
|
|
@ -4,9 +4,13 @@ import (
|
|||
"errors"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
)
|
||||
|
||||
// DefaultAPIListenAddress is the default listen address for the API.
|
||||
var DefaultAPIListenAddress = "127.0.0.1:817"
|
||||
|
||||
// Base is the base module.
|
||||
type Base struct {
|
||||
mgr *mgr.Manager
|
||||
|
@ -47,9 +51,9 @@ func New(instance instance) (*Base, error) {
|
|||
instance: instance,
|
||||
}
|
||||
|
||||
if err := prep(instance); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Set api listen address.
|
||||
api.SetDefaultAPIListenAddress(DefaultAPIListenAddress)
|
||||
|
||||
if err := registerDatabases(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portmaster/base/config"
|
||||
"github.com/safing/portmaster/base/database"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/metrics"
|
||||
"github.com/safing/portmaster/base/utils/debug"
|
||||
|
@ -19,6 +21,11 @@ import (
|
|||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var db = database.NewInterface(&database.Options{
|
||||
Local: true,
|
||||
Internal: true,
|
||||
})
|
||||
|
||||
// Core is the core service module.
|
||||
type Core struct {
|
||||
m *mgr.Manager
|
||||
|
@ -56,8 +63,10 @@ func init() {
|
|||
|
||||
func prep() error {
|
||||
// init config
|
||||
err := registerConfig()
|
||||
if err != nil {
|
||||
if err := registerConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := registerUpdateConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -77,6 +86,10 @@ func start() error {
|
|||
return fmt.Errorf("failed to start plattform-specific components: %w", err)
|
||||
}
|
||||
|
||||
// Setup update system.
|
||||
initUpdateConfig()
|
||||
initVersionExport()
|
||||
|
||||
// Enable persistent metrics.
|
||||
if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil {
|
||||
log.Warningf("core: failed to enable persisted metrics: %s", err)
|
||||
|
@ -116,6 +129,7 @@ type instance interface {
|
|||
Shutdown()
|
||||
Restart()
|
||||
AddWorkerInfoToDebugInfo(di *debug.Info)
|
||||
Config() *config.Config
|
||||
BinaryUpdates() *updates.Updater
|
||||
IntelUpdates() *updates.Updater
|
||||
}
|
||||
|
|
134
service/core/update_config.go
Normal file
134
service/core/update_config.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/safing/portmaster/base/config"
|
||||
"github.com/safing/portmaster/service/configure"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
)
|
||||
|
||||
// Release Channel Configuration Keys.
|
||||
const (
|
||||
ReleaseChannelKey = "core/releaseChannel"
|
||||
ReleaseChannelJSONKey = "core.releaseChannel"
|
||||
)
|
||||
|
||||
// Release Channels.
|
||||
const (
|
||||
ReleaseChannelStable = "stable"
|
||||
ReleaseChannelBeta = "beta"
|
||||
ReleaseChannelStaging = "staging"
|
||||
ReleaseChannelSupport = "support"
|
||||
)
|
||||
|
||||
const (
|
||||
enableSoftwareUpdatesKey = "core/automaticUpdates"
|
||||
enableIntelUpdatesKey = "core/automaticIntelUpdates"
|
||||
)
|
||||
|
||||
var (
|
||||
releaseChannel config.StringOption
|
||||
enableSoftwareUpdates config.BoolOption
|
||||
enableIntelUpdates config.BoolOption
|
||||
|
||||
initialReleaseChannel string
|
||||
)
|
||||
|
||||
func registerUpdateConfig() error {
|
||||
err := config.Register(&config.Option{
|
||||
Name: "Release Channel",
|
||||
Key: ReleaseChannelKey,
|
||||
Description: `Use "Stable" for the best experience. The "Beta" channel will have the newest features and fixes, but may also break and cause interruption. Use others only temporarily and when instructed.`,
|
||||
OptType: config.OptTypeString,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
RequiresRestart: true,
|
||||
DefaultValue: ReleaseChannelStable,
|
||||
PossibleValues: []config.PossibleValue{
|
||||
{
|
||||
Name: "Stable",
|
||||
Description: "Production releases.",
|
||||
Value: ReleaseChannelStable,
|
||||
},
|
||||
{
|
||||
Name: "Beta",
|
||||
Description: "Production releases for testing new features that may break and cause interruption.",
|
||||
Value: ReleaseChannelBeta,
|
||||
},
|
||||
{
|
||||
Name: "Support",
|
||||
Description: "Support releases or version changes for troubleshooting. Only use temporarily and when instructed.",
|
||||
Value: ReleaseChannelSupport,
|
||||
},
|
||||
{
|
||||
Name: "Staging",
|
||||
Description: "Dangerous development releases for testing random things and experimenting. Only use temporarily and when instructed.",
|
||||
Value: ReleaseChannelStaging,
|
||||
},
|
||||
},
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayOrderAnnotation: -4,
|
||||
config.DisplayHintAnnotation: config.DisplayHintOneOf,
|
||||
config.CategoryAnnotation: "Updates",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Automatic Software Updates",
|
||||
Key: enableSoftwareUpdatesKey,
|
||||
Description: "Automatically check for and download software updates. This does not include intelligence data updates.",
|
||||
OptType: config.OptTypeBool,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
RequiresRestart: false,
|
||||
DefaultValue: true,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayOrderAnnotation: -12,
|
||||
config.CategoryAnnotation: "Updates",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Automatic Intelligence Data Updates",
|
||||
Key: enableIntelUpdatesKey,
|
||||
Description: "Automatically check for and download intelligence data updates. This includes filter lists, geo-ip data, and more. Does not include software updates.",
|
||||
OptType: config.OptTypeBool,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
ReleaseLevel: config.ReleaseLevelStable,
|
||||
RequiresRestart: false,
|
||||
DefaultValue: true,
|
||||
Annotations: config.Annotations{
|
||||
config.DisplayOrderAnnotation: -11,
|
||||
config.CategoryAnnotation: "Updates",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func initUpdateConfig() {
|
||||
releaseChannel = config.Concurrent.GetAsString(ReleaseChannelKey, ReleaseChannelStable)
|
||||
enableSoftwareUpdates = config.Concurrent.GetAsBool(enableSoftwareUpdatesKey, true)
|
||||
enableIntelUpdates = config.Concurrent.GetAsBool(enableIntelUpdatesKey, true)
|
||||
|
||||
initialReleaseChannel = releaseChannel()
|
||||
|
||||
module.instance.Config().EventConfigChange.AddCallback("configure updates", func(wc *mgr.WorkerCtx, s struct{}) (cancel bool, err error) {
|
||||
configureUpdates()
|
||||
return false, nil
|
||||
})
|
||||
configureUpdates()
|
||||
}
|
||||
|
||||
func configureUpdates() {
|
||||
module.instance.BinaryUpdates().Configure(enableSoftwareUpdates(), configure.GetBinaryUpdateURLs(releaseChannel()))
|
||||
module.instance.IntelUpdates().Configure(enableIntelUpdates(), configure.DefaultIntelIndexURLs)
|
||||
}
|
176
service/core/update_versions.go
Normal file
176
service/core/update_versions.go
Normal file
|
@ -0,0 +1,176 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/safing/portmaster/base/database/record"
|
||||
"github.com/safing/portmaster/base/info"
|
||||
"github.com/safing/portmaster/base/utils/debug"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
const (
|
||||
// versionsDBKey is the database key for update version information.
|
||||
versionsDBKey = "core:status/versions"
|
||||
|
||||
// versionsDBKey is the database key for simple update version information.
|
||||
simpleVersionsDBKey = "core:status/simple-versions"
|
||||
)
|
||||
|
||||
// Versions holds update versions and status information.
|
||||
type Versions struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
Core *info.Info
|
||||
Resources map[string]*updates.Artifact
|
||||
Channel string
|
||||
Beta bool
|
||||
Staging bool
|
||||
}
|
||||
|
||||
// SimpleVersions holds simplified update versions and status information.
|
||||
type SimpleVersions struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
Build *info.Info
|
||||
Resources map[string]*SimplifiedResourceVersion
|
||||
Channel string
|
||||
}
|
||||
|
||||
// SimplifiedResourceVersion holds version information about one resource.
|
||||
type SimplifiedResourceVersion struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
// GetVersions returns the update versions and status information.
|
||||
// Resources must be locked when accessed.
|
||||
func GetVersions() *Versions {
|
||||
// Get all artifacts.
|
||||
resources := make(map[string]*updates.Artifact)
|
||||
if artifacts, err := module.instance.BinaryUpdates().GetFiles(); err == nil {
|
||||
for _, artifact := range artifacts {
|
||||
resources[artifact.Filename] = artifact
|
||||
}
|
||||
}
|
||||
if artifacts, err := module.instance.IntelUpdates().GetFiles(); err == nil {
|
||||
for _, artifact := range artifacts {
|
||||
resources[artifact.Filename] = artifact
|
||||
}
|
||||
}
|
||||
|
||||
return &Versions{
|
||||
Core: info.GetInfo(),
|
||||
Resources: resources,
|
||||
Channel: initialReleaseChannel,
|
||||
Beta: initialReleaseChannel == ReleaseChannelBeta,
|
||||
Staging: initialReleaseChannel == ReleaseChannelStaging,
|
||||
}
|
||||
}
|
||||
|
||||
// GetSimpleVersions returns the simplified update versions and status information.
|
||||
func GetSimpleVersions() *SimpleVersions {
|
||||
// Get all artifacts, simply map.
|
||||
resources := make(map[string]*SimplifiedResourceVersion)
|
||||
if artifacts, err := module.instance.BinaryUpdates().GetFiles(); err == nil {
|
||||
for _, artifact := range artifacts {
|
||||
resources[artifact.Filename] = &SimplifiedResourceVersion{
|
||||
Version: artifact.Version,
|
||||
}
|
||||
}
|
||||
}
|
||||
if artifacts, err := module.instance.IntelUpdates().GetFiles(); err == nil {
|
||||
for _, artifact := range artifacts {
|
||||
resources[artifact.Filename] = &SimplifiedResourceVersion{
|
||||
Version: artifact.Version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fill base info.
|
||||
return &SimpleVersions{
|
||||
Build: info.GetInfo(),
|
||||
Resources: resources,
|
||||
Channel: initialReleaseChannel,
|
||||
}
|
||||
}
|
||||
|
||||
func initVersionExport() {
|
||||
module.instance.BinaryUpdates().EventResourcesUpdated.AddCallback("export version status", export)
|
||||
module.instance.IntelUpdates().EventResourcesUpdated.AddCallback("export version status", export)
|
||||
|
||||
_, _ = export(nil, struct{}{})
|
||||
}
|
||||
|
||||
func (v *Versions) save() error {
|
||||
if !v.KeyIsSet() {
|
||||
v.SetKey(versionsDBKey)
|
||||
}
|
||||
return db.Put(v)
|
||||
}
|
||||
|
||||
func (v *SimpleVersions) save() error {
|
||||
if !v.KeyIsSet() {
|
||||
v.SetKey(simpleVersionsDBKey)
|
||||
}
|
||||
return db.Put(v)
|
||||
}
|
||||
|
||||
// export is an event hook.
|
||||
func export(_ *mgr.WorkerCtx, _ struct{}) (cancel bool, err error) {
|
||||
// Export versions.
|
||||
if err := GetVersions().save(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := GetSimpleVersions().save(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// AddVersionsToDebugInfo adds the update system status to the given debug.Info.
|
||||
func AddVersionsToDebugInfo(di *debug.Info) {
|
||||
overviewBuf := bytes.NewBuffer(nil)
|
||||
tableBuf := bytes.NewBuffer(nil)
|
||||
tabWriter := tabwriter.NewWriter(tableBuf, 8, 4, 3, ' ', 0)
|
||||
fmt.Fprint(tabWriter, "\nFile\tVersion\tIndex\tSHA256\n")
|
||||
|
||||
// Collect data for debug info.
|
||||
var cnt int
|
||||
if index, err := module.instance.BinaryUpdates().GetIndex(); err == nil {
|
||||
fmt.Fprintf(overviewBuf, "Binaries Index: v%s from %s\n", index.Version, index.Published)
|
||||
for _, artifact := range index.Artifacts {
|
||||
fmt.Fprintf(tabWriter, "\n%s\t%s\t%s\t%s", artifact.Filename, vStr(artifact.Version), "binaries", artifact.SHA256)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
if index, err := module.instance.IntelUpdates().GetIndex(); err == nil {
|
||||
fmt.Fprintf(overviewBuf, "Intel Index: v%s from %s\n", index.Version, index.Published)
|
||||
for _, artifact := range index.Artifacts {
|
||||
fmt.Fprintf(tabWriter, "\n%s\t%s\t%s\t%s", artifact.Filename, vStr(artifact.Version), "intel", artifact.SHA256)
|
||||
cnt++
|
||||
}
|
||||
}
|
||||
_ = tabWriter.Flush()
|
||||
|
||||
// Add section.
|
||||
di.AddSection(
|
||||
fmt.Sprintf("Updates: %s (%d)", initialReleaseChannel, cnt),
|
||||
debug.UseCodeSection,
|
||||
overviewBuf.String(),
|
||||
tableBuf.String(),
|
||||
)
|
||||
}
|
||||
|
||||
func vStr(v string) string {
|
||||
if v != "" {
|
||||
return v
|
||||
}
|
||||
return "unknown"
|
||||
}
|
|
@ -167,10 +167,6 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|||
}
|
||||
|
||||
// Service modules
|
||||
instance.core, err = core.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create core module: %w", err)
|
||||
}
|
||||
binaryUpdateConfig, intelUpdateConfig, err := MakeUpdateConfigs(svcCfg)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates config: %w", err)
|
||||
|
@ -183,6 +179,10 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
|||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates module: %w", err)
|
||||
}
|
||||
instance.core, err = core.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create core module: %w", err)
|
||||
}
|
||||
instance.geoip, err = geoip.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create customlist module: %w", err)
|
||||
|
@ -708,3 +708,23 @@ func (i *Instance) ShutdownComplete() <-chan struct{} {
|
|||
func (i *Instance) ExitCode() int {
|
||||
return int(i.exitCode.Load())
|
||||
}
|
||||
|
||||
// ShouldRestartIsSet returns whether the service/instance should be restarted.
|
||||
func (i *Instance) ShouldRestartIsSet() bool {
|
||||
return i.ShouldRestart
|
||||
}
|
||||
|
||||
// CommandLineOperationIsSet returns whether the command line option is set.
|
||||
func (i *Instance) CommandLineOperationIsSet() bool {
|
||||
return i.CommandLineOperation != nil
|
||||
}
|
||||
|
||||
// CommandLineOperationExecute executes the set command line option.
|
||||
func (i *Instance) CommandLineOperationExecute() error {
|
||||
return i.CommandLineOperation()
|
||||
}
|
||||
|
||||
// AddModule adds a module to the service group.
|
||||
func (i *Instance) AddModule(m mgr.Module) {
|
||||
i.serviceGroup.Add(m)
|
||||
}
|
||||
|
|
|
@ -1,120 +0,0 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
go_runtime "runtime"
|
||||
|
||||
"github.com/safing/jess"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultStableBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/stable.v3.json",
|
||||
}
|
||||
DefaultBetaBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/beta.v3.json",
|
||||
}
|
||||
DefaultStagingBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/staging.v3.json",
|
||||
}
|
||||
DefaultSupportBinaryIndexURLs = []string{
|
||||
"https://updates.safing.io/support.v3.json",
|
||||
}
|
||||
|
||||
DefaultIntelIndexURLs = []string{
|
||||
"https://updates.safing.io/intel.v3.json",
|
||||
}
|
||||
|
||||
// BinarySigningKeys holds the signing keys in text format.
|
||||
BinarySigningKeys = []string{
|
||||
// Safing Code Signing Key #1
|
||||
"recipient:public-ed25519-key:safing-code-signing-key-1:92bgBLneQUWrhYLPpBDjqHbpFPuNVCPAaivQ951A4aq72HcTiw7R1QmPJwFM1mdePAvEVDjkeb8S4fp2pmRCsRa8HrCvWQEjd88rfZ6TznJMfY4g7P8ioGFjfpyx2ZJ8WCZJG5Qt4Z9nkabhxo2Nbi3iywBTYDLSbP5CXqi7jryW7BufWWuaRVufFFzhwUC2ryWFWMdkUmsAZcvXwde4KLN9FrkWAy61fGaJ8GCwGnGCSitANnU2cQrsGBXZzxmzxwrYD",
|
||||
// Safing Code Signing Key #2
|
||||
"recipient:public-ed25519-key:safing-code-signing-key-2:92bgBLneQUWrhYLPpBDjqHbPC2d1o5JMyZFdavWBNVtdvbPfzDewLW95ScXfYPHd3QvWHSWCtB4xpthaYWxSkK1kYiGp68DPa2HaU8yQ5dZhaAUuV4Kzv42pJcWkCeVnBYqgGBXobuz52rFqhDJy3rz7soXEmYhJEJWwLwMeioK3VzN3QmGSYXXjosHMMNC76rjufSoLNtUQUWZDSnHmqbuxbKMCCsjFXUGGhtZVyb7bnu7QLTLk6SKHBJDMB6zdL9sw3",
|
||||
}
|
||||
|
||||
// BinarySigningTrustStore is an in-memory trust store with the signing keys.
|
||||
BinarySigningTrustStore = jess.NewMemTrustStore()
|
||||
)
|
||||
|
||||
func init() {
|
||||
for _, signingKey := range BinarySigningKeys {
|
||||
rcpt, err := jess.RecipientFromTextFormat(signingKey)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = BinarySigningTrustStore.StoreSignet(rcpt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func MakeUpdateConfigs(svcCfg *ServiceConfig) (binaryUpdateConfig, intelUpdateConfig *updates.Config, err error) {
|
||||
switch go_runtime.GOOS {
|
||||
case "windows":
|
||||
binaryUpdateConfig = &updates.Config{
|
||||
Name: "binaries",
|
||||
Directory: svcCfg.BinDir,
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.BinDir, "upgrade_obsolete_binaries"),
|
||||
Ignore: []string{"databases", "intel", "config.json"},
|
||||
IndexURLs: svcCfg.BinariesIndexURLs,
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyBinaryUpdates,
|
||||
AutoCheck: true, // FIXME: Get from setting.
|
||||
AutoDownload: false,
|
||||
AutoApply: false,
|
||||
NeedsRestart: true,
|
||||
Notify: true,
|
||||
}
|
||||
intelUpdateConfig = &updates.Config{
|
||||
Name: "intel",
|
||||
Directory: filepath.Join(svcCfg.DataDir, "intel"),
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
|
||||
IndexURLs: svcCfg.IntelIndexURLs,
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyIntelUpdates,
|
||||
AutoCheck: true, // FIXME: Get from setting.
|
||||
AutoDownload: true,
|
||||
AutoApply: true,
|
||||
NeedsRestart: false,
|
||||
Notify: false,
|
||||
}
|
||||
|
||||
case "linux":
|
||||
binaryUpdateConfig = &updates.Config{
|
||||
Name: "binaries",
|
||||
Directory: svcCfg.BinDir,
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_binaries"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_binaries"),
|
||||
Ignore: []string{"databases", "intel", "config.json"},
|
||||
IndexURLs: svcCfg.BinariesIndexURLs,
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyBinaryUpdates,
|
||||
AutoCheck: true, // FIXME: Get from setting.
|
||||
AutoDownload: false,
|
||||
AutoApply: false,
|
||||
NeedsRestart: true,
|
||||
Notify: true,
|
||||
}
|
||||
intelUpdateConfig = &updates.Config{
|
||||
Name: "intel",
|
||||
Directory: filepath.Join(svcCfg.DataDir, "intel"),
|
||||
DownloadDirectory: filepath.Join(svcCfg.DataDir, "download_intel"),
|
||||
PurgeDirectory: filepath.Join(svcCfg.DataDir, "upgrade_obsolete_intel"),
|
||||
IndexURLs: svcCfg.IntelIndexURLs,
|
||||
IndexFile: "index.json",
|
||||
Verify: svcCfg.VerifyIntelUpdates,
|
||||
AutoCheck: true, // FIXME: Get from setting.
|
||||
AutoDownload: true,
|
||||
AutoApply: true,
|
||||
NeedsRestart: false,
|
||||
Notify: false,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -192,7 +192,7 @@ artifacts:
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Downloader) getArtifact(ctx context.Context, artifact Artifact, url string) ([]byte, error) {
|
||||
func (d *Downloader) getArtifact(ctx context.Context, artifact *Artifact, url string) ([]byte, error) {
|
||||
// Download data from URL.
|
||||
artifactData, err := d.downloadData(ctx, url)
|
||||
if err != nil {
|
||||
|
|
|
@ -117,10 +117,10 @@ func (a *Artifact) export(dir string, indexVersion *semver.Version) *Artifact {
|
|||
|
||||
// Index represents a collection of artifacts with metadata.
|
||||
type Index struct {
|
||||
Name string `json:"Name"`
|
||||
Version string `json:"Version"`
|
||||
Published time.Time `json:"Published"`
|
||||
Artifacts []Artifact `json:"Artifacts"`
|
||||
Name string `json:"Name"`
|
||||
Version string `json:"Version"`
|
||||
Published time.Time `json:"Published"`
|
||||
Artifacts []*Artifact `json:"Artifacts"`
|
||||
|
||||
versionNum *semver.Version
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ func (index *Index) init() error {
|
|||
}
|
||||
|
||||
// Filter artifacts by current platform.
|
||||
filtered := make([]Artifact, 0)
|
||||
filtered := make([]*Artifact, 0)
|
||||
for _, a := range index.Artifacts {
|
||||
if a.Platform == "" || a.Platform == currentPlatform {
|
||||
filtered = append(filtered, a)
|
||||
|
@ -189,6 +189,7 @@ func (index *Index) init() error {
|
|||
a.versionNum = v
|
||||
}
|
||||
} else {
|
||||
a.Version = index.Version
|
||||
a.versionNum = index.versionNum
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ settings:
|
|||
|
||||
// GenerateIndexFromDir generates a index from a given folder.
|
||||
func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) { //nolint:maintidx
|
||||
artifacts := make(map[string]Artifact)
|
||||
artifacts := make(map[string]*Artifact)
|
||||
|
||||
// Initialize.
|
||||
err := cfg.init()
|
||||
|
@ -187,11 +187,12 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
|
|||
|
||||
// Step 3: Create new Artifact.
|
||||
|
||||
artifact := Artifact{}
|
||||
artifact := &Artifact{}
|
||||
|
||||
// Check if the caller provided a template for the artifact.
|
||||
if t, ok := cfg.Templates[identifier]; ok {
|
||||
artifact = t
|
||||
fromTemplate := t
|
||||
artifact = &fromTemplate
|
||||
}
|
||||
|
||||
// Set artifact properties.
|
||||
|
@ -249,7 +250,7 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
|
|||
}
|
||||
|
||||
// Convert to slice and compute hashes.
|
||||
export := make([]Artifact, 0, len(artifacts))
|
||||
export := make([]*Artifact, 0, len(artifacts))
|
||||
for _, artifact := range artifacts {
|
||||
// Compute hash.
|
||||
hash, err := getSHA256(artifact.localFile, artifact.Unpack)
|
||||
|
@ -273,7 +274,7 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
|
|||
}
|
||||
|
||||
// Sort final artifacts.
|
||||
slices.SortFunc(export, func(a, b Artifact) int {
|
||||
slices.SortFunc(export, func(a, b *Artifact) int {
|
||||
switch {
|
||||
case a.Filename != b.Filename:
|
||||
return strings.Compare(a.Filename, b.Filename)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -132,9 +133,11 @@ type Updater struct {
|
|||
|
||||
EventResourcesUpdated *mgr.EventMgr[struct{}]
|
||||
|
||||
corruptedInstallation bool
|
||||
corruptedInstallation error
|
||||
|
||||
isUpdateRunning *abool.AtomicBool
|
||||
started *abool.AtomicBool
|
||||
configureLock sync.Mutex
|
||||
|
||||
instance instance
|
||||
}
|
||||
|
@ -150,6 +153,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
|
|||
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
|
||||
|
||||
isUpdateRunning: abool.NewBool(false),
|
||||
started: abool.NewBool(false),
|
||||
|
||||
instance: instance,
|
||||
}
|
||||
|
@ -166,6 +170,12 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
|
|||
// Load index.
|
||||
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify)
|
||||
if err == nil {
|
||||
// Verify artifacts.
|
||||
if err := index.VerifyArtifacts(cfg.Directory); err != nil {
|
||||
module.corruptedInstallation = fmt.Errorf("invalid artifact: %w", err)
|
||||
}
|
||||
|
||||
// Save index to module and return.
|
||||
module.index = index
|
||||
return module, nil
|
||||
}
|
||||
|
@ -173,6 +183,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
|
|||
// Fall back to scanning the directory.
|
||||
if !errors.Is(err, os.ErrNotExist) {
|
||||
log.Errorf("updates/%s: invalid index file, falling back to dir scan: %s", cfg.Name, err)
|
||||
module.corruptedInstallation = fmt.Errorf("invalid index: %w", err)
|
||||
}
|
||||
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
|
||||
if err == nil && index.init() == nil {
|
||||
|
@ -259,6 +270,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
|||
|
||||
// Check if automatic downloads are enabled.
|
||||
if !u.cfg.AutoDownload && !forceApply {
|
||||
log.Infof("updates/%s: new update to v%s available, action required to download and upgrade", u.cfg.Name, downloader.index.Version)
|
||||
if u.cfg.Notify && u.instance.Notifications() != nil {
|
||||
u.instance.Notifications().Notify(¬ifications.Notification{
|
||||
EventID: updateAvailableNotificationID,
|
||||
|
@ -304,6 +316,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
|||
|
||||
// Notify the user that an upgrade is available.
|
||||
if !u.cfg.AutoApply && !forceApply {
|
||||
log.Infof("updates/%s: new update to v%s available, action required to upgrade", u.cfg.Name, downloader.index.Version)
|
||||
if u.cfg.Notify && u.instance.Notifications() != nil {
|
||||
u.instance.Notifications().Notify(¬ifications.Notification{
|
||||
EventID: updateAvailableNotificationID,
|
||||
|
@ -387,8 +400,15 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
|||
return nil
|
||||
}
|
||||
|
||||
func (u *Updater) getIndexURLsWithLock() []string {
|
||||
u.configureLock.Lock()
|
||||
defer u.configureLock.Unlock()
|
||||
|
||||
return u.cfg.IndexURLs
|
||||
}
|
||||
|
||||
func (u *Updater) updateCheckWorker(w *mgr.WorkerCtx) error {
|
||||
err := u.updateAndUpgrade(w, u.cfg.IndexURLs, false, false)
|
||||
err := u.updateAndUpgrade(w, u.getIndexURLsWithLock(), false, false)
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil // Success!
|
||||
|
@ -404,7 +424,7 @@ func (u *Updater) updateCheckWorker(w *mgr.WorkerCtx) error {
|
|||
}
|
||||
|
||||
func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error {
|
||||
err := u.updateAndUpgrade(w, u.cfg.IndexURLs, false, true)
|
||||
err := u.updateAndUpgrade(w, u.getIndexURLsWithLock(), false, true)
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil // Success!
|
||||
|
@ -423,7 +443,7 @@ func (u *Updater) upgradeWorker(w *mgr.WorkerCtx) error {
|
|||
// and is intended to be used only within a tool, not a service.
|
||||
func (u *Updater) ForceUpdate() error {
|
||||
return u.m.Do("update and upgrade", func(w *mgr.WorkerCtx) error {
|
||||
return u.updateAndUpgrade(w, u.cfg.IndexURLs, true, true)
|
||||
return u.updateAndUpgrade(w, u.getIndexURLsWithLock(), true, true)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -437,6 +457,33 @@ func (u *Updater) UpdateFromURL(url string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Configure makes slight configuration changes to the updater.
|
||||
// It locks the index, which can take a while an update is running.
|
||||
func (u *Updater) Configure(autoCheck bool, indexURLs []string) {
|
||||
u.configureLock.Lock()
|
||||
defer u.configureLock.Unlock()
|
||||
|
||||
// Apply new config.
|
||||
var changed bool
|
||||
if u.cfg.AutoCheck != autoCheck {
|
||||
u.cfg.AutoCheck = autoCheck
|
||||
changed = true
|
||||
}
|
||||
if !slices.Equal(u.cfg.IndexURLs, indexURLs) {
|
||||
u.cfg.IndexURLs = indexURLs
|
||||
changed = true
|
||||
}
|
||||
|
||||
// Trigger update check if enabled and something changed.
|
||||
if changed && u.started.IsSet() {
|
||||
if autoCheck {
|
||||
u.updateCheckWorkerMgr.Repeat(updateTaskRepeatDuration).Go()
|
||||
} else {
|
||||
u.updateCheckWorkerMgr.Repeat(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerUpdateCheck triggers an update check.
|
||||
func (u *Updater) TriggerUpdateCheck() {
|
||||
u.updateCheckWorkerMgr.Go()
|
||||
|
@ -459,13 +506,17 @@ func (u *Updater) Manager() *mgr.Manager {
|
|||
|
||||
// Start starts the module.
|
||||
func (u *Updater) Start() error {
|
||||
if u.corruptedInstallation && u.cfg.Notify && u.instance.Notifications() != nil {
|
||||
// FIXME: this might make sense as a module state
|
||||
u.instance.Notifications().NotifyError(
|
||||
corruptInstallationNotificationID,
|
||||
"Install Corruption",
|
||||
"Portmaster has detected that one or more of its own files have been corrupted. Please re-install the software.",
|
||||
)
|
||||
u.configureLock.Lock()
|
||||
defer u.configureLock.Unlock()
|
||||
|
||||
if u.corruptedInstallation != nil && u.cfg.Notify && u.instance.Notifications() != nil {
|
||||
u.states.Add(mgr.State{
|
||||
ID: corruptInstallationNotificationID,
|
||||
Name: "Install Corruption",
|
||||
Message: "Portmaster has detected that one or more of its own files have been corrupted. Please re-install the software. Error: " + u.corruptedInstallation.Error(),
|
||||
Type: mgr.StateTypeError,
|
||||
Data: u.corruptedInstallation,
|
||||
})
|
||||
}
|
||||
|
||||
// Check for updates automatically, if enabled.
|
||||
|
@ -474,6 +525,8 @@ func (u *Updater) Start() error {
|
|||
Repeat(updateTaskRepeatDuration).
|
||||
Delay(15 * time.Second)
|
||||
}
|
||||
|
||||
u.started.SetTo(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -481,6 +534,62 @@ func (u *Updater) GetMainDir() string {
|
|||
return u.cfg.Directory
|
||||
}
|
||||
|
||||
// GetIndex returns a copy of the index.
|
||||
func (u *Updater) GetIndex() (*Index, error) {
|
||||
// Copy Artifacts.
|
||||
artifacts, err := u.GetFiles()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.indexLock.Lock()
|
||||
defer u.indexLock.Unlock()
|
||||
|
||||
// Check if any index is active.
|
||||
if u.index == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return &Index{
|
||||
Name: u.index.Name,
|
||||
Version: u.index.Version,
|
||||
Published: u.index.Published,
|
||||
Artifacts: artifacts,
|
||||
versionNum: u.index.versionNum,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetFiles returns all artifacts. Returns ErrNotFound if no artifacts are found.
|
||||
func (u *Updater) GetFiles() ([]*Artifact, error) {
|
||||
u.indexLock.Lock()
|
||||
defer u.indexLock.Unlock()
|
||||
|
||||
// Check if any index is active.
|
||||
if u.index == nil {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// Export all artifacts.
|
||||
export := make([]*Artifact, 0, len(u.index.Artifacts))
|
||||
for _, artifact := range u.index.Artifacts {
|
||||
switch {
|
||||
case artifact.Platform != "" && artifact.Platform != currentPlatform:
|
||||
// Platform is defined and does not match.
|
||||
// Platforms are usually pre-filtered, but just to be sure.
|
||||
default:
|
||||
// Artifact matches!
|
||||
export = append(export, artifact.export(u.cfg.Directory, u.index.versionNum))
|
||||
}
|
||||
}
|
||||
|
||||
// Check if anything was exported.
|
||||
if len(export) == 0 {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return export, nil
|
||||
}
|
||||
|
||||
// GetFile returns the path of a file given the name. Returns ErrNotFound if file is not found.
|
||||
func (u *Updater) GetFile(name string) (*Artifact, error) {
|
||||
u.indexLock.Lock()
|
||||
|
@ -509,6 +618,7 @@ func (u *Updater) GetFile(name string) (*Artifact, error) {
|
|||
|
||||
// Stop stops the module.
|
||||
func (u *Updater) Stop() error {
|
||||
u.started.SetTo(false)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -12,8 +12,6 @@ import (
|
|||
"github.com/safing/portmaster/base/log"
|
||||
)
|
||||
|
||||
// FIXME: previous update system did in-place service file upgrades. Check if this is still necessary and if changes are in current installers.
|
||||
|
||||
const (
|
||||
defaultFileMode = os.FileMode(0o0644)
|
||||
executableFileMode = os.FileMode(0o0744)
|
||||
|
|
|
@ -3,17 +3,18 @@ package spn
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"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"
|
||||
"github.com/safing/portmaster/base/runtime"
|
||||
"github.com/safing/portmaster/service"
|
||||
"github.com/safing/portmaster/service/core"
|
||||
"github.com/safing/portmaster/service/core/base"
|
||||
"github.com/safing/portmaster/service/intel/filterlists"
|
||||
|
@ -79,27 +80,27 @@ type Instance struct {
|
|||
}
|
||||
|
||||
// New returns a new Portmaster service instance.
|
||||
func New() (*Instance, error) {
|
||||
func New(svcCfg *service.ServiceConfig) (*Instance, error) {
|
||||
// Initialize config.
|
||||
err := svcCfg.Init()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("internal service config error: %w", err)
|
||||
}
|
||||
|
||||
// Make sure data dir exists, so that child directories don't dictate the permissions.
|
||||
err = os.MkdirAll(svcCfg.DataDir, 0o0755)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("data directory %s is not accessible: %w", svcCfg.DataDir, err)
|
||||
}
|
||||
|
||||
// Create instance to pass it to modules.
|
||||
instance := &Instance{}
|
||||
instance := &Instance{
|
||||
binDir: svcCfg.BinDir,
|
||||
dataDir: svcCfg.DataDir,
|
||||
}
|
||||
instance.ctx, instance.cancelCtx = context.WithCancel(context.Background())
|
||||
instance.shutdownCtx, instance.cancelShutdownCtx = context.WithCancel(context.Background())
|
||||
|
||||
binaryUpdateIndex := updates.Config{
|
||||
// FIXME: fill
|
||||
}
|
||||
|
||||
intelUpdateIndex := updates.Config{
|
||||
// FIXME: fill
|
||||
}
|
||||
|
||||
// Initialize log
|
||||
log.GlobalWriter = log.NewStdoutWriter()
|
||||
|
||||
// FIXME: initialize log file.
|
||||
|
||||
var err error
|
||||
|
||||
// Base modules
|
||||
instance.base, err = base.New(instance)
|
||||
if err != nil {
|
||||
|
@ -131,18 +132,22 @@ func New() (*Instance, error) {
|
|||
}
|
||||
|
||||
// Service modules
|
||||
binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(svcCfg)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates config: %w", err)
|
||||
}
|
||||
instance.binaryUpdates, err = updates.New(instance, "Binary Updater", *binaryUpdateConfig)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates module: %w", err)
|
||||
}
|
||||
instance.intelUpdates, err = updates.New(instance, "Intel Updater", *intelUpdateConfig)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates module: %w", err)
|
||||
}
|
||||
instance.core, err = core.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create core module: %w", err)
|
||||
}
|
||||
instance.binaryUpdates, err = updates.New(instance, "Binary Updater", binaryUpdateIndex)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates module: %w", err)
|
||||
}
|
||||
instance.intelUpdates, err = updates.New(instance, "Intel Updater", intelUpdateIndex)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create updates module: %w", err)
|
||||
}
|
||||
instance.geoip, err = geoip.New(instance)
|
||||
if err != nil {
|
||||
return instance, fmt.Errorf("create customlist module: %w", err)
|
||||
|
|
Loading…
Add table
Reference in a new issue