Add pmctl command

This commit is contained in:
Daniel 2019-03-13 10:45:35 +01:00
parent 6f268906ea
commit 9517b27a92
6 changed files with 465 additions and 0 deletions

6
pmctl/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
# binaries
pmctl
pmctl.exe
# test dir
test

52
pmctl/build Executable file
View file

@ -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}" $*

45
pmctl/get.go Normal file
View file

@ -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)
}

104
pmctl/main.go Normal file
View file

@ -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
}

145
pmctl/run.go Normal file
View file

@ -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
}

113
pmctl/upgrade.go Normal file
View file

@ -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
}