From 9517b27a9250488f26cdc127338d8974b8ee4c07 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 13 Mar 2019 10:45:35 +0100 Subject: [PATCH] Add pmctl command --- pmctl/.gitignore | 6 ++ pmctl/build | 52 +++++++++++++++++ pmctl/get.go | 45 +++++++++++++++ pmctl/main.go | 104 +++++++++++++++++++++++++++++++++ pmctl/run.go | 145 +++++++++++++++++++++++++++++++++++++++++++++++ pmctl/upgrade.go | 113 ++++++++++++++++++++++++++++++++++++ 6 files changed, 465 insertions(+) create mode 100644 pmctl/.gitignore create mode 100755 pmctl/build create mode 100644 pmctl/get.go create mode 100644 pmctl/main.go create mode 100644 pmctl/run.go create mode 100644 pmctl/upgrade.go diff --git a/pmctl/.gitignore b/pmctl/.gitignore new file mode 100644 index 00000000..cd5f05f4 --- /dev/null +++ b/pmctl/.gitignore @@ -0,0 +1,6 @@ +# binaries +pmctl +pmctl.exe + +# test dir +test diff --git a/pmctl/build b/pmctl/build new file mode 100755 index 00000000..bbde1f9f --- /dev/null +++ b/pmctl/build @@ -0,0 +1,52 @@ +#!/bin/bash + +# get build data +if [[ "$BUILD_COMMIT" == "" ]]; then + BUILD_COMMIT=$(git describe --all --long --abbrev=99 --dirty 2>/dev/null) +fi +if [[ "$BUILD_USER" == "" ]]; then + BUILD_USER=$(id -un) +fi +if [[ "$BUILD_HOST" == "" ]]; then + BUILD_HOST=$(hostname -f) +fi +if [[ "$BUILD_DATE" == "" ]]; then + BUILD_DATE=$(date +%d.%m.%Y) +fi +if [[ "$BUILD_SOURCE" == "" ]]; then + BUILD_SOURCE=$(git remote -v | grep origin | cut -f2 | cut -d" " -f1 | head -n 1) +fi +if [[ "$BUILD_SOURCE" == "" ]]; then + BUILD_SOURCE=$(git remote -v | cut -f2 | cut -d" " -f1 | head -n 1) +fi +BUILD_BUILDOPTIONS=$(echo $* | sed "s/ /ยง/g") + +# check +if [[ "$BUILD_COMMIT" == "" ]]; then + echo "could not automatically determine BUILD_COMMIT, please supply manually as environment variable." + exit 1 +fi +if [[ "$BUILD_USER" == "" ]]; then + echo "could not automatically determine BUILD_USER, please supply manually as environment variable." + exit 1 +fi +if [[ "$BUILD_HOST" == "" ]]; then + echo "could not automatically determine BUILD_HOST, please supply manually as environment variable." + exit 1 +fi +if [[ "$BUILD_DATE" == "" ]]; then + echo "could not automatically determine BUILD_DATE, please supply manually as environment variable." + exit 1 +fi +if [[ "$BUILD_SOURCE" == "" ]]; then + echo "could not automatically determine BUILD_SOURCE, please supply manually as environment variable." + exit 1 +fi + +echo "Please notice, that this build script includes metadata into the build." +echo "This information is useful for debugging and license compliance." +echo "Run the compiled binary with the -version flag to see the information included." + +# build +BUILD_PATH="github.com/Safing/portbase/info" +go build -ldflags "-X ${BUILD_PATH}.commit=${BUILD_COMMIT} -X ${BUILD_PATH}.buildOptions=${BUILD_BUILDOPTIONS} -X ${BUILD_PATH}.buildUser=${BUILD_USER} -X ${BUILD_PATH}.buildHost=${BUILD_HOST} -X ${BUILD_PATH}.buildDate=${BUILD_DATE} -X ${BUILD_PATH}.buildSource=${BUILD_SOURCE}" $* diff --git a/pmctl/get.go b/pmctl/get.go new file mode 100644 index 00000000..300a3ebf --- /dev/null +++ b/pmctl/get.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "os" + + "github.com/Safing/portmaster/updates" +) + +func getFile(identifier string) (*updates.File, error) { + // get newest local file + updates.LoadLatest() + file, err := updates.GetPlatformFile(identifier) + if err == nil { + return file, nil + } + if err != updates.ErrNotFound { + return nil, err + } + + fmt.Printf("%s downloading %s...\n", logPrefix, identifier) + + // if no matching file exists, load index + err = updates.LoadIndexes() + if err != nil { + if os.IsNotExist(err) { + // create dirs + err = updates.CheckDir(updateStoragePath) + if err != nil { + return nil, err + } + + // download indexes + err = updates.CheckForUpdates() + if err != nil { + return nil, err + } + } else { + return nil, err + } + } + + // get file + return updates.GetPlatformFile(identifier) +} diff --git a/pmctl/main.go b/pmctl/main.go new file mode 100644 index 00000000..929bdf81 --- /dev/null +++ b/pmctl/main.go @@ -0,0 +1,104 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/Safing/portbase/info" + "github.com/Safing/portbase/log" + "github.com/Safing/portmaster/updates" + "github.com/spf13/cobra" +) + +const ( + logPrefix = "[pmctl]" +) + +var ( + updateStoragePath string + databaseRootDir *string + + rootCmd = &cobra.Command{ + Use: "pmctl", + Short: "contoller for all portmaster components", + PersistentPreRunE: initPmCtl, + RunE: func(cmd *cobra.Command, args []string) error { + return cmd.Help() + }, + } +) + +func init() { + databaseRootDir = rootCmd.PersistentFlags().String("db", "", "set database directory") + err := rootCmd.MarkPersistentFlagRequired("db") + if err != nil { + panic(err) + } +} + +func main() { + flag.Parse() + + // not using portbase logger + log.SetLogLevel(log.CriticalLevel) + + // 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) + // }() + + // set meta info + info.Set("Portmaster Control", "0.1.1") + + // check if meta info is ok + err := info.CheckVersion() + if err != nil { + fmt.Printf("%s compile error: please compile using the provided build script\n", logPrefix) + os.Exit(1) + } + + // react to version flag + if info.PrintVersion() { + os.Exit(0) + } + + // start root command + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } + os.Exit(0) +} + +func initPmCtl(cmd *cobra.Command, args []string) error { + + // transform from db base path to updates path + if *databaseRootDir != "" { + updates.SetDatabaseRoot(*databaseRootDir) + updateStoragePath = filepath.Join(*databaseRootDir, "updates") + } else { + return errors.New("please supply the database directory using the --db flag") + } + + err := removeOldBin() + if err != nil { + fmt.Printf("%s warning: failed to remove old upgrade: %s\n", logPrefix, err) + } + + update := checkForUpgrade() + if update != nil { + err = doSelfUpgrade(update) + if err != nil { + return fmt.Errorf("%s failed to upgrade self: %s", logPrefix, err) + } + fmt.Println("upgraded pmctl") + } + + return nil +} diff --git a/pmctl/run.go b/pmctl/run.go new file mode 100644 index 00000000..9809161a --- /dev/null +++ b/pmctl/run.go @@ -0,0 +1,145 @@ +package main + +import ( + "fmt" + "io" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" +) + +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 run("core/portmaster", cmd, args) + }, + 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 run("app/portmaster-app", cmd, args) + }, + 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 run("notifier/portmaster-notifier", cmd, args) + }, + FParseErrWhitelist: cobra.FParseErrWhitelist{ + // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags + UnknownFlags: true, + }, +} + +func run(identifier string, cmd *cobra.Command, args []string) error { + + if len(os.Args) <= 3 { + return cmd.Help() + } + args = os.Args[3:] + + for { + file, err := getFile(identifier) + if err != nil { + return fmt.Errorf("%s could not get component: %s", logPrefix, err) + } + + // check permission + info, err := os.Stat(file.Path()) + if info.Mode() != 0755 { + err := os.Chmod(file.Path(), 0755) + if err != nil { + return fmt.Errorf("%s failed to set exec permissions on %s: %s", logPrefix, file.Path(), err) + } + } + + fmt.Printf("%s starting %s %s\n", logPrefix, file.Path(), strings.Join(args, " ")) + // os.Exit(0) + + // create command + exc := exec.Command(file.Path(), args...) + + // consume stdout/stderr + stdout, err := exc.StdoutPipe() + if err != nil { + return fmt.Errorf("%s failed to connect stdout: %s", logPrefix, err) + } + stderr, err := exc.StderrPipe() + if err != nil { + return fmt.Errorf("%s failed to connect stderr: %s", logPrefix, err) + } + + // start + err = exc.Start() + if err != nil { + return fmt.Errorf("%s failed to start %s: %s", logPrefix, identifier, err) + } + + // start output writers + go func() { + io.Copy(os.Stdout, stdout) + }() + go func() { + io.Copy(os.Stderr, stderr) + }() + + // wait for completion + err = exc.Wait() + if err != nil { + exErr, ok := err.(*exec.ExitError) + if ok { + switch exErr.ProcessState.ExitCode() { + case 0: + // clean exit + fmt.Printf("%s clean exit of %s, but with error: %s\n", logPrefix, identifier, err) + break + case 1: + // error exit + fmt.Printf("%s error during execution of %s: %s\n", logPrefix, identifier, err) + os.Exit(1) + case 2: + // restart request + fmt.Printf("%s restarting %s\n", logPrefix, identifier) + continue + default: + fmt.Printf("%s unexpected error during execution of %s: %s\n", logPrefix, identifier, err) + os.Exit(1) + } + } else { + fmt.Printf("%s unexpected error type during execution of %s: %s\n", logPrefix, identifier, err) + os.Exit(1) + } + } + + // clean exit + break + } + return nil +} diff --git a/pmctl/upgrade.go b/pmctl/upgrade.go new file mode 100644 index 00000000..f3d600e5 --- /dev/null +++ b/pmctl/upgrade.go @@ -0,0 +1,113 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/Safing/portbase/info" + "github.com/Safing/portmaster/updates" +) + +func checkForUpgrade() (update *updates.File) { + info := info.GetInfo() + file, err := getFile("pmctl/pmctl") + if err != nil { + return nil + } + if info.Version != file.Version() { + return file + } + return nil +} + +func doSelfUpgrade(file *updates.File) error { + + // get destination + dst, err := os.Executable() + if err != nil { + return err + } + dst, err = filepath.EvalSymlinks(dst) + if err != nil { + return err + } + + // mv destination + err = os.Rename(dst, dst+"_old") + if err != nil { + return err + } + + // hard link + err = os.Link(file.Path(), dst) + if err != nil { + fmt.Printf("%s failed to hardlink self upgrade: %s, will copy...\n", logPrefix, err) + err = copyFile(file.Path(), dst) + if err != nil { + return err + } + } + + // check permission + info, err := os.Stat(dst) + if info.Mode() != 0755 { + err := os.Chmod(dst, 0755) + if err != nil { + return fmt.Errorf("failed to set permissions on %s: %s", dst, err) + } + } + + return nil +} + +func copyFile(srcPath, dstPath string) (err error) { + srcFile, err := os.Open(srcPath) + if err != nil { + return + } + defer srcFile.Close() + + dstFile, err := os.Create(dstPath) + if err != nil { + return + } + defer func() { + closeErr := dstFile.Close() + if err == nil { + err = closeErr + } + }() + + _, err = io.Copy(dstFile, srcFile) + if err != nil { + return + } + err = dstFile.Sync() + return +} + +func removeOldBin() error { + // get location + dst, err := os.Executable() + if err != nil { + return err + } + dst, err = filepath.EvalSymlinks(dst) + if err != nil { + return err + } + + // delete old + err = os.Remove(dst + "_old") + if err != nil { + if !os.IsNotExist(err) { + return err + } + return nil + } + + fmt.Println("removed previous pmctl") + return nil +}