mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
Merge pull request #96 from safing/feature/portmaster-start
Refactor pmctl to new portmaster-start
This commit is contained in:
commit
b876ca3ca1
34 changed files with 1159 additions and 1003 deletions
7
cmds/portmaster-core/.gitignore
vendored
Normal file
7
cmds/portmaster-core/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Compiled binaries
|
||||
portmaster
|
||||
portmaster.exe
|
||||
dnsonly
|
||||
dnsonly.exe
|
||||
main
|
||||
main.exe
|
|
@ -7,7 +7,7 @@ COL_OFF="\033[00m"
|
|||
COL_BOLD="\033[01;01m"
|
||||
COL_RED="\033[31m"
|
||||
|
||||
destDirPart1="dist"
|
||||
destDirPart1="../../dist"
|
||||
destDirPart2="core"
|
||||
|
||||
function check {
|
6
cmds/portmaster-start/.gitignore
vendored
Normal file
6
cmds/portmaster-start/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
# binaries
|
||||
portmaster-start
|
||||
portmaster-start.exe
|
||||
|
||||
# test dir
|
||||
test
|
|
@ -19,9 +19,7 @@ import (
|
|||
"golang.org/x/sys/windows/svc/mgr"
|
||||
)
|
||||
|
||||
const (
|
||||
exeSuffix = ".exe"
|
||||
)
|
||||
const exeSuffix = ".exe"
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(installCmd)
|
||||
|
@ -57,39 +55,18 @@ var uninstallService = &cobra.Command{
|
|||
RunE: uninstallWindowsService,
|
||||
}
|
||||
|
||||
func getExePath() (string, error) {
|
||||
// get own filepath
|
||||
prog := os.Args[0]
|
||||
p, err := filepath.Abs(prog)
|
||||
func getAbsBinaryPath() (string, error) {
|
||||
p, err := filepath.Abs(os.Args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// check if the path is valid
|
||||
fi, err := os.Stat(p)
|
||||
if err == nil {
|
||||
if !fi.Mode().IsDir() {
|
||||
return p, nil
|
||||
}
|
||||
err = fmt.Errorf("%s is directory", p)
|
||||
}
|
||||
// check if we have a .exe extension, add and check if not
|
||||
if filepath.Ext(p) == "" {
|
||||
p += exeSuffix
|
||||
fi, err = os.Stat(p)
|
||||
if err == nil {
|
||||
if !fi.Mode().IsDir() {
|
||||
return p, nil
|
||||
}
|
||||
err = fmt.Errorf("%s is directory", p)
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func getServiceExecCommand(exePath string, escape bool) []string {
|
||||
return []string{
|
||||
maybeEscape(exePath, escape),
|
||||
"run",
|
||||
"core-service",
|
||||
"--data",
|
||||
maybeEscape(dataRoot.Path, escape),
|
||||
|
@ -126,7 +103,7 @@ func getRecoveryActions() (recoveryActions []mgr.RecoveryAction, resetPeriod uin
|
|||
|
||||
func installWindowsService(cmd *cobra.Command, args []string) error {
|
||||
// get exe path
|
||||
exePath, err := getExePath()
|
||||
exePath, err := getAbsBinaryPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get exe path: %s", err)
|
||||
}
|
||||
|
@ -180,7 +157,7 @@ func uninstallWindowsService(cmd *cobra.Command, args []string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer m.Disconnect() //nolint:errcheck // TODO
|
||||
defer m.Disconnect() //nolint:errcheck // we don't care if we failed to disconnect from the service manager, we're quitting anyway.
|
||||
|
||||
// open service
|
||||
s, err := m.OpenService(serviceName)
|
|
@ -35,13 +35,7 @@ func checkAndCreateInstanceLock(name string) (pid int32, err error) {
|
|||
// check if process exists
|
||||
p, err := processInfo.NewProcess(int32(parsedPid))
|
||||
if err == nil {
|
||||
// TODO: remove this workaround as soon as NewProcess really returns an error on windows when the process does not exist
|
||||
// Issue: https://github.com/shirou/gopsutil/issues/729
|
||||
_, err = p.Name()
|
||||
if err == nil {
|
||||
// process exists
|
||||
return p.Pid, nil
|
||||
}
|
||||
return p.Pid, nil
|
||||
}
|
||||
|
||||
// else create new lock
|
|
@ -6,7 +6,6 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/container"
|
||||
|
@ -35,7 +34,7 @@ func initializeLogFile(logFilePath string, identifier string, version string) *o
|
|||
metaSection, err := dsd.Dump(meta, dsd.JSON)
|
||||
if err != nil {
|
||||
log.Printf("failed to serialize header for log file %s: %s\n", logFilePath, err)
|
||||
finalizeLogFile(logFile, logFilePath)
|
||||
finalizeLogFile(logFile)
|
||||
return nil
|
||||
}
|
||||
c.AppendAsBlock(metaSection)
|
||||
|
@ -46,14 +45,16 @@ func initializeLogFile(logFilePath string, identifier string, version string) *o
|
|||
_, err = logFile.Write(c.CompileData())
|
||||
if err != nil {
|
||||
log.Printf("failed to write header for log file %s: %s\n", logFilePath, err)
|
||||
finalizeLogFile(logFile, logFilePath)
|
||||
finalizeLogFile(logFile)
|
||||
return nil
|
||||
}
|
||||
|
||||
return logFile
|
||||
}
|
||||
|
||||
func finalizeLogFile(logFile *os.File, logFilePath string) {
|
||||
func finalizeLogFile(logFile *os.File) {
|
||||
logFilePath := logFile.Name()
|
||||
|
||||
err := logFile.Close()
|
||||
if err != nil {
|
||||
log.Printf("failed to close log file %s: %s\n", logFilePath, err)
|
||||
|
@ -61,28 +62,38 @@ func finalizeLogFile(logFile *os.File, logFilePath string) {
|
|||
|
||||
// check file size
|
||||
stat, err := os.Stat(logFilePath)
|
||||
if err == nil {
|
||||
// delete if file is smaller than
|
||||
if stat.Size() < 200 { // header + info is about 150 bytes
|
||||
err := os.Remove(logFilePath)
|
||||
if err != nil {
|
||||
log.Printf("failed to delete empty log file %s: %s\n", logFilePath, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// delete if file is smaller than
|
||||
if stat.Size() >= 200 { // header + info is about 150 bytes
|
||||
return
|
||||
}
|
||||
|
||||
if err := os.Remove(logFilePath); err != nil {
|
||||
log.Printf("failed to delete empty log file %s: %s\n", logFilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
func initControlLogFile() *os.File {
|
||||
func getLogFile(options *Options, version, ext string) *os.File {
|
||||
// check logging dir
|
||||
logFileBasePath := filepath.Join(logsRoot.Path, "control")
|
||||
logFileBasePath := filepath.Join(logsRoot.Path, options.ShortIdentifier)
|
||||
err := logsRoot.EnsureAbsPath(logFileBasePath)
|
||||
if err != nil {
|
||||
log.Printf("failed to check/create log file folder %s: %s\n", logFileBasePath, err)
|
||||
}
|
||||
|
||||
// open log file
|
||||
logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.log", time.Now().UTC().Format("2006-01-02-15-04-05")))
|
||||
return initializeLogFile(logFilePath, "control/portmaster-control", info.Version())
|
||||
logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s%s", time.Now().UTC().Format("2006-01-02-15-04-05"), ext))
|
||||
return initializeLogFile(logFilePath, options.Identifier, version)
|
||||
}
|
||||
|
||||
func getPmStartLogFile(ext string) *os.File {
|
||||
return getLogFile(&Options{
|
||||
ShortIdentifier: "start",
|
||||
Identifier: "start/portmaster-start",
|
||||
}, info.Version(), ext)
|
||||
}
|
||||
|
||||
//nolint:deadcode,unused // false positive on linux, currently used by windows only
|
||||
|
@ -92,44 +103,13 @@ func logControlError(cErr error) {
|
|||
return
|
||||
}
|
||||
|
||||
// check logging dir
|
||||
logFileBasePath := filepath.Join(logsRoot.Path, "control")
|
||||
err := logsRoot.EnsureAbsPath(logFileBasePath)
|
||||
if err != nil {
|
||||
log.Printf("failed to check/create log file folder %s: %s\n", logFileBasePath, err)
|
||||
}
|
||||
|
||||
// open log file
|
||||
logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.error.log", time.Now().UTC().Format("2006-01-02-15-04-05")))
|
||||
errorFile := initializeLogFile(logFilePath, "control/portmaster-control", info.Version())
|
||||
errorFile := getPmStartLogFile(".error.log")
|
||||
if errorFile == nil {
|
||||
return
|
||||
}
|
||||
defer errorFile.Close()
|
||||
|
||||
// write error and close
|
||||
fmt.Fprintln(errorFile, cErr.Error())
|
||||
errorFile.Close()
|
||||
}
|
||||
|
||||
//nolint:deadcode,unused // TODO
|
||||
func logControlStack() {
|
||||
// check logging dir
|
||||
logFileBasePath := filepath.Join(logsRoot.Path, "control")
|
||||
err := logsRoot.EnsureAbsPath(logFileBasePath)
|
||||
if err != nil {
|
||||
log.Printf("failed to check/create log file folder %s: %s\n", logFileBasePath, err)
|
||||
}
|
||||
|
||||
// open log file
|
||||
logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.stack.log", time.Now().UTC().Format("2006-01-02-15-04-05")))
|
||||
errorFile := initializeLogFile(logFilePath, "control/portmaster-control", info.Version())
|
||||
if errorFile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// write error and close
|
||||
_ = pprof.Lookup("goroutine").WriteTo(errorFile, 2)
|
||||
errorFile.Close()
|
||||
}
|
||||
|
||||
//nolint:deadcode,unused // false positive on linux, currently used by windows only
|
234
cmds/portmaster-start/main.go
Normal file
234
cmds/portmaster-start/main.go
Normal file
|
@ -0,0 +1,234 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/safing/portbase/dataroot"
|
||||
"github.com/safing/portbase/info"
|
||||
portlog "github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/updater"
|
||||
"github.com/safing/portbase/utils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
dataDir string
|
||||
maxRetries int
|
||||
dataRoot *utils.DirStructure
|
||||
logsRoot *utils.DirStructure
|
||||
|
||||
// create registry
|
||||
registry = &updater.ResourceRegistry{
|
||||
Name: "updates",
|
||||
UpdateURLs: []string{
|
||||
"https://updates.safing.io",
|
||||
},
|
||||
Beta: false,
|
||||
DevMode: false,
|
||||
Online: true, // is disabled later based on command
|
||||
}
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "portmaster-start",
|
||||
Short: "Start Portmaster components",
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) {
|
||||
mustLoadIndex := cmd == updatesCmd
|
||||
if err := configureDataRoot(mustLoadIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := configureLogging(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Let cobra ignore if we are running as "GUI" or not
|
||||
cobra.MousetrapHelpText = ""
|
||||
|
||||
flags := rootCmd.PersistentFlags()
|
||||
{
|
||||
flags.StringVar(&dataDir, "data", "", "Configures the data directory. Alternatively, this can also be set via the environment variable PORTMASTER_DATA.")
|
||||
flags.IntVar(&maxRetries, "max-retries", 5, "Maximum number of retries when starting a Portmaster component")
|
||||
flags.BoolVar(&stdinSignals, "input-signals", false, "Emulate signals using stdid.")
|
||||
_ = rootCmd.MarkPersistentFlagDirname("data")
|
||||
_ = flags.MarkHidden("input-signals")
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
cobra.OnInitialize(initCobra)
|
||||
|
||||
// set meta info
|
||||
info.Set("Portmaster Start", "0.4.0", "AGPLv3", false)
|
||||
|
||||
// catch interrupt for clean shutdown
|
||||
signalCh := make(chan os.Signal, 2)
|
||||
signal.Notify(
|
||||
signalCh,
|
||||
os.Interrupt,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT,
|
||||
)
|
||||
|
||||
// start root command
|
||||
go func() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// wait for signals
|
||||
for sig := range signalCh {
|
||||
if childIsRunning.IsSet() {
|
||||
log.Printf("got %s signal (ignoring), waiting for child to exit...\n", sig)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("got %s signal, exiting... (not executing anything)\n", sig)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func initCobra() {
|
||||
// check if we are running in a console (try to attach to parent console if available)
|
||||
var err error
|
||||
runningInConsole, err = attachToParentConsole()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to attach to parent console: %s\n", err)
|
||||
}
|
||||
|
||||
// check if meta info is ok
|
||||
err = info.CheckVersion()
|
||||
if err != nil {
|
||||
log.Fatalf("compile error: please compile using the provided build script")
|
||||
}
|
||||
|
||||
// set up logging
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.LUTC)
|
||||
log.SetPrefix("[control] ")
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// not using portbase logger
|
||||
portlog.SetLogLevel(portlog.CriticalLevel)
|
||||
}
|
||||
|
||||
func configureDataRoot(mustLoadIndex bool) error {
|
||||
// The data directory is not
|
||||
// check for environment variable
|
||||
// PORTMASTER_DATA
|
||||
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 redundant escape characters and quotes
|
||||
dataDir = strings.Trim(dataDir, `\"`)
|
||||
// initialize dataroot
|
||||
err := dataroot.Initialize(dataDir, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize data root: %s", err)
|
||||
}
|
||||
dataRoot = dataroot.Root()
|
||||
|
||||
// initialize registry
|
||||
err = registry.Initialize(dataRoot.ChildDir("updates", 0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry.AddIndex(updater.Index{
|
||||
Path: "stable.json",
|
||||
Stable: true,
|
||||
Beta: false,
|
||||
})
|
||||
|
||||
// TODO: enable loading beta versions
|
||||
// registry.AddIndex(updater.Index{
|
||||
// Path: "beta.json",
|
||||
// Stable: false,
|
||||
// Beta: true,
|
||||
// })
|
||||
|
||||
return updateRegistryIndex(mustLoadIndex)
|
||||
}
|
||||
|
||||
func configureLogging() error {
|
||||
// set up logs root
|
||||
logsRoot = dataRoot.ChildDir("logs", 0777)
|
||||
err := logsRoot.Ensure()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize logs root: %s", err)
|
||||
}
|
||||
|
||||
// warn about CTRL-C on windows
|
||||
if runningInConsole && onWindows {
|
||||
log.Println("WARNING: portmaster-start is marked as a GUI application in order to get rid of the console window.")
|
||||
log.Println("WARNING: CTRL-C will immediately kill without clean shutdown.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateRegistryIndex(mustLoadIndex bool) error {
|
||||
err := registry.LoadIndexes(context.Background())
|
||||
if err != nil {
|
||||
log.Printf("WARNING: error loading indexes: %s\n", err)
|
||||
if mustLoadIndex {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = registry.ScanStorage("")
|
||||
if err != nil {
|
||||
log.Printf("WARNING: error during storage scan: %s\n", err)
|
||||
}
|
||||
|
||||
registry.SelectVersions()
|
||||
return nil
|
||||
}
|
||||
|
||||
func detectInstallationDir() string {
|
||||
exePath, err := filepath.Abs(os.Args[0])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
parent := filepath.Dir(exePath)
|
||||
stableJSONFile := filepath.Join(parent, "updates", "stable.json")
|
||||
stat, err := os.Stat(stableJSONFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if stat.IsDir() {
|
||||
return ""
|
||||
}
|
||||
|
||||
return parent
|
||||
}
|
|
@ -7,16 +7,16 @@ COL_OFF="\033[00m"
|
|||
COL_BOLD="\033[01;01m"
|
||||
COL_RED="\033[31m"
|
||||
|
||||
destDirPart1="../dist"
|
||||
destDirPart2="control"
|
||||
destDirPart1="../../dist"
|
||||
destDirPart2="start"
|
||||
|
||||
function check {
|
||||
# output
|
||||
output="pmctl"
|
||||
output="portmaster-start"
|
||||
# get version
|
||||
version=$(grep "info.Set" main.go | cut -d'"' -f4)
|
||||
# build versioned file name
|
||||
filename="portmaster-control_v${version//./-}"
|
||||
filename="portmaster-start_v${version//./-}"
|
||||
# platform
|
||||
platform="${GOOS}_${GOARCH}"
|
||||
if [[ $GOOS == "windows" ]]; then
|
||||
|
@ -28,19 +28,19 @@ function check {
|
|||
|
||||
# check if file exists
|
||||
if [[ -f $destPath ]]; then
|
||||
echo "[control] $platform $version already built"
|
||||
echo "[start] $platform $version already built"
|
||||
else
|
||||
echo -e "${COL_BOLD}[control] $platform $version${COL_OFF}"
|
||||
echo -e "${COL_BOLD}[start] $platform $version${COL_OFF}"
|
||||
fi
|
||||
}
|
||||
|
||||
function build {
|
||||
# output
|
||||
output="pmctl"
|
||||
output="portmaster-start"
|
||||
# get version
|
||||
version=$(grep "info.Set" main.go | cut -d'"' -f4)
|
||||
# build versioned file name
|
||||
filename="portmaster-control_v${version//./-}"
|
||||
filename="portmaster-start_v${version//./-}"
|
||||
# platform
|
||||
platform="${GOOS}_${GOARCH}"
|
||||
if [[ $GOOS == "windows" ]]; then
|
||||
|
@ -52,19 +52,19 @@ function build {
|
|||
|
||||
# check if file exists
|
||||
if [[ -f $destPath ]]; then
|
||||
echo "[control] $platform already built in version $version, skipping..."
|
||||
echo "[start] $platform already built in version $version, skipping..."
|
||||
return
|
||||
fi
|
||||
|
||||
# build
|
||||
./build
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo -e "\n${COL_BOLD}[control] $platform: ${COL_RED}BUILD FAILED.${COL_OFF}"
|
||||
echo -e "\n${COL_BOLD}[start] $platform: ${COL_RED}BUILD FAILED.${COL_OFF}"
|
||||
exit 1
|
||||
fi
|
||||
mkdir -p $(dirname $destPath)
|
||||
cp $output $destPath
|
||||
echo -e "\n${COL_BOLD}[control] $platform: successfully built.${COL_OFF}"
|
||||
echo -e "\n${COL_BOLD}[start] $platform: successfully built.${COL_OFF}"
|
||||
}
|
||||
|
||||
function check_all {
|
19
cmds/portmaster-start/recover_linux.go
Normal file
19
cmds/portmaster-start/recover_linux.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/safing/portmaster/firewall/interception"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var recoverIPTablesCmd = &cobra.Command{
|
||||
Use: "recover-iptables",
|
||||
Short: "Removes obsolete IP tables rules in case of an unclean shutdown",
|
||||
RunE: func(*cobra.Command, []string) error {
|
||||
return interception.DeactivateNfqueueFirewall()
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(recoverIPTablesCmd)
|
||||
}
|
383
cmds/portmaster-start/run.go
Normal file
383
cmds/portmaster-start/run.go
Normal file
|
@ -0,0 +1,383 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
const (
|
||||
// RestartExitCode is the exit code that any service started by portmaster-start
|
||||
// can return in order to trigger a restart after a clean shutdown.
|
||||
RestartExitCode = 23
|
||||
)
|
||||
|
||||
var (
|
||||
runningInConsole bool
|
||||
onWindows = runtime.GOOS == "windows"
|
||||
stdinSignals bool
|
||||
childIsRunning = abool.NewBool(false)
|
||||
)
|
||||
|
||||
// Options for starting component
|
||||
type Options struct {
|
||||
Name string
|
||||
Identifier string // component identifier
|
||||
ShortIdentifier string // populated automatically
|
||||
SuppressArgs bool // do not use any args
|
||||
AllowDownload bool // allow download of component if it is not yet available
|
||||
AllowHidingWindow bool // allow hiding the window of the subprocess
|
||||
NoOutput bool // do not use stdout/err if logging to file is available (did not fail to open log file)
|
||||
}
|
||||
|
||||
func init() {
|
||||
registerComponent([]Options{
|
||||
{
|
||||
Name: "Portmaster Core",
|
||||
Identifier: "core/portmaster-core",
|
||||
AllowDownload: true,
|
||||
AllowHidingWindow: true,
|
||||
},
|
||||
{
|
||||
Name: "Portmaster App",
|
||||
Identifier: "app/portmaster-app",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: false,
|
||||
},
|
||||
{
|
||||
Name: "Portmaster Notifier",
|
||||
Identifier: "notifier/portmaster-notifier",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func registerComponent(opts []Options) {
|
||||
for idx := range opts {
|
||||
opt := &opts[idx] // we need a copy
|
||||
if opt.ShortIdentifier == "" {
|
||||
opt.ShortIdentifier = path.Dir(opt.Identifier)
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: opt.ShortIdentifier,
|
||||
Short: "Run the " + opt.Name,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := run(opt, args)
|
||||
initiateShutdown(err)
|
||||
return err
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
showCmd.AddCommand(
|
||||
&cobra.Command{
|
||||
Use: opt.ShortIdentifier,
|
||||
Short: "Show command to execute the " + opt.Name,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return show(opt, args)
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func getExecArgs(opts *Options, cmdArgs []string) []string {
|
||||
if opts.SuppressArgs {
|
||||
return nil
|
||||
}
|
||||
|
||||
args := []string{"--data", dataDir}
|
||||
if stdinSignals {
|
||||
args = append(args, "--input-signals")
|
||||
}
|
||||
args = append(args, cmdArgs...)
|
||||
return args
|
||||
}
|
||||
|
||||
func run(opts *Options, cmdArgs []string) (err error) {
|
||||
// set download option
|
||||
registry.Online = opts.AllowDownload
|
||||
|
||||
if isShuttingDown() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// get original arguments
|
||||
// additional parameters can be specified using -- --some-parameter
|
||||
args := getExecArgs(opts, cmdArgs)
|
||||
|
||||
// check for duplicate instances
|
||||
if opts.ShortIdentifier == "core" {
|
||||
pid, _ := checkAndCreateInstanceLock(opts.ShortIdentifier)
|
||||
if pid != 0 {
|
||||
return fmt.Errorf("another instance of Portmaster Core is already running: PID %d", pid)
|
||||
}
|
||||
defer func() {
|
||||
err := deleteInstanceLock(opts.ShortIdentifier)
|
||||
if err != nil {
|
||||
log.Printf("failed to delete instance lock: %s\n", err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// notify service after some time
|
||||
go func() {
|
||||
// assume that after 3 seconds service has finished starting
|
||||
time.Sleep(3 * time.Second)
|
||||
startupComplete <- struct{}{}
|
||||
}()
|
||||
|
||||
// adapt identifier
|
||||
if onWindows {
|
||||
opts.Identifier += ".exe"
|
||||
}
|
||||
|
||||
// setup logging
|
||||
// init log file
|
||||
logFile := getPmStartLogFile(".log")
|
||||
if logFile != nil {
|
||||
// don't close logFile, will be closed by system
|
||||
if opts.NoOutput {
|
||||
log.Println("disabling log output to stdout... bye!")
|
||||
log.SetOutput(logFile)
|
||||
} else {
|
||||
log.SetOutput(io.MultiWriter(os.Stdout, logFile))
|
||||
}
|
||||
}
|
||||
|
||||
return runAndRestart(opts, args)
|
||||
}
|
||||
|
||||
func runAndRestart(opts *Options, args []string) error {
|
||||
tries := 0
|
||||
for {
|
||||
tryAgain, err := execute(opts, args)
|
||||
if err != nil {
|
||||
log.Printf("%s failed with: %s\n", opts.Identifier, err)
|
||||
tries++
|
||||
if tries >= maxRetries {
|
||||
log.Printf("encountered %d consecutive errors, giving up ...", tries)
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
tries = 0
|
||||
log.Printf("%s exited without error", opts.Identifier)
|
||||
}
|
||||
|
||||
if !tryAgain {
|
||||
return err
|
||||
}
|
||||
|
||||
// if a restart was requested `tries` is set to 0 so
|
||||
// this becomes a no-op.
|
||||
time.Sleep(time.Duration(2*tries) * time.Second)
|
||||
|
||||
if tries >= 2 || err == nil {
|
||||
// if we are constantly failing or a restart was requested
|
||||
// try to update the resources.
|
||||
log.Printf("updating registry index")
|
||||
_ = updateRegistryIndex(false) // will always return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fixExecPerm(path string) error {
|
||||
if onWindows {
|
||||
return nil
|
||||
}
|
||||
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stat %s: %w", path, err)
|
||||
}
|
||||
|
||||
if info.Mode() == 0755 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.Chmod(path, 0755); err != nil {
|
||||
return fmt.Errorf("failed to chmod %s: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyLogs(opts *Options, consoleSink io.Writer, version, ext string, logSource io.Reader, notifier chan<- struct{}) {
|
||||
defer func() { notifier <- struct{}{} }()
|
||||
|
||||
sink := consoleSink
|
||||
|
||||
fileSink := getLogFile(opts, version, ext)
|
||||
if fileSink != nil {
|
||||
defer finalizeLogFile(fileSink)
|
||||
if opts.NoOutput {
|
||||
sink = fileSink
|
||||
} else {
|
||||
sink = io.MultiWriter(consoleSink, fileSink)
|
||||
}
|
||||
}
|
||||
|
||||
if bytes, err := io.Copy(sink, logSource); err != nil {
|
||||
log.Printf("%s: writing logs failed after %d bytes: %s", fileSink.Name(), bytes, err)
|
||||
}
|
||||
}
|
||||
|
||||
func persistOutputStreams(opts *Options, version string, cmd *exec.Cmd) (chan struct{}, error) {
|
||||
var (
|
||||
done = make(chan struct{})
|
||||
copyNotifier = make(chan struct{}, 2)
|
||||
)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect stdout: %w", err)
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect stderr: %w", err)
|
||||
}
|
||||
|
||||
go copyLogs(opts, os.Stdout, version, ".log", stdout, copyNotifier)
|
||||
go copyLogs(opts, os.Stderr, version, ".error.log", stderr, copyNotifier)
|
||||
|
||||
go func() {
|
||||
<-copyNotifier
|
||||
<-copyNotifier
|
||||
close(copyNotifier)
|
||||
close(done)
|
||||
}()
|
||||
|
||||
return done, nil
|
||||
}
|
||||
|
||||
func execute(opts *Options, args []string) (cont bool, err error) {
|
||||
file, err := registry.GetFile(platform(opts.Identifier))
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("could not get component: %w", err)
|
||||
}
|
||||
|
||||
// check permission
|
||||
if err := fixExecPerm(file.Path()); err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
log.Printf("starting %s %s\n", file.Path(), strings.Join(args, " "))
|
||||
|
||||
// create command
|
||||
exc := exec.Command(file.Path(), args...) //nolint:gosec // everything is okay
|
||||
|
||||
if !runningInConsole && opts.AllowHidingWindow {
|
||||
// Windows only:
|
||||
// only hide (all) windows of program if we are not running in console and windows may be hidden
|
||||
hideWindow(exc)
|
||||
}
|
||||
|
||||
outputsWritten, err := persistOutputStreams(opts, file.Version(), exc)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
interrupt, err := getProcessSignalFunc(exc)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
|
||||
err = exc.Start()
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("failed to start %s: %w", opts.Identifier, err)
|
||||
}
|
||||
childIsRunning.Set()
|
||||
|
||||
// wait for completion
|
||||
finished := make(chan error, 1)
|
||||
go func() {
|
||||
defer close(finished)
|
||||
|
||||
<-outputsWritten
|
||||
// wait for process to return
|
||||
finished <- exc.Wait()
|
||||
// update status
|
||||
childIsRunning.UnSet()
|
||||
}()
|
||||
|
||||
// state change listeners
|
||||
select {
|
||||
case <-shuttingDown:
|
||||
if err := interrupt(); err != nil {
|
||||
log.Printf("failed to signal %s to shutdown: %s\n", opts.Identifier, err)
|
||||
err = exc.Process.Kill()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to kill %s: %w", opts.Identifier, err)
|
||||
}
|
||||
return false, fmt.Errorf("killed %s", opts.Identifier)
|
||||
}
|
||||
|
||||
// wait until shut down
|
||||
select {
|
||||
case <-finished:
|
||||
case <-time.After(3 * time.Minute): // portmaster core prints stack if not able to shutdown in 3 minutes, give it one more ...
|
||||
err = exc.Process.Kill()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to kill %s: %s", opts.Identifier, err)
|
||||
}
|
||||
return false, fmt.Errorf("killed %s", opts.Identifier)
|
||||
}
|
||||
return false, nil
|
||||
|
||||
case err := <-finished:
|
||||
return parseExitError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func getProcessSignalFunc(cmd *exec.Cmd) (func() error, error) {
|
||||
if stdinSignals {
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect stdin: %w", err)
|
||||
}
|
||||
|
||||
return func() error {
|
||||
_, err := fmt.Fprintln(stdin, "SIGINT")
|
||||
return err
|
||||
}, nil
|
||||
}
|
||||
|
||||
return func() error {
|
||||
return cmd.Process.Signal(os.Interrupt)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseExitError(err error) (restart bool, errWithCtx error) {
|
||||
if err == nil {
|
||||
// clean and coordinated exit
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if exErr, ok := err.(*exec.ExitError); ok {
|
||||
switch exErr.ProcessState.ExitCode() {
|
||||
case 0:
|
||||
return false, fmt.Errorf("clean exit with error: %w", err)
|
||||
case 1:
|
||||
return true, fmt.Errorf("error during execution: %w", err)
|
||||
case RestartExitCode:
|
||||
return true, nil
|
||||
default:
|
||||
return true, fmt.Errorf("unknown exit code %w", exErr)
|
||||
}
|
||||
}
|
||||
|
||||
return true, fmt.Errorf("unexpected error type: %w", err)
|
||||
}
|
|
@ -27,7 +27,7 @@ var (
|
|||
AllowDownload: true,
|
||||
AllowHidingWindow: false,
|
||||
NoOutput: true,
|
||||
})
|
||||
}, args)
|
||||
}),
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
|
@ -41,7 +41,7 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
runCmd.AddCommand(runCoreService)
|
||||
rootCmd.AddCommand(runCoreService)
|
||||
}
|
||||
|
||||
const serviceName = "PortmasterCore"
|
||||
|
@ -88,7 +88,7 @@ service:
|
|||
return ssec, errno
|
||||
}
|
||||
|
||||
func runService(cmd *cobra.Command, opts *Options) error {
|
||||
func runService(cmd *cobra.Command, opts *Options, cmdArgs []string) error {
|
||||
// check if we are running interactively
|
||||
isDebug, err := svc.IsAnInteractiveSession()
|
||||
if err != nil {
|
||||
|
@ -122,7 +122,8 @@ func runService(cmd *cobra.Command, opts *Options) error {
|
|||
go func() {
|
||||
// run slightly delayed
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
_ = handleRun(cmd, opts) // error handled by shutdown routine
|
||||
err := run(opts, getExecArgs(opts, cmdArgs))
|
||||
initiateShutdown(err)
|
||||
finishWg.Done()
|
||||
runWg.Done()
|
||||
}()
|
41
cmds/portmaster-start/show.go
Normal file
41
cmds/portmaster-start/show.go
Normal file
|
@ -0,0 +1,41 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(showCmd)
|
||||
// sub-commands of show are registered using registerComponent
|
||||
}
|
||||
|
||||
var showCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
PersistentPreRunE: func(*cobra.Command, []string) error {
|
||||
// all show sub-commands need the data-root but no logging.
|
||||
return configureDataRoot(false)
|
||||
},
|
||||
Short: "Show the command to run a Portmaster component yourself",
|
||||
}
|
||||
|
||||
func show(opts *Options, cmdArgs []string) error {
|
||||
// get original arguments
|
||||
args := getExecArgs(opts, cmdArgs)
|
||||
|
||||
// adapt identifier
|
||||
if onWindows {
|
||||
opts.Identifier += ".exe"
|
||||
}
|
||||
|
||||
file, err := registry.GetFile(platform(opts.Identifier))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get component: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s\n", file.Path(), strings.Join(args, " "))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -5,11 +5,10 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
startupComplete = make(chan struct{}) // signal that the start procedure completed (is never closed, just signaled once)
|
||||
shuttingDown = make(chan struct{}) // signal that we are shutting down (will be closed, may not be closed directly, use initiateShutdown)
|
||||
shutdownInitiated = false // not to be used directly
|
||||
startupComplete = make(chan struct{}) // signal that the start procedure completed (is never closed, just signaled once)
|
||||
shuttingDown = make(chan struct{}) // signal that we are shutting down (will be closed, may not be closed directly, use initiateShutdown)
|
||||
//nolint:deadcode,unused // false positive on linux, currently used by windows only
|
||||
shutdownError error // may not be read or written to directly
|
||||
shutdownError error // protected by shutdownLock
|
||||
shutdownLock sync.Mutex
|
||||
)
|
||||
|
||||
|
@ -17,13 +16,24 @@ func initiateShutdown(err error) {
|
|||
shutdownLock.Lock()
|
||||
defer shutdownLock.Unlock()
|
||||
|
||||
if !shutdownInitiated {
|
||||
shutdownInitiated = true
|
||||
select {
|
||||
case <-shuttingDown:
|
||||
return
|
||||
default:
|
||||
shutdownError = err
|
||||
close(shuttingDown)
|
||||
}
|
||||
}
|
||||
|
||||
func isShuttingDown() bool {
|
||||
select {
|
||||
case <-shuttingDown:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
//nolint:deadcode,unused // false positive on linux, currently used by windows only
|
||||
func getShutdownError() error {
|
||||
shutdownLock.Lock()
|
14
cmds/portmaster-start/snoretoast_windows.go
Normal file
14
cmds/portmaster-start/snoretoast_windows.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package main
|
||||
|
||||
func init() {
|
||||
registerComponent([]Options{
|
||||
{
|
||||
Name: "Portmaster SnoreToast Notifier",
|
||||
ShortIdentifier: "notifier-snoretoast", // would otherwise conflict with notifier.
|
||||
Identifier: "notifier/portmaster-snoretoast",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: true,
|
||||
SuppressArgs: true,
|
||||
},
|
||||
})
|
||||
}
|
|
@ -26,7 +26,7 @@ func downloadUpdates() error {
|
|||
if onWindows {
|
||||
registry.MandatoryUpdates = []string{
|
||||
platform("core/portmaster-core.exe"),
|
||||
platform("control/portmaster-control.exe"),
|
||||
platform("start/portmaster-start.exe"),
|
||||
platform("app/portmaster-app.exe"),
|
||||
platform("notifier/portmaster-notifier.exe"),
|
||||
platform("notifier/portmaster-snoretoast.exe"),
|
||||
|
@ -34,13 +34,21 @@ func downloadUpdates() error {
|
|||
} else {
|
||||
registry.MandatoryUpdates = []string{
|
||||
platform("core/portmaster-core"),
|
||||
platform("control/portmaster-control"),
|
||||
platform("start/portmaster-start"),
|
||||
platform("app/portmaster-app"),
|
||||
platform("notifier/portmaster-notifier"),
|
||||
}
|
||||
}
|
||||
|
||||
// ok, now we want logging.
|
||||
// add updates that we require on all platforms.
|
||||
registry.MandatoryUpdates = append(
|
||||
registry.MandatoryUpdates,
|
||||
"all/ui/modules/base.zip",
|
||||
)
|
||||
|
||||
// logging is configured as a persistent pre-run method inherited from
|
||||
// the root command but since we don't use run.Run() we need to start
|
||||
// logging ourself.
|
||||
err := log.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("failed to start logging: %s\n", err)
|
80
cmds/portmaster-start/version.go
Normal file
80
cmds/portmaster-start/version.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/safing/portbase/info"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var showShortVersion bool
|
||||
var showAllVersions bool
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Display various portmaster versions",
|
||||
Args: cobra.NoArgs,
|
||||
PersistentPreRunE: func(*cobra.Command, []string) error {
|
||||
if showAllVersions {
|
||||
// if we are going to show all component versions
|
||||
// we need the dataroot to be configured.
|
||||
if err := configureDataRoot(false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
RunE: func(*cobra.Command, []string) error {
|
||||
if !showAllVersions {
|
||||
if showShortVersion {
|
||||
fmt.Println(info.Version())
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(info.FullVersion())
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("portmaster-start %s\n\n", info.Version())
|
||||
fmt.Printf("Assets:\n")
|
||||
|
||||
all := registry.Export()
|
||||
keys := make([]string, 0, len(all))
|
||||
for identifier := range all {
|
||||
keys = append(keys, identifier)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
|
||||
for _, identifier := range keys {
|
||||
res := all[identifier]
|
||||
|
||||
if showShortVersion {
|
||||
// in "short" mode, skip all resources that are irrelevant on that platform
|
||||
if !strings.HasPrefix(identifier, "all") && !strings.HasPrefix(identifier, runtime.GOOS) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(tw, " %s\t%s\n", identifier, res.SelectedVersion.VersionNumber)
|
||||
}
|
||||
tw.Flush()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
flags := versionCmd.Flags()
|
||||
{
|
||||
flags.BoolVar(&showShortVersion, "short", false, "Print only the version number.")
|
||||
flags.BoolVar(&showAllVersions, "all", false, "Dump versions for all assets.")
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
1
cmds/uptool/.gitignore
vendored
Normal file
1
cmds/uptool/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
uptool
|
34
cmds/uptool/main.go
Normal file
34
cmds/uptool/main.go
Normal file
|
@ -0,0 +1,34 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/safing/portbase/updater"
|
||||
"github.com/safing/portbase/utils"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var registry *updater.ResourceRegistry
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "uptool",
|
||||
Short: "A simple tool to assist in the update and release process",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
absPath, err := filepath.Abs(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry = &updater.ResourceRegistry{}
|
||||
return registry.Initialize(utils.NewDirStructure(absPath, 0o755))
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
80
cmds/uptool/scan.go
Normal file
80
cmds/uptool/scan.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(scanCmd)
|
||||
}
|
||||
|
||||
var scanCmd = &cobra.Command{
|
||||
Use: "scan",
|
||||
Short: "Scan the specified directory and print the result",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: scan,
|
||||
}
|
||||
|
||||
func scan(cmd *cobra.Command, args []string) error {
|
||||
err := scanStorage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// export beta
|
||||
data, err := json.MarshalIndent(exportSelected(true), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// print
|
||||
fmt.Println("beta:")
|
||||
fmt.Println(string(data))
|
||||
|
||||
// export stable
|
||||
data, err = json.MarshalIndent(exportSelected(false), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// print
|
||||
fmt.Println("\nstable:")
|
||||
fmt.Println(string(data))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func scanStorage() error {
|
||||
files, err := ioutil.ReadDir(registry.StorageDir().Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// scan "all" and all "os_platform" dirs
|
||||
for _, file := range files {
|
||||
if file.IsDir() && (file.Name() == "all" || strings.Contains(file.Name(), "_")) {
|
||||
err := registry.ScanStorage(filepath.Join(registry.StorageDir().Path, file.Name()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func exportSelected(beta bool) map[string]string {
|
||||
registry.SetBeta(beta)
|
||||
registry.SelectVersions()
|
||||
export := registry.Export()
|
||||
|
||||
versions := make(map[string]string)
|
||||
for _, rv := range export {
|
||||
versions[rv.Identifier] = rv.SelectedVersion.VersionNumber
|
||||
}
|
||||
return versions
|
||||
}
|
64
cmds/uptool/update.go
Normal file
64
cmds/uptool/update.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update scans the specified directory and registry the index and symlink structure",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: update,
|
||||
}
|
||||
|
||||
func update(cmd *cobra.Command, args []string) error {
|
||||
err := scanStorage()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// export beta
|
||||
data, err := json.MarshalIndent(exportSelected(true), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// print
|
||||
fmt.Println("beta:")
|
||||
fmt.Println(string(data))
|
||||
// write index
|
||||
err = ioutil.WriteFile(filepath.Join(registry.StorageDir().Dir, "beta.json"), data, 0o644) //nolint:gosec // 0644 is intended
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// export stable
|
||||
data, err = json.MarshalIndent(exportSelected(false), "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// print
|
||||
fmt.Println("\nstable:")
|
||||
fmt.Println(string(data))
|
||||
// write index
|
||||
err = ioutil.WriteFile(filepath.Join(registry.StorageDir().Dir, "stable.json"), data, 0o644) //nolint:gosec // 0644 is intended
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// create symlinks
|
||||
err = registry.CreateSymlinks(registry.StorageDir().ChildDir("latest", 0o755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("\nstable symlinks created")
|
||||
|
||||
return nil
|
||||
}
|
|
@ -108,69 +108,58 @@ func init() {
|
|||
}
|
||||
|
||||
func activateNfqueueFirewall() error {
|
||||
|
||||
// IPv4
|
||||
ip4tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
if err != nil {
|
||||
if err := activateIPTables(iptables.ProtocolIPv4, v4rules, v4once, v4chains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, chain := range v4chains {
|
||||
splittedRule := strings.Split(chain, " ")
|
||||
if err = ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := activateIPTables(iptables.ProtocolIPv6, v6rules, v6once, v6chains); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rule := range v4rules {
|
||||
splittedRule := strings.Split(rule, " ")
|
||||
if err = ip4tables.Append(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var ok bool
|
||||
for _, rule := range v4once {
|
||||
splittedRule := strings.Split(rule, " ")
|
||||
ok, err = ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
if err = ip4tables.Insert(splittedRule[0], splittedRule[1], 1, splittedRule[2:]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
// DeactivateNfqueueFirewall drops portmaster related IP tables rules.
|
||||
func DeactivateNfqueueFirewall() error {
|
||||
// IPv4
|
||||
errV4 := deactivateIPTables(iptables.ProtocolIPv4, v4once, v4chains)
|
||||
|
||||
// IPv6
|
||||
ip6tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
if errV6 := deactivateIPTables(iptables.ProtocolIPv6, v6once, v6chains); errV6 != nil && errV4 == nil {
|
||||
return errV6
|
||||
}
|
||||
|
||||
return errV4
|
||||
}
|
||||
|
||||
func activateIPTables(protocol iptables.Protocol, rules, once, chains []string) error {
|
||||
tbls, err := iptables.NewWithProtocol(protocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, chain := range v6chains {
|
||||
for _, chain := range chains {
|
||||
splittedRule := strings.Split(chain, " ")
|
||||
if err = ip6tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil {
|
||||
if err = tbls.ClearChain(splittedRule[0], splittedRule[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range v6rules {
|
||||
for _, rule := range rules {
|
||||
splittedRule := strings.Split(rule, " ")
|
||||
if err = ip6tables.Append(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil {
|
||||
if err = tbls.Append(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, rule := range v6once {
|
||||
for _, rule := range once {
|
||||
splittedRule := strings.Split(rule, " ")
|
||||
ok, err := ip6tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
|
||||
ok, err := tbls.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
if err = ip6tables.Insert(splittedRule[0], splittedRule[1], 1, splittedRule[2:]...); err != nil {
|
||||
if err = tbls.Insert(splittedRule[0], splittedRule[1], 1, splittedRule[2:]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -179,67 +168,37 @@ func activateNfqueueFirewall() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func deactivateNfqueueFirewall() error {
|
||||
// IPv4
|
||||
ip4tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
func deactivateIPTables(protocol iptables.Protocol, rules, chains []string) error {
|
||||
tbls, err := iptables.NewWithProtocol(protocol)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var ok bool
|
||||
for _, rule := range v4once {
|
||||
var firstErr error
|
||||
for _, rule := range rules {
|
||||
splittedRule := strings.Split(rule, " ")
|
||||
ok, err = ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
ok, err := tbls.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
if ok {
|
||||
if err = ip4tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil {
|
||||
return err
|
||||
if err = tbls.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, chain := range v4chains {
|
||||
for _, chain := range chains {
|
||||
splittedRule := strings.Split(chain, " ")
|
||||
if err = ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil {
|
||||
return err
|
||||
if err = tbls.ClearChain(splittedRule[0], splittedRule[1]); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
if err = ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil {
|
||||
return err
|
||||
if err = tbls.DeleteChain(splittedRule[0], splittedRule[1]); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
|
||||
// IPv6
|
||||
ip6tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv6)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, rule := range v6once {
|
||||
splittedRule := strings.Split(rule, " ")
|
||||
ok, err := ip6tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
if err = ip6tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, chain := range v6chains {
|
||||
splittedRule := strings.Split(chain, " ")
|
||||
if err := ip6tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ip6tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return firstErr
|
||||
}
|
||||
|
||||
// StartNfqueueInterception starts the nfqueue interception.
|
||||
|
@ -293,7 +252,7 @@ func StopNfqueueInterception() error {
|
|||
in6Queue.Destroy()
|
||||
}
|
||||
|
||||
err := deactivateNfqueueFirewall()
|
||||
err := DeactivateNfqueueFirewall()
|
||||
if err != nil {
|
||||
return fmt.Errorf("interception: error while deactivating nfqueue: %s", err)
|
||||
}
|
||||
|
|
22
pack
22
pack
|
@ -5,12 +5,19 @@ cd "$baseDir"
|
|||
|
||||
# first check what will be built
|
||||
|
||||
function packAll() {
|
||||
for i in ./cmds/* ; do
|
||||
if [ -e $i/pack ]; then
|
||||
$i/pack $1
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "pack list:"
|
||||
echo ""
|
||||
|
||||
./pmctl/pack check
|
||||
./pack_core check
|
||||
packAll check
|
||||
|
||||
# confirm
|
||||
|
||||
|
@ -20,15 +27,8 @@ echo ""
|
|||
|
||||
# build
|
||||
|
||||
./pmctl/pack build
|
||||
if [[ $? -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
./pack_core build
|
||||
if [[ $? -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
set -e
|
||||
packAll build
|
||||
|
||||
echo ""
|
||||
echo "finished packing."
|
||||
|
|
6
pmctl/.gitignore
vendored
6
pmctl/.gitignore
vendored
|
@ -1,6 +0,0 @@
|
|||
# binaries
|
||||
pmctl
|
||||
pmctl.exe
|
||||
|
||||
# test dir
|
||||
test
|
227
pmctl/main.go
227
pmctl/main.go
|
@ -1,227 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/safing/portbase/dataroot"
|
||||
"github.com/safing/portbase/info"
|
||||
portlog "github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/updater"
|
||||
"github.com/safing/portbase/utils"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
dataDir string
|
||||
databaseDir string
|
||||
dataRoot *utils.DirStructure
|
||||
logsRoot *utils.DirStructure
|
||||
|
||||
showShortVersion bool
|
||||
showFullVersion bool
|
||||
|
||||
// create registry
|
||||
registry = &updater.ResourceRegistry{
|
||||
Name: "updates",
|
||||
UpdateURLs: []string{
|
||||
"https://updates.safing.io",
|
||||
},
|
||||
Beta: false,
|
||||
DevMode: false,
|
||||
Online: true, // is disabled later based on command
|
||||
}
|
||||
|
||||
rootCmd = &cobra.Command{
|
||||
Use: "portmaster-control",
|
||||
Short: "Controller for all portmaster components",
|
||||
PersistentPreRunE: cmdSetup,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if showShortVersion {
|
||||
fmt.Println(info.Version())
|
||||
return nil
|
||||
}
|
||||
if showFullVersion {
|
||||
fmt.Println(info.FullVersion())
|
||||
return nil
|
||||
}
|
||||
return cmd.Help()
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Let cobra ignore if we are running as "GUI" or not
|
||||
cobra.MousetrapHelpText = ""
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "Configures the data directory. Alternatively, this can also be set via the environment variable PORTMASTER_DATA.")
|
||||
rootCmd.PersistentFlags().StringVar(&databaseDir, "db", "", "Alias to --data (deprecated)")
|
||||
_ = rootCmd.MarkPersistentFlagDirname("data")
|
||||
_ = rootCmd.MarkPersistentFlagDirname("db")
|
||||
rootCmd.Flags().BoolVar(&showFullVersion, "version", false, "Print version of portmaster-control.")
|
||||
rootCmd.Flags().BoolVar(&showShortVersion, "ver", false, "Print version number only")
|
||||
}
|
||||
|
||||
func main() {
|
||||
// set meta info
|
||||
info.Set("Portmaster Control", "0.3.5", "AGPLv3", false)
|
||||
|
||||
// for debugging
|
||||
// log.Start()
|
||||
// log.SetLogLevel(log.TraceLevel)
|
||||
// go func() {
|
||||
// time.Sleep(3 * time.Second)
|
||||
// pprof.Lookup("goroutine").WriteTo(os.Stdout, 2)
|
||||
// os.Exit(1)
|
||||
// }()
|
||||
|
||||
// catch interrupt for clean shutdown
|
||||
signalCh := make(chan os.Signal, 2)
|
||||
signal.Notify(
|
||||
signalCh,
|
||||
os.Interrupt,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT,
|
||||
)
|
||||
|
||||
// start root command
|
||||
go func() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// for debugging windows service (no stdout/err)
|
||||
// go func() {
|
||||
// time.Sleep(10 * time.Second)
|
||||
// // initiateShutdown(nil)
|
||||
// // logControlStack()
|
||||
// }()
|
||||
|
||||
// wait for signals
|
||||
for sig := range signalCh {
|
||||
if childIsRunning.IsSet() {
|
||||
log.Printf("got %s signal (ignoring), waiting for child to exit...\n", sig)
|
||||
} else {
|
||||
log.Printf("got %s signal, exiting... (not executing anything)\n", sig)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cmdSetup(cmd *cobra.Command, args []string) (err error) {
|
||||
// check if we are running in a console (try to attach to parent console if available)
|
||||
runningInConsole, err = attachToParentConsole()
|
||||
if err != nil {
|
||||
log.Printf("failed to attach to parent console: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// set up logging
|
||||
log.SetFlags(log.Ldate | log.Ltime | log.LUTC)
|
||||
log.SetPrefix("[control] ")
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
// not using portbase logger
|
||||
portlog.SetLogLevel(portlog.CriticalLevel)
|
||||
|
||||
// data directory
|
||||
if !showShortVersion && !showFullVersion {
|
||||
// set data root
|
||||
// backwards compatibility
|
||||
if dataDir == "" {
|
||||
dataDir = databaseDir
|
||||
}
|
||||
|
||||
// check for environment variable
|
||||
// PORTMASTER_DATA
|
||||
if dataDir == "" {
|
||||
dataDir = os.Getenv("PORTMASTER_DATA")
|
||||
}
|
||||
|
||||
// check data dir
|
||||
if dataDir == "" {
|
||||
return errors.New("please set the data directory using --data=/path/to/data/dir")
|
||||
}
|
||||
|
||||
// remove redundant escape characters and quotes
|
||||
dataDir = strings.Trim(dataDir, `\"`)
|
||||
// initialize dataroot
|
||||
err = dataroot.Initialize(dataDir, 0755)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize data root: %s", err)
|
||||
}
|
||||
dataRoot = dataroot.Root()
|
||||
|
||||
// initialize registry
|
||||
err := registry.Initialize(dataRoot.ChildDir("updates", 0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry.AddIndex(updater.Index{
|
||||
Path: "stable.json",
|
||||
Stable: true,
|
||||
Beta: false,
|
||||
})
|
||||
|
||||
// TODO: enable loading beta versions
|
||||
// registry.AddIndex(updater.Index{
|
||||
// Path: "beta.json",
|
||||
// Stable: false,
|
||||
// Beta: true,
|
||||
// })
|
||||
|
||||
updateRegistryIndex()
|
||||
}
|
||||
|
||||
// logs and warning
|
||||
if !showShortVersion && !showFullVersion && !strings.Contains(cmd.CommandPath(), " show ") {
|
||||
// set up logs root
|
||||
logsRoot = dataRoot.ChildDir("logs", 0777)
|
||||
err = logsRoot.Ensure()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize logs root: %s", err)
|
||||
}
|
||||
|
||||
// warn about CTRL-C on windows
|
||||
if runningInConsole && onWindows {
|
||||
log.Println("WARNING: portmaster-control is marked as a GUI application in order to get rid of the console window.")
|
||||
log.Println("WARNING: CTRL-C will immediately kill without clean shutdown.")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateRegistryIndex() {
|
||||
err := registry.LoadIndexes(context.TODO())
|
||||
if err != nil {
|
||||
log.Printf("WARNING: error loading indexes: %s\n", err)
|
||||
}
|
||||
|
||||
err = registry.ScanStorage("")
|
||||
if err != nil {
|
||||
log.Printf("WARNING: error during storage scan: %s\n", err)
|
||||
}
|
||||
|
||||
registry.SelectVersions()
|
||||
}
|
406
pmctl/run.go
406
pmctl/run.go
|
@ -1,406 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tevino/abool"
|
||||
)
|
||||
|
||||
const (
|
||||
restartCode = 23
|
||||
)
|
||||
|
||||
var (
|
||||
runningInConsole bool
|
||||
onWindows = runtime.GOOS == "windows"
|
||||
|
||||
childIsRunning = abool.NewBool(false)
|
||||
)
|
||||
|
||||
// Options for starting component
|
||||
type Options struct {
|
||||
Identifier string // component identifier
|
||||
ShortIdentifier string // populated automatically
|
||||
SuppressArgs bool // do not use any args
|
||||
AllowDownload bool // allow download of component if it is not yet available
|
||||
AllowHidingWindow bool // allow hiding the window of the subprocess
|
||||
NoOutput bool // do not use stdout/err if logging to file is available (did not fail to open log file)
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(runCmd)
|
||||
runCmd.AddCommand(runCore)
|
||||
runCmd.AddCommand(runApp)
|
||||
runCmd.AddCommand(runNotifier)
|
||||
}
|
||||
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run a Portmaster component in the foreground",
|
||||
}
|
||||
|
||||
var runCore = &cobra.Command{
|
||||
Use: "core",
|
||||
Short: "Run the Portmaster Core",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return handleRun(cmd, &Options{
|
||||
Identifier: "core/portmaster-core",
|
||||
AllowDownload: true,
|
||||
AllowHidingWindow: true,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
var runApp = &cobra.Command{
|
||||
Use: "app",
|
||||
Short: "Run the Portmaster App",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return handleRun(cmd, &Options{
|
||||
Identifier: "app/portmaster-app",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: false,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
var runNotifier = &cobra.Command{
|
||||
Use: "notifier",
|
||||
Short: "Run the Portmaster Notifier",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return handleRun(cmd, &Options{
|
||||
Identifier: "notifier/portmaster-notifier",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: true,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
func handleRun(cmd *cobra.Command, opts *Options) (err error) {
|
||||
err = run(cmd, opts)
|
||||
initiateShutdown(err)
|
||||
return
|
||||
}
|
||||
|
||||
func run(cmd *cobra.Command, opts *Options) (err error) { //nolint:gocognit
|
||||
|
||||
// set download option
|
||||
registry.Online = opts.AllowDownload
|
||||
|
||||
// parse identifier
|
||||
opts.ShortIdentifier = path.Dir(opts.Identifier)
|
||||
|
||||
// check for concurrent error (eg. service)
|
||||
shutdownLock.Lock()
|
||||
alreadyDead := shutdownInitiated
|
||||
shutdownLock.Unlock()
|
||||
if alreadyDead {
|
||||
return
|
||||
}
|
||||
|
||||
// check for duplicate instances
|
||||
if opts.ShortIdentifier == "core" {
|
||||
pid, _ := checkAndCreateInstanceLock(opts.ShortIdentifier)
|
||||
if pid != 0 {
|
||||
return fmt.Errorf("another instance of Portmaster Core is already running: PID %d", pid)
|
||||
}
|
||||
defer func() {
|
||||
err := deleteInstanceLock(opts.ShortIdentifier)
|
||||
if err != nil {
|
||||
log.Printf("failed to delete instance lock: %s\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// notify service after some time
|
||||
go func() {
|
||||
// assume that after 3 seconds service has finished starting
|
||||
time.Sleep(3 * time.Second)
|
||||
startupComplete <- struct{}{}
|
||||
}()
|
||||
|
||||
// get original arguments
|
||||
var args []string
|
||||
if len(os.Args) < 4 {
|
||||
return cmd.Help()
|
||||
}
|
||||
args = os.Args[3:]
|
||||
if opts.SuppressArgs {
|
||||
args = nil
|
||||
}
|
||||
|
||||
// adapt identifier
|
||||
if onWindows {
|
||||
opts.Identifier += ".exe"
|
||||
}
|
||||
|
||||
// setup logging
|
||||
// init log file
|
||||
logFile := initControlLogFile()
|
||||
if logFile != nil {
|
||||
// don't close logFile, will be closed by system
|
||||
if opts.NoOutput {
|
||||
log.Println("disabling log output to stdout... bye!")
|
||||
log.SetOutput(logFile)
|
||||
} else {
|
||||
log.SetOutput(io.MultiWriter(os.Stdout, logFile))
|
||||
}
|
||||
}
|
||||
|
||||
// run
|
||||
tries := 0
|
||||
for {
|
||||
// normal execution
|
||||
tryAgain := false
|
||||
tryAgain, err = execute(opts, args)
|
||||
switch {
|
||||
case tryAgain && err != nil:
|
||||
// temporary? execution error
|
||||
log.Printf("execution of %s failed: %s\n", opts.Identifier, err)
|
||||
tries++
|
||||
if tries >= 5 {
|
||||
log.Println("error seems to be permanent, giving up...")
|
||||
return err
|
||||
}
|
||||
// resilience
|
||||
time.Sleep(time.Duration(tries) * 2 * time.Second)
|
||||
if tries >= 2 {
|
||||
// try updating
|
||||
updateRegistryIndex()
|
||||
}
|
||||
log.Println("trying again...")
|
||||
case tryAgain && err == nil:
|
||||
// reset error count
|
||||
tries = 0
|
||||
// upgrade
|
||||
log.Println("restarting by request...")
|
||||
// update index
|
||||
log.Println("checking versions...")
|
||||
updateRegistryIndex()
|
||||
case !tryAgain && err != nil:
|
||||
// fatal error
|
||||
return err
|
||||
case !tryAgain && err == nil:
|
||||
// clean exit
|
||||
log.Printf("%s completed successfully\n", opts.Identifier)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nolint:gocyclo,gocognit // TODO: simplify
|
||||
func execute(opts *Options, args []string) (cont bool, err error) {
|
||||
file, err := registry.GetFile(platform(opts.Identifier))
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("could not get component: %s", err)
|
||||
}
|
||||
|
||||
// check permission
|
||||
if !onWindows {
|
||||
info, err := os.Stat(file.Path())
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("failed to get file info on %s: %s", file.Path(), err)
|
||||
}
|
||||
if info.Mode() != 0755 {
|
||||
err := os.Chmod(file.Path(), 0755)
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("failed to set exec permissions on %s: %s", file.Path(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("starting %s %s\n", file.Path(), strings.Join(args, " "))
|
||||
|
||||
// log files
|
||||
var logFile, errorFile *os.File
|
||||
logFileBasePath := filepath.Join(logsRoot.Path, opts.ShortIdentifier)
|
||||
err = logsRoot.EnsureAbsPath(logFileBasePath)
|
||||
if err != nil {
|
||||
log.Printf("failed to check/create log file dir %s: %s\n", logFileBasePath, err)
|
||||
} else {
|
||||
// open log file
|
||||
logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.log", time.Now().UTC().Format("2006-01-02-15-04-05")))
|
||||
logFile = initializeLogFile(logFilePath, opts.Identifier, file.Version())
|
||||
if logFile != nil {
|
||||
defer finalizeLogFile(logFile, logFilePath)
|
||||
}
|
||||
// open error log file
|
||||
errorFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.error.log", time.Now().UTC().Format("2006-01-02-15-04-05")))
|
||||
errorFile = initializeLogFile(errorFilePath, opts.Identifier, file.Version())
|
||||
if errorFile != nil {
|
||||
defer finalizeLogFile(errorFile, errorFilePath)
|
||||
}
|
||||
}
|
||||
|
||||
// create command
|
||||
exc := exec.Command(file.Path(), args...) //nolint:gosec // everything is okay
|
||||
|
||||
if !runningInConsole && opts.AllowHidingWindow {
|
||||
// Windows only:
|
||||
// only hide (all) windows of program if we are not running in console and windows may be hidden
|
||||
hideWindow(exc)
|
||||
}
|
||||
|
||||
// check if input signals are enabled
|
||||
inputSignalsEnabled := false
|
||||
for _, arg := range args {
|
||||
if strings.HasSuffix(arg, "-input-signals") {
|
||||
inputSignalsEnabled = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// consume stdout/stderr
|
||||
stdout, err := exc.StdoutPipe()
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("failed to connect stdout: %s", err)
|
||||
}
|
||||
stderr, err := exc.StderrPipe()
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("failed to connect stderr: %s", err)
|
||||
}
|
||||
var stdin io.WriteCloser
|
||||
if inputSignalsEnabled {
|
||||
stdin, err = exc.StdinPipe()
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("failed to connect stdin: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// start
|
||||
err = exc.Start()
|
||||
if err != nil {
|
||||
return true, fmt.Errorf("failed to start %s: %s", opts.Identifier, err)
|
||||
}
|
||||
childIsRunning.Set()
|
||||
|
||||
// start output writers
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
var logFileError error
|
||||
if logFile == nil {
|
||||
_, logFileError = io.Copy(os.Stdout, stdout)
|
||||
} else {
|
||||
if opts.NoOutput {
|
||||
_, logFileError = io.Copy(logFile, stdout)
|
||||
} else {
|
||||
_, logFileError = io.Copy(io.MultiWriter(os.Stdout, logFile), stdout)
|
||||
}
|
||||
}
|
||||
if logFileError != nil {
|
||||
log.Printf("failed write logs: %s\n", logFileError)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
var errorFileError error
|
||||
if logFile == nil {
|
||||
_, errorFileError = io.Copy(os.Stderr, stderr)
|
||||
} else {
|
||||
if opts.NoOutput {
|
||||
_, errorFileError = io.Copy(errorFile, stderr)
|
||||
} else {
|
||||
_, errorFileError = io.Copy(io.MultiWriter(os.Stderr, errorFile), stderr)
|
||||
}
|
||||
}
|
||||
if errorFileError != nil {
|
||||
log.Printf("failed write error logs: %s\n", errorFileError)
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
// wait for completion
|
||||
finished := make(chan error)
|
||||
go func() {
|
||||
// wait for output writers to complete
|
||||
wg.Wait()
|
||||
// wait for process to return
|
||||
finished <- exc.Wait()
|
||||
// update status
|
||||
childIsRunning.UnSet()
|
||||
// notify manager
|
||||
close(finished)
|
||||
}()
|
||||
|
||||
// state change listeners
|
||||
for {
|
||||
select {
|
||||
case <-shuttingDown:
|
||||
// signal process shutdown
|
||||
if inputSignalsEnabled {
|
||||
// for windows
|
||||
_, err = stdin.Write([]byte("SIGINT\n"))
|
||||
} else {
|
||||
err = exc.Process.Signal(os.Interrupt)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("failed to signal %s to shutdown: %s\n", opts.Identifier, err)
|
||||
err = exc.Process.Kill()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to kill %s: %s", opts.Identifier, err)
|
||||
}
|
||||
return false, fmt.Errorf("killed %s", opts.Identifier)
|
||||
}
|
||||
// wait until shut down
|
||||
select {
|
||||
case <-finished:
|
||||
case <-time.After(11 * time.Second): // portmaster core prints stack if not able to shutdown in 10 seconds
|
||||
// kill
|
||||
err = exc.Process.Kill()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to kill %s: %s", opts.Identifier, err)
|
||||
}
|
||||
return false, fmt.Errorf("killed %s", opts.Identifier)
|
||||
}
|
||||
return false, nil
|
||||
case err := <-finished:
|
||||
if err != nil {
|
||||
exErr, ok := err.(*exec.ExitError)
|
||||
if ok {
|
||||
switch exErr.ProcessState.ExitCode() {
|
||||
case 0:
|
||||
// clean exit
|
||||
return false, fmt.Errorf("clean exit, but with error: %s", err)
|
||||
case 1:
|
||||
// error exit
|
||||
return true, fmt.Errorf("error during execution: %s", err)
|
||||
case restartCode:
|
||||
// restart request
|
||||
log.Printf("restarting %s\n", opts.Identifier)
|
||||
return true, nil
|
||||
default:
|
||||
return true, fmt.Errorf("unexpected error during execution: %s", err)
|
||||
}
|
||||
} else {
|
||||
return true, fmt.Errorf("unexpected error type during execution: %s", err)
|
||||
}
|
||||
}
|
||||
// clean exit
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,90 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(showCmd)
|
||||
showCmd.AddCommand(showCore)
|
||||
showCmd.AddCommand(showApp)
|
||||
showCmd.AddCommand(showNotifier)
|
||||
}
|
||||
|
||||
var showCmd = &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show the command to run a Portmaster component yourself",
|
||||
}
|
||||
|
||||
var showCore = &cobra.Command{
|
||||
Use: "core",
|
||||
Short: "Show command to run the Portmaster Core",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return show(cmd, &Options{
|
||||
Identifier: "core/portmaster-core",
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
var showApp = &cobra.Command{
|
||||
Use: "app",
|
||||
Short: "Show command to run the Portmaster App",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return show(cmd, &Options{
|
||||
Identifier: "app/portmaster-app",
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
var showNotifier = &cobra.Command{
|
||||
Use: "notifier",
|
||||
Short: "Show command to run the Portmaster Notifier",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return show(cmd, &Options{
|
||||
Identifier: "notifier/portmaster-notifier",
|
||||
SuppressArgs: true,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
func show(cmd *cobra.Command, opts *Options) error {
|
||||
// get original arguments
|
||||
var args []string
|
||||
if len(os.Args) < 4 {
|
||||
return cmd.Help()
|
||||
}
|
||||
args = os.Args[3:]
|
||||
if opts.SuppressArgs {
|
||||
args = nil
|
||||
}
|
||||
|
||||
// adapt identifier
|
||||
if onWindows {
|
||||
opts.Identifier += ".exe"
|
||||
}
|
||||
|
||||
file, err := registry.GetFile(platform(opts.Identifier))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get component: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s\n", file.Path(), strings.Join(args, " "))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package main
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
func init() {
|
||||
showCmd.AddCommand(showSnoreToast)
|
||||
runCmd.AddCommand(runSnoreToast)
|
||||
}
|
||||
|
||||
var showSnoreToast = &cobra.Command{
|
||||
Use: "notifier-snoretoast",
|
||||
Short: "Show command to run the Notifier component SnoreToast",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return show(cmd, &Options{
|
||||
Identifier: "notifier/portmaster-snoretoast",
|
||||
SuppressArgs: true,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
||||
|
||||
var runSnoreToast = &cobra.Command{
|
||||
Use: "notifier-snoretoast",
|
||||
Short: "Run the Notifier component SnoreToast",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return handleRun(cmd, &Options{
|
||||
Identifier: "notifier/portmaster-snoretoast",
|
||||
AllowDownload: false,
|
||||
AllowHidingWindow: true,
|
||||
SuppressArgs: true,
|
||||
})
|
||||
},
|
||||
FParseErrWhitelist: cobra.FParseErrWhitelist{
|
||||
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
|
||||
UnknownFlags: true,
|
||||
},
|
||||
}
|
|
@ -101,7 +101,7 @@ func start() error {
|
|||
if onWindows {
|
||||
mandatoryUpdates = []string{
|
||||
platform("core/portmaster-core.exe"),
|
||||
platform("control/portmaster-control.exe"),
|
||||
platform("start/portmaster-start.exe"),
|
||||
platform("app/portmaster-app.exe"),
|
||||
platform("notifier/portmaster-notifier.exe"),
|
||||
platform("notifier/portmaster-snoretoast.exe"),
|
||||
|
@ -109,7 +109,7 @@ func start() error {
|
|||
} else {
|
||||
mandatoryUpdates = []string{
|
||||
platform("core/portmaster-core"),
|
||||
platform("control/portmaster-control"),
|
||||
platform("start/portmaster-start"),
|
||||
platform("app/portmaster-app"),
|
||||
platform("notifier/portmaster-notifier"),
|
||||
}
|
||||
|
@ -185,7 +185,13 @@ func start() error {
|
|||
}
|
||||
|
||||
// react to upgrades
|
||||
return initUpgrader()
|
||||
if err := initUpgrader(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
warnOnIncorrectParentPath()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerUpdate queues the update task to execute ASAP.
|
||||
|
|
|
@ -25,6 +25,7 @@ import (
|
|||
|
||||
const (
|
||||
upgradedSuffix = "-upgraded"
|
||||
exeExt = ".exe"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -51,10 +52,10 @@ func upgrader(_ context.Context, _ interface{}) error {
|
|||
}
|
||||
defer upgraderActive.SetTo(false)
|
||||
|
||||
// upgrade portmaster control
|
||||
err := upgradePortmasterControl()
|
||||
// upgrade portmaster-start
|
||||
err := upgradePortmasterStart()
|
||||
if err != nil {
|
||||
log.Warningf("updates: failed to upgrade portmaster-control: %s", err)
|
||||
log.Warningf("updates: failed to upgrade portmaster-start: %s", err)
|
||||
}
|
||||
|
||||
err = upgradeCoreNotify()
|
||||
|
@ -68,7 +69,7 @@ func upgrader(_ context.Context, _ interface{}) error {
|
|||
func upgradeCoreNotify() error {
|
||||
identifier := "core/portmaster-core" // identifier, use forward slash!
|
||||
if onWindows {
|
||||
identifier += ".exe"
|
||||
identifier += exeExt
|
||||
}
|
||||
|
||||
// check if we can upgrade
|
||||
|
@ -122,16 +123,16 @@ func upgradeCoreNotifyActionHandler(n *notifications.Notification) {
|
|||
}
|
||||
}
|
||||
|
||||
func upgradePortmasterControl() error {
|
||||
filename := "portmaster-control"
|
||||
func upgradePortmasterStart() error {
|
||||
filename := "portmaster-start"
|
||||
if onWindows {
|
||||
filename += ".exe"
|
||||
filename += exeExt
|
||||
}
|
||||
|
||||
// check if we can upgrade
|
||||
if pmCtrlUpdate == nil || pmCtrlUpdate.UpgradeAvailable() {
|
||||
// get newest portmaster-control
|
||||
new, err := GetPlatformFile("control/" + filename) // identifier, use forward slash!
|
||||
// get newest portmaster-start
|
||||
new, err := GetPlatformFile("start/" + filename) // identifier, use forward slash!
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -140,38 +141,64 @@ func upgradePortmasterControl() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// update portmaster-control in data root
|
||||
rootControlPath := filepath.Join(filepath.Dir(registry.StorageDir().Path), filename)
|
||||
err := upgradeFile(rootControlPath, pmCtrlUpdate)
|
||||
// update portmaster-start in data root
|
||||
rootPmStartPath := filepath.Join(filepath.Dir(registry.StorageDir().Path), filename)
|
||||
err := upgradeFile(rootPmStartPath, pmCtrlUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("updates: upgraded %s", rootControlPath)
|
||||
log.Infof("updates: upgraded %s", rootPmStartPath)
|
||||
|
||||
// upgrade parent process, if it's portmaster-control
|
||||
return nil
|
||||
}
|
||||
|
||||
func warnOnIncorrectParentPath() {
|
||||
expectedFileName := "portmaster-start"
|
||||
if onWindows {
|
||||
expectedFileName += exeExt
|
||||
}
|
||||
|
||||
// upgrade parent process, if it's portmaster-start
|
||||
parent, err := processInfo.NewProcess(int32(os.Getppid()))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get parent process for upgrade checks: %w", err)
|
||||
log.Tracef("could not get parent process: %s", err)
|
||||
return
|
||||
}
|
||||
parentName, err := parent.Name()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get parent process name for upgrade checks: %w", err)
|
||||
log.Tracef("could not get parent process name: %s", err)
|
||||
return
|
||||
}
|
||||
if parentName != filename {
|
||||
log.Tracef("updates: parent process does not seem to be portmaster-control, name is %s", parentName)
|
||||
return nil
|
||||
if parentName != expectedFileName {
|
||||
log.Warningf("updates: parent process does not seem to be portmaster-start, name is %s", parentName)
|
||||
|
||||
// TODO(ppacher): once we released a new installer and folks had time
|
||||
// to update we should send a module warning/hint to the
|
||||
// UI notifying the user that he's still using portmaster-control.
|
||||
return
|
||||
}
|
||||
|
||||
parentPath, err := parent.Exe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not get parent process path for upgrade: %w", err)
|
||||
log.Tracef("could not get parent process path: %s", err)
|
||||
return
|
||||
}
|
||||
err = upgradeFile(parentPath, pmCtrlUpdate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("updates: upgraded %s", parentPath)
|
||||
|
||||
return nil
|
||||
absPath, err := filepath.Abs(parentPath)
|
||||
if err != nil {
|
||||
log.Tracef("could not get absolut parent process path: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
root := filepath.Dir(registry.StorageDir().Path)
|
||||
if !strings.HasPrefix(absPath, root) {
|
||||
log.Warningf("detected unexpected path %s for portmaster-start", absPath)
|
||||
|
||||
notifications.NotifyWarn(
|
||||
"updates:unsupported-parent",
|
||||
fmt.Sprintf("The portmaster has been launched by an unexpected %s binary at %s. Please configure your system to use the binary at %s as this version will be kept up to date automatically.", expectedFileName, absPath, filepath.Join(root, expectedFileName)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func upgradeFile(fileToUpgrade string, file *updater.File) error {
|
||||
|
@ -185,7 +212,7 @@ func upgradeFile(fileToUpgrade string, file *updater.File) error {
|
|||
if fileExists {
|
||||
// get current version
|
||||
var currentVersion string
|
||||
cmd := exec.Command(fileToUpgrade, "--ver")
|
||||
cmd := exec.Command(fileToUpgrade, "version", "--short")
|
||||
out, err := cmd.Output()
|
||||
if err == nil {
|
||||
// abort if version matches
|
||||
|
|
Loading…
Add table
Reference in a new issue