From e97d9fb8a0c8d19d119292f03fcbb79af022cc61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E7=A5=A5=E8=B6=85?= Date: Tue, 26 Dec 2023 09:09:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8F=AF=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/utils/exec/cmd.go | 162 ++++++++++++++++++ internal/utils/exec/cmd_test.go | 61 +++++++ internal/utils/exec/look_linux.go | 58 +++++++ internal/utils/exec/look_others.go | 10 ++ internal/utils/firewall.go | 3 +- internal/utils/service_linux.go | 9 +- internal/utils/upgrade_manager.go | 5 +- .../mysql/mysqlinstallers/mysql_installer.go | 20 +-- 8 files changed, 311 insertions(+), 17 deletions(-) create mode 100644 internal/utils/exec/cmd.go create mode 100644 internal/utils/exec/cmd_test.go create mode 100644 internal/utils/exec/look_linux.go create mode 100644 internal/utils/exec/look_others.go diff --git a/internal/utils/exec/cmd.go b/internal/utils/exec/cmd.go new file mode 100644 index 00000000..299b0a20 --- /dev/null +++ b/internal/utils/exec/cmd.go @@ -0,0 +1,162 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package executils + +import ( + "bytes" + "context" + "os" + "os/exec" + "strings" + "time" +) + +type Cmd struct { + name string + args []string + env []string + dir string + + ctx context.Context + timeout time.Duration + cancelFunc func() + + captureStdout bool + captureStderr bool + + stdout *bytes.Buffer + stderr *bytes.Buffer + + rawCmd *exec.Cmd +} + +func NewCmd(name string, args ...string) *Cmd { + return &Cmd{ + name: name, + args: args, + } +} + +func NewTimeoutCmd(timeout time.Duration, name string, args ...string) *Cmd { + return (&Cmd{ + name: name, + args: args, + }).WithTimeout(timeout) +} + +func (this *Cmd) WithTimeout(timeout time.Duration) *Cmd { + this.timeout = timeout + + ctx, cancelFunc := context.WithTimeout(context.Background(), timeout) + this.ctx = ctx + this.cancelFunc = cancelFunc + + return this +} + +func (this *Cmd) WithStdout() *Cmd { + this.captureStdout = true + return this +} + +func (this *Cmd) WithStderr() *Cmd { + this.captureStderr = true + return this +} + +func (this *Cmd) WithEnv(env []string) *Cmd { + this.env = env + return this +} + +func (this *Cmd) WithDir(dir string) *Cmd { + this.dir = dir + return this +} + +func (this *Cmd) Start() error { + var cmd = this.compose() + return cmd.Start() +} + +func (this *Cmd) Wait() error { + var cmd = this.compose() + return cmd.Wait() +} + +func (this *Cmd) Run() error { + if this.cancelFunc != nil { + defer this.cancelFunc() + } + + var cmd = this.compose() + return cmd.Run() +} + +func (this *Cmd) RawStdout() string { + if this.stdout != nil { + return this.stdout.String() + } + return "" +} + +func (this *Cmd) Stdout() string { + return strings.TrimSpace(this.RawStdout()) +} + +func (this *Cmd) RawStderr() string { + if this.stderr != nil { + return this.stderr.String() + } + return "" +} + +func (this *Cmd) Stderr() string { + return strings.TrimSpace(this.RawStderr()) +} + +func (this *Cmd) String() string { + if this.rawCmd != nil { + return this.rawCmd.String() + } + var newCmd = exec.Command(this.name, this.args...) + return newCmd.String() +} + +func (this *Cmd) Process() *os.Process { + if this.rawCmd != nil { + return this.rawCmd.Process + } + return nil +} + +func (this *Cmd) compose() *exec.Cmd { + if this.rawCmd != nil { + return this.rawCmd + } + + if this.ctx != nil { + this.rawCmd = exec.CommandContext(this.ctx, this.name, this.args...) + } else { + this.rawCmd = exec.Command(this.name, this.args...) + } + + if this.env != nil { + this.rawCmd.Env = this.env + } + + if len(this.dir) > 0 { + this.rawCmd.Dir = this.dir + } + + if this.captureStdout { + this.stdout = &bytes.Buffer{} + this.rawCmd.Stdout = this.stdout + } + if this.captureStderr { + this.stderr = &bytes.Buffer{} + this.rawCmd.Stderr = this.stderr + } + + return this.rawCmd +} diff --git a/internal/utils/exec/cmd_test.go b/internal/utils/exec/cmd_test.go new file mode 100644 index 00000000..d2102962 --- /dev/null +++ b/internal/utils/exec/cmd_test.go @@ -0,0 +1,61 @@ +// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . + +package executils_test + +import ( + executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec" + "testing" + "time" +) + +func TestNewTimeoutCmd_Sleep(t *testing.T) { + var cmd = executils.NewTimeoutCmd(1*time.Second, "sleep", "3") + cmd.WithStdout() + cmd.WithStderr() + err := cmd.Run() + t.Log("error:", err) + t.Log("stdout:", cmd.Stdout()) + t.Log("stderr:", cmd.Stderr()) +} + +func TestNewTimeoutCmd_Echo(t *testing.T) { + var cmd = executils.NewTimeoutCmd(10*time.Second, "echo", "-n", "hello") + cmd.WithStdout() + cmd.WithStderr() + err := cmd.Run() + t.Log("error:", err) + t.Log("stdout:", cmd.Stdout()) + t.Log("stderr:", cmd.Stderr()) +} + +func TestNewTimeoutCmd_Echo2(t *testing.T) { + var cmd = executils.NewCmd("echo", "hello") + cmd.WithStdout() + cmd.WithStderr() + err := cmd.Run() + t.Log("error:", err) + t.Log("stdout:", cmd.Stdout()) + t.Log("raw stdout:", cmd.RawStdout()) + t.Log("stderr:", cmd.Stderr()) + t.Log("raw stderr:", cmd.RawStderr()) +} + +func TestNewTimeoutCmd_Echo3(t *testing.T) { + var cmd = executils.NewCmd("echo", "-n", "hello") + err := cmd.Run() + t.Log("error:", err) + t.Log("stdout:", cmd.Stdout()) + t.Log("stderr:", cmd.Stderr()) +} + +func TestCmd_Process(t *testing.T) { + var cmd = executils.NewCmd("echo", "-n", "hello") + err := cmd.Run() + t.Log("error:", err) + t.Log(cmd.Process()) +} + +func TestNewTimeoutCmd_String(t *testing.T) { + var cmd = executils.NewCmd("echo", "-n", "hello") + t.Log("stdout:", cmd.String()) +} diff --git a/internal/utils/exec/look_linux.go b/internal/utils/exec/look_linux.go new file mode 100644 index 00000000..ab70077e --- /dev/null +++ b/internal/utils/exec/look_linux.go @@ -0,0 +1,58 @@ +// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . +//go:build linux + +package executils + +import ( + "golang.org/x/sys/unix" + "io/fs" + "os" + "os/exec" + "syscall" +) + +// LookPath customize our LookPath() function, to work in broken $PATH environment variable +func LookPath(file string) (string, error) { + result, err := exec.LookPath(file) + if err == nil && len(result) > 0 { + return result, nil + } + + // add common dirs contains executable files these may be excluded in $PATH environment variable + var binPaths = []string{ + "/usr/sbin", + "/usr/bin", + "/usr/local/sbin", + "/usr/local/bin", + } + + for _, binPath := range binPaths { + var fullPath = binPath + string(os.PathSeparator) + file + + stat, err := os.Stat(fullPath) + if err != nil { + continue + } + if stat.IsDir() { + return "", syscall.EISDIR + } + + var mode = stat.Mode() + if mode.IsDir() { + return "", syscall.EISDIR + } + err = syscall.Faccessat(unix.AT_FDCWD, fullPath, unix.X_OK, unix.AT_EACCESS) + if err == nil || (err != syscall.ENOSYS && err != syscall.EPERM) { + return fullPath, err + } + if mode&0111 != 0 { + return fullPath, nil + } + return "", fs.ErrPermission + } + + return "", &exec.Error{ + Name: file, + Err: exec.ErrNotFound, + } +} diff --git a/internal/utils/exec/look_others.go b/internal/utils/exec/look_others.go new file mode 100644 index 00000000..a759c8ef --- /dev/null +++ b/internal/utils/exec/look_others.go @@ -0,0 +1,10 @@ +// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn . +//go:build !linux + +package executils + +import "os/exec" + +func LookPath(file string) (string, error) { + return exec.LookPath(file) +} diff --git a/internal/utils/firewall.go b/internal/utils/firewall.go index f386b6a3..98dc0b9d 100644 --- a/internal/utils/firewall.go +++ b/internal/utils/firewall.go @@ -3,6 +3,7 @@ package utils import ( + executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec" "github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/types" "os/exec" @@ -14,7 +15,7 @@ func AddPortsToFirewall(ports []int) { // Linux if runtime.GOOS == "linux" { // firewalld - firewallCmd, _ := exec.LookPath("firewall-cmd") + firewallCmd, _ := executils.LookPath("firewall-cmd") if len(firewallCmd) > 0 { err := exec.Command(firewallCmd, "--add-port="+types.String(port)+"/tcp").Run() if err == nil { diff --git a/internal/utils/service_linux.go b/internal/utils/service_linux.go index 417ced98..03802c80 100644 --- a/internal/utils/service_linux.go +++ b/internal/utils/service_linux.go @@ -5,6 +5,7 @@ package utils import ( "errors" teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" + executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec" "github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/files" "os" @@ -21,7 +22,7 @@ func (this *ServiceManager) Install(exePath string, args []string) error { return errors.New("only root users can install the service") } - systemd, err := exec.LookPath("systemctl") + systemd, err := executils.LookPath("systemctl") if err != nil { return this.installInitService(exePath, args) } @@ -36,7 +37,7 @@ func (this *ServiceManager) Start() error { } if files.NewFile(systemdServiceFile).Exists() { - systemd, err := exec.LookPath("systemctl") + systemd, err := executils.LookPath("systemctl") if err != nil { return err } @@ -53,7 +54,7 @@ func (this *ServiceManager) Uninstall() error { } if files.NewFile(systemdServiceFile).Exists() { - systemd, err := exec.LookPath("systemctl") + systemd, err := executils.LookPath("systemctl") if err != nil { return err } @@ -93,7 +94,7 @@ func (this *ServiceManager) installInitService(exePath string, args []string) er return err } - chkCmd, err := exec.LookPath("chkconfig") + chkCmd, err := executils.LookPath("chkconfig") if err != nil { return err } diff --git a/internal/utils/upgrade_manager.go b/internal/utils/upgrade_manager.go index 1504520a..d220ecda 100644 --- a/internal/utils/upgrade_manager.go +++ b/internal/utils/upgrade_manager.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" + executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec" "github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/types" @@ -87,10 +88,10 @@ func (this *UpgradeManager) Start() error { }() // 检查unzip - unzipExe, _ := exec.LookPath("unzip") + unzipExe, _ := executils.LookPath("unzip") // 检查cp - cpExe, _ := exec.LookPath("cp") + cpExe, _ := executils.LookPath("cp") if len(cpExe) == 0 { return errors.New("can not find 'cp' command") } diff --git a/internal/web/actions/default/setup/mysql/mysqlinstallers/mysql_installer.go b/internal/web/actions/default/setup/mysql/mysqlinstallers/mysql_installer.go index ec1a2f48..21506f77 100644 --- a/internal/web/actions/default/setup/mysql/mysqlinstallers/mysql_installer.go +++ b/internal/web/actions/default/setup/mysql/mysqlinstallers/mysql_installer.go @@ -7,6 +7,7 @@ import ( "crypto/rand" "errors" "fmt" + executils "github.com/TeaOSLab/EdgeAdmin/internal/utils/exec" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/setup/mysql/mysqlinstallers/utils" stringutil "github.com/iwind/TeaGo/utils/string" timeutil "github.com/iwind/TeaGo/utils/time" @@ -14,7 +15,6 @@ import ( "net" "net/http" "os" - "os/exec" "path/filepath" "regexp" "runtime" @@ -57,7 +57,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string) // check 'tar' command this.log("checking 'tar' command ...") - var tarExe, _ = exec.LookPath("tar") + var tarExe, _ = executils.LookPath("tar") if len(tarExe) == 0 { this.log("installing 'tar' command ...") err = this.installTarCommand() @@ -70,7 +70,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string) this.log("checking system commands ...") var cmdList = []string{"tar" /** again **/, "chown", "sh"} for _, cmd := range cmdList { - cmdPath, err := exec.LookPath(cmd) + cmdPath, err := executils.LookPath(cmd) if err != nil || len(cmdPath) == 0 { return errors.New("could not find '" + cmd + "' command in this system") } @@ -87,7 +87,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string) } // ubuntu apt - aptExe, err := exec.LookPath("apt") + aptExe, err := executils.LookPath("apt") if err == nil && len(aptExe) > 0 { for _, lib := range []string{"libaio1", "libncurses5"} { this.log("checking " + lib + " ...") @@ -100,7 +100,7 @@ func (this *MySQLInstaller) InstallFromFile(xzFilePath string, targetDir string) time.Sleep(1 * time.Second) } } else { // yum - yumExe, err := exec.LookPath("yum") + yumExe, err := executils.LookPath("yum") if err == nil && len(yumExe) > 0 { for _, lib := range []string{"libaio", "ncurses-libs", "ncurses-compat-libs", "numactl-libs"} { var cmd = utils.NewCmd("yum", "-y", "install", lib) @@ -562,7 +562,7 @@ func (this *MySQLInstaller) log(message string) { // copy file func (this *MySQLInstaller) installService(baseDir string) error { - _, err := exec.LookPath("systemctl") + _, err := executils.LookPath("systemctl") if err != nil { return err } @@ -618,14 +618,14 @@ WantedBy=multi-user.target` // install 'tar' command automatically func (this *MySQLInstaller) installTarCommand() error { // yum - yumExe, err := exec.LookPath("yum") + yumExe, err := executils.LookPath("yum") if err == nil && len(yumExe) > 0 { var cmd = utils.NewTimeoutCmd(10*time.Second, yumExe, "-y", "install", "tar") return cmd.Run() } // apt - aptExe, err := exec.LookPath("apt") + aptExe, err := executils.LookPath("apt") if err == nil && len(aptExe) > 0 { var cmd = utils.NewTimeoutCmd(10*time.Second, aptExe, "-y", "install", "tar") return cmd.Run() @@ -636,7 +636,7 @@ func (this *MySQLInstaller) installTarCommand() error { func (this *MySQLInstaller) lookupGroupAdd() (string, error) { for _, cmd := range []string{"groupadd", "addgroup"} { - path, err := exec.LookPath(cmd) + path, err := executils.LookPath(cmd) if err == nil && len(path) > 0 { return path, nil } @@ -646,7 +646,7 @@ func (this *MySQLInstaller) lookupGroupAdd() (string, error) { func (this *MySQLInstaller) lookupUserAdd() (string, error) { for _, cmd := range []string{"useradd", "adduser"} { - path, err := exec.LookPath(cmd) + path, err := executils.LookPath(cmd) if err == nil && len(path) > 0 { return path, nil }