mirror of
https://github.com/safing/portmaster
synced 2025-04-07 20:49:10 +00:00
287 lines
6.3 KiB
Go
287 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"runtime/pprof"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/tevino/abool"
|
|
|
|
"github.com/safing/portmaster/base/api/client"
|
|
"github.com/safing/portmaster/base/dataroot"
|
|
"github.com/safing/portmaster/base/info"
|
|
"github.com/safing/portmaster/base/log"
|
|
"github.com/safing/portmaster/base/updater"
|
|
"github.com/safing/portmaster/base/utils"
|
|
"github.com/safing/portmaster/service/updates/helper"
|
|
)
|
|
|
|
var (
|
|
dataDir string
|
|
printStackOnExit bool
|
|
showVersion bool
|
|
|
|
apiClient = client.NewClient("127.0.0.1:817")
|
|
connected = abool.New()
|
|
shuttingDown = abool.New()
|
|
restarting = abool.New()
|
|
|
|
mainCtx, cancelMainCtx = context.WithCancel(context.Background())
|
|
mainWg = &sync.WaitGroup{}
|
|
|
|
dataRoot *utils.DirStructure
|
|
// Create registry.
|
|
registry = &updater.ResourceRegistry{
|
|
Name: "updates",
|
|
UpdateURLs: []string{
|
|
"https://updates.safing.io",
|
|
},
|
|
DevMode: false,
|
|
Online: false, // disable download of resources (this is job for the core).
|
|
}
|
|
)
|
|
|
|
const query = "query "
|
|
|
|
func init() {
|
|
flag.StringVar(&dataDir, "data", "", "set data directory")
|
|
flag.BoolVar(&printStackOnExit, "print-stack-on-exit", false, "prints the stack before of shutting down")
|
|
flag.BoolVar(&showVersion, "version", false, "show version and exit")
|
|
|
|
runtime.GOMAXPROCS(2)
|
|
}
|
|
|
|
func main() {
|
|
// parse flags
|
|
flag.Parse()
|
|
|
|
// set meta info
|
|
info.Set("Portmaster Notifier", "0.3.6", "GPLv3")
|
|
|
|
// check if meta info is ok
|
|
err := info.CheckVersion()
|
|
if err != nil {
|
|
fmt.Println("compile error: please compile using the provided build script")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// print help
|
|
// if modules.HelpFlag {
|
|
// flag.Usage()
|
|
// os.Exit(0)
|
|
// }
|
|
|
|
if showVersion {
|
|
fmt.Println(info.FullVersion())
|
|
os.Exit(0)
|
|
}
|
|
|
|
// auto detect
|
|
if dataDir == "" {
|
|
dataDir = detectDataDir()
|
|
}
|
|
|
|
// check data dir
|
|
if dataDir == "" {
|
|
fmt.Fprintln(os.Stderr, "please set the data directory using --data=/path/to/data/dir")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// switch to safe exec dir
|
|
err = os.Chdir(filepath.Join(dataDir, "exec"))
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "warning: failed to switch to safe exec dir: %s\n", err)
|
|
}
|
|
|
|
// start log writer
|
|
err = log.Start()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to start logging: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// load registry
|
|
err = configureRegistry(true)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "failed to load registry: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// connect to API
|
|
go apiClient.StayConnected()
|
|
go apiStatusMonitor()
|
|
|
|
// start subsystems
|
|
go tray()
|
|
go subsystemsClient()
|
|
go spnStatusClient()
|
|
go notifClient()
|
|
go startShutdownEventListener()
|
|
|
|
// Shutdown
|
|
// catch interrupt for clean shutdown
|
|
signalCh := make(chan os.Signal, 1)
|
|
signal.Notify(
|
|
signalCh,
|
|
os.Interrupt,
|
|
syscall.SIGHUP,
|
|
syscall.SIGINT,
|
|
syscall.SIGTERM,
|
|
syscall.SIGQUIT,
|
|
)
|
|
|
|
// wait for shutdown
|
|
select {
|
|
case <-signalCh:
|
|
fmt.Println(" <INTERRUPT>")
|
|
log.Warning("program was interrupted, shutting down")
|
|
case <-mainCtx.Done():
|
|
log.Warning("program is shutting down")
|
|
}
|
|
|
|
if printStackOnExit {
|
|
fmt.Println("=== PRINTING STACK ===")
|
|
_ = pprof.Lookup("goroutine").WriteTo(os.Stdout, 2)
|
|
fmt.Println("=== END STACK ===")
|
|
}
|
|
go func() {
|
|
time.Sleep(10 * time.Second)
|
|
fmt.Println("===== TAKING TOO LONG FOR SHUTDOWN - PRINTING STACK TRACES =====")
|
|
_ = pprof.Lookup("goroutine").WriteTo(os.Stdout, 2)
|
|
os.Exit(1)
|
|
}()
|
|
|
|
// clear all notifications
|
|
clearNotifications()
|
|
|
|
// shutdown
|
|
cancelMainCtx()
|
|
mainWg.Wait()
|
|
|
|
apiClient.Shutdown()
|
|
exitTray()
|
|
log.Shutdown()
|
|
|
|
os.Exit(0)
|
|
}
|
|
|
|
func apiStatusMonitor() {
|
|
for {
|
|
// Wait for connection.
|
|
<-apiClient.Online()
|
|
connected.Set()
|
|
triggerTrayUpdate()
|
|
|
|
// Wait for lost connection.
|
|
<-apiClient.Offline()
|
|
connected.UnSet()
|
|
triggerTrayUpdate()
|
|
}
|
|
}
|
|
|
|
func detectDataDir() string {
|
|
// get path of executable
|
|
binPath, err := os.Executable()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
// get directory
|
|
binDir := filepath.Dir(binPath)
|
|
// check if we in the updates directory
|
|
identifierDir := filepath.Join("updates", runtime.GOOS+"_"+runtime.GOARCH, "notifier")
|
|
// check if there is a match and return data dir
|
|
if strings.HasSuffix(binDir, identifierDir) {
|
|
return filepath.Clean(strings.TrimSuffix(binDir, identifierDir))
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func configureRegistry(mustLoadIndex bool) error {
|
|
// If dataDir is not set, check the environment variable.
|
|
if dataDir == "" {
|
|
dataDir = os.Getenv("PORTMASTER_DATA")
|
|
}
|
|
|
|
// If it's still empty, try to auto-detect it.
|
|
if dataDir == "" {
|
|
dataDir = detectInstallationDir()
|
|
}
|
|
|
|
// Finally, if it's still empty, the user must provide it.
|
|
if dataDir == "" {
|
|
return errors.New("please set the data directory using --data=/path/to/data/dir")
|
|
}
|
|
|
|
// Remove left over quotes.
|
|
dataDir = strings.Trim(dataDir, `\"`)
|
|
// Initialize data root.
|
|
err := dataroot.Initialize(dataDir, utils.PublicReadPermission)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize data root: %w", err)
|
|
}
|
|
dataRoot = dataroot.Root()
|
|
|
|
// Initialize registry.
|
|
err = registry.Initialize(dataRoot.ChildDir("updates", utils.PublicReadPermission))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return updateRegistryIndex(mustLoadIndex)
|
|
}
|
|
|
|
func detectInstallationDir() string {
|
|
exePath, err := filepath.Abs(os.Args[0])
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
parent := filepath.Dir(exePath) // parent should be "...\updates\windows_amd64\notifier"
|
|
stableJSONFile := filepath.Join(parent, "..", "..", "stable.json") // "...\updates\stable.json"
|
|
stat, err := os.Stat(stableJSONFile)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
if stat.IsDir() {
|
|
return ""
|
|
}
|
|
|
|
return parent
|
|
}
|
|
|
|
func updateRegistryIndex(mustLoadIndex bool) error {
|
|
// Set indexes based on the release channel.
|
|
warning := helper.SetIndexes(registry, "", false, false, false)
|
|
if warning != nil {
|
|
log.Warningf("%q", warning)
|
|
}
|
|
|
|
// Load indexes from disk or network, if needed and desired.
|
|
err := registry.LoadIndexes(context.Background())
|
|
if err != nil {
|
|
log.Warningf("error loading indexes %q", warning)
|
|
if mustLoadIndex {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Load versions from disk to know which others we have and which are available.
|
|
err = registry.ScanStorage("")
|
|
if err != nil {
|
|
log.Warningf("error during storage scan: %q\n", err)
|
|
}
|
|
|
|
registry.SelectVersions()
|
|
return nil
|
|
}
|