mirror of
https://github.com/safing/portmaster
synced 2025-09-04 03:29:12 +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"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FIXME: version does not show in portmaster
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
name string
|
name string
|
||||||
license string
|
license string
|
||||||
|
@ -167,9 +165,9 @@ func CondensedVersion() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf(
|
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,
|
info.Name, version,
|
||||||
runtime.GOOS,
|
runtime.GOOS, runtime.GOARCH,
|
||||||
runtime.Version(), runtime.Compiler, cgoInfo,
|
runtime.Version(), runtime.Compiler, cgoInfo,
|
||||||
info.Commit, dirtyInfo, info.CommitTime,
|
info.Commit, dirtyInfo, info.CommitTime,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/lmittmann/tint"
|
"github.com/lmittmann/tint"
|
||||||
|
"github.com/mattn/go-colorable"
|
||||||
|
"github.com/mattn/go-isatty"
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupSLog(level Severity) {
|
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.
|
// Set highest possible level, so it can be changed in runtime.
|
||||||
handlerLogLevel := level.toSLogLevel()
|
handlerLogLevel := level.toSLogLevel()
|
||||||
|
|
||||||
|
@ -17,21 +22,23 @@ func setupSLog(level Severity) {
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
case "windows":
|
case "windows":
|
||||||
logHandler = tint.NewHandler(
|
logHandler = tint.NewHandler(
|
||||||
GlobalWriter,
|
windowsColoring(GlobalWriter), // Enable coloring on Windows.
|
||||||
&tint.Options{
|
&tint.Options{
|
||||||
AddSource: true,
|
AddSource: true,
|
||||||
Level: handlerLogLevel,
|
Level: handlerLogLevel,
|
||||||
TimeFormat: timeFormat,
|
TimeFormat: timeFormat,
|
||||||
NoColor: !GlobalWriter.IsStdout(), // FIXME: also check for tty.
|
NoColor: !( /* Color: */ GlobalWriter.IsStdout() && isatty.IsTerminal(GlobalWriter.file.Fd())),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
case "linux":
|
case "linux":
|
||||||
logHandler = tint.NewHandler(GlobalWriter, &tint.Options{
|
logHandler = tint.NewHandler(GlobalWriter, &tint.Options{
|
||||||
AddSource: true,
|
AddSource: true,
|
||||||
Level: handlerLogLevel,
|
Level: handlerLogLevel,
|
||||||
TimeFormat: timeFormat,
|
TimeFormat: timeFormat,
|
||||||
NoColor: !GlobalWriter.IsStdout(), // FIXME: also check for tty.
|
NoColor: !( /* Color: */ GlobalWriter.IsStdout() && isatty.IsTerminal(GlobalWriter.file.Fd())),
|
||||||
})
|
})
|
||||||
|
|
||||||
default:
|
default:
|
||||||
logHandler = tint.NewHandler(os.Stdout, &tint.Options{
|
logHandler = tint.NewHandler(os.Stdout, &tint.Options{
|
||||||
AddSource: true,
|
AddSource: true,
|
||||||
|
@ -43,6 +50,11 @@ func setupSLog(level Severity) {
|
||||||
|
|
||||||
// Set as default logger.
|
// Set as default logger.
|
||||||
slog.SetDefault(slog.New(logHandler))
|
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 (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -15,14 +17,12 @@ import (
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/service"
|
"github.com/safing/portmaster/service"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"github.com/safing/portmaster/service/mgr"
|
||||||
"github.com/safing/portmaster/spn/conf"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var printStackOnExit bool
|
var (
|
||||||
|
RebootOnRestart bool
|
||||||
func init() {
|
PrintStackOnExit bool
|
||||||
flag.BoolVar(&printStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down")
|
)
|
||||||
}
|
|
||||||
|
|
||||||
type SystemService interface {
|
type SystemService interface {
|
||||||
Run()
|
Run()
|
||||||
|
@ -30,21 +30,47 @@ type SystemService interface {
|
||||||
RestartService() error
|
RestartService() error
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdRun(cmd *cobra.Command, args []string) {
|
type ServiceInstance interface {
|
||||||
// Run platform specific setup or switches.
|
Ready() bool
|
||||||
runPlatformSpecifics(cmd, args)
|
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.
|
func RunService(cmd *cobra.Command, args []string) {
|
||||||
// TODO: Move this to service config.
|
if SvcFactory == nil || SvcConfig == nil {
|
||||||
conf.EnableClient(true)
|
fmt.Fprintln(os.Stderr, "internal error: service not set up in cmdbase")
|
||||||
conf.EnableIntegration(true)
|
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.
|
// Create instance.
|
||||||
// Instance modules might request a cmdline execution of a function.
|
// Instance modules might request a cmdline execution of a function.
|
||||||
var execCmdLine bool
|
var execCmdLine bool
|
||||||
instance, err := service.New(svcCfg)
|
instance, err := SvcFactory(SvcConfig)
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
// Continue
|
// Continue
|
||||||
|
@ -59,13 +85,13 @@ func cmdRun(cmd *cobra.Command, args []string) {
|
||||||
switch {
|
switch {
|
||||||
case !execCmdLine:
|
case !execCmdLine:
|
||||||
// Run service.
|
// Run service.
|
||||||
case instance.CommandLineOperation == nil:
|
case !instance.CommandLineOperationIsSet():
|
||||||
fmt.Println("command line operation execution requested, but not set")
|
fmt.Println("command line operation execution requested, but not set")
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
default:
|
default:
|
||||||
// Run the function and exit.
|
// Run the function and exit.
|
||||||
fmt.Println("executing cmdline op")
|
fmt.Println("executing cmdline op")
|
||||||
err = instance.CommandLineOperation()
|
err = instance.CommandLineOperationExecute()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err)
|
fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err)
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
|
@ -75,16 +101,6 @@ func cmdRun(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
// START
|
// 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.
|
// Create system service.
|
||||||
service := NewSystemService(instance)
|
service := NewSystemService(instance)
|
||||||
|
|
||||||
|
@ -102,7 +118,7 @@ func cmdRun(cmd *cobra.Command, args []string) {
|
||||||
select {
|
select {
|
||||||
case <-instance.ShutdownComplete():
|
case <-instance.ShutdownComplete():
|
||||||
// Print stack on shutdown, if enabled.
|
// Print stack on shutdown, if enabled.
|
||||||
if printStackOnExit {
|
if PrintStackOnExit {
|
||||||
printStackTo(log.GlobalWriter, "PRINTING STACK ON EXIT")
|
printStackTo(log.GlobalWriter, "PRINTING STACK ON EXIT")
|
||||||
}
|
}
|
||||||
case <-time.After(3 * time.Minute):
|
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.
|
// Check if restart was triggered and send start service command if true.
|
||||||
if instance.ShouldRestart && service.IsService() {
|
if instance.ShouldRestartIsSet() && service.IsService() {
|
||||||
if err := service.RestartService(); err != nil {
|
// Check if we should reboot instead.
|
||||||
slog.Error("failed to restart service", "err", err)
|
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)
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -9,17 +9,15 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
processInfo "github.com/shirou/gopsutil/process"
|
processInfo "github.com/shirou/gopsutil/process"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LinuxSystemService struct {
|
type LinuxSystemService struct {
|
||||||
instance *service.Instance
|
instance ServiceInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemService(instance *service.Instance) *LinuxSystemService {
|
func NewSystemService(instance ServiceInstance) *LinuxSystemService {
|
||||||
return &LinuxSystemService{instance: instance}
|
return &LinuxSystemService{instance: instance}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +28,7 @@ func (s *LinuxSystemService) Run() {
|
||||||
slog.Error("failed to start", "err", err)
|
slog.Error("failed to start", "err", err)
|
||||||
|
|
||||||
// Print stack on start failure, if enabled.
|
// Print stack on start failure, if enabled.
|
||||||
if printStackOnExit {
|
if PrintStackOnExit {
|
||||||
printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE")
|
printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +60,7 @@ wait:
|
||||||
continue wait
|
continue wait
|
||||||
} else {
|
} else {
|
||||||
// Trigger shutdown.
|
// Trigger shutdown.
|
||||||
fmt.Printf(" <SIGNAL: %v>", sig) // CLI output.
|
fmt.Printf(" <SIGNAL: %v>\n", sig) // CLI output.
|
||||||
slog.Warn("received stop signal", "signal", sig)
|
slog.Warn("received stop signal", "signal", sig)
|
||||||
s.instance.Shutdown()
|
s.instance.Shutdown()
|
||||||
break wait
|
break wait
|
||||||
|
@ -128,18 +126,3 @@ func (s *LinuxSystemService) IsService() bool {
|
||||||
// Check if the parent process ID is 1 == init system
|
// Check if the parent process ID is 1 == init system
|
||||||
return ppid == 1
|
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
|
// Based on the official Go examples from
|
||||||
// https://github.com/golang/sys/blob/master/windows/svc/example
|
// https://github.com/golang/sys/blob/master/windows/svc/example
|
||||||
|
@ -13,21 +13,19 @@ import (
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"golang.org/x/sys/windows/svc"
|
"golang.org/x/sys/windows/svc"
|
||||||
"golang.org/x/sys/windows/svc/debug"
|
"golang.org/x/sys/windows/svc/debug"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/service"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const serviceName = "PortmasterCore"
|
const serviceName = "PortmasterCore"
|
||||||
|
|
||||||
type WindowsSystemService struct {
|
type WindowsSystemService struct {
|
||||||
instance *service.Instance
|
instance ServiceInstance
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSystemService(instance *service.Instance) *WindowsSystemService {
|
func NewSystemService(instance ServiceInstance) *WindowsSystemService {
|
||||||
return &WindowsSystemService{instance: instance}
|
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)
|
fmt.Printf("failed to start: %s\n", err)
|
||||||
|
|
||||||
// Print stack on start failure, if enabled.
|
// Print stack on start failure, if enabled.
|
||||||
if printStackOnExit {
|
if PrintStackOnExit {
|
||||||
printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE")
|
printStackTo(log.GlobalWriter, "PRINTING STACK ON START FAILURE")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +100,7 @@ waitSignal:
|
||||||
select {
|
select {
|
||||||
case sig := <-signalCh:
|
case sig := <-signalCh:
|
||||||
// Trigger shutdown.
|
// Trigger shutdown.
|
||||||
fmt.Printf(" <SIGNAL: %v>", sig) // CLI output.
|
fmt.Printf(" <SIGNAL: %v>\n", sig) // CLI output.
|
||||||
slog.Warn("received stop signal", "signal", sig)
|
slog.Warn("received stop signal", "signal", sig)
|
||||||
break waitSignal
|
break waitSignal
|
||||||
|
|
||||||
|
@ -112,7 +110,7 @@ waitSignal:
|
||||||
changes <- c.CurrentStatus
|
changes <- c.CurrentStatus
|
||||||
|
|
||||||
case svc.Stop, svc.Shutdown:
|
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)
|
slog.Warn("received service shutdown command", "cmd", c.Cmd)
|
||||||
break waitSignal
|
break waitSignal
|
||||||
|
|
||||||
|
@ -201,8 +199,6 @@ sc.exe start $serviceName`
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPlatformSpecifics(cmd *cobra.Command, args []string)
|
|
||||||
|
|
||||||
func serviceCmdName(cmd svc.Cmd) string {
|
func serviceCmdName(cmd svc.Cmd) string {
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case svc.Stop:
|
case svc.Stop:
|
|
@ -1,4 +1,4 @@
|
||||||
package main
|
package cmdbase
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -12,32 +12,28 @@ import (
|
||||||
"github.com/safing/portmaster/service/updates"
|
"github.com/safing/portmaster/service/updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
var updateCmd = &cobra.Command{
|
var UpdateCmd = &cobra.Command{
|
||||||
Use: "update",
|
Use: "update",
|
||||||
Short: "Force an update of all components.",
|
Short: "Force an update of all components.",
|
||||||
RunE: update,
|
RunE: update,
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(updateCmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(cmd *cobra.Command, args []string) error {
|
func update(cmd *cobra.Command, args []string) error {
|
||||||
// Finalize config.
|
// Finalize config.
|
||||||
err := svcCfg.Init()
|
err := SvcConfig.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("internal configuration error: %w", err)
|
return fmt.Errorf("internal configuration error: %w", err)
|
||||||
}
|
}
|
||||||
// Force logging to stdout.
|
// Force logging to stdout.
|
||||||
svcCfg.LogToStdout = true
|
SvcConfig.LogToStdout = true
|
||||||
|
|
||||||
// Start logging.
|
// Start logging.
|
||||||
_ = log.Start(svcCfg.LogLevel, svcCfg.LogToStdout, svcCfg.LogDir)
|
_ = log.Start(SvcConfig.LogLevel, SvcConfig.LogToStdout, SvcConfig.LogDir)
|
||||||
defer log.Shutdown()
|
defer log.Shutdown()
|
||||||
|
|
||||||
// Create updaters.
|
// Create updaters.
|
||||||
instance := &updateDummyInstance{}
|
instance := &updateDummyInstance{}
|
||||||
binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(svcCfg)
|
binaryUpdateConfig, intelUpdateConfig, err := service.MakeUpdateConfigs(SvcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("init updater config: %w", err)
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
|
||||||
"syscall"
|
"github.com/spf13/cobra"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/info"
|
"github.com/safing/portmaster/base/info"
|
||||||
"github.com/safing/portmaster/base/log"
|
|
||||||
"github.com/safing/portmaster/base/metrics"
|
"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/service/updates"
|
||||||
"github.com/safing/portmaster/spn"
|
|
||||||
"github.com/safing/portmaster/spn/conf"
|
"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() {
|
func init() {
|
||||||
// flag.BoolVar(&updates.RebootOnRestart, "reboot-on-restart", false, "reboot server on auto-upgrade")
|
// Add persisent flags for all commands.
|
||||||
// FIXME
|
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() {
|
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")
|
info.Set("SPN Hub", "", "GPLv3")
|
||||||
|
|
||||||
// Configure metrics.
|
// Configure metrics.
|
||||||
_ = metrics.SetNamespace("hub")
|
_ = metrics.SetNamespace("hub")
|
||||||
|
|
||||||
// Configure user agent and updates.
|
// Configure user agent.
|
||||||
updates.UserAgent = fmt.Sprintf("SPN Hub (%s %s)", runtime.GOOS, runtime.GOARCH)
|
updates.UserAgent = fmt.Sprintf("SPN Hub (%s %s)", runtime.GOOS, runtime.GOARCH)
|
||||||
// helper.IntelOnly()
|
|
||||||
|
|
||||||
// Set SPN public hub mode.
|
// Set SPN public hub mode.
|
||||||
conf.EnablePublicHub(true)
|
conf.EnablePublicHub(true)
|
||||||
|
|
||||||
// Start logger with default log level.
|
// Configure service.
|
||||||
_ = log.Start(log.WarningLevel)
|
cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) {
|
||||||
|
svc, err := service.New(svcCfg)
|
||||||
// FIXME: Use service?
|
return svc, err
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
cmdbase.SvcConfig = &service.ServiceConfig{
|
||||||
|
BinDir: binDir,
|
||||||
|
DataDir: dataDir,
|
||||||
|
|
||||||
// Execute command line operation, if requested or available.
|
LogToStdout: logToStdout,
|
||||||
switch {
|
LogDir: logDir,
|
||||||
case !execCmdLine:
|
LogLevel: logLevel,
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start
|
BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs,
|
||||||
go func() {
|
IntelIndexURLs: configure.DefaultIntelIndexURLs,
|
||||||
err = instance.Start()
|
VerifyBinaryUpdates: configure.BinarySigningTrustStore,
|
||||||
if err != nil {
|
VerifyIntelUpdates: configure.BinarySigningTrustStore,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,75 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
"runtime/pprof"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/api"
|
"github.com/safing/portmaster/base/api"
|
||||||
"github.com/safing/portmaster/base/info"
|
"github.com/safing/portmaster/base/info"
|
||||||
"github.com/safing/portmaster/base/log"
|
|
||||||
"github.com/safing/portmaster/base/metrics"
|
"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/service/updates"
|
||||||
"github.com/safing/portmaster/spn"
|
|
||||||
"github.com/safing/portmaster/spn/captain"
|
"github.com/safing/portmaster/spn/captain"
|
||||||
"github.com/safing/portmaster/spn/conf"
|
"github.com/safing/portmaster/spn/conf"
|
||||||
"github.com/safing/portmaster/spn/sluice"
|
"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() {
|
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")
|
info.Set("SPN Observation Hub", "", "GPLv3")
|
||||||
|
|
||||||
// Configure metrics.
|
// Configure metrics.
|
||||||
_ = metrics.SetNamespace("observer")
|
_ = metrics.SetNamespace("observer")
|
||||||
|
|
||||||
// Configure user agent and updates.
|
// Configure user agent.
|
||||||
updates.UserAgent = fmt.Sprintf("SPN Observation Hub (%s %s)", runtime.GOOS, runtime.GOARCH)
|
updates.UserAgent = fmt.Sprintf("SPN Observation Hub (%s %s)", runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
// Configure SPN mode.
|
// Configure SPN mode.
|
||||||
|
@ -46,129 +80,37 @@ func main() {
|
||||||
sluice.EnableListener = false
|
sluice.EnableListener = false
|
||||||
api.EnableServer = false
|
api.EnableServer = false
|
||||||
|
|
||||||
// Start logger with default log level.
|
// Configure service.
|
||||||
_ = log.Start(log.WarningLevel)
|
cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) {
|
||||||
|
svc, err := service.New(svcCfg)
|
||||||
|
|
||||||
// Create instance.
|
// Add additional modules.
|
||||||
var execCmdLine bool
|
observer, err := New(svc)
|
||||||
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()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "command line operation failed: %s\n", err)
|
fmt.Printf("error creating an instance: create observer module: %s\n", err)
|
||||||
os.Exit(3)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
svc.AddModule(observer)
|
||||||
}
|
_, err = NewApprise(svc)
|
||||||
|
|
||||||
// Start
|
|
||||||
go func() {
|
|
||||||
err = instance.Start()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("instance start failed: %s\n", err)
|
fmt.Printf("error creating an instance: create apprise module: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
}()
|
svc.AddModule(observer)
|
||||||
|
|
||||||
// Wait for signal.
|
return svc, err
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
cmdbase.SvcConfig = &service.ServiceConfig{
|
||||||
|
BinDir: binDir,
|
||||||
|
DataDir: dataDir,
|
||||||
|
|
||||||
// Catch signals during shutdown.
|
LogToStdout: logToStdout,
|
||||||
// Rapid unplanned disassembly after 5 interrupts.
|
LogDir: logDir,
|
||||||
go func() {
|
LogLevel: logLevel,
|
||||||
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.
|
BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs,
|
||||||
go func() {
|
IntelIndexURLs: configure.DefaultIntelIndexURLs,
|
||||||
time.Sleep(3 * time.Minute)
|
VerifyBinaryUpdates: configure.BinarySigningTrustStore,
|
||||||
printStackTo(os.Stderr, "PRINTING STACK - TAKING TOO LONG FOR SHUTDOWN")
|
VerifyIntelUpdates: configure.BinarySigningTrustStore,
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,9 @@ import (
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/info"
|
"github.com/safing/portmaster/base/info"
|
||||||
"github.com/safing/portmaster/base/metrics"
|
"github.com/safing/portmaster/base/metrics"
|
||||||
|
"github.com/safing/portmaster/cmds/cmdbase"
|
||||||
"github.com/safing/portmaster/service"
|
"github.com/safing/portmaster/service"
|
||||||
|
"github.com/safing/portmaster/service/configure"
|
||||||
"github.com/safing/portmaster/service/updates"
|
"github.com/safing/portmaster/service/updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -18,7 +20,7 @@ var (
|
||||||
rootCmd = &cobra.Command{
|
rootCmd = &cobra.Command{
|
||||||
Use: "portmaster-core",
|
Use: "portmaster-core",
|
||||||
PersistentPreRun: initializeGlobals,
|
PersistentPreRun: initializeGlobals,
|
||||||
Run: cmdRun,
|
Run: mainRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
binDir string
|
binDir string
|
||||||
|
@ -28,14 +30,10 @@ var (
|
||||||
logDir string
|
logDir string
|
||||||
logLevel string
|
logLevel string
|
||||||
|
|
||||||
svcCfg *service.ServiceConfig
|
printVersion bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
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.
|
// Add persisent flags for all commands.
|
||||||
rootCmd.PersistentFlags().StringVar(&binDir, "bin-dir", "", "set directory for executable binaries (rw/ro)")
|
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)")
|
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().BoolVar(&logToStdout, "log-stdout", false, "log to stdout instead of file")
|
||||||
rootCmd.Flags().StringVar(&logDir, "log-dir", "", "set directory for logs")
|
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().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() {
|
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 {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mainRun(cmd *cobra.Command, args []string) {
|
||||||
|
runPlatformSpecifics(cmd, args)
|
||||||
|
cmdbase.RunService(cmd, args)
|
||||||
|
}
|
||||||
|
|
||||||
func initializeGlobals(cmd *cobra.Command, args []string) {
|
func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||||
// set information
|
// Set version info.
|
||||||
info.Set("Portmaster", "", "GPLv3")
|
info.Set("Portmaster", "", "GPLv3")
|
||||||
|
|
||||||
// Configure metrics.
|
// Configure metrics.
|
||||||
|
@ -63,8 +76,12 @@ func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||||
// Configure user agent.
|
// Configure user agent.
|
||||||
updates.UserAgent = fmt.Sprintf("Portmaster Core (%s %s)", runtime.GOOS, runtime.GOARCH)
|
updates.UserAgent = fmt.Sprintf("Portmaster Core (%s %s)", runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
// Create service config.
|
// Configure service.
|
||||||
svcCfg = &service.ServiceConfig{
|
cmdbase.SvcFactory = func(svcCfg *service.ServiceConfig) (cmdbase.ServiceInstance, error) {
|
||||||
|
svc, err := service.New(svcCfg)
|
||||||
|
return svc, err
|
||||||
|
}
|
||||||
|
cmdbase.SvcConfig = &service.ServiceConfig{
|
||||||
BinDir: binDir,
|
BinDir: binDir,
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
|
|
||||||
|
@ -72,9 +89,18 @@ func initializeGlobals(cmd *cobra.Command, args []string) {
|
||||||
LogDir: logDir,
|
LogDir: logDir,
|
||||||
LogLevel: logLevel,
|
LogLevel: logLevel,
|
||||||
|
|
||||||
BinariesIndexURLs: service.DefaultStableBinaryIndexURLs,
|
BinariesIndexURLs: configure.DefaultStableBinaryIndexURLs,
|
||||||
IntelIndexURLs: service.DefaultIntelIndexURLs,
|
IntelIndexURLs: configure.DefaultIntelIndexURLs,
|
||||||
VerifyBinaryUpdates: service.BinarySigningTrustStore,
|
VerifyBinaryUpdates: configure.BinarySigningTrustStore,
|
||||||
VerifyIntelUpdates: service.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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -13,23 +12,17 @@ import (
|
||||||
"github.com/safing/portmaster/service/firewall/interception"
|
"github.com/safing/portmaster/service/firewall/interception"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var recoverCmd = &cobra.Command{
|
||||||
recoverCmd = &cobra.Command{
|
Use: "recover-iptables",
|
||||||
Use: "recover-iptables",
|
Short: "Clean up Portmaster rules in iptables",
|
||||||
Short: "Force an update of all components.",
|
RunE: recoverIPTables,
|
||||||
RunE: update,
|
}
|
||||||
}
|
|
||||||
|
|
||||||
recoverIPTables bool
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(recoverCmd)
|
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
|
// interception.DeactiveNfqueueFirewall uses coreos/go-iptables
|
||||||
// which shells out to the /sbin/iptables binary. As a result,
|
// 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
|
// 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 {
|
func setupDatabases(path string) error {
|
||||||
err := database.InitializeWithPath(path)
|
err := database.Initialize(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,12 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start logging.
|
// Start logging.
|
||||||
err := log.Start()
|
err := log.Start("trace", true, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to start logging: %s\n", err)
|
fmt.Printf("failed to start logging: %s\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
defer log.Shutdown()
|
defer log.Shutdown()
|
||||||
log.SetLogLevel(log.TraceLevel)
|
|
||||||
log.Info("starting traffic generator")
|
log.Info("starting traffic generator")
|
||||||
|
|
||||||
// Execute requests
|
// Execute requests
|
||||||
|
|
|
@ -238,13 +238,13 @@ export class EditProfileDialog implements OnInit, OnDestroy {
|
||||||
this.portapi.delete(icon.Value).subscribe();
|
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 !== '') {
|
if (this.iconData !== '') {
|
||||||
// save the new icon in the cache database
|
// 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.
|
// does not update the profile but just saves the file and returns the filename.
|
||||||
// So we still need to update the profile manually.
|
// So we still need to update the profile manually.
|
||||||
updateIcon = this.profileService
|
updateIcon = this.profileService
|
||||||
|
@ -261,7 +261,7 @@ export class EditProfileDialog implements OnInit, OnDestroy {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIXME(ppacher): reset presentationpath
|
// TODO(ppacher): reset presentationpath
|
||||||
} else {
|
} else {
|
||||||
// just clear out that there was an icon
|
// just clear out that there was an icon
|
||||||
this.profile.Icons = [];
|
this.profile.Icons = [];
|
||||||
|
|
|
@ -543,7 +543,7 @@ export class SfngNetqueryLineChartComponent<D extends SeriesData = any> implemen
|
||||||
.append("title")
|
.append("title")
|
||||||
.text(d => d.text)
|
.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
|
// or re-placed. For now, just remove them all
|
||||||
this.svgInner
|
this.svgInner
|
||||||
.select('.points')
|
.select('.points')
|
||||||
|
|
|
@ -184,7 +184,7 @@ export class SfngNetquerySearchbarComponent implements ControlValueAccessor, OnI
|
||||||
const queries: Observable<SfngSearchbarSuggestion<any>>[] = [];
|
const queries: Observable<SfngSearchbarSuggestion<any>>[] = [];
|
||||||
const queryKeys: (keyof Partial<NetqueryConnection>)[] = [];
|
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) {
|
if (!!parser.lastUnterminatedCondition) {
|
||||||
fields = [parser.lastUnterminatedCondition.type as keyof NetqueryConnection];
|
fields = [parser.lastUnterminatedCondition.type as keyof NetqueryConnection];
|
||||||
limit = 0;
|
limit = 0;
|
||||||
|
|
|
@ -21,7 +21,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
broadcastsResourcePath = "intel/portmaster/notifications.yaml"
|
broadcastsResourceName = "notifications.yaml"
|
||||||
|
|
||||||
broadcastNotificationIDPrefix = "broadcasts:"
|
broadcastNotificationIDPrefix = "broadcasts:"
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ type BroadcastNotification struct {
|
||||||
|
|
||||||
func broadcastNotify(ctx *mgr.WorkerCtx) error {
|
func broadcastNotify(ctx *mgr.WorkerCtx) error {
|
||||||
// Get broadcast notifications file, load it from disk and parse it.
|
// 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 {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get broadcast notifications update: %w", err)
|
return fmt.Errorf("failed to get broadcast notifications update: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@ import (
|
||||||
|
|
||||||
"github.com/safing/jess"
|
"github.com/safing/jess"
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
|
"github.com/safing/portmaster/service/configure"
|
||||||
|
"github.com/safing/portmaster/service/updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceConfig struct {
|
type ServiceConfig struct {
|
||||||
|
@ -76,11 +78,10 @@ func (sc *ServiceConfig) Init() error {
|
||||||
|
|
||||||
// Apply defaults for required fields.
|
// Apply defaults for required fields.
|
||||||
if len(sc.BinariesIndexURLs) == 0 {
|
if len(sc.BinariesIndexURLs) == 0 {
|
||||||
// FIXME: Select based on setting.
|
sc.BinariesIndexURLs = configure.DefaultStableBinaryIndexURLs
|
||||||
sc.BinariesIndexURLs = DefaultStableBinaryIndexURLs
|
|
||||||
}
|
}
|
||||||
if len(sc.IntelIndexURLs) == 0 {
|
if len(sc.IntelIndexURLs) == 0 {
|
||||||
sc.IntelIndexURLs = DefaultIntelIndexURLs
|
sc.IntelIndexURLs = configure.DefaultIntelIndexURLs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check log level.
|
// Check log level.
|
||||||
|
@ -109,3 +110,71 @@ func getCurrentBinaryFolder() (string, error) {
|
||||||
|
|
||||||
return installDir, nil
|
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
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ghodss/yaml"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/api"
|
"github.com/safing/portmaster/base/api"
|
||||||
"github.com/safing/portmaster/base/config"
|
"github.com/safing/portmaster/base/config"
|
||||||
"github.com/safing/portmaster/base/log"
|
"github.com/safing/portmaster/base/log"
|
||||||
"github.com/safing/portmaster/base/notifications"
|
"github.com/safing/portmaster/base/notifications"
|
||||||
"github.com/safing/portmaster/base/rng"
|
"github.com/safing/portmaster/base/rng"
|
||||||
|
"github.com/safing/portmaster/base/utils"
|
||||||
"github.com/safing/portmaster/base/utils/debug"
|
"github.com/safing/portmaster/base/utils/debug"
|
||||||
"github.com/safing/portmaster/service/compat"
|
"github.com/safing/portmaster/service/compat"
|
||||||
"github.com/safing/portmaster/service/process"
|
"github.com/safing/portmaster/service/process"
|
||||||
|
@ -149,6 +157,17 @@ func registerAPIEndpoints() error {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +189,113 @@ func restart(_ *api.Request) (msg string, err error) {
|
||||||
return "restart initiated", nil
|
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.
|
// debugInfo returns the debugging information for support requests.
|
||||||
func debugInfo(ar *api.Request) (data []byte, err error) {
|
func debugInfo(ar *api.Request) (data []byte, err error) {
|
||||||
// Create debug information helper.
|
// Create debug information helper.
|
||||||
|
@ -192,7 +318,7 @@ func debugInfo(ar *api.Request) (data []byte, err error) {
|
||||||
config.AddToDebugInfo(di)
|
config.AddToDebugInfo(di)
|
||||||
|
|
||||||
// Detailed information.
|
// Detailed information.
|
||||||
// TODO(vladimir): updates.AddToDebugInfo(di)
|
AddVersionsToDebugInfo(di)
|
||||||
compat.AddToDebugInfo(di)
|
compat.AddToDebugInfo(di)
|
||||||
module.instance.AddWorkerInfoToDebugInfo(di)
|
module.instance.AddWorkerInfoToDebugInfo(di)
|
||||||
di.AddGoroutineStack()
|
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"
|
"errors"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/safing/portmaster/base/api"
|
||||||
"github.com/safing/portmaster/service/mgr"
|
"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.
|
// Base is the base module.
|
||||||
type Base struct {
|
type Base struct {
|
||||||
mgr *mgr.Manager
|
mgr *mgr.Manager
|
||||||
|
@ -47,9 +51,9 @@ func New(instance instance) (*Base, error) {
|
||||||
instance: instance,
|
instance: instance,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := prep(instance); err != nil {
|
// Set api listen address.
|
||||||
return nil, err
|
api.SetDefaultAPIListenAddress(DefaultAPIListenAddress)
|
||||||
}
|
|
||||||
if err := registerDatabases(); err != nil {
|
if err := registerDatabases(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync/atomic"
|
"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/log"
|
||||||
"github.com/safing/portmaster/base/metrics"
|
"github.com/safing/portmaster/base/metrics"
|
||||||
"github.com/safing/portmaster/base/utils/debug"
|
"github.com/safing/portmaster/base/utils/debug"
|
||||||
|
@ -19,6 +21,11 @@ import (
|
||||||
"github.com/safing/portmaster/service/updates"
|
"github.com/safing/portmaster/service/updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var db = database.NewInterface(&database.Options{
|
||||||
|
Local: true,
|
||||||
|
Internal: true,
|
||||||
|
})
|
||||||
|
|
||||||
// Core is the core service module.
|
// Core is the core service module.
|
||||||
type Core struct {
|
type Core struct {
|
||||||
m *mgr.Manager
|
m *mgr.Manager
|
||||||
|
@ -56,8 +63,10 @@ func init() {
|
||||||
|
|
||||||
func prep() error {
|
func prep() error {
|
||||||
// init config
|
// init config
|
||||||
err := registerConfig()
|
if err := registerConfig(); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
|
}
|
||||||
|
if err := registerUpdateConfig(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,6 +86,10 @@ func start() error {
|
||||||
return fmt.Errorf("failed to start plattform-specific components: %w", err)
|
return fmt.Errorf("failed to start plattform-specific components: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup update system.
|
||||||
|
initUpdateConfig()
|
||||||
|
initVersionExport()
|
||||||
|
|
||||||
// Enable persistent metrics.
|
// Enable persistent metrics.
|
||||||
if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil {
|
if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil {
|
||||||
log.Warningf("core: failed to enable persisted metrics: %s", err)
|
log.Warningf("core: failed to enable persisted metrics: %s", err)
|
||||||
|
@ -116,6 +129,7 @@ type instance interface {
|
||||||
Shutdown()
|
Shutdown()
|
||||||
Restart()
|
Restart()
|
||||||
AddWorkerInfoToDebugInfo(di *debug.Info)
|
AddWorkerInfoToDebugInfo(di *debug.Info)
|
||||||
|
Config() *config.Config
|
||||||
BinaryUpdates() *updates.Updater
|
BinaryUpdates() *updates.Updater
|
||||||
IntelUpdates() *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
|
// 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)
|
binaryUpdateConfig, intelUpdateConfig, err := MakeUpdateConfigs(svcCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return instance, fmt.Errorf("create updates config: %w", err)
|
return instance, fmt.Errorf("create updates config: %w", err)
|
||||||
|
@ -183,6 +179,10 @@ func New(svcCfg *ServiceConfig) (*Instance, error) { //nolint:maintidx
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return instance, fmt.Errorf("create updates module: %w", err)
|
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)
|
instance.geoip, err = geoip.New(instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return instance, fmt.Errorf("create customlist module: %w", err)
|
return instance, fmt.Errorf("create customlist module: %w", err)
|
||||||
|
@ -708,3 +708,23 @@ func (i *Instance) ShutdownComplete() <-chan struct{} {
|
||||||
func (i *Instance) ExitCode() int {
|
func (i *Instance) ExitCode() int {
|
||||||
return int(i.exitCode.Load())
|
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
|
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.
|
// Download data from URL.
|
||||||
artifactData, err := d.downloadData(ctx, url)
|
artifactData, err := d.downloadData(ctx, url)
|
||||||
if err != nil {
|
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.
|
// Index represents a collection of artifacts with metadata.
|
||||||
type Index struct {
|
type Index struct {
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
Version string `json:"Version"`
|
Version string `json:"Version"`
|
||||||
Published time.Time `json:"Published"`
|
Published time.Time `json:"Published"`
|
||||||
Artifacts []Artifact `json:"Artifacts"`
|
Artifacts []*Artifact `json:"Artifacts"`
|
||||||
|
|
||||||
versionNum *semver.Version
|
versionNum *semver.Version
|
||||||
}
|
}
|
||||||
|
@ -173,7 +173,7 @@ func (index *Index) init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter artifacts by current platform.
|
// Filter artifacts by current platform.
|
||||||
filtered := make([]Artifact, 0)
|
filtered := make([]*Artifact, 0)
|
||||||
for _, a := range index.Artifacts {
|
for _, a := range index.Artifacts {
|
||||||
if a.Platform == "" || a.Platform == currentPlatform {
|
if a.Platform == "" || a.Platform == currentPlatform {
|
||||||
filtered = append(filtered, a)
|
filtered = append(filtered, a)
|
||||||
|
@ -189,6 +189,7 @@ func (index *Index) init() error {
|
||||||
a.versionNum = v
|
a.versionNum = v
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
a.Version = index.Version
|
||||||
a.versionNum = index.versionNum
|
a.versionNum = index.versionNum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ settings:
|
||||||
|
|
||||||
// GenerateIndexFromDir generates a index from a given folder.
|
// GenerateIndexFromDir generates a index from a given folder.
|
||||||
func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) { //nolint:maintidx
|
func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error) { //nolint:maintidx
|
||||||
artifacts := make(map[string]Artifact)
|
artifacts := make(map[string]*Artifact)
|
||||||
|
|
||||||
// Initialize.
|
// Initialize.
|
||||||
err := cfg.init()
|
err := cfg.init()
|
||||||
|
@ -187,11 +187,12 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
|
||||||
|
|
||||||
// Step 3: Create new Artifact.
|
// Step 3: Create new Artifact.
|
||||||
|
|
||||||
artifact := Artifact{}
|
artifact := &Artifact{}
|
||||||
|
|
||||||
// Check if the caller provided a template for the artifact.
|
// Check if the caller provided a template for the artifact.
|
||||||
if t, ok := cfg.Templates[identifier]; ok {
|
if t, ok := cfg.Templates[identifier]; ok {
|
||||||
artifact = t
|
fromTemplate := t
|
||||||
|
artifact = &fromTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set artifact properties.
|
// Set artifact properties.
|
||||||
|
@ -249,7 +250,7 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to slice and compute hashes.
|
// Convert to slice and compute hashes.
|
||||||
export := make([]Artifact, 0, len(artifacts))
|
export := make([]*Artifact, 0, len(artifacts))
|
||||||
for _, artifact := range artifacts {
|
for _, artifact := range artifacts {
|
||||||
// Compute hash.
|
// Compute hash.
|
||||||
hash, err := getSHA256(artifact.localFile, artifact.Unpack)
|
hash, err := getSHA256(artifact.localFile, artifact.Unpack)
|
||||||
|
@ -273,7 +274,7 @@ func GenerateIndexFromDir(sourceDir string, cfg IndexScanConfig) (*Index, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort final artifacts.
|
// Sort final artifacts.
|
||||||
slices.SortFunc(export, func(a, b Artifact) int {
|
slices.SortFunc(export, func(a, b *Artifact) int {
|
||||||
switch {
|
switch {
|
||||||
case a.Filename != b.Filename:
|
case a.Filename != b.Filename:
|
||||||
return strings.Compare(a.Filename, b.Filename)
|
return strings.Compare(a.Filename, b.Filename)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -132,9 +133,11 @@ type Updater struct {
|
||||||
|
|
||||||
EventResourcesUpdated *mgr.EventMgr[struct{}]
|
EventResourcesUpdated *mgr.EventMgr[struct{}]
|
||||||
|
|
||||||
corruptedInstallation bool
|
corruptedInstallation error
|
||||||
|
|
||||||
isUpdateRunning *abool.AtomicBool
|
isUpdateRunning *abool.AtomicBool
|
||||||
|
started *abool.AtomicBool
|
||||||
|
configureLock sync.Mutex
|
||||||
|
|
||||||
instance instance
|
instance instance
|
||||||
}
|
}
|
||||||
|
@ -150,6 +153,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
|
||||||
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
|
EventResourcesUpdated: mgr.NewEventMgr[struct{}](ResourceUpdateEvent, m),
|
||||||
|
|
||||||
isUpdateRunning: abool.NewBool(false),
|
isUpdateRunning: abool.NewBool(false),
|
||||||
|
started: abool.NewBool(false),
|
||||||
|
|
||||||
instance: instance,
|
instance: instance,
|
||||||
}
|
}
|
||||||
|
@ -166,6 +170,12 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
|
||||||
// Load index.
|
// Load index.
|
||||||
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify)
|
index, err := LoadIndex(filepath.Join(cfg.Directory, cfg.IndexFile), cfg.Verify)
|
||||||
if err == nil {
|
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
|
module.index = index
|
||||||
return module, nil
|
return module, nil
|
||||||
}
|
}
|
||||||
|
@ -173,6 +183,7 @@ func New(instance instance, name string, cfg Config) (*Updater, error) {
|
||||||
// Fall back to scanning the directory.
|
// Fall back to scanning the directory.
|
||||||
if !errors.Is(err, os.ErrNotExist) {
|
if !errors.Is(err, os.ErrNotExist) {
|
||||||
log.Errorf("updates/%s: invalid index file, falling back to dir scan: %s", cfg.Name, err)
|
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"})
|
index, err = GenerateIndexFromDir(cfg.Directory, IndexScanConfig{Version: "0.0.0"})
|
||||||
if err == nil && index.init() == nil {
|
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.
|
// Check if automatic downloads are enabled.
|
||||||
if !u.cfg.AutoDownload && !forceApply {
|
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 {
|
if u.cfg.Notify && u.instance.Notifications() != nil {
|
||||||
u.instance.Notifications().Notify(¬ifications.Notification{
|
u.instance.Notifications().Notify(¬ifications.Notification{
|
||||||
EventID: updateAvailableNotificationID,
|
EventID: updateAvailableNotificationID,
|
||||||
|
@ -304,6 +316,7 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
||||||
|
|
||||||
// Notify the user that an upgrade is available.
|
// Notify the user that an upgrade is available.
|
||||||
if !u.cfg.AutoApply && !forceApply {
|
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 {
|
if u.cfg.Notify && u.instance.Notifications() != nil {
|
||||||
u.instance.Notifications().Notify(¬ifications.Notification{
|
u.instance.Notifications().Notify(¬ifications.Notification{
|
||||||
EventID: updateAvailableNotificationID,
|
EventID: updateAvailableNotificationID,
|
||||||
|
@ -387,8 +400,15 @@ func (u *Updater) updateAndUpgrade(w *mgr.WorkerCtx, indexURLs []string, ignoreV
|
||||||
return nil
|
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 {
|
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 {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
return nil // Success!
|
return nil // Success!
|
||||||
|
@ -404,7 +424,7 @@ func (u *Updater) updateCheckWorker(w *mgr.WorkerCtx) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) upgradeWorker(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 {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
return nil // Success!
|
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.
|
// and is intended to be used only within a tool, not a service.
|
||||||
func (u *Updater) ForceUpdate() error {
|
func (u *Updater) ForceUpdate() error {
|
||||||
return u.m.Do("update and upgrade", func(w *mgr.WorkerCtx) 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
|
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.
|
// TriggerUpdateCheck triggers an update check.
|
||||||
func (u *Updater) TriggerUpdateCheck() {
|
func (u *Updater) TriggerUpdateCheck() {
|
||||||
u.updateCheckWorkerMgr.Go()
|
u.updateCheckWorkerMgr.Go()
|
||||||
|
@ -459,13 +506,17 @@ func (u *Updater) Manager() *mgr.Manager {
|
||||||
|
|
||||||
// Start starts the module.
|
// Start starts the module.
|
||||||
func (u *Updater) Start() error {
|
func (u *Updater) Start() error {
|
||||||
if u.corruptedInstallation && u.cfg.Notify && u.instance.Notifications() != nil {
|
u.configureLock.Lock()
|
||||||
// FIXME: this might make sense as a module state
|
defer u.configureLock.Unlock()
|
||||||
u.instance.Notifications().NotifyError(
|
|
||||||
corruptInstallationNotificationID,
|
if u.corruptedInstallation != nil && u.cfg.Notify && u.instance.Notifications() != nil {
|
||||||
"Install Corruption",
|
u.states.Add(mgr.State{
|
||||||
"Portmaster has detected that one or more of its own files have been corrupted. Please re-install the software.",
|
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.
|
// Check for updates automatically, if enabled.
|
||||||
|
@ -474,6 +525,8 @@ func (u *Updater) Start() error {
|
||||||
Repeat(updateTaskRepeatDuration).
|
Repeat(updateTaskRepeatDuration).
|
||||||
Delay(15 * time.Second)
|
Delay(15 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u.started.SetTo(true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -481,6 +534,62 @@ func (u *Updater) GetMainDir() string {
|
||||||
return u.cfg.Directory
|
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.
|
// 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) {
|
func (u *Updater) GetFile(name string) (*Artifact, error) {
|
||||||
u.indexLock.Lock()
|
u.indexLock.Lock()
|
||||||
|
@ -509,6 +618,7 @@ func (u *Updater) GetFile(name string) (*Artifact, error) {
|
||||||
|
|
||||||
// Stop stops the module.
|
// Stop stops the module.
|
||||||
func (u *Updater) Stop() error {
|
func (u *Updater) Stop() error {
|
||||||
|
u.started.SetTo(false)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,6 @@ import (
|
||||||
"github.com/safing/portmaster/base/log"
|
"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 (
|
const (
|
||||||
defaultFileMode = os.FileMode(0o0644)
|
defaultFileMode = os.FileMode(0o0644)
|
||||||
executableFileMode = os.FileMode(0o0744)
|
executableFileMode = os.FileMode(0o0744)
|
||||||
|
|
|
@ -3,17 +3,18 @@ package spn
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/safing/portmaster/base/api"
|
"github.com/safing/portmaster/base/api"
|
||||||
"github.com/safing/portmaster/base/config"
|
"github.com/safing/portmaster/base/config"
|
||||||
"github.com/safing/portmaster/base/database/dbmodule"
|
"github.com/safing/portmaster/base/database/dbmodule"
|
||||||
"github.com/safing/portmaster/base/log"
|
|
||||||
"github.com/safing/portmaster/base/metrics"
|
"github.com/safing/portmaster/base/metrics"
|
||||||
"github.com/safing/portmaster/base/notifications"
|
"github.com/safing/portmaster/base/notifications"
|
||||||
"github.com/safing/portmaster/base/rng"
|
"github.com/safing/portmaster/base/rng"
|
||||||
"github.com/safing/portmaster/base/runtime"
|
"github.com/safing/portmaster/base/runtime"
|
||||||
|
"github.com/safing/portmaster/service"
|
||||||
"github.com/safing/portmaster/service/core"
|
"github.com/safing/portmaster/service/core"
|
||||||
"github.com/safing/portmaster/service/core/base"
|
"github.com/safing/portmaster/service/core/base"
|
||||||
"github.com/safing/portmaster/service/intel/filterlists"
|
"github.com/safing/portmaster/service/intel/filterlists"
|
||||||
|
@ -79,27 +80,27 @@ type Instance struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new Portmaster service instance.
|
// 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.
|
// 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.ctx, instance.cancelCtx = context.WithCancel(context.Background())
|
||||||
instance.shutdownCtx, instance.cancelShutdownCtx = 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
|
// Base modules
|
||||||
instance.base, err = base.New(instance)
|
instance.base, err = base.New(instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -131,18 +132,22 @@ func New() (*Instance, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Service modules
|
// 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)
|
instance.core, err = core.New(instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return instance, fmt.Errorf("create core module: %w", err)
|
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)
|
instance.geoip, err = geoip.New(instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return instance, fmt.Errorf("create customlist module: %w", err)
|
return instance, fmt.Errorf("create customlist module: %w", err)
|
||||||
|
|
Loading…
Add table
Reference in a new issue