Add pmctl command
This commit is contained in:
6
pmctl/.gitignore
vendored
Normal file
6
pmctl/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# binaries
|
||||||
|
pmctl
|
||||||
|
pmctl.exe
|
||||||
|
|
||||||
|
# test dir
|
||||||
|
test
|
||||||
52
pmctl/build
Executable file
52
pmctl/build
Executable 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
45
pmctl/get.go
Normal 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
104
pmctl/main.go
Normal 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
145
pmctl/run.go
Normal 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
113
pmctl/upgrade.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user