mirror of
https://github.com/safing/portmaster
synced 2025-09-01 10:09:11 +00:00
Add pmctl command
This commit is contained in:
parent
6f268906ea
commit
9517b27a92
6 changed files with 465 additions and 0 deletions
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
|
||||
}
|
Loading…
Add table
Reference in a new issue