From d55982bde094b06bad511fa07d51a2533d6430f9 Mon Sep 17 00:00:00 2001 From: ssongliu Date: Tue, 28 Apr 2026 11:17:30 +0800 Subject: [PATCH] refactor: update agent and core utilities (#12621) --- agent/app/service/agents.go | 4 +- agent/app/service/agents_agents.go | 16 +- agent/app/service/agents_channels.go | 75 ++-- agent/app/service/agents_overview.go | 3 +- agent/app/service/agents_skills.go | 38 +- agent/app/service/agents_utils.go | 6 - agent/app/service/ai.go | 13 +- agent/app/service/alert.go | 30 +- agent/app/service/app_utils.go | 40 +- agent/app/service/backup_redis.go | 6 +- agent/app/service/backup_website.go | 2 +- agent/app/service/clam.go | 5 +- agent/app/service/container.go | 9 +- agent/app/service/cronjob_helper.go | 4 +- agent/app/service/dashboard.go | 16 +- agent/app/service/device.go | 13 +- agent/app/service/disk.go | 5 +- agent/app/service/disk_utils.go | 14 +- agent/app/service/docker.go | 7 +- agent/app/service/host_tool.go | 10 +- agent/app/service/iptables.go | 12 +- agent/app/service/nginx.go | 2 +- agent/app/service/nginx_utils.go | 10 +- agent/app/service/runtime.go | 7 +- agent/app/service/snapshot.go | 13 +- agent/app/service/snapshot_create.go | 7 +- agent/app/service/snapshot_recover.go | 14 +- agent/app/service/ssh.go | 74 ++-- agent/app/service/website_ca.go | 4 +- agent/app/service/website_ssl.go | 20 +- agent/app/service/website_utils.go | 2 +- agent/init/firewall/firewall.go | 2 +- agent/init/hook/hook.go | 5 +- agent/init/lang/lang.go | 10 +- agent/middleware/certificate.go | 3 +- agent/utils/ai_tools/gpu/gpu.go | 2 +- agent/utils/ai_tools/xpu/xpu.go | 14 +- agent/utils/clam/clam.go | 2 +- agent/utils/cmd/cmd.go | 60 ++- agent/utils/cmd/cmdx.go | 347 +++++++++++++++--- agent/utils/common/common.go | 36 +- agent/utils/controller/manager/common.go | 3 +- agent/utils/controller/manager/openrc.go | 38 +- agent/utils/controller/manager/sysvinit.go | 13 +- agent/utils/ctl_conf/ctl_conf.go | 88 +++++ agent/utils/files/file_op.go | 134 +++---- agent/utils/files/rar.go | 2 +- agent/utils/files/tar.go | 2 +- agent/utils/files/tar_gz.go | 57 +-- agent/utils/files/x7z.go | 2 +- agent/utils/files/zip.go | 2 +- agent/utils/firewall/client.go | 23 +- agent/utils/firewall/client/firewalld.go | 33 +- agent/utils/firewall/client/iptables.go | 41 ++- .../utils/firewall/client/iptables/common.go | 88 +++-- .../utils/firewall/client/iptables/filter.go | 44 ++- .../utils/firewall/client/iptables/forward.go | 33 +- .../firewall/client/iptables/persistence.go | 17 +- agent/utils/firewall/client/ufw.go | 67 ++-- agent/utils/mysql/client/local.go | 25 +- agent/utils/mysql/client/remote.go | 70 ++-- agent/utils/ntp/ntp.go | 6 +- agent/utils/postgresql/client/local.go | 33 +- agent/utils/postgresql/client/remote.go | 116 +++++- agent/utils/toolbox/fail2ban.go | 29 +- agent/utils/toolbox/pure-ftpd.go | 57 +-- core/app/service/logs.go | 26 +- core/app/service/upgrade.go | 9 +- core/cmd/server/cmd/restore.go | 17 +- core/cmd/server/cmd/root.go | 5 +- core/cmd/server/cmd/update.go | 2 +- core/i18n/i18n.go | 6 +- core/init/geo/lang.go | 10 +- core/init/hook/hook.go | 14 +- core/init/migration/migrations/init.go | 4 +- core/init/viper/viper.go | 26 +- core/utils/cmd/cmd.go | 54 +-- core/utils/cmd/cmdx.go | 296 ++++++++++++--- core/utils/common/common.go | 21 +- core/utils/common/time.go | 19 +- core/utils/controller/manager/common.go | 2 +- core/utils/controller/manager/openrc.go | 38 +- core/utils/controller/manager/sysvinit.go | 13 +- core/utils/ctl_conf/ctl_conf.go | 88 +++++ core/utils/files/files.go | 126 +++++-- core/utils/firewall/firewall.go | 17 +- 86 files changed, 1854 insertions(+), 924 deletions(-) create mode 100644 agent/utils/ctl_conf/ctl_conf.go create mode 100644 core/utils/ctl_conf/ctl_conf.go diff --git a/agent/app/service/agents.go b/agent/app/service/agents.go index 67619403c..121762002 100644 --- a/agent/app/service/agents.go +++ b/agent/app/service/agents.go @@ -990,7 +990,7 @@ func validateAgentConfigFileContent(agentType, content string) error { } func getOpenclawNPMRegistry(containerName string) (string, error) { - registry, err := runDockerExecWithStdout(20*time.Second, containerName, "npm", "get", "registry") + registry, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "npm", "get", "registry") if err != nil { return "", err } @@ -1002,7 +1002,7 @@ func getOpenclawNPMRegistry(containerName string) (string, error) { } func setOpenclawNPMRegistry(containerName, registry string) error { - return cmd.RunDefaultBashCf("docker exec %s npm set registry %q", containerName, registry) + return cmd.NewCommandMgr().Run("docker", "exec", containerName, "npm", "set", "registry", registry) } func (a AgentService) loadAgentAndInstall(agentID uint) (*model.Agent, *model.AppInstall, error) { diff --git a/agent/app/service/agents_agents.go b/agent/app/service/agents_agents.go index 4614a4758..8f89ed28b 100644 --- a/agent/app/service/agents_agents.go +++ b/agent/app/service/agents_agents.go @@ -23,7 +23,7 @@ func (a AgentService) CreateRole(req dto.AgentRoleCreateReq) (*dto.AgentRoleCrea return nil, err } - args := []string{"exec", install.ContainerName, "openclaw", "agents", "add", req.Name} + args := []string{"openclaw", "agents", "add", req.Name} workspace := "/home/node/.openclaw/workspace-agent_" + req.Name agentDir := "/home/node/.openclaw/agents/" + req.Name args = append(args, "--workspace", workspace) @@ -43,8 +43,7 @@ func (a AgentService) CreateRole(req dto.AgentRoleCreateReq) (*dto.AgentRoleCrea args = append(args, "--agent-dir", agentDir) args = append(args, "--non-interactive", "--json") - mgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Minute)) - output, err := mgr.RunWithStdout("docker", args...) + output, err := cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, args...) if err != nil { return nil, err } @@ -125,10 +124,8 @@ func (a AgentService) DeleteRole(req dto.AgentRoleDeleteReq) error { return buserr.New("ErrRecordNotFound") } - args := []string{"exec", install.ContainerName, "openclaw", "agents", "delete", req.ID, "--force"} - - mgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Minute)) - if _, err = mgr.RunWithStdout("docker", args...); err != nil { + args := []string{"openclaw", "agents", "delete", req.ID, "--force"} + if _, err = cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, args...); err != nil { return err } if target.Workspace != "" { @@ -171,8 +168,6 @@ func (a AgentService) operateRoleBinding(req dto.AgentRoleBindReq, action string return buserr.New("ErrInvalidParams") } args := []string{ - "exec", - install.ContainerName, "openclaw", "agents", action, @@ -182,8 +177,7 @@ func (a AgentService) operateRoleBinding(req dto.AgentRoleBindReq, action string binding, } args = append(args, "--json") - mgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Minute)) - _, err = mgr.RunWithStdout("docker", args...) + _, err = cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, args...) return err } diff --git a/agent/app/service/agents_channels.go b/agent/app/service/agents_channels.go index 3fe1bb8e4..3833a9a86 100644 --- a/agent/app/service/agents_channels.go +++ b/agent/app/service/agents_channels.go @@ -372,14 +372,14 @@ func (a AgentService) InstallPlugin(req dto.AgentPluginInstallReq) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute)) if req.Type == "qqbot" { legacyPluginPath := path.Join(openclawPluginBaseDir, "qqbot") - if err := mgr.RunBashCf("docker exec %s test -d %s", install.ContainerName, legacyPluginPath); err == nil { - if _, err := mgr.RunWithStdout("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript("qqbot")); err != nil { + if err := mgr.Run("docker", "exec", install.ContainerName, "test", "-d", legacyPluginPath); err == nil { + if err := uninstallOpenclawPlugin(mgr, install.ContainerName, "qqbot"); err != nil { return err } time.Sleep(2 * time.Second) } } - if _, err := mgr.RunWithStdout("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawPluginInstallScript(spec, pluginID)); err != nil { + if err := installOpenclawPlugin(mgr, install.ContainerName, spec, pluginID); err != nil { return err } conf, err := readOpenclawConfig(agent.ConfigPath) @@ -415,11 +415,11 @@ func (a AgentService) UpgradePlugin(req dto.AgentPluginUpgradeReq) error { } upgradeTask.AddSubTask("Upgrade OpenClaw plugin", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute)) - if _, err := mgr.RunWithStdout("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript(pluginID)); err != nil { + if err := uninstallOpenclawPlugin(mgr, install.ContainerName, pluginID); err != nil { return err } time.Sleep(2 * time.Second) - if _, err := mgr.RunWithStdout("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawPluginInstallScript(spec, pluginID)); err != nil { + if err := installOpenclawPlugin(mgr, install.ContainerName, spec, pluginID); err != nil { return err } conf, err := readOpenclawConfig(agent.ConfigPath) @@ -455,7 +455,7 @@ func (a AgentService) UninstallPlugin(req dto.AgentPluginUninstallReq) error { } uninstallTask.AddSubTask("Uninstall OpenClaw plugin", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(10*time.Minute)) - if _, err := mgr.RunWithStdout("docker", "exec", "-i", install.ContainerName, "sh", "-c", buildOpenclawPluginUninstallScript(pluginID)); err != nil { + if err := uninstallOpenclawPlugin(mgr, install.ContainerName, pluginID); err != nil { return err } conf, err := readOpenclawConfig(agent.ConfigPath) @@ -487,7 +487,7 @@ func (a AgentService) LoginWeixinChannel(req dto.AgentWeixinLoginReq) error { if agent.AgentType == constant.AppHermesAgent { return mgr.Run("docker", buildHermesWeixinLoginArgs(install.ContainerName)...) } - return mgr.RunBashCf("docker exec %s openclaw channels login --channel openclaw-weixin", install.ContainerName) + return mgr.Run("docker", "exec", install.ContainerName, "openclaw", "channels", "login", "--channel", "openclaw-weixin") }, nil) if agent.AgentType == constant.AppHermesAgent { loginTask.AddSubTask("Restart Hermes-Agent container", func(t *task.Task) error { @@ -584,17 +584,26 @@ func (a AgentService) ApproveChannelPairing(req dto.AgentChannelPairingApproveRe return validateHermesPairingApproveResult(output, err) } if req.AccountID != "" { - return cmd.RunDefaultBashCf( - "docker exec %s openclaw pairing approve %s %q --account %q", + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run( + "docker", + "exec", install.ContainerName, + "openclaw", + "pairing", + "approve", req.Type, req.PairingCode, + "--account", req.AccountID, ) } - return cmd.RunDefaultBashCf( - "docker exec %s openclaw pairing approve %s %q", + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).Run( + "docker", + "exec", install.ContainerName, + "openclaw", + "pairing", + "approve", req.Type, req.PairingCode, ) @@ -1289,20 +1298,40 @@ func appendPluginAllow(conf map[string]interface{}, pluginID string) { plugins["allow"] = append(allow, pluginID) } -func buildOpenclawPluginInstallScript(spec, pluginID string) string { - return fmt.Sprintf( - "set -e; workdir=%s/%s; rm -rf \"$workdir\"; mkdir -p \"$workdir\"; cd \"$workdir\"; npm pack --silent %q >/dev/null 2>&1; pkg=$(find \"$workdir\" -maxdepth 1 -type f -name '*.tgz' | head -n 1); printf '%%s\\n' \"$pkg\"; openclaw plugins install \"$pkg\" --dangerously-force-unsafe-install; rm -rf \"$workdir\"", - openclawPluginPackageTmpDir, - pluginID, - spec, - ) +func installOpenclawPlugin(mgr *cmd.CommandHelper, containerName, spec, pluginID string) error { + workdir := path.Join(openclawPluginPackageTmpDir, pluginID) + defer func() { + _ = mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir) + }() + if err := mgr.Run("docker", "exec", containerName, "rm", "-rf", workdir); err != nil { + return err + } + if err := mgr.Run("docker", "exec", containerName, "mkdir", "-p", workdir); err != nil { + return err + } + if err := mgr.Run("docker", "exec", "-w", workdir, containerName, "npm", "pack", "--silent", spec); err != nil { + return err + } + pkgPath, err := mgr.RunWithStdout("docker", "exec", containerName, "find", workdir, "-maxdepth", "1", "-type", "f", "-name", "*.tgz", "-print", "-quit") + if err != nil { + return err + } + pkgPath = strings.TrimSpace(pkgPath) + if pkgPath == "" { + return fmt.Errorf("openclaw plugin package not found") + } + return mgr.Run("docker", "exec", containerName, "openclaw", "plugins", "install", pkgPath, "--dangerously-force-unsafe-install") } -func buildOpenclawPluginUninstallScript(pluginID string) string { - return fmt.Sprintf( - "set +e; printf 'yes\\n' | openclaw plugins uninstall %s; code=$?; if [ \"$code\" -eq 137 ]; then exit 0; fi; exit \"$code\"", - pluginID, +func uninstallOpenclawPlugin(mgr *cmd.CommandHelper, containerName, pluginID string) error { + _, err := mgr.RunPipe( + cmd.PipeCommand{Name: "printf", Args: []string{"yes\n"}}, + cmd.PipeCommand{Name: "docker", Args: []string{"exec", "-i", containerName, "openclaw", "plugins", "uninstall", pluginID}}, ) + if err != nil && strings.Contains(err.Error(), "exit status 137") { + return nil + } + return err } func resolvePluginMeta(pluginType string) (string, string, error) { @@ -1357,7 +1386,7 @@ func loadOpenclawPluginLatestVersion(containerName, pluginType string) (string, if err != nil { return "", err } - output, err := runDockerExecWithStdout(20*time.Second, containerName, "npm", "view", spec, "version", "--json") + output, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "npm", "view", spec, "version", "--json") if err != nil { return "", err } diff --git a/agent/app/service/agents_overview.go b/agent/app/service/agents_overview.go index 73a6952e8..38d74897a 100644 --- a/agent/app/service/agents_overview.go +++ b/agent/app/service/agents_overview.go @@ -10,6 +10,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" ) const ( @@ -101,7 +102,7 @@ func countOpenclawConfiguredChannels(conf map[string]interface{}) int { } func loadOpenclawOverviewSkillStats(containerName string) (int, error) { - output, err := runDockerExecWithStdout(5*time.Minute, containerName, "sh", "-c", "openclaw skills list --json 2>&1") + output, err := cmd.RunDockerExecWithStdout(5*time.Minute, containerName, "openclaw", "skills", "list", "--json") if err != nil { return 0, err } diff --git a/agent/app/service/agents_skills.go b/agent/app/service/agents_skills.go index 578248909..d796e142b 100644 --- a/agent/app/service/agents_skills.go +++ b/agent/app/service/agents_skills.go @@ -55,7 +55,7 @@ func (a AgentService) ListSkills(req dto.AgentIDReq) ([]dto.AgentSkillItem, erro if agent.AgentType != constant.AppOpenclaw { return nil, fmt.Errorf("%s does not support", agent.AgentType) } - output, err := runDockerExecWithStdout(5*time.Minute, install.ContainerName, "sh", "-c", "openclaw skills list --json 2>&1") + output, err := cmd.RunDockerExecWithStdout(5*time.Minute, install.ContainerName, "openclaw", "skills", "list", "--json") if err != nil { return nil, err } @@ -143,7 +143,7 @@ func (a AgentService) InstallSkill(req dto.AgentSkillInstallReq) error { } installTask.AddSubTask("Install OpenClaw skill", func(t *task.Task) error { mgr := cmd.NewCommandMgr(cmd.WithTask(*t), cmd.WithContext(t.TaskCtx), cmd.WithTimeout(20*time.Minute)) - return mgr.Run("docker", "exec", install.ContainerName, "sh", "-c", buildOpenclawSkillInstallCommand(req.Source, req.Slug)) + return installOpenclawSkill(mgr, install.ContainerName, req.Source, req.Slug) }, nil) go func() { if err := installTask.Execute(); err != nil { @@ -205,9 +205,9 @@ func parseOpenclawSkillsList(output string) ([]dto.AgentSkillItem, error) { func loadOpenclawSkillSearchOutput(containerName, source, keyword string) (string, error) { switch source { case "skillhub": - return runDockerExecWithStdout(2*time.Minute, containerName, "skillhub", "search", keyword, "--json") + return cmd.RunDockerExecWithStdout(2*time.Minute, containerName, "skillhub", "search", keyword, "--json") default: - return runDockerExecWithStdout( + return cmd.RunDockerExecWithStdout( 2*time.Minute, containerName, "sh", @@ -261,22 +261,28 @@ func parseClawhubSearchResult(output, source string) []dto.AgentSkillSearchItem return items } -func buildOpenclawSkillInstallCommand(source, slug string) string { +func installOpenclawSkill(mgr *cmd.CommandHelper, containerName, source, slug string) error { + if err := mgr.Run("docker", "exec", containerName, "mkdir", "-p", openclawManagedSkillsDir); err != nil { + return err + } switch source { case "clawhub-global", "clawhub-cn": - return fmt.Sprintf( - "mkdir -p %s && CLAWHUB_REGISTRY=%q clawhub --workdir /home/node/.openclaw --dir skills install %q", - openclawManagedSkillsDir, - resolveClawhubRegistry(source), + return mgr.Run( + "docker", + "exec", + "-e", + "CLAWHUB_REGISTRY="+resolveClawhubRegistry(source), + containerName, + "clawhub", + "--workdir", + "/home/node/.openclaw", + "--dir", + "skills", + "install", slug, ) default: - return fmt.Sprintf( - "mkdir -p %s && skillhub --dir %s install %q", - openclawManagedSkillsDir, - openclawManagedSkillsDir, - slug, - ) + return mgr.Run("docker", "exec", containerName, "skillhub", "--dir", openclawManagedSkillsDir, "install", slug) } } @@ -290,7 +296,7 @@ func resolveClawhubRegistry(source string) string { } func getOpenclawSkillKey(containerName, name string) (string, error) { - output, err := runDockerExecWithStdout(2*time.Minute, containerName, "sh", "-c", fmt.Sprintf("openclaw skills info %q --json 2>&1", name)) + output, err := cmd.RunDockerExecWithStdout(2*time.Minute, containerName, "openclaw", "skills", "info", name, "--json") if err != nil { return "", err } diff --git a/agent/app/service/agents_utils.go b/agent/app/service/agents_utils.go index bf6320330..dcb81ae12 100644 --- a/agent/app/service/agents_utils.go +++ b/agent/app/service/agents_utils.go @@ -19,7 +19,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" - "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/req_helper" @@ -60,11 +59,6 @@ func ensureContainerRunning(containerName string) error { return nil } -func runDockerExecWithStdout(timeout time.Duration, containerName string, args ...string) (string, error) { - commandArgs := append([]string{"exec", containerName}, args...) - return cmd.NewCommandMgr(cmd.WithTimeout(timeout)).RunWithStdout("docker", commandArgs...) -} - func resolveAgentAccountInput(provider, apiKey, baseURL string) (resolvedAgentAccountInput, error) { resolvedAPIKey := strings.TrimSpace(apiKey) resolvedBaseURL := strings.TrimSpace(baseURL) diff --git a/agent/app/service/ai.go b/agent/app/service/ai.go index 9c9d68ba3..5ab8af8be 100644 --- a/agent/app/service/ai.go +++ b/agent/app/service/ai.go @@ -77,7 +77,7 @@ func (u *AIToolService) LoadDetail(name string) (string, error) { if err != nil { return "", err } - stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama show %s", containerName, name) + stdout, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "ollama", "show", name) if err != nil { return "", err } @@ -137,7 +137,7 @@ func (u *AIToolService) Close(name string) error { if err != nil { return err } - if err := cmd.RunDefaultBashCf("docker exec %s ollama stop %s", containerName, name); err != nil { + if err := cmd.NewCommandMgr().Run("docker", "exec", containerName, "ollama", "stop", name); err != nil { return fmt.Errorf("handle ollama stop %s failed, %v", name, err) } return nil @@ -194,7 +194,7 @@ func (u *AIToolService) Delete(req dto.ForceDelete) error { } for _, item := range ollamaList { if item.Status != constant.StatusDeleted { - if err := cmd.RunDefaultBashCf("docker exec %s ollama rm %s", containerName, item.Name); err != nil && !req.ForceDelete { + if err := cmd.NewCommandMgr().Run("docker", "exec", containerName, "ollama", "rm", item.Name); err != nil && !req.ForceDelete { return fmt.Errorf("handle ollama rm %s failed, %v", item.Name, err) } } @@ -210,7 +210,7 @@ func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) { if err != nil { return nil, err } - stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama list", containerName) + stdout, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "ollama", "list") if err != nil { return nil, err } @@ -382,12 +382,15 @@ func LoadContainerName() (string, error) { } func loadModelSize(name string, containerName string) (string, error) { - stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama list | grep %s", containerName, name) + stdout, err := cmd.RunDockerExecWithStdout(20*time.Second, containerName, "ollama", "list") if err != nil { return "", err } lines := strings.Split(stdout, "\n") for _, line := range lines { + if !strings.Contains(line, name) { + continue + } parts := strings.Fields(line) if len(parts) < 5 { continue diff --git a/agent/app/service/alert.go b/agent/app/service/alert.go index ff9c67b1d..6b4ebd78c 100644 --- a/agent/app/service/alert.go +++ b/agent/app/service/alert.go @@ -3,6 +3,12 @@ package service import ( "encoding/json" "fmt" + "mime" + "sort" + "strings" + "sync" + "time" + "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/app/repo" @@ -15,11 +21,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/utils/email" "github.com/1Panel-dev/1Panel/agent/utils/xpack" "github.com/shirou/gopsutil/v4/disk" - "mime" - "sort" - "strings" - "sync" - "time" ) type AlertService struct{} @@ -308,12 +309,25 @@ func (a AlertService) GetDisks() ([]dto.DiskDTO, error) { func executeDiskCommand() (string, error) { cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second)) - stdout, err := cmdMgr.RunWithStdoutBashC("df -hT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev") + stdout, err := cmdMgr.RunWithStdout("df", "-hT", "-P") if err != nil { cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second)) - stdout, err = cmdMgr2.RunWithStdoutBashC("df -lhT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev") + stdout, err = cmdMgr2.RunWithStdout("df", "-lhT", "-P") } - return stdout, err + if err != nil { + return stdout, err + } + var lines []string + for _, line := range strings.Split(stdout, "\n") { + if !strings.Contains(line, "/") || strings.Contains(line, "tmpfs") || strings.Contains(line, "snap/core") || strings.Contains(line, "udev") { + continue + } + lines = append(lines, line) + } + if len(lines) == 0 { + return "", nil + } + return strings.Join(lines, "\n"), nil } func shouldExclude(fields []string, mountPoint string, excludes map[string]struct{}) bool { diff --git a/agent/app/service/app_utils.go b/agent/app/service/app_utils.go index ecceca6dc..3a56e81ce 100644 --- a/agent/app/service/app_utils.go +++ b/agent/app/service/app_utils.go @@ -665,7 +665,7 @@ func buildNginx(parentTask *task.Task) error { logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image")) parentTask.LogStart(logStr) cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(60*time.Minute)) - if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil { + if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil { return err } parentTask.LogSuccess(logStr) @@ -768,8 +768,7 @@ func upgradeInstall(req request.AppInstallUpgrade) error { } } - command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath())) - _, _ = command.CombinedOutput() + _ = copyAppDetailMissing(fileOp, detailDir, install.GetPath()) if install.App.Key == constant.AppOpenresty { installBuildDir := path.Join(install.GetPath(), "build") detailBuildDir := path.Join(detailDir, "build") @@ -1129,7 +1128,7 @@ func runScript(task *task.Task, appInstall *model.AppInstall, operate string) er task.LogStart(logStr) cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir)) - if err := cmdMgr.RunBashC(scriptPath); err != nil { + if err := cmdMgr.Run("bash", scriptPath); err != nil { task.LogFailedWithErr(logStr, err) return err } @@ -1935,6 +1934,39 @@ func isHostModel(dockerCompose string) bool { return false } +func copyAppDetailMissing(fileOp files.FileOp, srcDir, dstDir string) error { + entries, err := os.ReadDir(srcDir) + if err != nil { + return err + } + for _, entry := range entries { + if strings.HasPrefix(entry.Name(), ".") { + continue + } + srcPath := path.Join(srcDir, entry.Name()) + dstPath := path.Join(dstDir, entry.Name()) + if !fileOp.Stat(dstPath) { + if entry.IsDir() { + if err := fileOp.CopyDir(srcPath, dstDir); err != nil { + return err + } + continue + } + if err := fileOp.CopyFile(srcPath, dstDir); err != nil { + return err + } + continue + } + if !entry.IsDir() { + continue + } + if err := copyAppDetailMissing(fileOp, srcPath, dstPath); err != nil { + return err + } + } + return nil +} + func getRestartPolicy(yml string) string { var project docker.ComposeProject if err := yaml.Unmarshal([]byte(yml), &project); err != nil { diff --git a/agent/app/service/backup_redis.go b/agent/app/service/backup_redis.go index 713397607..0507048cb 100644 --- a/agent/app/service/backup_redis.go +++ b/agent/app/service/backup_redis.go @@ -97,7 +97,7 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, recordID } } - if err := cmd.RunDefaultBashCf("docker exec %s redis-cli -a %s --no-auth-warning save", redisInfo.ContainerName, redisInfo.Password); err != nil { + if err := cmd.NewCommandMgr().Run("docker", "exec", redisInfo.ContainerName, "redis-cli", "-a", redisInfo.Password, "--no-auth-warning", "save"); err != nil { return err } @@ -109,13 +109,13 @@ func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, recordID return nil } if strings.HasSuffix(fileName, ".aof") { - if err := cmd.RunDefaultBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName); err != nil { + if err := cmd.NewCommandMgr().Run("docker", "cp", redisInfo.ContainerName+":/data/appendonly.aof", path.Join(backupDir, fileName)); err != nil { return err } return nil } - if err := cmd.RunDefaultBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName); err != nil { + if err := cmd.NewCommandMgr().Run("docker", "cp", redisInfo.ContainerName+":/data/dump.rdb", path.Join(backupDir, fileName)); err != nil { return err } return nil diff --git a/agent/app/service/backup_website.go b/agent/app/service/backup_website.go index b0adfd431..51b8fe6b1 100644 --- a/agent/app/service/backup_website.go +++ b/agent/app/service/backup_website.go @@ -178,7 +178,7 @@ func handleWebsiteRecover(website *model.Website, parentTask *task.Task, recover if err = fileOp.TarGzExtractPro(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), GetOpenrestyDir(SitesRootDir), ""); err != nil { return err } - if err := cmd.RunDefaultBashCf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName); err != nil { + if err := cmd.NewCommandMgr().Run("docker", "exec", "-i", nginxInfo.ContainerName, "nginx", "-s", "reload"); err != nil { return err } oldWebsite.ID = website.ID diff --git a/agent/app/service/clam.go b/agent/app/service/clam.go index 881435127..2effb982e 100644 --- a/agent/app/service/clam.go +++ b/agent/app/service/clam.go @@ -86,8 +86,9 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) { baseInfo.IsActive = false } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) if baseInfo.IsActive { - version, err := cmd.RunDefaultWithStdoutBashC("clamdscan --version") + version, err := cmdMgr.RunWithStdout("clamdscan", "--version") if err == nil { if strings.Contains(version, "/") { baseInfo.Version = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ") @@ -99,7 +100,7 @@ func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) { _ = clam.CheckWithStopAll(false, clamRepo) } if baseInfo.FreshIsActive { - version, err := cmd.RunDefaultWithStdoutBashC("freshclam --version") + version, err := cmdMgr.RunWithStdout("freshclam", "--version") if err == nil { if strings.Contains(version, "/") { baseInfo.FreshVersion = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ") diff --git a/agent/app/service/container.go b/agent/app/service/container.go index 23b341c60..e3fe540b4 100644 --- a/agent/app/service/container.go +++ b/agent/app/service/container.go @@ -1167,7 +1167,7 @@ func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error func (u *ContainerService) LoadUsers(req dto.OperationWithName) []string { var users []string - std, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("docker", "exec", req.Name, "cat", "/etc/passwd") + std, err := cmd.RunDockerExecWithStdout(20*time.Second, req.Name, "cat", "/etc/passwd") if err != nil { return users } @@ -1440,7 +1440,7 @@ func (u *ContainerService) DownloadContainerFile(req dto.ContainerFileReq) (io.R fileName = "container-file" } if stat.Mode.IsDir() { - if _, err := runContainerCommand(cli, req.ContainerID, []string{"sh", "-c", "command -v tar >/dev/null 2>&1"}); err != nil { + if _, err := runContainerCommand(cli, req.ContainerID, []string{"tar", "--help"}); err != nil { _ = cli.Close() return nil, "", "", fmt.Errorf("tar command not found in container") } @@ -1620,9 +1620,8 @@ func toContainerFileInfo(filePath string, stat container.PathStat, isDir bool) d } func isContainerDir(cli *client.Client, containerID, targetPath string) (bool, error) { - _, err := runContainerCommand(cli, containerID, []string{ - "sh", "-c", "[ -d \"$1\" ]", "sh", targetPath, - }) + checkPath := strings.TrimSuffix(targetPath, "/") + "/." + _, err := runContainerCommand(cli, containerID, []string{"ls", "-d", "--", checkPath}) if err != nil { return false, err } diff --git a/agent/app/service/cronjob_helper.go b/agent/app/service/cronjob_helper.go index 9c4ebcfb0..53edd56a2 100644 --- a/agent/app/service/cronjob_helper.go +++ b/agent/app/service/cronjob_helper.go @@ -391,7 +391,7 @@ func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime t func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error { cmdMgr := cmd.NewCommandMgr() - if err := cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, websiteLogDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil { + if err := cmdMgr.Run("tar", "-czf", dstFilePath, "-C", websiteLogDir, "access.log", "error.log"); err != nil { dstDir := pathUtils.Dir(dstFilePath) if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "access.log"), dstDir); err != nil { return err @@ -399,7 +399,7 @@ func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "error.log"), dstDir); err != nil { return err } - if err = cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, dstDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil { + if err = cmdMgr.Run("tar", "-czf", dstFilePath, "-C", dstDir, "access.log", "error.log"); err != nil { return err } _ = fileOp.DeleteFile(pathUtils.Join(dstDir, "access.log")) diff --git a/agent/app/service/dashboard.go b/agent/app/service/dashboard.go index 1da383a24..556739bd4 100644 --- a/agent/app/service/dashboard.go +++ b/agent/app/service/dashboard.go @@ -60,7 +60,9 @@ func (u *DashboardService) Restart(operation string) error { case "system": { go func() { - if err := cmd.RunDefaultBashCf("%s reboot", cmd.SudoHandleCmd()); err != nil { + cmdMgr := cmd.NewCommandMgr() + err := cmdMgr.RunWithOptionalSudo("reboot") + if err != nil { global.LOG.Errorf("handle reboot failed, %v", err) } }() @@ -429,11 +431,17 @@ type diskInfo struct { func loadDiskInfo() []dto.DiskInfo { var datas []dto.DiskInfo cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second)) - format := `awk 'NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}'` - stdout, err := cmdMgr.RunWithStdout("bash", "-c", `timeout 2 df -hT -P | `+format) + format := `NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}` + stdout, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: "df", Args: []string{"-hT", "-P"}}, + cmd.PipeCommand{Name: "awk", Args: []string{format}}, + ) if err != nil { cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second)) - stdout, err = cmdMgr2.RunWithStdout("bash", "-c", `timeout 1 df -lhT -P | `+format) + stdout, err = cmdMgr2.RunPipe( + cmd.PipeCommand{Name: "df", Args: []string{"-lhT", "-P"}}, + cmd.PipeCommand{Name: "awk", Args: []string{format}}, + ) if err != nil { return datas } diff --git a/agent/app/service/device.go b/agent/app/service/device.go index 8702c4063..ed2f363ec 100644 --- a/agent/app/service/device.go +++ b/agent/app/service/device.go @@ -82,7 +82,7 @@ func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) { } func (u *DeviceService) LoadTimeZone() ([]string, error) { - std, err := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Minute)).RunWithStdoutBashC("timedatectl list-timezones") + std, err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunWithStdout("timedatectl", "list-timezones") if err != nil { return []string{}, err } @@ -223,7 +223,9 @@ func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error { if cmd.CheckIllegal(req.User, req.Passwd) { return buserr.New("ErrCmdIllegal") } - if err := cmd.RunDefaultBashCf("%s echo '%s:%s' | %s chpasswd", cmd.SudoHandleCmd(), req.User, req.Passwd, cmd.SudoHandleCmd()); err != nil { + cmdItem := cmd.ExecCommandWithOptionalSudo("chpasswd") + cmdItem.Stdin = strings.NewReader(req.User + ":" + req.Passwd) + if err := cmdItem.Run(); err != nil { if strings.Contains(err.Error(), "does not exist") { return buserr.New("ErrNotExistUser") } @@ -393,7 +395,7 @@ func loadHosts() []dto.HostHelper { } func loadHostname() string { - std, err := cmd.RunDefaultWithStdoutBashC("hostname") + std, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("hostname") if err != nil { return "" } @@ -401,7 +403,7 @@ func loadHostname() string { } func loadUser() string { - std, err := cmd.RunDefaultWithStdoutBashC("whoami") + std, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("whoami") if err != nil { return "" } @@ -410,7 +412,8 @@ func loadUser() string { func loadSwap() []dto.SwapHelper { var data []dto.SwapHelper - std, err := cmd.RunDefaultWithStdoutBashCf("%s swapon --summary", cmd.SudoHandleCmd()) + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) + std, err := cmdMgr.RunWithOptionalSudoAndStdout("swapon", "--summary") if err != nil { return data } diff --git a/agent/app/service/disk.go b/agent/app/service/disk.go index 30563343a..2dff99bc4 100644 --- a/agent/app/service/disk.go +++ b/agent/app/service/disk.go @@ -30,7 +30,8 @@ func NewIDiskService() IDiskService { func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) { var diskInfos []response.DiskBasicInfo - output, err := cmd.RunDefaultWithStdoutBashC("lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA") + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) + output, err := cmdMgr.RunWithStdout("lsblk", "-J", "-o", "NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA") if err == nil { diskInfos, err = parseLsblkJsonOutput(output) if err == nil { @@ -38,7 +39,7 @@ func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) return &result, nil } } - output, err = cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA") + output, err = cmdMgr.RunWithStdout("lsblk", "-P", "-o", "NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA") if err != nil { return nil, fmt.Errorf("failed to run lsblk command: %v", err) } diff --git a/agent/app/service/disk_utils.go b/agent/app/service/disk_utils.go index e237cfa8a..a71f33c63 100644 --- a/agent/app/service/disk_utils.go +++ b/agent/app/service/disk_utils.go @@ -4,11 +4,13 @@ import ( "bufio" "encoding/json" "fmt" - "github.com/1Panel-dev/1Panel/agent/utils/re" "os" "os/exec" "strconv" "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/re" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/app/dto/response" @@ -386,10 +388,14 @@ func getParentDevice(device string) string { } func getDiskUsageInfo(device string) (size, used, avail string, usePercent int, err error) { - output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("df -h %s | tail -1", device)) + output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("df", "-h", device) if err != nil { return "", "", "", 0, nil } + lines := strings.Split(strings.TrimSpace(output), "\n") + if len(lines) > 1 { + output = lines[len(lines)-1] + } fields := strings.Fields(output) if len(fields) >= 5 { @@ -515,7 +521,7 @@ func removeFromFstab(mountPoint string) error { } func getFilesystemType(device string) (string, error) { - output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -o value -s TYPE %s", device)) + output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("blkid", "-o", "value", "-s", "TYPE", device) if err != nil { return "", err } @@ -575,7 +581,7 @@ func parseSizeToBytes(sizeStr string) int64 { } func getDeviceUUID(device string) (string, error) { - output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -s UUID -o value %s", device)) + output, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("blkid", "-s", "UUID", "-o", "value", device) if err != nil { return "", err } diff --git a/agent/app/service/docker.go b/agent/app/service/docker.go index 78952d547..9fc06a1e6 100644 --- a/agent/app/service/docker.go +++ b/agent/app/service/docker.go @@ -8,6 +8,7 @@ import ( "os" "path" "strings" + "time" "github.com/1Panel-dev/1Panel/agent/app/dto" "github.com/1Panel-dev/1Panel/agent/constant" @@ -89,8 +90,8 @@ func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) { data.Version = itemVersion.Version } data.IsSwarm = false - stdout2, _ := cmd.RunDefaultWithStdoutBashC("docker info | grep Swarm") - if string(stdout2) == " Swarm: active\n" { + stdout2, _ := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("docker", "info") + if strings.Contains(stdout2, "Swarm: active") { data.IsSwarm = true } if _, err := os.Stat(constant.DaemonJsonPath); err != nil { @@ -423,7 +424,7 @@ func validateDockerConfig() error { if !cmd.Which("dockerd") { return nil } - stdout, err := cmd.RunDefaultWithStdoutBashC("dockerd --validate") + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("dockerd", "--validate") if strings.Contains(stdout, "unknown flag: --validate") { return nil } diff --git a/agent/app/service/host_tool.go b/agent/app/service/host_tool.go index cf91d8528..20b5943f4 100644 --- a/agent/app/service/host_tool.go +++ b/agent/app/service/host_tool.go @@ -71,7 +71,7 @@ func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.Host supervisorConfig.ServiceName = serviceNameSet.Value } - versionRes, _ := cmd.RunDefaultWithStdoutBashC("supervisord -v") + versionRes, _ := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("supervisord", "-v") supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n") _, ctlRrr := exec.LookPath("supervisorctl") supervisorConfig.CtlExist = ctlRrr == nil @@ -550,8 +550,8 @@ func operateSupervisorCtl(operate, name, group, includeDir, containerName string err error ) if containerName != "" { - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30 * time.Second)) - output, err = cmdMgr.RunWithStdoutBashCf("docker exec %s supervisorctl %s", containerName, strings.Join(processNames, " ")) + args := append([]string{"supervisorctl"}, processNames...) + output, err = cmd.RunDockerExecWithStdout(30*time.Second, containerName, args...) } else { var out []byte out, err = exec.Command("supervisorctl", processNames...).Output() @@ -593,8 +593,8 @@ func getProcessStatus(config *response.SupervisorProcessConfig, containerName st ) processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...) if containerName != "" { - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(3 * time.Second)) - output, err = cmdMgr.RunWithStdoutBashCf("docker exec %s supervisorctl %s", containerName, strings.Join(processNames, " ")) + args := append([]string{"supervisorctl"}, processNames...) + output, err = cmd.RunDockerExecWithStdout(3*time.Second, containerName, args...) } else { var out []byte out, err = exec.Command("supervisorctl", processNames...).Output() diff --git a/agent/app/service/iptables.go b/agent/app/service/iptables.go index b2306c539..3d1adbf58 100644 --- a/agent/app/service/iptables.go +++ b/agent/app/service/iptables.go @@ -359,10 +359,10 @@ func loadBindNumber(chain string) int { } func initPreRules() error { - if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.IoRuleIn); err != nil { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, "-i", "lo", "-j", "ACCEPT", "-m", "comment", "--comment", "Loopback Whitelist"); err != nil { return err } - if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.EstablishedRule); err != nil { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT", "-m", "comment", "--comment", "ESTABLISHED Whitelist"); err != nil { return err } panelPort := LoadPanelPort() @@ -371,17 +371,17 @@ func initPreRules() error { } ports := []string{"80", "443", panelPort, loadSSHPort()} for _, item := range ports { - if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, fmt.Sprintf("-p tcp -m tcp --dport %v -j ACCEPT", item)); err != nil { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, "-p", "tcp", "-m", "tcp", "--dport", item, "-j", "ACCEPT"); err != nil { return err } } - if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p udp -m udp --dport 443 -j ACCEPT"); err != nil { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p", "udp", "-m", "udp", "--dport", "443", "-j", "ACCEPT"); err != nil { return err } - if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.DropAllTcp); err != nil { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p", "tcp", "-j", "DROP"); err != nil { return err } - if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.DropAllUdp); err != nil { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p", "udp", "-j", "DROP"); err != nil { return err } return nil diff --git a/agent/app/service/nginx.go b/agent/app/service/nginx.go index 6fec62b9f..d5c044616 100644 --- a/agent/app/service/nginx.go +++ b/agent/app/service/nginx.go @@ -241,7 +241,7 @@ func (n NginxService) Build(req request.NginxBuildReq) error { } buildTask.AddSubTaskWithOps("", func(t *task.Task) error { cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*buildTask), cmd.WithTimeout(120*time.Minute)) - if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil { + if err = cmdMgr.Run("docker", "compose", "-f", nginxInstall.GetComposePath(), "build"); err != nil { return err } _, err = compose.DownAndUp(nginxInstall.GetComposePath()) diff --git a/agent/app/service/nginx_utils.go b/agent/app/service/nginx_utils.go index 8e665efb9..9143e7200 100644 --- a/agent/app/service/nginx_utils.go +++ b/agent/app/service/nginx_utils.go @@ -240,16 +240,10 @@ func getNginxParamsFromStaticFile(scope dto.NginxKey, newParams []dto.NginxParam func opNginx(containerName, operate string) error { cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) - cmdStr := fmt.Sprintf("docker exec -i %s nginx ", containerName) if operate == constant.NginxCheck { - cmdStr = cmdStr + "-t" - } else { - cmdStr = cmdStr + "-s reload" + return cmdMgr.Run("docker", "exec", "-i", containerName, "nginx", "-t") } - if err := cmdMgr.RunBashC(cmdStr); err != nil { - return err - } - return nil + return cmdMgr.Run("docker", "exec", "-i", containerName, "nginx", "-s", "reload") } func nginxCheckAndReload(oldContent string, filePath string, containerName string) error { diff --git a/agent/app/service/runtime.go b/agent/app/service/runtime.go index b37e9faab..a52b5a900 100644 --- a/agent/app/service/runtime.go +++ b/agent/app/service/runtime.go @@ -630,11 +630,14 @@ func (r *RuntimeService) OperateNodeModules(req request.NodeModuleOperateReq) er return err } operation := getOperation(req.Operate, req.PkgManager) - execScript := fmt.Sprintf("%s %s %s", req.PkgManager, operation, req.Module) + execArgs := []string{"exec", "-i", containerName, req.PkgManager, operation} + if strings.TrimSpace(req.Module) != "" { + execArgs = append(execArgs, req.Module) + } ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute) defer cancel() - installCmd := exec.CommandContext(ctx, "docker", "exec", "-i", containerName, "bash", "-c", execScript) + installCmd := exec.CommandContext(ctx, "docker", execArgs...) output, err := installCmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to execute command: %s, error: %w", string(output), err) diff --git a/agent/app/service/snapshot.go b/agent/app/service/snapshot.go index 6d3db1dad..e659d4143 100644 --- a/agent/app/service/snapshot.go +++ b/agent/app/service/snapshot.go @@ -15,7 +15,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/app/model" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" - "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/docker" fileUtils "github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/docker/docker/api/types/image" @@ -303,12 +302,20 @@ func loadAppImage(list []dto.DataTree) []dto.DataTree { if list[i].IsLocal { appPath = path.Join(global.Dir.AppDir, "local", strings.TrimPrefix(list[i].Key, "local"), list[i].Name) } - stdout, err := cmd.RunDefaultWithStdoutBashCf("cat %s | grep image: ", path.Join(appPath, "docker-compose.yml")) + composePath := path.Join(appPath, "docker-compose.yml") + content, err := os.ReadFile(composePath) if err != nil { list[i].Children = append(list[i].Children, itemAppImage) continue } - itemAppImage.Name = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(stdout), "\n", ""), "image: ", "") + for _, line := range strings.Split(string(content), "\n") { + trimmed := strings.TrimSpace(line) + if !strings.HasPrefix(trimmed, "image:") { + continue + } + itemAppImage.Name = strings.TrimSpace(strings.TrimPrefix(trimmed, "image:")) + break + } for _, imageItem := range imageList { for _, tag := range imageItem.RepoTags { if tag == itemAppImage.Name { diff --git a/agent/app/service/snapshot_create.go b/agent/app/service/snapshot_create.go index 8e9073f09..415a7b2c7 100644 --- a/agent/app/service/snapshot_create.go +++ b/agent/app/service/snapshot_create.go @@ -405,7 +405,12 @@ func snapAppImage(snap snapHelper, req dto.SnapshotCreate, targetDir string) err if len(imageList) != 0 { snap.Task.Log(strings.Join(imageList, " ")) snap.Task.Logf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz")) - if err := cmd.RunDefaultBashCf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz")); err != nil { + outputPath := path.Join(targetDir, "images.tar.gz") + cmdMgr := cmd.NewCommandMgr(cmd.WithOutputFile(outputPath)) + if _, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: "docker", Args: append([]string{"save"}, imageList...)}, + cmd.PipeCommand{Name: "gzip", Args: []string{"-c"}}, + ); err != nil { snap.Task.LogFailedWithErr(i18n.GetMsgByKey("SnapDockerSave"), err) return err } diff --git a/agent/app/service/snapshot_recover.go b/agent/app/service/snapshot_recover.go index 1001027f0..8fd8b53ff 100644 --- a/agent/app/service/snapshot_recover.go +++ b/agent/app/service/snapshot_recover.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "os/exec" "path" "strings" "time" @@ -376,7 +377,15 @@ func recoverAppData(src string, itemHelper *snapRecoverHelper) error { itemHelper.Task.Log(i18n.GetMsgByKey("RecoverAppEmpty")) return nil } - if err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunBashCf("docker load < %s", path.Join(src, "images.tar.gz")); err != nil { + imageFile, err := os.Open(path.Join(src, "images.tar.gz")) + if err != nil { + itemHelper.Task.LogFailedWithErr(i18n.GetMsgByKey("RecoverAppImage"), err) + return fmt.Errorf("docker load images failed, %v", err) + } + defer func() { _ = imageFile.Close() }() + loadCmd := exec.Command("docker", "load") + loadCmd.Stdin = imageFile + if err := loadCmd.Run(); err != nil { itemHelper.Task.LogFailedWithErr(i18n.GetMsgByKey("RecoverAppImage"), err) return fmt.Errorf("docker load images failed, %v", err) } @@ -457,8 +466,7 @@ func restartCompose(composePath string, itemHelper *snapRecoverHelper) error { if _, err := os.Stat(pathItem); err != nil { continue } - upCmd := fmt.Sprintf("docker compose -f %s up -d", pathItem) - if err := cmd.RunDefaultBashC(upCmd); err != nil { + if err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).Run("docker", "compose", "-f", pathItem, "up", "-d"); err != nil { itemHelper.Task.LogFailedWithErr(i18n.GetMsgByKey("RecoverCompose"), err) continue } diff --git a/agent/app/service/ssh.go b/agent/app/service/ssh.go index 700e8c387..7f6cdf09f 100644 --- a/agent/app/service/ssh.go +++ b/agent/app/service/ssh.go @@ -586,12 +586,12 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d } fileList = sortFileList(fileList) - command := "" + filter := "" if len(req.Info) != 0 { if cmd.CheckIllegal(req.Info) { return 0, data, buserr.New("ErrCmdIllegal") } - command = fmt.Sprintf(" | grep '%s'", req.Info) + filter = req.Info } showCountFrom := (req.Page - 1) * req.PageSize @@ -599,28 +599,7 @@ func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []d nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) itemFailed, itemTotal := 0, 0 for _, file := range fileList { - commandItem := "" - if strings.HasPrefix(path.Base(file.Name), "secure") { - switch req.Status { - case constant.StatusSuccess: - commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) - case constant.StatusFailed: - commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' %s", file.Name, command) - default: - commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command) - } - } - if strings.HasPrefix(path.Base(file.Name), "auth.log") { - switch req.Status { - case constant.StatusSuccess: - commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) - case constant.StatusFailed: - commandItem = fmt.Sprintf("cat %s | grep -aE 'Failed password for|Connection closed by authenticating user' %s", file.Name, command) - default: - commandItem = fmt.Sprintf("cat %s | grep -aE \"(Failed password for|Connection closed by authenticating user|Accepted)\" %s", file.Name, command) - } - } - dataItem, successCount, failedCount := loadSSHData(ctx, commandItem, showCountFrom, showCountTo, file.Year, nyc) + dataItem, successCount, failedCount := loadSSHData(ctx, file.Name, path.Base(file.Name), req.Status, filter, showCountFrom, showCountTo, file.Year, nyc) itemFailed += failedCount itemTotal += successCount + failedCount showCountFrom = showCountFrom - (successCount + failedCount) @@ -1124,7 +1103,7 @@ func isSSHConfigPathAllowed(fileName string) bool { return false } -func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, currentYear int, nyc *time.Location) ([]dto.SSHHistory, int, int) { +func loadSSHData(ctx *gin.Context, filePath, fileName, status, filter string, showCountFrom, showCountTo, currentYear int, nyc *time.Location) ([]dto.SSHHistory, int, int) { var ( datas []dto.SSHHistory successCount int @@ -1134,12 +1113,15 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c if err != nil { return datas, 0, 0 } - stdout, err := cmd.RunDefaultWithStdoutBashC(command) + content, err := os.ReadFile(filePath) if err != nil { return datas, 0, 0 } - lines := strings.Split(stdout, "\n") + lines := strings.Split(string(content), "\n") for i := len(lines) - 1; i >= 0; i-- { + if !matchSSHLogLine(fileName, status, filter, lines[i]) { + continue + } var itemData dto.SSHHistory switch { case strings.Contains(lines[i], "Failed password for"): @@ -1177,6 +1159,44 @@ func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, c return datas, successCount, failedCount } +func matchSSHLogLine(fileName, status, filter, line string) bool { + if filter != "" && !strings.Contains(line, filter) { + return false + } + containsFailed := strings.Contains(line, "Failed password for") + containsClosed := strings.Contains(line, "Connection closed by authenticating user") + containsAccepted := strings.Contains(line, "Accepted ") + switch { + case strings.HasPrefix(fileName, "secure"): + switch status { + case constant.StatusSuccess: + return containsAccepted + case constant.StatusFailed: + return containsFailed + default: + return containsFailed || containsAccepted + } + case strings.HasPrefix(fileName, "auth.log"): + switch status { + case constant.StatusSuccess: + return containsAccepted + case constant.StatusFailed: + return containsFailed || containsClosed + default: + return containsFailed || containsClosed || containsAccepted + } + default: + switch status { + case constant.StatusSuccess: + return containsAccepted + case constant.StatusFailed: + return containsFailed || containsClosed + default: + return containsFailed || containsClosed || containsAccepted + } + } +} + func loadSuccessDatas(line string) dto.SSHHistory { var data dto.SSHHistory parts := strings.Fields(line) diff --git a/agent/app/service/website_ca.go b/agent/app/service/website_ca.go index da0522272..e6f899cf1 100644 --- a/agent/app/service/website_ca.go +++ b/agent/app/service/website_ca.go @@ -28,7 +28,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/i18n" - "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/files" "github.com/1Panel-dev/1Panel/agent/utils/ssl" "github.com/go-acme/lego/v4/certcrypto" @@ -378,8 +377,7 @@ func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.Website workDir = websiteSSL.Dir } logger.Println(i18n.GetMsgByKey("ExecShellStart")) - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30*time.Minute), cmd.WithLogger(logger), cmd.WithWorkDir(workDir)) - if err = cmdMgr.RunBashC(websiteSSL.Shell); err != nil { + if err = runShellScriptFile(workDir, websiteSSL.Shell, logger); err != nil { logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()})) } else { logger.Println(i18n.GetMsgByKey("ExecShellSuccess")) diff --git a/agent/app/service/website_ssl.go b/agent/app/service/website_ssl.go index 6039ef959..92ac08aaa 100644 --- a/agent/app/service/website_ssl.go +++ b/agent/app/service/website_ssl.go @@ -402,8 +402,7 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error { workDir = websiteSSL.Dir } printSSLLog(logger, "ExecShellStart", nil) - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30*time.Minute), cmd.WithLogger(logger), cmd.WithWorkDir(workDir)) - if err = cmdMgr.RunBashC(websiteSSL.Shell); err != nil { + if err = runShellScriptFile(workDir, websiteSSL.Shell, logger); err != nil { printSSLLog(logger, "ErrExecShell", map[string]interface{}{"err": err.Error()}) } else { printSSLLog(logger, "ExecShellSuccess", nil) @@ -447,6 +446,23 @@ func (w WebsiteSSLService) obtainSSL(id uint, autoRenew bool) error { return nil } +func runShellScriptFile(workDir, shell string, logger *log.Logger) error { + file, err := os.CreateTemp("", "1panel-shell-*.sh") + if err != nil { + return err + } + defer func() { _ = os.Remove(file.Name()) }() + if _, err := file.WriteString(shell); err != nil { + _ = file.Close() + return err + } + if err := file.Close(); err != nil { + return err + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30*time.Minute), cmd.WithLogger(logger), cmd.WithWorkDir(workDir)) + return cmdMgr.Run("bash", file.Name()) +} + func handleError(websiteSSL *model.WebsiteSSL, err error) { if websiteSSL.Status == constant.SSLInit || websiteSSL.Status == constant.SSLError { websiteSSL.Status = constant.StatusError diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go index 3bca11e8e..903bb0c9f 100644 --- a/agent/app/service/website_utils.go +++ b/agent/app/service/website_utils.go @@ -1221,7 +1221,7 @@ func checkIsLinkApp(website model.Website) bool { func chownRootDir(path string) error { cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second)) - if err := cmdMgr.RunBashCf(`chown -R 1000:1000 "%s"`, path); err != nil { + if err := cmdMgr.Run("chown", "-R", "1000:1000", path); err != nil { return err } return nil diff --git a/agent/init/firewall/firewall.go b/agent/init/firewall/firewall.go index 089d9976c..5459d41d2 100644 --- a/agent/init/firewall/firewall.go +++ b/agent/init/firewall/firewall.go @@ -71,7 +71,7 @@ func Init() { global.LOG.Errorf("find 1panel service port failed") return } - if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, fmt.Sprintf("-p tcp -m tcp --dport %v -j ACCEPT", panelPort)); err != nil { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, "-p", "tcp", "-m", "tcp", "--dport", panelPort, "-j", "ACCEPT"); err != nil { global.LOG.Errorf("add port accept rule %v failed, err: %v", panelPort, err) return } diff --git a/agent/init/hook/hook.go b/agent/init/hook/hook.go index 156e6d914..ac8e4b3ab 100644 --- a/agent/init/hook/hook.go +++ b/agent/init/hook/hook.go @@ -2,6 +2,7 @@ package hook import ( "os" + "os/exec" "strings" "github.com/1Panel-dev/1Panel/agent/app/dto" @@ -11,7 +12,6 @@ import ( "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" "github.com/1Panel-dev/1Panel/agent/utils/alert_push" - "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/xpack" ) @@ -140,11 +140,10 @@ func loadLocalDir() { } func initDockerConf() { - stdout, err := cmd.RunDefaultWithStdoutBashC("which docker") + dockerPath, err := exec.LookPath("docker") if err != nil { return } - dockerPath := stdout if strings.Contains(dockerPath, "snap") { constant.DaemonJsonPath = "/var/snap/docker/current/config/daemon.json" } diff --git a/agent/init/lang/lang.go b/agent/init/lang/lang.go index f13bc83bc..ffe903801 100644 --- a/agent/init/lang/lang.go +++ b/agent/init/lang/lang.go @@ -62,7 +62,7 @@ func initLang() { downloadLangFromRemote(fileOp) return } - if err := cmd.RunDefaultBashCf("cp -r %s %s", path.Join(tmpPath, "lang"), "/usr/local/bin/"); err != nil { + if err := cmd.NewCommandMgr().Run("cp", "-r", "--", path.Join(tmpPath, "lang"), "/usr/local/bin/"); err != nil { global.LOG.Errorf("load lang from package failed, %v", err) return } @@ -73,7 +73,11 @@ func initLang() { downloadGeoFromRemote(fileOp, geoPath) return } - if err := cmd.RunDefaultBashCf("mkdir %s && cp %s %s/", path.Dir(geoPath), path.Join(tmpPath, "GeoIP.mmdb"), path.Dir(geoPath)); err != nil { + if err := os.MkdirAll(path.Dir(geoPath), os.ModePerm); err != nil { + global.LOG.Errorf("load geo ip from package failed, %v", err) + return + } + if err := cmd.NewCommandMgr().Run("cp", "--", path.Join(tmpPath, "GeoIP.mmdb"), path.Dir(geoPath)+"/"); err != nil { global.LOG.Errorf("load geo ip from package failed, %v", err) return } @@ -114,7 +118,7 @@ func downloadLangFromRemote(fileOp files.FileOp) { global.LOG.Error("download lang.tar.gz failed, no such file") return } - if err := cmd.RunDefaultBashCf("tar zxvfC %s %s", "/usr/local/bin/lang.tar.gz", "/usr/local/bin/"); err != nil { + if err := cmd.NewCommandMgr().Run("tar", "zxvfC", "/usr/local/bin/lang.tar.gz", "/usr/local/bin/"); err != nil { global.LOG.Errorf("decompress lang.tar.gz failed, %v", err) return } diff --git a/agent/middleware/certificate.go b/agent/middleware/certificate.go index 634fecd90..efb8162b4 100644 --- a/agent/middleware/certificate.go +++ b/agent/middleware/certificate.go @@ -5,6 +5,7 @@ import ( "net" "net/http" "strings" + "time" "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" "github.com/1Panel-dev/1Panel/agent/global" @@ -29,7 +30,7 @@ func Certificate() gin.HandlerFunc { return } masterProxyID := c.Request.Header.Get("Proxy-Id") - proxyID, err := cmd.RunDefaultWithStdoutBashC("cat /etc/1panel/.nodeProxyID") + proxyID, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("cat", "/etc/1panel/.nodeProxyID") if err == nil && len(proxyID) != 0 && strings.TrimSpace(proxyID) != strings.TrimSpace(masterProxyID) { helper.InternalServer(c, fmt.Errorf("err proxy id")) return diff --git a/agent/utils/ai_tools/gpu/gpu.go b/agent/utils/ai_tools/gpu/gpu.go index 7bed74464..a6ab841a8 100644 --- a/agent/utils/ai_tools/gpu/gpu.go +++ b/agent/utils/ai_tools/gpu/gpu.go @@ -24,7 +24,7 @@ func New() (bool, NvidiaSMI) { func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) { cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) - itemData, err := cmdMgr.RunWithStdoutBashC("nvidia-smi -q -x") + itemData, err := cmdMgr.RunWithStdout("nvidia-smi", "-q", "-x") if err != nil { return nil, fmt.Errorf("calling nvidia-smi failed, %v", err) } diff --git a/agent/utils/ai_tools/xpu/xpu.go b/agent/utils/ai_tools/xpu/xpu.go index 66702ab4d..ea9239010 100644 --- a/agent/utils/ai_tools/xpu/xpu.go +++ b/agent/utils/ai_tools/xpu/xpu.go @@ -34,12 +34,12 @@ func (x XpuSMI) loadDeviceData(device Device, wg *sync.WaitGroup, res *[]XPUSimp cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) go func() { defer wgCmd.Done() - xpuData, xpuErr = cmdMgr.RunWithStdoutBashCf("xpu-smi discovery -d %d -j", device.DeviceID) + xpuData, xpuErr = cmdMgr.RunWithStdout("xpu-smi", "discovery", "-d", strconv.Itoa(device.DeviceID), "-j") }() go func() { defer wgCmd.Done() - statsData, statsErr = cmdMgr.RunWithStdoutBashCf("xpu-smi stats -d %d -j", device.DeviceID) + statsData, statsErr = cmdMgr.RunWithStdout("xpu-smi", "stats", "-d", strconv.Itoa(device.DeviceID), "-j") }() wgCmd.Wait() @@ -93,7 +93,7 @@ func (x XpuSMI) loadDeviceData(device Device, wg *sync.WaitGroup, res *[]XPUSimp func (x XpuSMI) LoadDashData() ([]XPUSimpleInfo, error) { cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) - data, err := cmdMgr.RunWithStdoutBashC("xpu-smi discovery -j") + data, err := cmdMgr.RunWithStdout("xpu-smi", "discovery", "-j") if err != nil { return nil, fmt.Errorf("calling xpu-smi failed, %v", err) } @@ -122,7 +122,7 @@ func (x XpuSMI) LoadDashData() ([]XPUSimpleInfo, error) { func (x XpuSMI) LoadGpuInfo() (*XpuInfo, error) { cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) - data, err := cmdMgr.RunWithStdoutBashC("xpu-smi discovery -j") + data, err := cmdMgr.RunWithStdout("xpu-smi", "discovery", "-j") if err != nil { return nil, fmt.Errorf("calling xpu-smi failed, %v", err) } @@ -144,7 +144,7 @@ func (x XpuSMI) LoadGpuInfo() (*XpuInfo, error) { wg.Wait() - processData, err := cmdMgr.RunWithStdoutBashC("xpu-smi ps -j") + processData, err := cmdMgr.RunWithStdout("xpu-smi", "ps", "-j") if err != nil { return nil, fmt.Errorf("calling xpu-smi ps failed, %s", err) } @@ -194,12 +194,12 @@ func (x XpuSMI) loadDeviceInfo(device Device, wg *sync.WaitGroup, res *XpuInfo, cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) go func() { defer wgCmd.Done() - xpuData, xpuErr = cmdMgr.RunWithStdoutBashCf("xpu-smi discovery -d %d -j", device.DeviceID) + xpuData, xpuErr = cmdMgr.RunWithStdout("xpu-smi", "discovery", "-d", strconv.Itoa(device.DeviceID), "-j") }() go func() { defer wgCmd.Done() - statsData, statsErr = cmdMgr.RunWithStdoutBashCf("xpu-smi stats -d %d -j", device.DeviceID) + statsData, statsErr = cmdMgr.RunWithStdout("xpu-smi", "stats", "-d", strconv.Itoa(device.DeviceID), "-j") }() wgCmd.Wait() diff --git a/agent/utils/clam/clam.go b/agent/utils/clam/clam.go index 003c6cc73..29a867170 100644 --- a/agent/utils/clam/clam.go +++ b/agent/utils/clam/clam.go @@ -34,7 +34,7 @@ func AddScanTask(taskItem *task.Task, clam model.Clam, timeNow string) { } taskItem.Logf("clamdscan --fdpass %s %s", strategy, clam.Path) mgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(time.Duration(clam.Timeout)*time.Second), cmd.WithTask(*taskItem)) - if err := mgr.RunBashCf("clamdscan --fdpass %s %s", strategy, clam.Path); err != nil { + if err := mgr.Run("clamdscan", "--fdpass", strategy, clam.Path); err != nil { return fmt.Errorf("clamdscan failed, %v", err) } return nil diff --git a/agent/utils/cmd/cmd.go b/agent/utils/cmd/cmd.go index 5043923ef..ddfcb0a1e 100644 --- a/agent/utils/cmd/cmd.go +++ b/agent/utils/cmd/cmd.go @@ -1,8 +1,17 @@ package cmd import ( + "bytes" + "io" + "os" "os/exec" "strings" + "sync" +) + +var ( + sudoHandleCmd string + sudoCheckOnce sync.Once ) func CheckIllegal(args ...string) bool { @@ -21,27 +30,40 @@ func CheckIllegal(args ...string) bool { } func SudoHandleCmd() string { - cmd := exec.Command("sudo", "-n", "ls") - if err := cmd.Run(); err == nil { - return "sudo " + sudoCheckOnce.Do(func() { + cmd := exec.Command("sudo", "-n", "ls") + if err := cmd.Run(); err == nil { + sudoHandleCmd = "sudo " + } + }) + return sudoHandleCmd +} + +func WrapWithOptionalSudo(name string, args ...string) (string, []string) { + if SudoHandleCmd() == "" { + return name, args } - return "" + return "sudo", append([]string{"-n", name}, args...) +} + +func ExecCommandWithOptionalSudo(name string, args ...string) *exec.Cmd { + commandName, commandArgs := WrapWithOptionalSudo(name, args...) + return exec.Command(commandName, commandArgs...) +} + +func WriteFileWithOptionalSudo(name string, data []byte, perm os.FileMode) error { + if err := os.WriteFile(name, data, perm); err == nil { + return nil + } else if SudoHandleCmd() == "" { + return err + } + command := exec.Command("sudo", "-n", "tee", name) + command.Stdin = bytes.NewReader(data) + command.Stdout = io.Discard + return command.Run() } func Which(name string) bool { - // Prefer Go's built-in PATH lookup so we don't depend on an external - // `which` binary, which is not installed by default on minimal - // distributions (e.g. Arch Linux, some Alpine images, slim containers). - // See 1Panel-dev/1Panel#12605. - if _, err := exec.LookPath(name); err == nil { - return true - } - // Fall back to shelling out for environments where PATH inside the - // agent process differs from the user's interactive shell PATH (the - // previous behaviour, preserved for compatibility). - stdout, err := RunDefaultWithStdoutBashCf("which %s", name) - if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) { - return false - } - return true + _, err := exec.LookPath(name) + return err == nil } diff --git a/agent/utils/cmd/cmdx.go b/agent/utils/cmd/cmdx.go index 27689dce2..998962389 100644 --- a/agent/utils/cmd/cmdx.go +++ b/agent/utils/cmd/cmdx.go @@ -5,10 +5,12 @@ import ( "context" "errors" "fmt" + "io" "log" "os" "os/exec" "strings" + "sync" "syscall" "time" @@ -17,11 +19,14 @@ import ( "github.com/1Panel-dev/1Panel/agent/constant" ) +const maxStreamOutputCapture = 64 * 1024 + type CommandHelper struct { context context.Context workDir string outputFile string scriptPath string + env []string timeout time.Duration taskItem *task.Task logger *log.Logger @@ -30,6 +35,46 @@ type CommandHelper struct { type Option func(*CommandHelper) +type PipeCommand struct { + Name string + Args []string + Env []string + Dir string + Stdin io.Reader +} + +type lockedBuffer struct { + mu sync.Mutex + buf bytes.Buffer + limit int + truncated int +} + +func (b *lockedBuffer) Write(p []byte) (int, error) { + b.mu.Lock() + defer b.mu.Unlock() + if b.limit > 0 && b.buf.Len() >= b.limit { + b.truncated += len(p) + return len(p), nil + } + if b.limit > 0 && b.buf.Len()+len(p) > b.limit { + keep := b.limit - b.buf.Len() + _, _ = b.buf.Write(p[:keep]) + b.truncated += len(p) - keep + return len(p), nil + } + return b.buf.Write(p) +} + +func (b *lockedBuffer) String() string { + b.mu.Lock() + defer b.mu.Unlock() + if b.truncated == 0 { + return b.buf.String() + } + return fmt.Sprintf("%s\n... truncated %d bytes ...", b.buf.String(), b.truncated) +} + func NewCommandMgr(opts ...Option) *CommandHelper { s := &CommandHelper{} for _, opt := range opts { @@ -38,82 +83,214 @@ func NewCommandMgr(opts ...Option) *CommandHelper { return s } -func RunDefaultBashC(command string) error { - mgr := NewCommandMgr() - return mgr.RunBashC(command) +func RunDockerExecWithStdout(timeout time.Duration, containerName string, args ...string) (string, error) { + commandArgs := append([]string{"exec", containerName}, args...) + return NewCommandMgr(WithTimeout(timeout)).RunWithStdout("docker", commandArgs...) } -func RunDefaultBashCf(command string, arg ...interface{}) error { - mgr := NewCommandMgr() - return mgr.RunBashCf(command, arg...) -} -func RunDefaultWithStdoutBashC(command string) (string, error) { - mgr := NewCommandMgr(WithTimeout(20 * time.Second)) - return mgr.RunWithStdoutBashC(command) -} -func RunDefaultWithStdoutBashCf(command string, arg ...interface{}) (string, error) { - mgr := NewCommandMgr(WithTimeout(20 * time.Second)) - return mgr.RunWithStdoutBashCf(command, arg...) -} -func RunDefaultWithStdoutBashCfAndTimeOut(command string, timeout time.Duration, arg ...interface{}) (string, error) { - mgr := NewCommandMgr(WithTimeout(timeout)) - return mgr.RunWithStdoutBashCf(command, arg...) +func RunDockerExec(timeout time.Duration, containerName string, args ...string) error { + commandArgs := append([]string{"exec", containerName}, args...) + return NewCommandMgr(WithTimeout(timeout)).Run("docker", commandArgs...) } func (c *CommandHelper) Run(name string, arg ...string) error { _, err := c.run(name, arg...) return err } -func (c *CommandHelper) RunBashCWithArgs(arg ...string) error { - arg = append([]string{"-c"}, arg...) - _, err := c.run("bash", arg...) - return err -} -func (c *CommandHelper) RunBashC(command string) error { - if _, err := c.run("bash", "-c", command); err != nil { - return err - } - return nil -} -func (c *CommandHelper) RunBashCf(command string, arg ...interface{}) error { - if _, err := c.run("bash", "-c", fmt.Sprintf(command, arg...)); err != nil { - return err - } - return nil +func (c *CommandHelper) RunWithOptionalSudo(name string, arg ...string) error { + commandName, commandArgs := WrapWithOptionalSudo(name, arg...) + return c.Run(commandName, commandArgs...) } func (c *CommandHelper) RunWithStdout(name string, arg ...string) (string, error) { return c.run(name, arg...) } -func (c *CommandHelper) RunWithStdoutBashC(command string) (string, error) { - return c.run("bash", "-c", command) + +func (c *CommandHelper) RunWithOptionalSudoAndStdout(name string, arg ...string) (string, error) { + commandName, commandArgs := WrapWithOptionalSudo(name, arg...) + return c.RunWithStdout(commandName, commandArgs...) } -func (c *CommandHelper) RunWithStdoutBashCf(command string, arg ...interface{}) (string, error) { - return c.run("bash", "-c", fmt.Sprintf(command, arg...)) + +func (c *CommandHelper) RunPipe(commands ...PipeCommand) (string, error) { + if len(commands) == 0 { + return "", nil + } + + ctx, cancel := c.pipeContext() + if cancel != nil { + defer cancel() + } + + cmds := c.buildPipeCommands(ctx, commands) + customWriter := &CustomWriter{taskItem: c.taskItem} + var outputFile *os.File + limitOutputCapture := c.taskItem != nil || c.logger != nil || len(c.outputFile) != 0 + stdout, stderr := &lockedBuffer{}, &lockedBuffer{} + if limitOutputCapture { + stdout.limit = maxStreamOutputCapture + stderr.limit = maxStreamOutputCapture + } + if commands[0].Stdin != nil { + cmds[0].Stdin = commands[0].Stdin + } + var pipeStderr io.Writer = stderr + var lastStdout io.Writer = stdout + var lastStderr io.Writer = stderr + var streamWriter io.Writer + var streamClosers []io.Closer + if c.taskItem != nil { + streamWriter = customWriter + } else if c.logger != nil { + streamWriter = c.logger.Writer() + if closer, ok := streamWriter.(io.Closer); ok { + streamClosers = append(streamClosers, closer) + } + } else if len(c.outputFile) != 0 { + file, err := os.OpenFile(c.outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm) + if err != nil { + return "", err + } + outputFile = file + lastStdout = outputFile + } + if streamWriter != nil { + pipeStderr = io.MultiWriter(stderr, streamWriter) + lastStdout = io.MultiWriter(stdout, streamWriter) + lastStderr = io.MultiWriter(stderr, streamWriter) + } + defer func() { + if c.taskItem != nil { + customWriter.Flush() + } + for _, closer := range streamClosers { + _ = closer.Close() + } + if outputFile != nil { + _ = outputFile.Close() + } + }() + if err := connectPipeCommands(cmds, lastStdout, lastStderr, pipeStderr); err != nil { + return "", err + } + + if err := startPipeCommands(cmds); err != nil { + return handleErrString(stdout.String(), stderr.String(), c.IgnoreExist1, err) + } + + runErr := waitPipeCommands(ctx, cmds) + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return "", buserr.New("ErrCmdTimeout") + } + if errors.Is(ctx.Err(), context.Canceled) { + return "", buserr.New("ErrShutDown") + } + if runErr != nil { + return handleErrString(stdout.String(), stderr.String(), c.IgnoreExist1, runErr) + } + return stdout.String(), nil +} + +func (c *CommandHelper) pipeContext() (context.Context, context.CancelFunc) { + ctx := c.context + if ctx == nil { + ctx = context.Background() + } + if c.timeout == 0 { + return ctx, nil + } + return context.WithTimeout(ctx, c.timeout) +} + +func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCommand) []*exec.Cmd { + cmds := make([]*exec.Cmd, 0, len(commands)) + for _, item := range commands { + cmdItem := exec.CommandContext(ctx, item.Name, item.Args...) + cmdItem.Env = append(os.Environ(), c.env...) + cmdItem.Env = append(cmdItem.Env, item.Env...) + cmdItem.Dir = c.workDir + if item.Dir != "" { + cmdItem.Dir = item.Dir + } + cmdItem.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + cmds = append(cmds, cmdItem) + } + return cmds +} + +func connectPipeCommands(cmds []*exec.Cmd, stdout, stderr, pipeStderr io.Writer) error { + for i := 0; i < len(cmds)-1; i++ { + pipe, err := cmds[i].StdoutPipe() + if err != nil { + return err + } + cmds[i+1].Stdin = pipe + cmds[i].Stderr = pipeStderr + } + last := cmds[len(cmds)-1] + last.Stdout = stdout + last.Stderr = stderr + return nil +} + +func startPipeCommands(cmds []*exec.Cmd) error { + for i := len(cmds) - 1; i >= 0; i-- { + if err := cmds[i].Start(); err != nil { + killStarted(cmds[i+1:]) + return err + } + } + return nil +} + +func waitPipeCommands(ctx context.Context, cmds []*exec.Cmd) error { + done := make(chan error, 1) + go func() { + var runErr error + for _, item := range cmds { + if err := item.Wait(); err != nil && runErr == nil { + runErr = err + } + } + done <- runErr + }() + select { + case runErr := <-done: + return runErr + case <-ctx.Done(): + killProcessGroups(cmds) + return <-done + } } func (c *CommandHelper) run(name string, arg ...string) (string, error) { var cmd *exec.Cmd var newContext context.Context var cancel context.CancelFunc + var outputFile *os.File if c.timeout != 0 { if c.context == nil { newContext, cancel = context.WithTimeout(context.Background(), c.timeout) - defer cancel() } else { newContext, cancel = context.WithTimeout(c.context, c.timeout) - defer cancel() } + defer cancel() + } else if c.context != nil { + newContext = c.context + } + + if len(c.scriptPath) != 0 { + if newContext != nil { + cmd = exec.CommandContext(newContext, "bash", c.scriptPath) + } else { + cmd = exec.Command("bash", c.scriptPath) + } + } else if newContext != nil { cmd = exec.CommandContext(newContext, name, arg...) } else { - if c.context == nil { - newContext = context.Background() - cmd = exec.Command(name, arg...) - } else { - newContext = c.context - cmd = exec.CommandContext(c.context, name, arg...) - } + cmd = exec.Command(name, arg...) } cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, @@ -121,36 +298,50 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) { customWriter := &CustomWriter{taskItem: c.taskItem} var stdout, stderr bytes.Buffer + var loggerClosers []io.Closer if c.taskItem != nil { cmd.Stdout = customWriter cmd.Stderr = customWriter } else if c.logger != nil { - cmd.Stdout = c.logger.Writer() - cmd.Stderr = c.logger.Writer() + stdoutWriter := c.logger.Writer() + stderrWriter := c.logger.Writer() + if closer, ok := stdoutWriter.(io.Closer); ok { + loggerClosers = append(loggerClosers, closer) + } + if closer, ok := stderrWriter.(io.Closer); ok { + loggerClosers = append(loggerClosers, closer) + } + cmd.Stdout = stdoutWriter + cmd.Stderr = stderrWriter } else if len(c.outputFile) != 0 { file, err := os.OpenFile(c.outputFile, os.O_WRONLY|os.O_CREATE, constant.FilePerm) if err != nil { return "", err } - defer file.Close() - cmd.Stdout = file - cmd.Stderr = file - } else if len(c.scriptPath) != 0 { - cmd = exec.Command("bash", c.scriptPath) - cmd.Stdout = &stdout - cmd.Stderr = &stderr + outputFile = file + cmd.Stdout = outputFile + cmd.Stderr = outputFile } else { cmd.Stdout = &stdout cmd.Stderr = &stderr } env := os.Environ() + env = append(env, c.env...) cmd.Env = env if len(c.workDir) != 0 { cmd.Dir = c.workDir } + defer func() { + for _, closer := range loggerClosers { + _ = closer.Close() + } + if outputFile != nil { + _ = outputFile.Close() + } + }() if err := cmd.Start(); err != nil { - return "", fmt.Errorf("cmd.Start() failed with '%s'\n", err) + return "", fmt.Errorf("cmd start failed: %w", err) } if c.taskItem != nil { defer customWriter.Flush() @@ -166,9 +357,10 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) { return handleErr(stdout, stderr, c.IgnoreExist1, err) } return stdout.String(), nil - case <-newContext.Done(): + case <-contextDone(newContext): if cmd.Process != nil && cmd.Process.Pid > 0 { - syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + killErr := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + _ = killErr } var err error switch newContext.Err() { @@ -184,6 +376,30 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) { } } +func contextDone(ctx context.Context) <-chan struct{} { + if ctx == nil { + return nil + } + return ctx.Done() +} + +func killStarted(cmds []*exec.Cmd) { + killProcessGroups(cmds) + for _, item := range cmds { + if item.Process != nil { + _ = item.Wait() + } + } +} + +func killProcessGroups(cmds []*exec.Cmd) { + for _, item := range cmds { + if item.Process != nil { + _ = syscall.Kill(-item.Process.Pid, syscall.SIGKILL) + } + } +} + func WithContext(ctx context.Context) Option { return func(s *CommandHelper) { s.context = ctx @@ -219,6 +435,11 @@ func WithScriptPath(scriptPath string) Option { s.scriptPath = scriptPath } } +func WithEnv(env ...string) Option { + return func(s *CommandHelper) { + s.env = append(s.env, env...) + } +} func WithIgnoreExist1() Option { return func(s *CommandHelper) { s.IgnoreExist1 = true @@ -250,6 +471,10 @@ func (cw *CustomWriter) Flush() { } func handleErr(stdout, stderr bytes.Buffer, ignoreExist1 bool, err error) (string, error) { + return handleErrString(stdout.String(), stderr.String(), ignoreExist1, err) +} + +func handleErrString(stdout, stderr string, ignoreExist1 bool, err error) (string, error) { var exitError *exec.ExitError if ignoreExist1 && errors.As(err, &exitError) { if status, ok := exitError.Sys().(syscall.WaitStatus); ok { @@ -258,8 +483,8 @@ func handleErr(stdout, stderr bytes.Buffer, ignoreExist1 bool, err error) (strin } } } - outItem := stdout.String() - errItem := stderr.String() + outItem := stdout + errItem := stderr if len(errItem) != 0 && len(outItem) != 0 { return outItem, fmt.Errorf("stdout: %s; stderr: %s, err: %v", outItem, errItem, err) } diff --git a/agent/utils/common/common.go b/agent/utils/common/common.go index bd27a54dd..9304c611f 100644 --- a/agent/utils/common/common.go +++ b/agent/utils/common/common.go @@ -18,6 +18,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/ctl_conf" "github.com/1Panel-dev/1Panel/agent/utils/re" "golang.org/x/net/idna" ) @@ -289,18 +290,21 @@ func LoadTimeZoneByCmd() string { if _, err := time.LoadLocation(loc); err != nil { loc = "Asia/Shanghai" } - std, err := cmd.RunDefaultWithStdoutBashC("timedatectl | grep 'Time zone'") + std, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("timedatectl") if err != nil { return loc } - fields := strings.Fields(string(std)) - if len(fields) != 5 { - return loc + for _, line := range strings.Split(std, "\n") { + fields := strings.Fields(line) + if len(fields) != 5 || fields[0] != "Time" || fields[1] != "zone:" { + continue + } + if _, err := time.LoadLocation(fields[2]); err != nil { + return loc + } + return fields[2] } - if _, err := time.LoadLocation(fields[2]); err != nil { - return loc - } - return fields[2] + return loc } func IsValidDomain(domain string) bool { @@ -442,20 +446,8 @@ func GetDockerComposeCommand() string { } func LoadParams(param string) string { - stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param) - if err != nil { - panic(err) - } - info := strings.ReplaceAll(stdout, "\n", "") - if len(info) == 0 || info == `""` { - panic(fmt.Sprintf("error `%s` find in /usr/local/bin/1pctl", param)) - } - return info + return ctl_conf.Load(param) } func LoadParamsWithoutPanic(param string) string { - stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param) - if err != nil { - return "" - } - return strings.ReplaceAll(stdout, "\n", "") + return ctl_conf.LoadWithoutPanic(param) } diff --git a/agent/utils/controller/manager/common.go b/agent/utils/controller/manager/common.go index 46a310d94..fe5598f2d 100644 --- a/agent/utils/controller/manager/common.go +++ b/agent/utils/controller/manager/common.go @@ -2,7 +2,6 @@ package manager import ( "errors" - "strings" "time" "github.com/1Panel-dev/1Panel/agent/utils/cmd" @@ -19,5 +18,5 @@ func handlerErr(out string, err error) error { } func run(name string, args ...string) (string, error) { - return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second)).RunWithStdoutBashCf("LANGUAGE=en_US:en %s %s", name, strings.Join(args, " ")) + return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second), cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout(name, args...) } diff --git a/agent/utils/controller/manager/openrc.go b/agent/utils/controller/manager/openrc.go index 68cb5ba57..92dd73d51 100644 --- a/agent/utils/controller/manager/openrc.go +++ b/agent/utils/controller/manager/openrc.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/1Panel-dev/1Panel/agent/utils/cmd" ) @@ -18,18 +19,11 @@ func (s *Openrc) Name() string { return "openrc" } func (s *Openrc) IsActive(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) - if err != nil { - return false, err - } - return out == "active\n", nil + _, err := cmd.NewCommandMgr().RunWithStdout("service", serviceName, "status") + return err == nil, nil } func (s *Openrc) IsEnable(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) - if err != nil { - return false, err - } - return out == "enabled\n", nil + return isSysvServiceEnabled(serviceName) } func (s *Openrc) IsExist(serviceName string) (bool, error) { _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)) @@ -59,3 +53,27 @@ func (s *Openrc) Operate(operate, serviceName string) error { func (s *Openrc) Reload() error { return nil } + +func isSysvServiceEnabled(serviceName string) (bool, error) { + entries, err := os.ReadDir("/etc") + if err != nil { + return false, err + } + for _, entry := range entries { + name := entry.Name() + if !entry.IsDir() || !strings.HasPrefix(name, "rc") || !strings.HasSuffix(name, ".d") { + continue + } + items, err := os.ReadDir(filepath.Join("/etc", name)) + if err != nil { + continue + } + for _, item := range items { + itemName := item.Name() + if strings.HasPrefix(itemName, "S") && strings.HasSuffix(itemName, serviceName) { + return true, nil + } + } + } + return false, nil +} diff --git a/agent/utils/controller/manager/sysvinit.go b/agent/utils/controller/manager/sysvinit.go index a1a8b1af5..aeaa309f0 100644 --- a/agent/utils/controller/manager/sysvinit.go +++ b/agent/utils/controller/manager/sysvinit.go @@ -18,18 +18,11 @@ func (s *Sysvinit) Name() string { return "sysvinit" } func (s *Sysvinit) IsActive(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) - if err != nil { - return false, err - } - return out == "active\n", nil + _, err := cmd.NewCommandMgr().RunWithStdout("service", serviceName, "status") + return err == nil, nil } func (s *Sysvinit) IsEnable(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) - if err != nil { - return false, err - } - return out == "enabled\n", nil + return isSysvServiceEnabled(serviceName) } func (s *Sysvinit) IsExist(serviceName string) (bool, error) { _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)) diff --git a/agent/utils/ctl_conf/ctl_conf.go b/agent/utils/ctl_conf/ctl_conf.go new file mode 100644 index 000000000..6b5848b6a --- /dev/null +++ b/agent/utils/ctl_conf/ctl_conf.go @@ -0,0 +1,88 @@ +package ctl_conf + +import ( + "fmt" + "os" + "strings" +) + +const defaultFile = "/usr/local/bin/1pctl" + +func Load(key string) string { + info, err := LoadFromFile(defaultFile, key) + if err != nil { + panic(err) + } + if len(info) == 0 || info == `""` { + panic(fmt.Sprintf("error `%s` find in %s", key, defaultFile)) + } + return info +} + +func LoadWithoutPanic(key string) string { + info, err := LoadFromFile(defaultFile, key) + if err != nil { + return "" + } + return info +} + +func LoadFromFile(filePath, key string) (string, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + prefix := key + "=" + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, prefix) { + return strings.TrimSpace(strings.TrimPrefix(line, prefix)), nil + } + } + return "", fmt.Errorf("error `%s` find in %s", key, filePath) +} + +func UpdateInFile(filePath, key, value string) error { + return rewriteFile(filePath, func(line string) (string, bool) { + if strings.HasPrefix(line, key+"=") { + return key + "=" + value, true + } + return line, true + }) +} + +func RemoveValueFromFile(filePath, key, value string) error { + target := key + "=" + value + return rewriteFile(filePath, func(line string) (string, bool) { + return line, line != target + }) +} + +func rewriteFile(filePath string, rewrite func(string) (string, bool)) error { + info, err := os.Stat(filePath) + if err != nil { + return err + } + data, err := os.ReadFile(filePath) + if err != nil { + return err + } + content := rewriteLines(string(data), rewrite) + return os.WriteFile(filePath, []byte(content), info.Mode().Perm()) +} + +func rewriteLines(data string, rewrite func(string) (string, bool)) string { + hasTrailingNewline := strings.HasSuffix(data, "\n") + lines := strings.Split(strings.TrimSuffix(data, "\n"), "\n") + rewritten := make([]string, 0, len(lines)) + for _, line := range lines { + newLine, keep := rewrite(line) + if keep { + rewritten = append(rewritten, newLine) + } + } + content := strings.Join(rewritten, "\n") + if hasTrailingNewline { + content += "\n" + } + return content +} diff --git a/agent/utils/files/file_op.go b/agent/utils/files/file_op.go index 50a97dc14..d44cbc1bf 100644 --- a/agent/utils/files/file_op.go +++ b/agent/utils/files/file_op.go @@ -173,14 +173,23 @@ func (f FileOp) CleanDir(dst string) error { if IsProtected(dst) { return buserr.New("ErrPathNotDelete") } - return cmd.RunDefaultBashCf("rm -rf %s/*", dst) + items, err := afero.ReadDir(f.Fs, dst) + if err != nil { + return err + } + for _, item := range items { + if err := f.Fs.RemoveAll(filepath.Join(dst, item.Name())); err != nil { + return err + } + } + return nil } func (f FileOp) RmRf(dst string) error { if IsProtected(dst) { return buserr.New("ErrPathNotDelete") } - return cmd.RunDefaultBashCf("rm -rf %s", dst) + return f.Fs.RemoveAll(dst) } func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error { @@ -231,48 +240,48 @@ func (f FileOp) SaveFileWithByte(dst string, content []byte, mode fs.FileMode) e } func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error { - cmdStr := fmt.Sprintf(`%s chown %s:%s "%s"`, cmd.SudoHandleCmd(), uid, gid, dst) + args := []string{uid + ":" + gid, dst} if sub { - cmdStr = fmt.Sprintf(`chown -R %s:%s "%s"`, uid, gid, dst) + args = append([]string{"-R", uid + ":" + gid}, dst) } timeout := cmdDefaultTimeout if sub { timeout = cmdRecursiveTimeout } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout)) - if err := cmdMgr.RunBashC(cmdStr); err != nil { + if err := cmdMgr.RunWithOptionalSudo("chown", args...); err != nil { return err } return nil } func (f FileOp) ChmodR(dst string, mode int64, sub bool) error { - cmdStr := fmt.Sprintf(`%s chmod %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%04o", mode), dst) + args := []string{fmt.Sprintf("%04o", mode), dst} if sub { - cmdStr = fmt.Sprintf(`%s chmod -R %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%04o", mode), dst) + args = append([]string{"-R", fmt.Sprintf("%04o", mode)}, dst) } timeout := cmdDefaultTimeout if sub { timeout = cmdRecursiveTimeout } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout)) - if err := cmdMgr.RunBashC(cmdStr); err != nil { + if err := cmdMgr.RunWithOptionalSudo("chmod", args...); err != nil { return err } return nil } func (f FileOp) ChmodRWithMode(dst string, mode fs.FileMode, sub bool) error { - cmdStr := fmt.Sprintf(`%s chmod %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%o", mode.Perm()), dst) + args := []string{fmt.Sprintf("%o", mode.Perm()), dst} if sub { - cmdStr = fmt.Sprintf(`%s chmod -R %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%o", mode.Perm()), dst) + args = append([]string{"-R", fmt.Sprintf("%o", mode.Perm())}, dst) } timeout := cmdDefaultTimeout if sub { timeout = cmdRecursiveTimeout } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout)) - if err := cmdMgr.RunBashC(cmdStr); err != nil { + if err := cmdMgr.RunWithOptionalSudo("chmod", args...); err != nil { return err } return nil @@ -285,23 +294,18 @@ func (f FileOp) ChownRPaths(paths []string, uid string, gid string, sub bool) er if len(paths) == 1 { return f.ChownR(paths[0], uid, gid, sub) } - quoted := make([]string, len(paths)) - for i, p := range paths { - quoted[i] = fmt.Sprintf(`"%s"`, p) - } - args := strings.Join(quoted, " ") - var cmdStr string + args := []string{uid + ":" + gid} if sub { - cmdStr = fmt.Sprintf(`chown -R %s:%s %s`, uid, gid, args) + args = append([]string{"-R", uid + ":" + gid}, paths...) } else { - cmdStr = fmt.Sprintf(`%s chown %s:%s %s`, cmd.SudoHandleCmd(), uid, gid, args) + args = append(args, paths...) } timeout := cmdDefaultTimeout if sub { timeout = cmdRecursiveTimeout } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout)) - if err := cmdMgr.RunBashC(cmdStr); err != nil { + if err := cmdMgr.RunWithOptionalSudo("chown", args...); err != nil { return err } return nil @@ -314,24 +318,19 @@ func (f FileOp) ChmodRPaths(paths []string, mode int64, sub bool) error { if len(paths) == 1 { return f.ChmodR(paths[0], mode, sub) } - quoted := make([]string, len(paths)) - for i, p := range paths { - quoted[i] = fmt.Sprintf(`"%s"`, p) - } - args := strings.Join(quoted, " ") modeStr := fmt.Sprintf("%04o", mode) - var cmdStr string + args := []string{modeStr} if sub { - cmdStr = fmt.Sprintf(`%s chmod -R %s %s`, cmd.SudoHandleCmd(), modeStr, args) + args = append([]string{"-R", modeStr}, paths...) } else { - cmdStr = fmt.Sprintf(`%s chmod %s %s`, cmd.SudoHandleCmd(), modeStr, args) + args = append(args, paths...) } timeout := cmdDefaultTimeout if sub { timeout = cmdRecursiveTimeout } cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(timeout)) - if err := cmdMgr.RunBashC(cmdStr); err != nil { + if err := cmdMgr.RunWithOptionalSudo("chmod", args...); err != nil { return err } return nil @@ -543,19 +542,20 @@ func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error { dstPath = dst coverFlag = "-f" } - var quotedPaths []string - for _, p := range oldPaths { - quotedPaths = append(quotedPaths, fmt.Sprintf("'%s'", p)) + args := []string{} + if coverFlag != "" { + args = append(args, coverFlag) } - mvCommand := fmt.Sprintf("mv %s %s '%s'", coverFlag, strings.Join(quotedPaths, " "), dstPath) - if err := cmd.RunDefaultBashC(mvCommand); err != nil { + args = append(args, oldPaths...) + args = append(args, dstPath) + if err := cmd.NewCommandMgr().Run("mv", args...); err != nil { return err } return nil } func (f FileOp) Mv(oldPath, dstPath string) error { - if err := cmd.RunDefaultBashCf(`mv '%s' '%s'`, oldPath, dstPath); err != nil { + if err := cmd.NewCommandMgr().Run("mv", oldPath, dstPath); err != nil { return err } return nil @@ -611,22 +611,22 @@ func (f FileOp) CopyAndReName(src, dst, name string, cover bool) error { if name != "" && !cover { dstPath = filepath.Join(dst, name) } - return cmd.RunDefaultBashCf(`cp -rfp '%s' '%s'`, src, dstPath) + return cmd.NewCommandMgr().Run("cp", "-rfp", src, dstPath) } else { dstPath := filepath.Join(dst, name) if cover { dstPath = dst } - return cmd.RunDefaultBashCf(`cp -fp '%s' '%s'`, src, dstPath) + return cmd.NewCommandMgr().Run("cp", "-fp", src, dstPath) } } func (f FileOp) CopyDirWithNewName(src, dst, newName string) error { if newName == "." || newName == "" { - return cmd.RunDefaultBashCf(`cp -rfp '%s'/. '%s'`, src, dst) + return cmd.NewCommandMgr().Run("cp", "-rfp", filepath.Clean(src)+"/.", dst) } dstDir := filepath.Join(dst, newName) - return cmd.RunDefaultBashCf(`cp -rfp '%s' '%s'`, src, dstDir) + return cmd.NewCommandMgr().Run("cp", "-rfp", src, dstDir) } func (f FileOp) CopyDir(src, dst string) error { @@ -638,7 +638,7 @@ func (f FileOp) CopyDir(src, dst string) error { if err = f.Fs.MkdirAll(dstDir, srcInfo.Mode()); err != nil { return err } - return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rfp '%s' '%s'`, src, dst+"/") + return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).Run("cp", "-rfp", src, dst+"/") } func (f FileOp) CopyDirWithExclude(src, dst string, excludeNames []string) error { @@ -651,7 +651,7 @@ func (f FileOp) CopyDirWithExclude(src, dst string, excludeNames []string) error return err } if len(excludeNames) == 0 { - return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rfp '%s' '%s'`, src, dst+"/") + return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).Run("cp", "-rfp", src, dst+"/") } tmpFiles, err := os.ReadDir(src) if err != nil { @@ -684,7 +684,7 @@ func (f FileOp) CopyDirWithExclude(src, dst string, excludeNames []string) error func (f FileOp) CopyFile(src, dst string) error { dst = filepath.Clean(dst) + string(filepath.Separator) - return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -fp '%s' '%s'`, src, dst+"/") + return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).Run("cp", "-fp", src, dst+"/") } func (f FileOp) GetDirSize(path string) (int64, error) { @@ -1263,10 +1263,8 @@ func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules workdir = path.Dir(src) srcItem = path.Base(src) } - commands := "" - exMap := make(map[string]struct{}) - exStr := "" + excludeArgs := []string{} excludes := strings.Split(exclusionRules, ",") for _, exclude := range excludes { if len(exclude) == 0 { @@ -1278,19 +1276,18 @@ func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules if _, ok := exMap[exclude]; ok { continue } - exStr += fmt.Sprintf(" --exclude '%s'", exclude) + excludeArgs = append(excludeArgs, "--exclude", exclude) exMap[exclude] = struct{}{} } + tarArgs := append([]string{}, excludeArgs...) if len(secret) != 0 { - commands = fmt.Sprintf("tar %s -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", exStr, srcItem, secret, dst) - global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(workdir), cmd.WithIgnoreExist1()) + return runTarGzEncryptToFile(cmdMgr, dst, secret, append(tarArgs, srcItem)...) } else { - commands = fmt.Sprintf("tar -zcf %s %s %s", dst, exStr, srcItem) - global.LOG.Debug(commands) + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(workdir), cmd.WithIgnoreExist1()) + return runTarGzToFile(cmdMgr, dst, append(tarArgs, srcItem)...) } - cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(workdir), cmd.WithIgnoreExist1()) - return cmdMgr.RunBashC(commands) } func (f FileOp) TarGzFilesWithCompressPro(list []string, dst, secret string) error { @@ -1300,20 +1297,17 @@ func (f FileOp) TarGzFilesWithCompressPro(list []string, dst, secret string) err } } - var filelist []string + var tarArgs []string for _, item := range list { - filelist = append(filelist, "-C '"+path.Dir(item)+"' '"+path.Base(item)+"' ") + tarArgs = append(tarArgs, "-C", path.Dir(item), path.Base(item)) } - commands := "" if len(secret) != 0 { - commands = fmt.Sprintf("tar -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", strings.Join(filelist, " "), secret, dst) - global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1()) + return runTarGzEncryptToFile(cmdMgr, dst, secret, tarArgs...) } else { - commands = fmt.Sprintf("tar -zcf %s %s", dst, strings.Join(filelist, " ")) - global.LOG.Debug(commands) + cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1()) + return runTarGzToFile(cmdMgr, dst, tarArgs...) } - cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1()) - return cmdMgr.RunBashC(commands) } func (f FileOp) TarGzExtractPro(src, dst string, secret string) error { @@ -1323,19 +1317,13 @@ func (f FileOp) TarGzExtractPro(src, dst string, secret string) error { } } - commands := "" if len(secret) != 0 { - commands = fmt.Sprintf("openssl enc -d -aes-256-cbc -salt -k '%s' -in %s | tar -zxf - > /root/log", secret, src) - global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(dst), cmd.WithIgnoreExist1()) + return runTarGzDecryptToDir(cmdMgr, src, dst, secret, true) } else { - commands = fmt.Sprintf("tar zxvf %s", src) - global.LOG.Debug(commands) + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(dst), cmd.WithIgnoreExist1()) + return runTarGzExtractToDir(cmdMgr, src, dst) } - cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(dst), cmd.WithIgnoreExist1()) - if len(secret) == 0 { - return cmdMgr.Run("tar", "zxvf", src) - } - return cmdMgr.RunBashC(commands) } func CopyCustomAppFile(srcPath, dstPath string) error { if _, err := os.Stat(srcPath); os.IsNotExist(err) { @@ -1375,7 +1363,7 @@ func CopyCustomAppFile(srcPath, dstPath string) error { func OpensslEncrypt(filePath, secret string) error { tmpName := path.Join(path.Dir(filePath), "tmp_"+path.Base(filePath)) - if err := cmd.RunDefaultBashCf("MY_PASS='%s' openssl enc -aes-256-cbc -salt -pass env:MY_PASS -in %s -out %s", secret, filePath, tmpName); err != nil { + if err := cmd.NewCommandMgr(cmd.WithEnv("MY_PASS="+secret)).Run("openssl", "enc", "-aes-256-cbc", "-salt", "-pass", "env:MY_PASS", "-in", filePath, "-out", tmpName); err != nil { _ = os.Remove(tmpName) return err } @@ -1384,7 +1372,7 @@ func OpensslEncrypt(filePath, secret string) error { func OpensslDecrypt(filePath, secret string) error { tmpName := path.Join(path.Dir(filePath), "tmp_"+path.Base(filePath)) - if err := cmd.RunDefaultBashCf("MY_PASS='%s' openssl enc -aes-256-cbc -d -salt -pass env:MY_PASS -in %s -out %s", secret, filePath, tmpName); err != nil { + if err := cmd.NewCommandMgr(cmd.WithEnv("MY_PASS="+secret)).Run("openssl", "enc", "-aes-256-cbc", "-d", "-salt", "-pass", "env:MY_PASS", "-in", filePath, "-out", tmpName); err != nil { if strings.Contains(err.Error(), "bad decrypt") || strings.Contains(err.Error(), "bad magic number") { return buserr.New("ErrBadDecrypt") } diff --git a/agent/utils/files/rar.go b/agent/utils/files/rar.go index e00dad1f7..03c7c2b37 100644 --- a/agent/utils/files/rar.go +++ b/agent/utils/files/rar.go @@ -23,7 +23,7 @@ func (z RarArchiver) Extract(ctx context.Context, filePath, dstDir string, _ str if err := checkCmdAvailability("unrar"); err != nil { return err } - return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("unrar x -y -o+ %q %q", filePath, dstDir) + return cmd.NewCommandMgr().Run("unrar", "x", "-y", "-o+", filePath, dstDir) } func (z RarArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, _ string) (err error) { diff --git a/agent/utils/files/tar.go b/agent/utils/files/tar.go index 7e63f1c10..18f72251e 100644 --- a/agent/utils/files/tar.go +++ b/agent/utils/files/tar.go @@ -19,7 +19,7 @@ func NewTarArchiver(compressType CompressType) ShellArchiver { } func (t TarArchiver) Extract(ctx context.Context, FilePath string, dstDir string, secret string) error { - return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("%s %s \"%s\" -C \"%s\"", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir) + return cmd.NewCommandMgr(cmd.WithContext(ctx)).Run(t.Cmd, t.getOptionStr("extract"), FilePath, "-C", dstDir) } func (t TarArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, secret string) error { diff --git a/agent/utils/files/tar_gz.go b/agent/utils/files/tar_gz.go index 46b5b2e9b..e1d4909f4 100644 --- a/agent/utils/files/tar_gz.go +++ b/agent/utils/files/tar_gz.go @@ -6,7 +6,6 @@ import ( "os" "path" "path/filepath" - "strings" "time" "github.com/1Panel-dev/1Panel/agent/global" @@ -25,20 +24,11 @@ func (t TarGzArchiver) Extract(ctx context.Context, filePath, dstDir string, sec if err := os.MkdirAll(dstDir, 0755); err != nil { return fmt.Errorf("failed to create destination dir: %w", err) } - var err error - commands := "" if len(secret) != 0 { - extraCmd := fmt.Sprintf("openssl enc -d -aes-256-cbc -k '%s' -in '%s' | ", secret, filePath) - commands = fmt.Sprintf("%s tar -zxvf - -C '%s' > /dev/null 2>&1", extraCmd, dstDir) - global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******")) + return runTarGzDecryptToDir(cmd.NewCommandMgr(cmd.WithIgnoreExist1()), filePath, dstDir, secret, false) } else { - commands = fmt.Sprintf("tar -zxvf '%s' -C '%s' > /dev/null 2>&1", filePath, dstDir) - global.LOG.Debug(commands) + return runTarGzExtractToDir(cmd.NewCommandMgr(cmd.WithIgnoreExist1()), filePath, dstDir) } - if err = cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashC(commands); err != nil { - return err - } - return nil } func (t TarGzArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, secret string) error { @@ -54,23 +44,17 @@ func (t TarGzArchiver) Compress(ctx context.Context, sourcePaths []string, dstFi var itemDirs []string for _, item := range sourcePaths { - itemDirs = append(itemDirs, fmt.Sprintf("\"%s\"", filepath.Base(item))) + itemDirs = append(itemDirs, filepath.Base(item)) } - itemDir := strings.Join(itemDirs, " ") aheadDir := filepath.Dir(sourcePaths[0]) if len(aheadDir) == 0 { aheadDir = "/" } - commands := "" if len(secret) != 0 { - extraCmd := fmt.Sprintf("| openssl enc -aes-256-cbc -salt -k '%s' -out '%s'", secret, tmpFile) - commands = fmt.Sprintf("tar -zcf - -C \"%s\" %s %s", aheadDir, itemDir, extraCmd) - global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + err = runTarGzEncryptToFile(cmd.NewCommandMgr(cmd.WithContext(ctx)), tmpFile, secret, append([]string{"-C", aheadDir}, itemDirs...)...) } else { - commands = fmt.Sprintf("tar -zcf \"%s\" -C \"%s\" %s", tmpFile, aheadDir, itemDir) - global.LOG.Debug(commands) + err = runTarGzToFile(cmd.NewCommandMgr(cmd.WithContext(ctx)), tmpFile, append([]string{"-C", aheadDir}, itemDirs...)...) } - err = cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashC(commands) if err != nil { return err } @@ -79,3 +63,34 @@ func (t TarGzArchiver) Compress(ctx context.Context, sourcePaths []string, dstFi } return nil } + +func runTarGzToFile(cmdMgr *cmd.CommandHelper, dst string, tarArgs ...string) error { + args := append([]string{"-zcf", dst}, tarArgs...) + return cmdMgr.Run("tar", args...) +} + +func runTarGzEncryptToFile(cmdMgr *cmd.CommandHelper, dst, secret string, tarArgs ...string) error { + args := append([]string{"-zcf", "-"}, tarArgs...) + _, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: "tar", Args: args}, + cmd.PipeCommand{Name: "openssl", Args: []string{"enc", "-aes-256-cbc", "-salt", "-pass", "env:BACKUP_SECRET", "-out", dst}, Env: []string{"BACKUP_SECRET=" + secret}}, + ) + return err +} + +func runTarGzExtractToDir(cmdMgr *cmd.CommandHelper, src, dst string) error { + return cmdMgr.Run("tar", "-zxvf", src, "-C", dst) +} + +func runTarGzDecryptToDir(cmdMgr *cmd.CommandHelper, src, dst, secret string, withSalt bool) error { + opensslArgs := []string{"enc", "-d", "-aes-256-cbc"} + if withSalt { + opensslArgs = append(opensslArgs, "-salt") + } + opensslArgs = append(opensslArgs, "-pass", "env:BACKUP_SECRET", "-in", src) + _, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: "openssl", Args: opensslArgs, Env: []string{"BACKUP_SECRET=" + secret}}, + cmd.PipeCommand{Name: "tar", Args: []string{"-zxf", "-", "-C", dst}}, + ) + return err +} diff --git a/agent/utils/files/x7z.go b/agent/utils/files/x7z.go index 8a4effa9b..634528d32 100644 --- a/agent/utils/files/x7z.go +++ b/agent/utils/files/x7z.go @@ -23,7 +23,7 @@ func (z X7zArchiver) Extract(ctx context.Context, filePath, dstDir string, _ str if err := checkCmdAvailability("7z"); err != nil { return err } - return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("7z x -y -o%q %q", dstDir, filePath) + return cmd.NewCommandMgr(cmd.WithContext(ctx)).Run("7z", "x", "-y", "-o"+dstDir, filePath) } func (z X7zArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, _ string) (err error) { diff --git a/agent/utils/files/zip.go b/agent/utils/files/zip.go index e6dda4957..147546dc5 100644 --- a/agent/utils/files/zip.go +++ b/agent/utils/files/zip.go @@ -24,7 +24,7 @@ func (z ZipArchiver) Extract(ctx context.Context, filePath, dstDir string, secre if err := checkCmdAvailability("unzip"); err != nil { return err } - return cmd.NewCommandMgr(cmd.WithContext(ctx)).RunBashCf("unzip -qo %s -d %s", filePath, dstDir) + return cmd.NewCommandMgr(cmd.WithContext(ctx)).Run("unzip", "-qo", filePath, "-d", dstDir) } func (z ZipArchiver) Compress(ctx context.Context, sourcePaths []string, dstFile string, _ string) error { diff --git a/agent/utils/firewall/client.go b/agent/utils/firewall/client.go index fef4310c5..66d35d05d 100644 --- a/agent/utils/firewall/client.go +++ b/agent/utils/firewall/client.go @@ -77,17 +77,16 @@ func UpdatePingStatus(enable string) error { const panelSysctlPath = "/etc/sysctl.d/98-onepanel.conf" var targetPath string - var applyCmd string + applyArgs := []string{"-p"} if _, err := os.Stat(confPath); os.IsNotExist(err) { targetPath = panelSysctlPath - applyCmd = fmt.Sprintf("%s sysctl --system", cmd.SudoHandleCmd()) - if err := cmd.RunDefaultBashCf("%s mkdir -p /etc/sysctl.d", cmd.SudoHandleCmd()); err != nil { + applyArgs = []string{"--system"} + if err := cmd.NewCommandMgr().RunWithOptionalSudo("mkdir", "-p", "/etc/sysctl.d"); err != nil { return fmt.Errorf("failed to create directory /etc/sysctl.d: %v", err) } } else { targetPath = confPath - applyCmd = fmt.Sprintf("%s sysctl -p", cmd.SudoHandleCmd()) } lineBytes, err := os.ReadFile(targetPath) @@ -95,14 +94,14 @@ func UpdatePingStatus(enable string) error { return fmt.Errorf("failed to read %s: %v", targetPath, err) } - if err := cmd.RunDefaultBashCf("echo %s | %s tee /proc/sys/net/ipv4/icmp_echo_ignore_all > /dev/null", enable, cmd.SudoHandleCmd()); err != nil { + if err := cmd.WriteFileWithOptionalSudo("/proc/sys/net/ipv4/icmp_echo_ignore_all", []byte(enable), constant.FilePerm); err != nil { return fmt.Errorf("failed to apply ipv4 ping status temporarily: %v", err) } var hasIpv6 bool if _, err := os.Stat("/proc/sys/net/ipv6/icmp/echo_ignore_all"); err == nil { hasIpv6 = true - if err := cmd.RunDefaultBashCf("echo %s | %s tee /proc/sys/net/ipv6/icmp/echo_ignore_all > /dev/null", enable, cmd.SudoHandleCmd()); err != nil { + if err := cmd.WriteFileWithOptionalSudo("/proc/sys/net/ipv6/icmp/echo_ignore_all", []byte(enable), constant.FilePerm); err != nil { global.LOG.Warnf("failed to apply ipv6 ping status temporarily: %v", err) } } @@ -136,18 +135,12 @@ func UpdatePingStatus(enable string) error { newFiles = append(newFiles, "net.ipv6.icmp.echo_ignore_all="+enable) } - file, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm) - if err != nil { - return fmt.Errorf("failed to open %s: %v", targetPath, err) - } - defer file.Close() - - if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil { + if err = cmd.WriteFileWithOptionalSudo(targetPath, []byte(strings.Join(newFiles, "\n")), constant.FilePerm); err != nil { return fmt.Errorf("failed to write to %s: %v", targetPath, err) } - if err := cmd.RunDefaultBashC(applyCmd); err != nil { - global.LOG.Warnf("failed to apply persistent config with '%s': %v", applyCmd, err) + if err := cmd.NewCommandMgr().RunWithOptionalSudo("sysctl", applyArgs...); err != nil { + global.LOG.Warnf("failed to apply persistent config: %v", err) } return nil diff --git a/agent/utils/firewall/client/firewalld.go b/agent/utils/firewall/client/firewalld.go index 91d22867a..0480fb15c 100644 --- a/agent/utils/firewall/client/firewalld.go +++ b/agent/utils/firewall/client/firewalld.go @@ -22,12 +22,12 @@ func (f *Firewall) Name() string { } func (f *Firewall) Status() (bool, error) { - stdout, _ := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en firewall-cmd --state") + stdout, _ := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout("firewall-cmd", "--state") return stdout == "running\n", nil } func (f *Firewall) Version() (string, error) { - stdout, err := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en firewall-cmd --version") + stdout, err := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout("firewall-cmd", "--version") if err != nil { return "", fmt.Errorf("load the firewall version failed, %v", err) } @@ -56,7 +56,7 @@ func (f *Firewall) Restart() error { } func (f *Firewall) Reload() error { - if err := cmd.RunDefaultBashC("firewall-cmd --reload"); err != nil { + if err := cmd.NewCommandMgr().Run("firewall-cmd", "--reload"); err != nil { return fmt.Errorf("reload firewall failed, err: %v", err) } return nil @@ -68,7 +68,7 @@ func (f *Firewall) ListPort() ([]FireInfo, error) { wg.Add(2) go func() { defer wg.Done() - stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-ports") + stdout, err := cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--list-ports") if err != nil { return } @@ -89,7 +89,7 @@ func (f *Firewall) ListPort() ([]FireInfo, error) { go func() { defer wg.Done() - stdout1, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-rich-rules") + stdout1, err := cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--list-rich-rules") if err != nil { return } @@ -112,7 +112,7 @@ func (f *Firewall) ListForward() ([]FireInfo, error) { if err := f.EnableForward(); err != nil { global.LOG.Errorf("init port forward failed, err: %v", err) } - stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-forward-ports") + stdout, err := cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--list-forward-ports") if err != nil { return nil, err } @@ -137,7 +137,7 @@ func (f *Firewall) ListForward() ([]FireInfo, error) { } func (f *Firewall) ListAddress() ([]FireInfo, error) { - stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-rich-rules") + stdout, err := cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--list-rich-rules") if err != nil { return nil, err } @@ -160,7 +160,7 @@ func (f *Firewall) Port(port FireInfo, operation string) error { return buserr.New("ErrCmdIllegal") } - if err := cmd.RunDefaultBashCf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol); err != nil { + if err := cmd.NewCommandMgr().Run("firewall-cmd", "--zone=public", "--"+operation+"-port="+port.Port+"/"+port.Protocol, "--permanent"); err != nil { return fmt.Errorf("%s (port: %s/%s strategy: %s) failed, %v", operation, port.Port, port.Protocol, port.Strategy, err) } return nil @@ -184,12 +184,13 @@ func (f *Firewall) RichRules(rule FireInfo, operation string) error { ruleStr += fmt.Sprintf("protocol=%s ", rule.Protocol) } ruleStr += rule.Strategy - if err := cmd.RunDefaultBashCf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleStr); err != nil { + if err := cmd.NewCommandMgr().Run("firewall-cmd", "--zone=public", "--"+operation+"-rich-rule", ruleStr, "--permanent"); err != nil { return fmt.Errorf("%s rich rules (%s) failed, %v", operation, ruleStr, err) } if len(rule.Address) == 0 { - if err := cmd.RunDefaultBashCf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, strings.ReplaceAll(ruleStr, "family=ipv4 ", "family=ipv6 ")); err != nil { - return fmt.Errorf("%s rich rules (%s) failed, %v", operation, strings.ReplaceAll(ruleStr, "family=ipv4 ", "family=ipv6 "), err) + ipv6Rule := strings.ReplaceAll(ruleStr, "family=ipv4 ", "family=ipv6 ") + if err := cmd.NewCommandMgr().Run("firewall-cmd", "--zone=public", "--"+operation+"-rich-rule", ipv6Rule, "--permanent"); err != nil { + return fmt.Errorf("%s rich rules (%s) failed, %v", operation, ipv6Rule, err) } } return nil @@ -199,12 +200,12 @@ func (f *Firewall) PortForward(info Forward, operation string) error { if cmd.CheckIllegal(operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort) { return buserr.New("ErrCmdIllegal") } - ruleStr := fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetPort) + forwardRule := fmt.Sprintf("--%s-forward-port=port=%s:proto=%s:toport=%s", operation, info.Port, info.Protocol, info.TargetPort) if info.TargetIP != "" && info.TargetIP != "127.0.0.1" && info.TargetIP != "localhost" { - ruleStr = fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort) + forwardRule = fmt.Sprintf("--%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s", operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort) } - if err := cmd.RunDefaultBashC(ruleStr); err != nil { + if err := cmd.NewCommandMgr().Run("firewall-cmd", "--zone=public", forwardRule, "--permanent"); err != nil { return fmt.Errorf("%s port forward failed, %s", operation, err) } if err := f.Reload(); err != nil { @@ -236,10 +237,10 @@ func (f *Firewall) loadInfo(line string) FireInfo { } func (f *Firewall) EnableForward() error { - stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --query-masquerade") + stdout, err := cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--query-masquerade") if err != nil { if strings.HasSuffix(strings.TrimSpace(stdout), "no") { - if err := cmd.RunDefaultBashC("firewall-cmd --zone=public --add-masquerade --permanent"); err != nil { + if err := cmd.NewCommandMgr().Run("firewall-cmd", "--zone=public", "--add-masquerade", "--permanent"); err != nil { return err } return f.Reload() diff --git a/agent/utils/firewall/client/iptables.go b/agent/utils/firewall/client/iptables.go index 5659541d4..ced540738 100644 --- a/agent/utils/firewall/client/iptables.go +++ b/agent/utils/firewall/client/iptables.go @@ -2,8 +2,10 @@ package client import ( "fmt" + "os" "strconv" "strings" + "time" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/global" @@ -22,11 +24,12 @@ func (i *Iptables) Name() string { } func (i *Iptables) Status() (bool, error) { - stdout, err := cmd.RunDefaultWithStdoutBashC("iptables -L -n | head -1") + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("iptables", "-L", "-n") if err != nil { return false, err } - return strings.Contains(stdout, "Chain"), nil + firstLine := strings.Split(strings.TrimSpace(stdout), "\n")[0] + return strings.Contains(firstLine, "Chain"), nil } func (i *Iptables) Start() error { @@ -46,7 +49,7 @@ func (i *Iptables) Reload() error { } func (i *Iptables) Version() (string, error) { - stdout, err := cmd.RunDefaultWithStdoutBashC("iptables --version") + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("iptables", "--version") if err != nil { return "", fmt.Errorf("failed to get iptables version: %w", err) } @@ -131,15 +134,13 @@ func (i *Iptables) Port(port FireInfo, operation string) error { action = "DROP" } - ruleArgs := []string{fmt.Sprintf("-p %s", protocol)} - ruleArgs = append(ruleArgs, fmt.Sprintf("--dport %s", portSpec), fmt.Sprintf("-j %s", action)) - ruleSpec := strings.Join(ruleArgs, " ") + ruleArgs := []string{"-p", protocol, "--dport", portSpec, "-j", action} if operation == "add" { - if err := iptables.AddRule(iptables.FilterTab, port.Chain, ruleSpec); err != nil { + if err := iptables.AddRule(iptables.FilterTab, port.Chain, ruleArgs...); err != nil { return err } } else { - if err := iptables.DeleteRule(iptables.FilterTab, port.Chain, ruleSpec); err != nil { + if err := iptables.DeleteRule(iptables.FilterTab, port.Chain, ruleArgs...); err != nil { return err } } @@ -174,7 +175,7 @@ func (i *Iptables) RichRules(rule FireInfo, operation string) error { var ruleArgs []string if address != "" { - ruleArgs = append(ruleArgs, fmt.Sprintf("-s %s", address)) + ruleArgs = append(ruleArgs, "-s", address) } protocol := strings.TrimSpace(rule.Protocol) @@ -183,7 +184,7 @@ func (i *Iptables) RichRules(rule FireInfo, operation string) error { } if protocol != "" { - ruleArgs = append(ruleArgs, fmt.Sprintf("-p %s", protocol)) + ruleArgs = append(ruleArgs, "-p", protocol) } if rule.Port != "" { @@ -194,17 +195,16 @@ func (i *Iptables) RichRules(rule FireInfo, operation string) error { if protocol == "" { return fmt.Errorf("protocol is required when specifying a port") } - ruleArgs = append(ruleArgs, fmt.Sprintf("--dport %s", portSegment)) + ruleArgs = append(ruleArgs, "--dport", portSegment) } - ruleArgs = append(ruleArgs, fmt.Sprintf("-j %s", action)) - ruleSpec := strings.Join(ruleArgs, " ") + ruleArgs = append(ruleArgs, "-j", action) if operation == "add" { - if err := iptables.AddRule(iptables.FilterTab, rule.Chain, ruleSpec); err != nil { + if err := iptables.AddRule(iptables.FilterTab, rule.Chain, ruleArgs...); err != nil { return err } } else { - if err := iptables.DeleteRule(iptables.FilterTab, rule.Chain, ruleSpec); err != nil { + if err := iptables.DeleteRule(iptables.FilterTab, rule.Chain, ruleArgs...); err != nil { return err } } @@ -232,11 +232,16 @@ func (i *Iptables) ListForward() ([]FireInfo, error) { } func EnableIptablesForward() error { - if err := cmd.RunDefaultBashC("echo 1 > /proc/sys/net/ipv4/ip_forward"); err != nil { + if err := cmd.WriteFileWithOptionalSudo("/proc/sys/net/ipv4/ip_forward", []byte("1"), 0644); err != nil { return fmt.Errorf("failed to enable IP forwarding: %w", err) } - _ = cmd.RunDefaultBashC("grep -q '^net.ipv4.ip_forward' /etc/sysctl.conf || echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf") - _ = cmd.RunDefaultBashC("sysctl -p") + if data, err := os.ReadFile("/etc/sysctl.conf"); err == nil { + if !strings.Contains(string(data), "net.ipv4.ip_forward") { + content := strings.TrimRight(string(data), "\n") + "\nnet.ipv4.ip_forward = 1\n" + _ = cmd.WriteFileWithOptionalSudo("/etc/sysctl.conf", []byte(content), 0644) + } + } + _ = cmd.NewCommandMgr().RunWithOptionalSudo("sysctl", "-p") if err := iptables.AddChainWithAppend(iptables.NatTab, "PREROUTING", iptables.Chain1PanelPreRouting); err != nil { return err diff --git a/agent/utils/firewall/client/iptables/common.go b/agent/utils/firewall/client/iptables/common.go index 6ad99527c..a1fb4d4d1 100644 --- a/agent/utils/firewall/client/iptables/common.go +++ b/agent/utils/firewall/client/iptables/common.go @@ -24,8 +24,8 @@ const ( ) const ( - EstablishedRule = "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment 'ESTABLISHED Whitelist'" - IoRuleIn = "-i lo -j ACCEPT -m comment --comment 'Loopback Whitelist'" + EstablishedRule = "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment \"ESTABLISHED Whitelist\"" + IoRuleIn = "-i lo -j ACCEPT -m comment --comment \"Loopback Whitelist\"" DropAllTcp = "-p tcp -j DROP" DropAllUdp = "-p udp -j DROP" AllowSSH = "-p tcp --dport ssh -j ACCEPT" @@ -43,72 +43,91 @@ const ( NatTab = "nat" ) -func RunWithStd(tab, rule string) (string, error) { - cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(60*time.Second)) - stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s %s", cmd.SudoHandleCmd(), tab, rule) +func runIptables(tab string, ignoreExist1, withWait bool, ruleArgs ...string) (string, error) { + options := []cmd.Option{cmd.WithTimeout(60 * time.Second)} + if ignoreExist1 { + options = append(options, cmd.WithIgnoreExist1()) + } + cmdMgr := cmd.NewCommandMgr(options...) + args := []string{"-t", tab} + if withWait { + args = append(args, "-w") + } + args = append(args, ruleArgs...) + return cmdMgr.RunWithOptionalSudoAndStdout("iptables", args...) +} + +func RunWithStd(tab string, args ...string) (string, error) { + stdout, err := runIptables(tab, true, true, args...) if err != nil { - global.LOG.Errorf("iptables command failed [table=%s, rule=%s]: %v", tab, rule, err) + global.LOG.Errorf("iptables command failed [table=%s, args=%s]: %v", tab, strings.Join(args, " "), err) return stdout, err } return stdout, nil } -func RunWithoutIgnore(tab, rule string) (string, error) { - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(60 * time.Second)) - stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -t %s %s", cmd.SudoHandleCmd(), tab, rule) +func RunWithoutIgnore(tab string, args ...string) (string, error) { + stdout, err := runIptables(tab, false, false, args...) if err != nil { return stdout, err } return stdout, nil } -func Run(tab, rule string) error { - if _, err := RunWithStd(tab, rule); err != nil { +func Run(tab string, args ...string) error { + if _, err := RunWithStd(tab, args...); err != nil { return err } return nil } func NewChain(tab, chain string) error { - return Run(tab, "-N "+chain) + return Run(tab, "-N", chain) } func ClearChain(tab, chain string) error { - return Run(tab, "-F "+chain) + return Run(tab, "-F", chain) } -func AddRule(tab, chain, rule string) error { - if CheckRuleExist(tab, chain, rule) { +func AddRule(tab, chain string, ruleArgs ...string) error { + if CheckRuleExist(tab, chain, ruleArgs...) { return nil } - return Run(tab, fmt.Sprintf("-A %s %s", chain, rule)) + args := append([]string{"-A", chain}, ruleArgs...) + return Run(tab, args...) } -func DeleteRule(tab, chain, rule string) error { - return Run(tab, fmt.Sprintf("-D %s %s", chain, rule)) +func DeleteRule(tab, chain string, ruleArgs ...string) error { + args := append([]string{"-D", chain}, ruleArgs...) + return Run(tab, args...) } func CheckChainExist(tab, chain string) (bool, error) { - stdout, err := RunWithStd(tab, fmt.Sprintf("-S | grep -w 'N %s'", chain)) + stdout, err := RunWithStd(tab, "-S") if err != nil { global.LOG.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err) return false, fmt.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err) } - if strings.TrimSpace(stdout) == "" { - return false, nil + for _, line := range strings.Split(stdout, "\n") { + if strings.TrimSpace(line) == "-N "+chain { + return true, nil + } } - return true, nil + return false, nil } func CheckChainBind(tab, parentChain, chain string) (bool, error) { - stdout, err := RunWithStd(tab, fmt.Sprintf("-S %s | grep -- '-j %s'", parentChain, chain)) + stdout, err := RunWithStd(tab, "-S", parentChain) if err != nil { global.LOG.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err) return false, fmt.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err) } - if strings.TrimSpace(stdout) == "" { - return false, nil + for _, line := range strings.Split(stdout, "\n") { + if strings.Contains(line, "-j "+chain) { + return true, nil + } } - return true, nil + return false, nil } -func CheckRuleExist(tab, chain, rule string) bool { - _, err := RunWithoutIgnore(tab, fmt.Sprintf("-C %s %s", chain, rule)) +func CheckRuleExist(tab, chain string, ruleArgs ...string) bool { + args := append([]string{"-C", chain}, ruleArgs...) + _, err := RunWithoutIgnore(tab, args...) return err == nil } @@ -130,7 +149,7 @@ func BindChain(tab, targetChain, chain string, position int) error { return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err) } if line == 0 { - if err := Run(tab, fmt.Sprintf("-I %s %d -j %s", targetChain, position, chain)); err != nil { + if err := Run(tab, "-I", targetChain, strconv.Itoa(position), "-j", chain); err != nil { return fmt.Errorf("bind chain %s to %s failed, err: %w", chain, targetChain, err) } } @@ -142,13 +161,18 @@ func UnbindChain(tab, targetChain, chain string) error { return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err) } if line != 0 { - return Run(tab, fmt.Sprintf("-D %s %v", targetChain, line)) + return Run(tab, "-D", targetChain, strconv.Itoa(line)) } return nil } func FindChainNum(tab, targetChain, chain string) (int, error) { - stdout, err := RunWithStd(tab, fmt.Sprintf("-L %s --line-numbers -n | grep -w %s", targetChain, chain)) + cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(60*time.Second)) + commandName, commandArgs := cmd.WrapWithOptionalSudo("iptables", "-w", "-t", tab, "-L", targetChain, "--line-numbers", "-n") + stdout, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: commandName, Args: commandArgs}, + cmd.PipeCommand{Name: "grep", Args: []string{"-w", chain}}, + ) if err != nil { return 0, fmt.Errorf("failed to list rules in chain %s: %w", targetChain, err) } @@ -190,5 +214,5 @@ func AddChainWithAppend(tab, parentChain, chain string) error { return nil } func AppendChain(tab string, parentChain, chain string) error { - return Run(tab, fmt.Sprintf("-A %s -j %s", parentChain, chain)) + return Run(tab, "-A", parentChain, "-j", chain) } diff --git a/agent/utils/firewall/client/iptables/filter.go b/agent/utils/firewall/client/iptables/filter.go index 7f02d1c32..f8c59d80f 100644 --- a/agent/utils/firewall/client/iptables/filter.go +++ b/agent/utils/firewall/client/iptables/filter.go @@ -2,8 +2,8 @@ package iptables import ( "fmt" + "os" "strings" - "time" "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/global" @@ -26,47 +26,47 @@ func AddFilterRule(chain string, policy FilterRules) error { if err := validateRuleSafety(policy, chain); err != nil { return err } - iptablesArg := fmt.Sprintf("-A %s", chain) + args := []string{"-A", chain} if policy.Protocol != "" { - iptablesArg += fmt.Sprintf(" -p %s", policy.Protocol) + args = append(args, "-p", policy.Protocol) } if len(policy.SrcPort) != 0 { - iptablesArg += fmt.Sprintf(" --sport %s", policy.SrcPort) + args = append(args, "--sport", policy.SrcPort) } if len(policy.DstPort) != 0 { - iptablesArg += fmt.Sprintf(" --dport %s", policy.DstPort) + args = append(args, "--dport", policy.DstPort) } if policy.SrcIP != "" { - iptablesArg += fmt.Sprintf(" -s %s", policy.SrcIP) + args = append(args, "-s", policy.SrcIP) } if policy.DstIP != "" { - iptablesArg += fmt.Sprintf(" -d %s", policy.DstIP) + args = append(args, "-d", policy.DstIP) } - iptablesArg += fmt.Sprintf(" -j %s", policy.Strategy) + args = append(args, "-j", policy.Strategy) - return Run(FilterTab, iptablesArg) + return Run(FilterTab, args...) } func DeleteFilterRule(chain string, policy FilterRules) error { - iptablesArg := fmt.Sprintf("-D %s", chain) + args := []string{"-D", chain} if policy.Protocol != "" { - iptablesArg += fmt.Sprintf(" -p %s", policy.Protocol) + args = append(args, "-p", policy.Protocol) } if len(policy.SrcPort) != 0 { - iptablesArg += fmt.Sprintf(" --sport %s", policy.SrcPort) + args = append(args, "--sport", policy.SrcPort) } if len(policy.DstPort) != 0 { - iptablesArg += fmt.Sprintf(" --dport %s", policy.DstPort) + args = append(args, "--dport", policy.DstPort) } if policy.SrcIP != "" { - iptablesArg += fmt.Sprintf(" -s %s", policy.SrcIP) + args = append(args, "-s", policy.SrcIP) } if policy.DstIP != "" { - iptablesArg += fmt.Sprintf(" -d %s", policy.DstIP) + args = append(args, "-d", policy.DstIP) } - iptablesArg += fmt.Sprintf(" -j %s", policy.Strategy) + args = append(args, "-j", policy.Strategy) - return Run(FilterTab, iptablesArg) + return Run(FilterTab, args...) } func ReadFilterRulesByChain(chain string) ([]FilterRules, error) { @@ -74,8 +74,7 @@ func ReadFilterRulesByChain(chain string) ([]FilterRules, error) { if cmd.CheckIllegal(chain) { return rules, buserr.New("ErrCmdIllegal") } - cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second)) - stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s -nL %s", cmd.SudoHandleCmd(), FilterTab, chain) + stdout, err := RunWithStd(FilterTab, "-nL", chain) if err != nil { return rules, fmt.Errorf("load filter fules by chain %s failed, %v", chain, err) } @@ -107,8 +106,7 @@ func LoadDefaultStrategy(chain string) (string, error) { if cmd.CheckIllegal(chain) { return "", buserr.New("ErrCmdIllegal") } - cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second)) - stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s -L %s", cmd.SudoHandleCmd(), FilterTab, chain) + stdout, err := RunWithStd(FilterTab, "-L", chain) if err != nil { return "", fmt.Errorf("load filter fules by chain %s failed, %v", chain, err) } @@ -170,12 +168,12 @@ func LoadInitStatus(clientName, tab string) (bool, bool) { } return checkWithInitAndBind(initRules, bindRules, lines) case "forward": - stdout, err := cmd.RunDefaultWithStdoutBashC("cat /proc/sys/net/ipv4/ip_forward") + data, err := os.ReadFile("/proc/sys/net/ipv4/ip_forward") if err != nil { global.LOG.Errorf("check /proc/sys/net/ipv4/ip_forward failed, err: %v", err) return false, false } - if strings.TrimSpace(stdout) == "0" { + if strings.TrimSpace(string(data)) == "0" { return false, false } natRules, err := RunWithStd(NatTab, "-S") diff --git a/agent/utils/firewall/client/iptables/forward.go b/agent/utils/firewall/client/iptables/forward.go index 34926768e..19a045049 100644 --- a/agent/utils/firewall/client/iptables/forward.go +++ b/agent/utils/firewall/client/iptables/forward.go @@ -1,7 +1,6 @@ package iptables import ( - "fmt" "strings" ) @@ -9,33 +8,33 @@ func AddForward(protocol, srcPort, dest, destPort, iface string, save bool) erro srcPort = strings.ReplaceAll(srcPort, "-", ":") itemDstPort := strings.ReplaceAll(destPort, "-", ":") if dest != "" && dest != "127.0.0.1" && dest != "localhost" { - iptablesArg := fmt.Sprintf("-A %s", Chain1PanelPreRouting) + args := []string{"-A", Chain1PanelPreRouting} if iface != "" { - iptablesArg += fmt.Sprintf(" -i %s", iface) + args = append(args, "-i", iface) } - iptablesArg += fmt.Sprintf(" -p %s --dport %s -j DNAT --to-destination %s:%s", protocol, srcPort, dest, destPort) - if err := Run(NatTab, iptablesArg); err != nil { + args = append(args, "-p", protocol, "--dport", srcPort, "-j", "DNAT", "--to-destination", dest+":"+destPort) + if err := Run(NatTab, args...); err != nil { return err } - if err := Run(NatTab, fmt.Sprintf("-A %s -d %s -p %s --dport %s -j MASQUERADE", Chain1PanelPostRouting, dest, protocol, itemDstPort)); err != nil { + if err := Run(NatTab, "-A", Chain1PanelPostRouting, "-d", dest, "-p", protocol, "--dport", itemDstPort, "-j", "MASQUERADE"); err != nil { return err } - if err := Run(FilterTab, fmt.Sprintf("-A %s -d %s -p %s --dport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + if err := Run(FilterTab, "-A", Chain1PanelForward, "-d", dest, "-p", protocol, "--dport", itemDstPort, "-j", "ACCEPT"); err != nil { return err } - if err := Run(FilterTab, fmt.Sprintf("-A %s -s %s -p %s --sport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + if err := Run(FilterTab, "-A", Chain1PanelForward, "-s", dest, "-p", protocol, "--sport", itemDstPort, "-j", "ACCEPT"); err != nil { return err } } else { - iptablesArg := fmt.Sprintf("-A %s", Chain1PanelPreRouting) + args := []string{"-A", Chain1PanelPreRouting} if iface != "" { - iptablesArg += fmt.Sprintf(" -i %s", iface) + args = append(args, "-i", iface) } - iptablesArg += fmt.Sprintf(" -p %s --dport %s -j REDIRECT --to-port %s", protocol, srcPort, destPort) - if err := Run(NatTab, iptablesArg); err != nil { + args = append(args, "-p", protocol, "--dport", srcPort, "-j", "REDIRECT", "--to-port", destPort) + if err := Run(NatTab, args...); err != nil { return err } } @@ -44,20 +43,20 @@ func AddForward(protocol, srcPort, dest, destPort, iface string, save bool) erro func DeleteForward(num string, protocol, srcPort, dest, destPort, iface string) error { itemDstPort := strings.ReplaceAll(destPort, "-", ":") - if err := Run(NatTab, fmt.Sprintf("-D %s %s", Chain1PanelPreRouting, num)); err != nil { + if err := Run(NatTab, "-D", Chain1PanelPreRouting, num); err != nil { return err } if dest != "" && dest != "127.0.0.1" && dest != "localhost" { - if err := Run(NatTab, fmt.Sprintf("-D %s -d %s -p %s --dport %s -j MASQUERADE", Chain1PanelPostRouting, dest, protocol, itemDstPort)); err != nil { + if err := Run(NatTab, "-D", Chain1PanelPostRouting, "-d", dest, "-p", protocol, "--dport", itemDstPort, "-j", "MASQUERADE"); err != nil { return err } - if err := Run(FilterTab, fmt.Sprintf("-D %s -d %s -p %s --dport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + if err := Run(FilterTab, "-D", Chain1PanelForward, "-d", dest, "-p", protocol, "--dport", itemDstPort, "-j", "ACCEPT"); err != nil { return err } - if err := Run(FilterTab, fmt.Sprintf("-D %s -s %s -p %s --sport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + if err := Run(FilterTab, "-D", Chain1PanelForward, "-s", dest, "-p", protocol, "--sport", itemDstPort, "-j", "ACCEPT"); err != nil { return err } } @@ -68,7 +67,7 @@ func ListForward(chain ...string) ([]IptablesNatInfo, error) { if len(chain) == 0 { chain = append(chain, Chain1PanelPreRouting) } - stdout, err := RunWithStd(NatTab, fmt.Sprintf("-nvL %s --line-numbers", chain[0])) + stdout, err := RunWithStd(NatTab, "-nvL", chain[0], "--line-numbers") if err != nil { return nil, err } diff --git a/agent/utils/firewall/client/iptables/persistence.go b/agent/utils/firewall/client/iptables/persistence.go index 41f11bada..52a59bdad 100644 --- a/agent/utils/firewall/client/iptables/persistence.go +++ b/agent/utils/firewall/client/iptables/persistence.go @@ -2,12 +2,14 @@ package iptables import ( "bufio" + "bytes" "fmt" "os" "path" "strings" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" ) const ( @@ -24,7 +26,7 @@ const ( func SaveRulesToFile(tab, chain, fileName string) error { rulesFile := path.Join(global.Dir.FirewallDir, fileName) - stdout, err := RunWithStd(tab, fmt.Sprintf("-S %s", chain)) + stdout, err := RunWithStd(tab, "-S", chain) if err != nil { return fmt.Errorf("failed to list %s rules: %w", chain, err) } @@ -80,7 +82,7 @@ func LoadRulesFromFile(tab, chain, fileName string) error { for _, rule := range rules { if strings.HasPrefix(rule, fmt.Sprintf("-A %s", chain)) { - if err := Run(tab, rule); err != nil { + if err := restoreRule(tab, rule); err != nil { global.LOG.Errorf("apply rule '%s' failed, err: %v", rule, err) } } @@ -88,3 +90,14 @@ func LoadRulesFromFile(tab, chain, fileName string) error { return nil } + +func restoreRule(tab, rule string) error { + restoreInput := fmt.Sprintf("*%s\n%s\nCOMMIT\n", tab, rule) + commandName, commandArgs := cmd.WrapWithOptionalSudo("iptables-restore", "-n") + _, err := cmd.NewCommandMgr().RunPipe(cmd.PipeCommand{ + Name: commandName, + Args: commandArgs, + Stdin: bytes.NewReader([]byte(restoreInput)), + }) + return err +} diff --git a/agent/utils/firewall/client/ufw.go b/agent/utils/firewall/client/ufw.go index e7244b785..1a61e3e71 100644 --- a/agent/utils/firewall/client/ufw.go +++ b/agent/utils/firewall/client/ufw.go @@ -2,6 +2,7 @@ package client import ( "fmt" + "strconv" "strings" "github.com/1Panel-dev/1Panel/agent/buserr" @@ -24,35 +25,34 @@ func (f *Ufw) Name() string { } func (f *Ufw) Status() (bool, error) { - stdout, _ := cmd.RunDefaultWithStdoutBashCf("%s status | grep Status", f.CmdStr) - if stdout == "Status: active\n" { + stdout, _ := f.runWithStdout("status") + if strings.Contains(stdout, "Status: active") { return true, nil } - stdout1, _ := cmd.RunDefaultWithStdoutBashCf("%s status | grep 状态", f.CmdStr) - if stdout1 == "状态: 激活\n" { + if strings.Contains(stdout, "状态: 激活") { return true, nil } return false, nil } func (f *Ufw) Version() (string, error) { - stdout, err := cmd.RunDefaultWithStdoutBashCf("%s version | grep ufw", f.CmdStr) + stdout, err := f.runWithStdout("version") if err != nil { return "", fmt.Errorf("load the firewall status failed, %v", err) } - info := strings.ReplaceAll(stdout, "\n", "") + info := strings.Split(strings.TrimSpace(stdout), "\n")[0] return strings.ReplaceAll(info, "ufw ", ""), nil } func (f *Ufw) Start() error { - if err := cmd.RunDefaultBashCf("echo y | %s enable", f.CmdStr); err != nil { + if err := f.run("--force", "enable"); err != nil { return fmt.Errorf("enable the firewall failed, %v", err) } return nil } func (f *Ufw) Stop() error { - if err := cmd.RunDefaultBashCf("%s disable", f.CmdStr); err != nil { + if err := f.run("disable"); err != nil { return fmt.Errorf("stop the firewall failed, %v", err) } return nil @@ -73,7 +73,7 @@ func (f *Ufw) Reload() error { } func (f *Ufw) ListPort() ([]FireInfo, error) { - stdout, err := cmd.RunDefaultWithStdoutBashCf("%s status verbose", f.CmdStr) + stdout, err := f.runWithStdout("status", "verbose") if err != nil { return nil, err } @@ -98,7 +98,7 @@ func (f *Ufw) ListPort() ([]FireInfo, error) { } func (f *Ufw) ListAddress() ([]FireInfo, error) { - stdout, err := cmd.RunDefaultWithStdoutBashCf("%s status verbose", f.CmdStr) + stdout, err := f.runWithStdout("status", "verbose") if err != nil { return nil, err } @@ -141,15 +141,15 @@ func (f *Ufw) Port(port FireInfo, operation string) error { return buserr.New("ErrCmdIllegal") } - command := fmt.Sprintf("%s %s %s", f.CmdStr, port.Strategy, port.Port) + args := []string{port.Strategy, port.Port} if operation == "remove" { - command = fmt.Sprintf("%s delete %s %s", f.CmdStr, port.Strategy, port.Port) + args = []string{"delete", port.Strategy, port.Port} } if len(port.Protocol) != 0 { - command += fmt.Sprintf("/%s", port.Protocol) + args[len(args)-1] += "/" + port.Protocol } - if err := cmd.RunDefaultBashC(command); err != nil { - return fmt.Errorf("%s (%s) failed, %v", operation, command, err) + if err := f.run(args...); err != nil { + return fmt.Errorf("%s (%s) failed, %v", operation, strings.Join(args, " "), err) } return nil } @@ -169,31 +169,36 @@ func (f *Ufw) RichRules(rule FireInfo, operation string) error { } insertNum := f.loadInsertNum(rule, operation) - ruleStr := fmt.Sprintf("%s insert %d %s ", f.CmdStr, insertNum, rule.Strategy) + args := []string{"insert", strconv.Itoa(insertNum), rule.Strategy} if operation == "remove" { - ruleStr = fmt.Sprintf("%s delete %s ", f.CmdStr, rule.Strategy) + args = []string{"delete", rule.Strategy} } if len(rule.Protocol) != 0 { - ruleStr += fmt.Sprintf("proto %s ", rule.Protocol) + args = append(args, "proto", rule.Protocol) } if strings.Contains(rule.Address, "-") { - ruleStr += fmt.Sprintf("from %s to %s ", strings.Split(rule.Address, "-")[0], strings.Split(rule.Address, "-")[1]) + parts := strings.Split(rule.Address, "-") + args = append(args, "from", parts[0], "to", parts[1]) } else { - ruleStr += fmt.Sprintf("from %s ", rule.Address) + args = append(args, "from", rule.Address) } if len(rule.Port) != 0 { - ruleStr += fmt.Sprintf("to any port %s ", rule.Port) + args = append(args, "to", "any", "port", rule.Port) } - stdout, err := cmd.RunDefaultWithStdoutBashC(ruleStr) + stdout, err := f.runWithStdout(args...) if err != nil { if strings.Contains(stdout, "ERROR: Invalid position") || strings.Contains(stdout, "ERROR: 无效位置") { - if err := cmd.RunDefaultBashC(strings.ReplaceAll(ruleStr, "insert 1 ", "")); err != nil { - return fmt.Errorf("%s rich rules (%s), failed, %v", operation, ruleStr, err) + fallbackArgs := args + if len(args) >= 2 && args[0] == "insert" { + fallbackArgs = append([]string{}, args[2:]...) + } + if err := f.run(fallbackArgs...); err != nil { + return fmt.Errorf("%s rich rules (%s), failed, %v", operation, strings.Join(args, " "), err) } return nil } - return fmt.Errorf("%s rich rules (%s), failed, %v", operation, ruleStr, err) + return fmt.Errorf("%s rich rules (%s), failed, %v", operation, strings.Join(args, " "), err) } return nil } @@ -259,7 +264,7 @@ func (f *Ufw) loadInsertNum(rule FireInfo, operation string) int { if !strings.Contains(rule.Address, ":") || operation == "remove" { return 1 } - rules, err := cmd.RunDefaultWithStdoutBashCf("%s status numbered", f.CmdStr) + rules, err := f.runWithStdout("status", "numbered") if err != nil { global.LOG.Errorf("load ufw rules failed, err: %v", err) return 1 @@ -277,3 +282,13 @@ func (f *Ufw) loadInsertNum(rule FireInfo, operation string) int { } return i } + +func (f *Ufw) run(args ...string) error { + _, err := f.runWithStdout(args...) + return err +} + +func (f *Ufw) runWithStdout(args ...string) (string, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")) + return cmdMgr.RunWithOptionalSudoAndStdout("ufw", args...) +} diff --git a/agent/utils/mysql/client/local.go b/agent/utils/mysql/client/local.go index a9466b3e0..24ceda483 100644 --- a/agent/utils/mysql/client/local.go +++ b/agent/utils/mysql/client/local.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "compress/gzip" "context" "errors" @@ -16,6 +15,7 @@ import ( "github.com/1Panel-dev/1Panel/agent/buserr" "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/common" "github.com/1Panel-dev/1Panel/agent/utils/files" ) @@ -229,11 +229,6 @@ func (r *Local) Backup(info BackupInfo) error { return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err) } } - outfile, err := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, constant.DirPerm) - if err != nil { - return fmt.Errorf("open file %s failed, err: %v", path.Join(info.TargetDir, info.FileName), err) - } - defer func() { _ = outfile.Close() }() dumpCmd := "mysqldump" if r.Type == constant.AppMariaDB { dumpCmd = "mariadb-dump" @@ -250,19 +245,13 @@ func (r *Local) Backup(info BackupInfo) error { args = append(args, arg) } args = append(args, info.Name) - cmd := exec.Command("docker", args...) - var stderr bytes.Buffer - cmd.Stderr = &stderr - - gzipCmd := exec.Command("gzip", "-cf") - gzipCmd.Stdin, _ = cmd.StdoutPipe() - gzipCmd.Stdout = outfile - _ = gzipCmd.Start() - - if err := cmd.Run(); err != nil { - return fmt.Errorf("handle backup database failed, err: %v", stderr.String()) + cmdMgr := cmd.NewCommandMgr(cmd.WithOutputFile(path.Join(info.TargetDir, info.FileName))) + if _, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: "docker", Args: args}, + cmd.PipeCommand{Name: "gzip", Args: []string{"-cf"}}, + ); err != nil { + return fmt.Errorf("handle backup database failed, err: %v", err) } - _ = gzipCmd.Wait() return nil } diff --git a/agent/utils/mysql/client/remote.go b/agent/utils/mysql/client/remote.go index f3501484d..30370e9c2 100644 --- a/agent/utils/mysql/client/remote.go +++ b/agent/utils/mysql/client/remote.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "compress/gzip" "context" "database/sql" @@ -244,11 +243,6 @@ func (r *Remote) Backup(info BackupInfo) error { return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err) } } - outfile, err := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, constant.DirPerm) - if err != nil { - return fmt.Errorf("open file %s failed, err: %v", path.Join(info.TargetDir, info.FileName), err) - } - defer func() { _ = outfile.Close() }() dumpCmd := "mysqldump" if r.Type == constant.AppMariaDB { dumpCmd = "mariadb-dump" @@ -268,23 +262,32 @@ func (r *Remote) Backup(info BackupInfo) error { args = append(args, arg) } - backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s %s -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'", - image, dumpCmd, strings.Join(args, " "), r.Address, r.Port, r.User, r.Password, sslSkip(info.Version, r.Type), info.Format, info.Name) - - global.LOG.Debug(strings.ReplaceAll(backupCmd, r.Password, "******")) - cmd := exec.Command("bash", "-c", backupCmd) - var stderr bytes.Buffer - cmd.Stderr = &stderr - - gzipCmd := exec.Command("gzip", "-cf") - gzipCmd.Stdin, _ = cmd.StdoutPipe() - gzipCmd.Stdout = outfile - - _ = gzipCmd.Start() - if err := cmd.Run(); err != nil { - return fmt.Errorf("handle backup database failed, err: %v", stderr.String()) + backupArgs := []string{"run", "--rm", "--net=host", "-i", image, dumpCmd} + backupArgs = append(backupArgs, args...) + backupArgs = append( + backupArgs, + "-h", r.Address, + "-P", fmt.Sprintf("%d", r.Port), + "-u"+r.User, + "-p"+r.Password, + sslSkip(info.Version, r.Type), + "--default-character-set="+info.Format, + info.Name, + ) + debugArgs := append([]string{}, backupArgs...) + for i, arg := range debugArgs { + if strings.Contains(arg, r.Password) { + debugArgs[i] = strings.ReplaceAll(arg, r.Password, "******") + } + } + global.LOG.Debug("docker " + strings.Join(debugArgs, " ")) + cmdMgr := cmd.NewCommandMgr(cmd.WithOutputFile(path.Join(info.TargetDir, info.FileName))) + if _, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: "docker", Args: backupArgs}, + cmd.PipeCommand{Name: "gzip", Args: []string{"-cf"}}, + ); err != nil { + return fmt.Errorf("handle backup database failed, err: %v", err) } - _ = gzipCmd.Wait() return nil } @@ -300,11 +303,24 @@ func (r *Remote) Recover(info RecoverInfo) error { return err } - recoverCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'", - image, r.Type, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version, r.Type), info.Format, info.Name) - - global.LOG.Debug(strings.ReplaceAll(recoverCmd, r.Password, "******")) - cmd := exec.Command("bash", "-c", recoverCmd) + recoverArgs := []string{ + "run", "--rm", "--net=host", "-i", image, r.Type, + "-h", r.Address, + "-P", fmt.Sprintf("%d", r.Port), + "-u" + r.User, + "-p" + r.Password, + sslSkip(info.Version, r.Type), + "--default-character-set=" + info.Format, + info.Name, + } + debugArgs := append([]string{}, recoverArgs...) + for i, arg := range debugArgs { + if strings.Contains(arg, r.Password) { + debugArgs[i] = strings.ReplaceAll(arg, r.Password, "******") + } + } + global.LOG.Debug("docker " + strings.Join(debugArgs, " ")) + cmd := exec.Command("docker", recoverArgs...) if strings.HasSuffix(info.SourceFile, ".gz") { gzipFile, err := os.Open(info.SourceFile) diff --git a/agent/utils/ntp/ntp.go b/agent/utils/ntp/ntp.go index c50e9c41a..f4b9f65b8 100644 --- a/agent/utils/ntp/ntp.go +++ b/agent/utils/ntp/ntp.go @@ -62,7 +62,8 @@ func GetRemoteTime(site string) (time.Time, error) { func UpdateSystemTime(dateTime string) error { system := runtime.GOOS if system == "linux" { - if err := cmd.RunDefaultBashCf(`%s date -s "%s"`, cmd.SudoHandleCmd(), dateTime); err != nil { + cmdMgr := cmd.NewCommandMgr() + if err := cmdMgr.RunWithOptionalSudo("date", "-s", dateTime); err != nil { return fmt.Errorf("update system time failed, %v", err) } return nil @@ -73,7 +74,8 @@ func UpdateSystemTime(dateTime string) error { func UpdateSystemTimeZone(timezone string) error { system := runtime.GOOS if system == "linux" { - if err := cmd.RunDefaultBashCf(`%s timedatectl set-timezone "%s"`, cmd.SudoHandleCmd(), timezone); err != nil { + cmdMgr := cmd.NewCommandMgr() + if err := cmdMgr.RunWithOptionalSudo("timedatectl", "set-timezone", timezone); err != nil { return fmt.Errorf("update system time zone failed, %v", err) } return nil diff --git a/agent/utils/postgresql/client/local.go b/agent/utils/postgresql/client/local.go index 7f4b82479..63339d15a 100644 --- a/agent/utils/postgresql/client/local.go +++ b/agent/utils/postgresql/client/local.go @@ -1,7 +1,6 @@ package client import ( - "bytes" "compress/gzip" "context" "errors" @@ -14,8 +13,8 @@ import ( "time" "github.com/1Panel-dev/1Panel/agent/buserr" - "github.com/1Panel-dev/1Panel/agent/constant" "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" "github.com/1Panel-dev/1Panel/agent/utils/files" ) @@ -135,29 +134,15 @@ func (r *Local) Backup(info BackupInfo) error { return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err) } } - outfile, err := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, constant.DirPerm) - if err != nil { - return fmt.Errorf("open file %s failed, err: %v", path.Join(info.TargetDir, info.FileName), err) - } - defer outfile.Close() global.LOG.Infof("start to pg_dump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName) - cmd := exec.Command("docker", "exec", "-i", r.ContainerName, - "sh", "-c", - fmt.Sprintf("PGPASSWORD=%s pg_dump -F c -U %s -d %s", r.Password, r.Username, info.Name), - ) - var stderr bytes.Buffer - cmd.Stderr = &stderr - - gzipCmd := exec.Command("gzip", "-cf") - gzipCmd.Stdin, _ = cmd.StdoutPipe() - gzipCmd.Stdout = outfile - _ = gzipCmd.Start() - - if err := cmd.Run(); err != nil { - return fmt.Errorf("handle backup database failed, err: %v", stderr.String()) + cmdMgr := cmd.NewCommandMgr(cmd.WithOutputFile(path.Join(info.TargetDir, info.FileName))) + if _, err := cmdMgr.RunPipe( + cmd.PipeCommand{Name: "docker", Args: []string{"exec", "-i", "-e", "PGPASSWORD=" + r.Password, r.ContainerName, "pg_dump", "-F", "c", "-U", r.Username, "-d", info.Name}}, + cmd.PipeCommand{Name: "gzip", Args: []string{"-cf"}}, + ); err != nil { + return fmt.Errorf("handle backup database failed, err: %v", err) } - _ = gzipCmd.Wait() return nil } @@ -165,8 +150,8 @@ func (r *Local) Recover(info RecoverInfo) error { fi, _ := os.Open(info.SourceFile) defer fi.Close() - cmd := exec.Command("docker", "exec", "-i", r.ContainerName, "sh", "-c", - fmt.Sprintf("PGPASSWORD=%s pg_restore -F c -c --if-exists --no-owner -U %s -d %s", r.Password, r.Username, info.Name), + cmd := exec.Command("docker", "exec", "-i", "-e", "PGPASSWORD="+r.Password, r.ContainerName, + "pg_restore", "-F", "c", "-c", "--if-exists", "--no-owner", "-U", r.Username, "-d", info.Name, ) if strings.HasSuffix(info.SourceFile, ".gz") { gzipFile, err := os.Open(info.SourceFile) diff --git a/agent/utils/postgresql/client/remote.go b/agent/utils/postgresql/client/remote.go index 4b88e0613..fb3bd3589 100644 --- a/agent/utils/postgresql/client/remote.go +++ b/agent/utils/postgresql/client/remote.go @@ -2,6 +2,7 @@ package client import ( "bufio" + "bytes" "context" "database/sql" "fmt" @@ -24,6 +25,37 @@ import ( _ "github.com/jackc/pgx/v5/stdlib" ) +const maxPgDumpStderrCapture = 64 * 1024 + +var pgDumpMagic = []byte("PGDMP") + +type limitedBuffer struct { + buf bytes.Buffer + limit int + truncated int +} + +func (b *limitedBuffer) Write(p []byte) (int, error) { + if b.limit > 0 && b.buf.Len() >= b.limit { + b.truncated += len(p) + return len(p), nil + } + if b.limit > 0 && b.buf.Len()+len(p) > b.limit { + keep := b.limit - b.buf.Len() + _, _ = b.buf.Write(p[:keep]) + b.truncated += len(p) - keep + return len(p), nil + } + return b.buf.Write(p) +} + +func (b *limitedBuffer) String() string { + if b.truncated == 0 { + return b.buf.String() + } + return fmt.Sprintf("%s\n... truncated %d bytes ...", b.buf.String(), b.truncated) +} + type Remote struct { Client *sql.DB From string @@ -160,21 +192,51 @@ func (r *Remote) Backup(info BackupInfo) error { } } fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz") - backupCommand := exec.Command("bash", "-c", - fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='\\''%s'\\'' pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s", - imageTag, r.Password, r.Address, r.Port, r.User, info.Name, fileNameItem)) - _ = backupCommand.Run() - b := make([]byte, 5) - n := []byte{80, 71, 68, 77, 80} + backupFile, err := os.OpenFile(fileNameItem, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + backupFileClosed := false + defer func() { + if !backupFileClosed { + _ = backupFile.Close() + } + }() + backupCommand := exec.Command( + "docker", + "run", "--rm", "--net=host", "-i", + "-e", "PGPASSWORD="+r.Password, + imageTag, + "pg_dump", + "-h", r.Address, + "-p", fmt.Sprintf("%d", r.Port), + "--no-owner", + "-Fc", + "-U", r.User, + info.Name, + ) + backupCommand.Stdout = backupFile + stderr := &limitedBuffer{limit: maxPgDumpStderrCapture} + backupCommand.Stderr = stderr + if err := backupCommand.Run(); err != nil { + return fmt.Errorf("backup failed, stderr: %s, err: %v", strings.TrimSpace(stderr.String()), err) + } + if err := backupFile.Close(); err != nil { + return fmt.Errorf("close backup file failed, err: %v", err) + } + backupFileClosed = true + + b := make([]byte, len(pgDumpMagic)) handle, err := os.OpenFile(fileNameItem, os.O_RDONLY, os.ModePerm) if err != nil { return fmt.Errorf("backup file not found,err:%v", err) } defer handle.Close() - _, _ = handle.Read(b) - if string(b) != string(n) { - errBytes, _ := os.ReadFile(fileNameItem) - return fmt.Errorf("backup failed, err: %s", string(errBytes)) + if _, err := io.ReadFull(handle, b); err != nil { + return fmt.Errorf("read backup header failed, stderr: %s, err: %v", strings.TrimSpace(stderr.String()), err) + } + if !bytes.Equal(b, pgDumpMagic) { + return fmt.Errorf("backup failed, invalid pg dump header: %q, stderr: %s", string(b), strings.TrimSpace(stderr.String())) } gzipCmd := exec.Command("gzip", fileNameItem) @@ -207,9 +269,32 @@ func (r *Remote) Recover(info RecoverInfo) error { _, _ = gzipCmd.CombinedOutput() }() } - recoverCommand := exec.Command("bash", "-c", - fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='\\''%s'\\'' pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -c --if-exists --no-owner -U %s -d %s --role=%s' < %s", - imageTag, r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName)) + restoreFile, err := os.Open(fileName) + if err != nil { + return err + } + defer restoreFile.Close() + recoverCommand := exec.Command( + "docker", + "run", "--rm", "--net=host", "-i", + "-e", "PGPASSWORD="+r.Password, + imageTag, + "pg_restore", + "-h", r.Address, + "-p", fmt.Sprintf("%d", r.Port), + "--verbose", + "--clean", + "--no-privileges", + "--no-owner", + "-Fc", + "-c", + "--if-exists", + "--no-owner", + "-U", r.User, + "-d", info.Name, + "--role="+info.Username, + ) + recoverCommand.Stdin = restoreFile pipe, _ := recoverCommand.StdoutPipe() stderrPipe, _ := recoverCommand.StderrPipe() defer pipe.Close() @@ -230,6 +315,11 @@ func (r *Remote) Recover(info RecoverInfo) error { } global.LOG.Infof("[PostgreSQL] DB:[%s] Restoring: %s", info.Name, readString) } + if err := recoverCommand.Wait(); err != nil { + all, _ := io.ReadAll(stderrPipe) + global.LOG.Errorf("[PostgreSQL] DB:[%s] Recover Error: %s", info.Name, string(all)) + return err + } return nil } diff --git a/agent/utils/toolbox/fail2ban.go b/agent/utils/toolbox/fail2ban.go index afb859ef7..3a58edcae 100644 --- a/agent/utils/toolbox/fail2ban.go +++ b/agent/utils/toolbox/fail2ban.go @@ -47,7 +47,7 @@ func (f *Fail2ban) Status() (bool, bool, bool) { } func (f *Fail2ban) Version() string { - stdout, err := cmd.RunDefaultWithStdoutBashC("fail2ban-client version") + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("fail2ban-client", "version") if err != nil { global.LOG.Errorf("load the fail2ban version failed, %v", err) return "-" @@ -63,7 +63,7 @@ func (f *Fail2ban) Operate(operate string) error { } return nil case "reload": - if err := cmd.RunDefaultBashC("fail2ban-client reload"); err != nil { + if err := cmd.NewCommandMgr().Run("fail2ban-client", "reload"); err != nil { return fmt.Errorf("fail2ban-client reload, %v", err) } return nil @@ -91,27 +91,30 @@ func (f *Fail2ban) ReBanIPs(ips []string) error { func (f *Fail2ban) ListBanned() ([]string, error) { var lists []string - stdout, err := cmd.RunDefaultWithStdoutBashC("fail2ban-client status sshd | grep 'Banned IP list:'") + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("fail2ban-client", "status", "sshd") if err != nil { return lists, err } - itemList := strings.Split(strings.Trim(stdout, "\n"), "Banned IP list:") - if len(itemList) != 2 { - return lists, nil - } - - ips := strings.Fields(itemList[1]) - for _, item := range ips { - if len(item) != 0 { - lists = append(lists, item) + for _, line := range strings.Split(strings.Trim(stdout, "\n"), "\n") { + itemList := strings.Split(line, "Banned IP list:") + if len(itemList) != 2 { + continue } + + ips := strings.Fields(itemList[1]) + for _, item := range ips { + if len(item) != 0 { + lists = append(lists, item) + } + } + break } return lists, nil } func (f *Fail2ban) ListIgnore() ([]string, error) { var lists []string - stdout, err := cmd.RunDefaultWithStdoutBashC("fail2ban-client get sshd ignoreip") + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)).RunWithStdout("fail2ban-client", "get", "sshd", "ignoreip") if err != nil { return lists, err } diff --git a/agent/utils/toolbox/pure-ftpd.go b/agent/utils/toolbox/pure-ftpd.go index 2b740f4e4..d078ac9c2 100644 --- a/agent/utils/toolbox/pure-ftpd.go +++ b/agent/utils/toolbox/pure-ftpd.go @@ -63,7 +63,7 @@ func NewFtpClient() (*Ftp, error) { groupItem, err := user.LookupGroupId("1000") if err == nil { - if err := cmd.RunDefaultBashCf("useradd -u 1000 -g %s %s", groupItem.Name, "1panel"); err != nil { + if err := cmd.NewCommandMgr().Run("useradd", "-u", "1000", "-g", groupItem.Name, "1panel"); err != nil { return nil, err } return &Ftp{DefaultUser: "1panel", DefaultGroup: groupItem.Name}, nil @@ -71,10 +71,10 @@ func NewFtpClient() (*Ftp, error) { if err.Error() != user.UnknownGroupIdError("1000").Error() { return nil, err } - if err := cmd.RunDefaultBashC("groupadd -g 1000 1panel"); err != nil { + if err := cmd.NewCommandMgr().Run("groupadd", "-g", "1000", "1panel"); err != nil { return nil, err } - if err := cmd.RunDefaultBashC("useradd -u 1000 -g 1panel 1panel"); err != nil { + if err := cmd.NewCommandMgr().Run("useradd", "-u", "1000", "-g", "1panel", "1panel"); err != nil { return nil, err } return &Ftp{DefaultUser: "1panel", DefaultGroup: "1panel"}, nil @@ -118,7 +118,7 @@ func (f *Ftp) UserAdd(username, passwd, path string) error { return err } _ = f.Reload() - if err := cmd.RunDefaultBashCf("chown -R %s:%s %s", f.DefaultUser, f.DefaultGroup, path); err != nil { + if err := cmd.NewCommandMgr().Run("chown", "-R", f.DefaultUser+":"+f.DefaultGroup, path); err != nil { return err } return nil @@ -128,7 +128,7 @@ func (f *Ftp) UserDel(username string) error { if cmd.CheckIllegal(username) { return buserr.New("ErrCmdIllegal") } - if err := cmd.RunDefaultBashCf("pure-pw userdel %s", username); err != nil { + if err := cmd.NewCommandMgr().Run("pure-pw", "userdel", username); err != nil { return err } _ = f.Reload() @@ -189,10 +189,10 @@ func (f *Ftp) SetPath(username, path string) error { if cmd.CheckIllegal(username, path) { return buserr.New("ErrCmdIllegal") } - if err := cmd.RunDefaultBashCf("pure-pw usermod %s -d %s", username, path); err != nil { + if err := cmd.NewCommandMgr().Run("pure-pw", "usermod", username, "-d", path); err != nil { return err } - if err := cmd.RunDefaultBashCf("chown -R %s:%s %s", f.DefaultUser, f.DefaultGroup, path); err != nil { + if err := cmd.NewCommandMgr().Run("chown", "-R", f.DefaultUser+":"+f.DefaultGroup, path); err != nil { return err } return nil @@ -202,18 +202,18 @@ func (f *Ftp) SetStatus(username, status string) error { if cmd.CheckIllegal(username, status) { return buserr.New("ErrCmdIllegal") } - statusItem := "''" + statusItem := "" if status == constant.StatusDisable { statusItem = "1" } - if err := cmd.RunDefaultBashCf("pure-pw usermod %s -r %s", username, statusItem); err != nil { + if err := cmd.NewCommandMgr().Run("pure-pw", "usermod", username, "-r", statusItem); err != nil { return err } return nil } func (f *Ftp) LoadList() ([]FtpList, error) { - std, err := cmd.RunDefaultWithStdoutBashC("pure-pw list") + std, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("pure-pw", "list") if err != nil { return nil, err } @@ -224,14 +224,20 @@ func (f *Ftp) LoadList() ([]FtpList, error) { if len(parts) < 2 { continue } - std2, err := cmd.RunDefaultWithStdoutBashCf("pure-pw show %s | grep 'Allowed client IPs :'", parts[0]) + std2, err := cmd.NewCommandMgr(cmd.WithTimeout(20*time.Second)).RunWithStdout("pure-pw", "show", parts[0]) if err != nil { global.LOG.Errorf("handle pure-pw show %s failed, %v", parts[0], err) continue } status := constant.StatusDisable - itemStd := strings.ReplaceAll(std2, "\n", "") - if len(strings.TrimSpace(strings.ReplaceAll(itemStd, "Allowed client IPs :", ""))) == 0 { + allowedLine := "" + for _, line := range strings.Split(std2, "\n") { + if strings.Contains(line, "Allowed client IPs :") { + allowedLine = line + break + } + } + if len(strings.TrimSpace(strings.ReplaceAll(allowedLine, "Allowed client IPs :", ""))) == 0 { status = constant.StatusEnable } lists = append(lists, FtpList{User: parts[0], Path: strings.ReplaceAll(parts[1], "/./", ""), Status: status}) @@ -240,7 +246,7 @@ func (f *Ftp) LoadList() ([]FtpList, error) { } func (f *Ftp) Reload() error { - if err := cmd.RunDefaultBashC("pure-pw mkdb"); err != nil { + if err := cmd.NewCommandMgr().Run("pure-pw", "mkdb"); err != nil { return err } return nil @@ -250,19 +256,28 @@ func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) { var logs []FtpLog logItem := "" if _, err := os.Stat("/etc/pure-ftpd/conf"); err != nil && os.IsNotExist(err) { - std, err := cmd.RunDefaultWithStdoutBashC("cat /etc/pure-ftpd/pure-ftpd.conf | grep AltLog | grep clf:") logItem = "/var/log/pureftpd.log" - if err == nil && !strings.HasPrefix(std, "#") { - logItem = std + data, readErr := os.ReadFile("/etc/pure-ftpd/pure-ftpd.conf") + if readErr == nil { + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(strings.TrimSpace(line), "#") || !strings.Contains(line, "AltLog") || !strings.Contains(line, "clf:") { + continue + } + logItem = line + break + } } } else { if err != nil { return logs, err } - std, err := cmd.RunDefaultWithStdoutBashC("cat /etc/pure-ftpd/conf/AltLog") logItem = "/var/log/pure-ftpd/transfer.log" - if err != nil && !strings.HasPrefix(std, "#") { - logItem = std + data, readErr := os.ReadFile("/etc/pure-ftpd/conf/AltLog") + if readErr == nil { + std := string(data) + if !strings.HasPrefix(strings.TrimSpace(std), "#") { + logItem = std + } } } @@ -298,7 +313,7 @@ func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) { } func handleGunzip(path string) error { - if err := cmd.RunDefaultBashCf("gunzip %s", path); err != nil { + if err := cmd.NewCommandMgr().Run("gunzip", path); err != nil { return err } return nil diff --git a/core/app/service/logs.go b/core/app/service/logs.go index acf36dd5f..2ff423d3a 100644 --- a/core/app/service/logs.go +++ b/core/app/service/logs.go @@ -1,16 +1,22 @@ package service import ( + "bytes" + "fmt" + "net/http" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/utils/cmd" "github.com/1Panel-dev/1Panel/core/utils/common" geo2 "github.com/1Panel-dev/1Panel/core/utils/geo" + "github.com/1Panel-dev/1Panel/core/utils/req_helper" "github.com/gin-gonic/gin" "github.com/1Panel-dev/1Panel/core/app/dto" "github.com/1Panel-dev/1Panel/core/app/model" "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/global" - "github.com/1Panel-dev/1Panel/core/utils/cmd" "github.com/jinzhu/copier" ) @@ -109,5 +115,21 @@ func (u *LogService) CleanLogs(logtype string) error { } func writeLogs(version string) { - _, _ = cmd.RunDefaultWithStdoutBashCf("curl -sfL %s | sh -s 1p upgrade %s", logs, version) + _ = runRemoteShellScript(logs, "1p", "upgrade", version) +} + +func runRemoteShellScript(url string, args ...string) error { + statusCode, script, err := req_helper.HandleRequestWithProxy(url, http.MethodGet, constant.TimeOut20s) + if err != nil { + return err + } + if statusCode < http.StatusOK || statusCode >= http.StatusMultipleChoices { + return fmt.Errorf("download script failed, status code: %d", statusCode) + } + _, err = cmd.NewCommandMgr().RunPipe(cmd.PipeCommand{ + Name: "sh", + Args: append([]string{"-s"}, args...), + Stdin: bytes.NewReader(script), + }) + return err } diff --git a/core/app/service/upgrade.go b/core/app/service/upgrade.go index d2780c166..8f44006cb 100644 --- a/core/app/service/upgrade.go +++ b/core/app/service/upgrade.go @@ -21,6 +21,7 @@ import ( "github.com/1Panel-dev/1Panel/core/utils/cmd" "github.com/1Panel-dev/1Panel/core/utils/common" "github.com/1Panel-dev/1Panel/core/utils/controller" + "github.com/1Panel-dev/1Panel/core/utils/ctl_conf" "github.com/1Panel-dev/1Panel/core/utils/files" "github.com/1Panel-dev/1Panel/core/utils/req_helper" "github.com/1Panel-dev/1Panel/core/utils/xpack" @@ -173,7 +174,7 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error { fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch) _ = settingRepo.Update("SystemStatus", "Upgrading") go func() { - oldLang := common.LoadParams("LANGUAGE") + oldLang := ctl_conf.Load("LANGUAGE") if err := files.DownloadFileWithProxyStream(downloadPath+"/"+fileName, downloadDir+"/"+fileName); err != nil { global.LOG.Errorf("download service file failed, err: %v", err) _ = settingRepo.Update("SystemStatus", "Free") @@ -219,12 +220,12 @@ func (u *UpgradeService) Upgrade(req dto.Upgrade) error { u.handleRollback(originalDir, 2, svcInfo) return } - if _, err := cmd.RunDefaultWithStdoutBashCf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.Base.InstallDir); err != nil { + if err := ctl_conf.UpdateInFile("/usr/local/bin/1pctl", "BASE_DIR", global.CONF.Base.InstallDir); err != nil { global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err) u.handleRollback(originalDir, 2, svcInfo) return } - if _, err := cmd.RunDefaultWithStdoutBashCf("sed -i -e 's#LANGUAGE=.*#LANGUAGE=%s#g' /usr/local/bin/1pctl", oldLang); err != nil { + if err := ctl_conf.UpdateInFile("/usr/local/bin/1pctl", "LANGUAGE", oldLang); err != nil { global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err) u.handleRollback(originalDir, 2, svcInfo) return @@ -546,7 +547,7 @@ func (u *UpgradeService) loadReleaseNotes(path string) (string, error) { } func loadArch() (string, error) { - std, err := cmd.RunDefaultWithStdoutBashC("uname -a") + std, err := cmd.NewCommandMgr().RunWithStdout("uname", "-a") if err != nil { return "", fmt.Errorf("std: %s, err: %s", std, err.Error()) } diff --git a/core/cmd/server/cmd/restore.go b/core/cmd/server/cmd/restore.go index 0a797f55e..7aec6d460 100644 --- a/core/cmd/server/cmd/restore.go +++ b/core/cmd/server/cmd/restore.go @@ -12,6 +12,7 @@ import ( cmdUtils "github.com/1Panel-dev/1Panel/core/utils/cmd" "github.com/1Panel-dev/1Panel/core/utils/common" "github.com/1Panel-dev/1Panel/core/utils/controller" + "github.com/1Panel-dev/1Panel/core/utils/ctl_conf" "github.com/1Panel-dev/1Panel/core/utils/files" "github.com/spf13/cobra" @@ -29,11 +30,10 @@ var restoreCmd = &cobra.Command{ fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl restore"})) return nil } - stdout, err := cmdUtils.RunDefaultWithStdoutBashC("grep '^BASE_DIR=' /usr/local/bin/1pctl | cut -d'=' -f2") + baseDir, err := ctl_conf.LoadFromFile("/usr/local/bin/1pctl", "BASE_DIR") if err != nil { return fmt.Errorf("handle load `BASE_DIR` failed, err: %v", err) } - baseDir := strings.ReplaceAll(stdout, "\n", "") upgradeDir := path.Join(baseDir, "1panel", "tmp", "upgrade") tmpPath, err := loadRestorePath(upgradeDir) @@ -59,17 +59,17 @@ var restoreCmd = &cobra.Command{ if err := files.CopyItem(false, true, path.Join(tmpPath, "GeoIP.mmdb"), path.Join(baseDir, "1panel/geo")); err != nil { return err } - sudo := cmdUtils.SudoHandleCmd() - _, _ = cmdUtils.RunDefaultWithStdoutBashCf("%s chmod 755 /usr/local/bin/1panel-agent /usr/local/bin/1panel-core", sudo) + _, _ = cmdUtils.NewCommandMgr().RunWithStdout("chmod", "755", "/usr/local/bin/1panel-agent", "/usr/local/bin/1panel-core") fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep2")) if err := files.CopyItem(false, true, path.Join(tmpPath, "1pctl"), "/usr/local/bin"); err != nil { return err } - _, _ = cmdUtils.RunDefaultWithStdoutBashCf("%s chmod 755 /usr/local/bin/1pctl", sudo) - _, _ = cmdUtils.RunDefaultWithStdoutBashCf("cp -r %s /usr/local/bin", path.Join(tmpPath, "lang")) + _, _ = cmdUtils.NewCommandMgr().RunWithStdout("chmod", "755", "/usr/local/bin/1pctl") + _, _ = cmdUtils.NewCommandMgr().RunWithStdout("cp", "-r", "--", path.Join(tmpPath, "lang"), "/usr/local/bin") geoPath := path.Join(global.CONF.Base.InstallDir, "1panel/geo") - _, _ = cmdUtils.RunDefaultWithStdoutBashCf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpPath, "GeoIP.mmdb"), geoPath) + _ = os.MkdirAll(geoPath, os.ModePerm) + _, _ = cmdUtils.NewCommandMgr().RunWithStdout("cp", "--", path.Join(tmpPath, "GeoIP.mmdb"), geoPath+"/") fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep3")) svcBasePath, _ := controller.GetServicePath("") @@ -137,11 +137,10 @@ func loadRestorePath(upgradeDir string) (string, error) { } func loadRollbackVersion(upgradeDir string) string { - stdout, err := cmdUtils.RunDefaultWithStdoutBashCf("grep '^ORIGINAL_VERSION=' %s/1pctl | cut -d'=' -f2", upgradeDir) + info, err := ctl_conf.LoadFromFile(path.Join(upgradeDir, "1pctl"), "ORIGINAL_VERSION") if err != nil { return "-" } - info := strings.ReplaceAll(stdout, "\n", "") if len(info) == 0 || info == `""` { return "-" } diff --git a/core/cmd/server/cmd/root.go b/core/cmd/server/cmd/root.go index 29b652764..f61cbac5b 100644 --- a/core/cmd/server/cmd/root.go +++ b/core/cmd/server/cmd/root.go @@ -8,7 +8,7 @@ import ( "time" "github.com/1Panel-dev/1Panel/core/server" - cmdUtils "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/ctl_conf" "github.com/glebarez/sqlite" "github.com/spf13/cobra" "gorm.io/gorm" @@ -38,11 +38,10 @@ type setting struct { } func loadDBConn(dbName string) (*gorm.DB, error) { - stdout, err := cmdUtils.RunDefaultWithStdoutBashC("grep '^BASE_DIR=' /usr/local/bin/1pctl | cut -d'=' -f2") + baseDir, err := ctl_conf.LoadFromFile("/usr/local/bin/1pctl", "BASE_DIR") if err != nil { return nil, fmt.Errorf("handle load `BASE_DIR` failed, err: %v", err) } - baseDir := strings.ReplaceAll(stdout, "\n", "") if len(baseDir) == 0 { return nil, fmt.Errorf("error `BASE_DIR` find in /usr/local/bin/1pctl \n") } diff --git a/core/cmd/server/cmd/update.go b/core/cmd/server/cmd/update.go index afdf2c6f2..78c5e4811 100644 --- a/core/cmd/server/cmd/update.go +++ b/core/cmd/server/cmd/update.go @@ -237,7 +237,7 @@ func port() { fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful")) fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortResult", map[string]interface{}{"name": newPortStr})) - std, err := cmd.RunDefaultWithStdoutBashC("1pctl restart core") + std, err := cmd.NewCommandMgr().RunWithStdout("1pctl", "restart", "core") if err != nil { fmt.Println(std) } diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index b80f58ebd..8d99b407b 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -3,12 +3,12 @@ package i18n import ( "embed" "fmt" - "os/exec" "strings" "sync/atomic" "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/ctl_conf" "github.com/gin-gonic/gin" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" @@ -209,12 +209,10 @@ func getLanguageFromDBInternal() string { return lang } func getLanguageFrom1pctl() string { - cmd := exec.Command("bash", "-c", "grep '^LANGUAGE=' /usr/local/bin/1pctl | cut -d'=' -f2") - stdout, err := cmd.CombinedOutput() + info, err := ctl_conf.LoadFromFile("/usr/local/bin/1pctl", "LANGUAGE") if err != nil { panic(err) } - info := strings.ReplaceAll(string(stdout), "\n", "") if len(info) == 0 || info == `""` { panic("error `LANGUAGE` find in /usr/local/bin/1pctl") } diff --git a/core/init/geo/lang.go b/core/init/geo/lang.go index 1af9920b3..39b8a0850 100644 --- a/core/init/geo/lang.go +++ b/core/init/geo/lang.go @@ -61,7 +61,7 @@ func initLang() { downloadLangFromRemote() return } - if err := cmd.RunDefaultBashCf("cp -r %s %s", path.Join(tmpPath, "lang"), "/usr/local/bin/"); err != nil { + if err := cmd.NewCommandMgr().Run("cp", "-r", "--", path.Join(tmpPath, "lang"), "/usr/local/bin/"); err != nil { global.LOG.Errorf("load lang from package failed, %v", err) return } @@ -72,7 +72,11 @@ func initLang() { downloadGeoFromRemote(geoPath) return } - if err := cmd.RunDefaultBashCf("mkdir %s && cp %s %s/", path.Dir(geoPath), path.Join(tmpPath, "GeoIP.mmdb"), path.Dir(geoPath)); err != nil { + if err := os.MkdirAll(path.Dir(geoPath), os.ModePerm); err != nil { + global.LOG.Errorf("mkdir geo ip dir failed, %v", err) + return + } + if err := cmd.NewCommandMgr().Run("cp", "--", path.Join(tmpPath, "GeoIP.mmdb"), path.Dir(geoPath)+"/"); err != nil { global.LOG.Errorf("load geo ip from package failed, %v", err) return } @@ -113,7 +117,7 @@ func downloadLangFromRemote() { global.LOG.Error("download lang.tar.gz failed, no such file") return } - if err := cmd.RunDefaultBashCf("tar zxvfC %s %s", "/usr/local/bin/lang.tar.gz", "/usr/local/bin/"); err != nil { + if err := cmd.NewCommandMgr().Run("tar", "zxvfC", "/usr/local/bin/lang.tar.gz", "/usr/local/bin/"); err != nil { global.LOG.Errorf("decompress lang.tar.gz failed, %v", err) return } diff --git a/core/init/hook/hook.go b/core/init/hook/hook.go index e347d1af9..9ff7cb411 100644 --- a/core/init/hook/hook.go +++ b/core/init/hook/hook.go @@ -1,14 +1,15 @@ package hook import ( + "os/exec" "strings" "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/app/service" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" - "github.com/1Panel-dev/1Panel/core/utils/cmd" "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/ctl_conf" "github.com/1Panel-dev/1Panel/core/utils/encrypt" "github.com/1Panel-dev/1Panel/core/utils/xpack" ) @@ -37,12 +38,12 @@ func handleUserInfo(tags string, settingRepo repo.ISettingRepo) { } settingMap := make(map[string]string) if tags == "use_existing" { - settingMap["ServerPort"] = common.LoadParams("ORIGINAL_PORT") + settingMap["ServerPort"] = ctl_conf.Load("ORIGINAL_PORT") global.CONF.Conn.Port = settingMap["ServerPort"] settingMap["UserName"] = global.CONF.Base.Username settingMap["Password"] = global.CONF.Base.Password settingMap["SecurityEntrance"] = global.CONF.Conn.Entrance - settingMap["SystemVersion"] = common.LoadParams("ORIGINAL_VERSION") + settingMap["SystemVersion"] = ctl_conf.Load("ORIGINAL_VERSION") global.CONF.Base.Version = settingMap["SystemVersion"] settingMap["Language"] = global.CONF.Base.Language } @@ -81,8 +82,8 @@ func handleUserInfo(tags string, settingRepo repo.ISettingRepo) { } } - _, _ = cmd.RunDefaultWithStdoutBashCf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", cmd.SudoHandleCmd(), global.CONF.Base.ChangeUserInfo) - _, _ = cmd.RunDefaultWithStdoutBashCf("%s sed -i -e 's#ORIGINAL_PASSWORD=.*#ORIGINAL_PASSWORD=**********#g' /usr/local/bin/1pctl", cmd.SudoHandleCmd()) + _ = ctl_conf.RemoveValueFromFile("/usr/local/bin/1pctl", "CHANGE_USER_INFO", global.CONF.Base.ChangeUserInfo) + _ = ctl_conf.UpdateInFile("/usr/local/bin/1pctl", "ORIGINAL_PASSWORD", "**********") } func generateKey() { @@ -92,11 +93,10 @@ func generateKey() { } func initDockerConf() { - stdout, err := cmd.RunDefaultWithStdoutBashC("which docker") + dockerPath, err := exec.LookPath("docker") if err != nil { return } - dockerPath := stdout if strings.Contains(dockerPath, "snap") { constant.DaemonJsonPath = "/var/snap/docker/current/config/daemon.json" } diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go index fa4a95e7d..6c9fcd85b 100644 --- a/core/init/migration/migrations/init.go +++ b/core/init/migration/migrations/init.go @@ -14,8 +14,8 @@ import ( "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" "github.com/1Panel-dev/1Panel/core/init/migration/helper" - "github.com/1Panel-dev/1Panel/core/utils/cmd" "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/ctl_conf" "github.com/1Panel-dev/1Panel/core/utils/encrypt" "github.com/go-gormigrate/gormigrate/v2" "gorm.io/gorm" @@ -55,7 +55,7 @@ var InitSetting = &gormigrate.Migration{ return err } } - _, _ = cmd.RunDefaultWithStdoutBashCf("%s sed -i -e 's#ORIGINAL_PASSWORD=.*#ORIGINAL_PASSWORD=**********#g' /usr/local/bin/1pctl", cmd.SudoHandleCmd()) + _ = ctl_conf.UpdateInFile("/usr/local/bin/1pctl", "ORIGINAL_PASSWORD", "**********") if err := tx.Create(&model.Setting{Key: "Theme", Value: "light"}).Error; err != nil { return err } diff --git a/core/init/viper/viper.go b/core/init/viper/viper.go index 390d608b8..1762dad7a 100644 --- a/core/init/viper/viper.go +++ b/core/init/viper/viper.go @@ -5,12 +5,10 @@ import ( "fmt" "os" "path" - "strings" "github.com/1Panel-dev/1Panel/core/cmd/server/conf" "github.com/1Panel-dev/1Panel/core/global" - "github.com/1Panel-dev/1Panel/core/utils/cmd" - "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/ctl_conf" "github.com/fsnotify/fsnotify" "github.com/spf13/viper" "gopkg.in/yaml.v3" @@ -40,14 +38,14 @@ func Init() { panic(fmt.Errorf("fatal error config file: %s", err)) } } else { - baseDir = common.LoadParams("BASE_DIR") - port = common.LoadParams("ORIGINAL_PORT") - version = common.LoadParams("ORIGINAL_VERSION") - username = common.LoadParams("ORIGINAL_USERNAME") - password = common.LoadParams("ORIGINAL_PASSWORD") - entrance = common.LoadParams("ORIGINAL_ENTRANCE") - language = common.LoadParams("LANGUAGE") - edition = common.LoadParamsWithoutPanic("PANEL_EDITION") + baseDir = ctl_conf.Load("BASE_DIR") + port = ctl_conf.Load("ORIGINAL_PORT") + version = ctl_conf.Load("ORIGINAL_VERSION") + username = ctl_conf.Load("ORIGINAL_USERNAME") + password = ctl_conf.Load("ORIGINAL_PASSWORD") + entrance = ctl_conf.Load("ORIGINAL_ENTRANCE") + language = ctl_conf.Load("LANGUAGE") + edition = ctl_conf.LoadWithoutPanic("PANEL_EDITION") reader := bytes.NewReader(conf.AppYaml) if err := v.ReadConfig(reader); err != nil { @@ -106,9 +104,5 @@ func Init() { } func loadChangeInfo() string { - stdout, err := cmd.RunDefaultWithStdoutBashC("grep '^CHANGE_USER_INFO=' /usr/local/bin/1pctl | cut -d'=' -f2") - if err != nil { - return "" - } - return strings.ReplaceAll(stdout, "\n", "") + return ctl_conf.LoadWithoutPanic("CHANGE_USER_INFO") } diff --git a/core/utils/cmd/cmd.go b/core/utils/cmd/cmd.go index 4bf577c51..1a4456e7d 100644 --- a/core/utils/cmd/cmd.go +++ b/core/utils/cmd/cmd.go @@ -1,11 +1,7 @@ package cmd import ( - "bufio" - "fmt" - "io" "os/exec" - "strings" ) func SudoHandleCmd() string { @@ -17,52 +13,6 @@ func SudoHandleCmd() string { } func Which(name string) bool { - // Prefer Go's built-in PATH lookup so we don't depend on an external - // `which` binary, which is not installed by default on minimal - // distributions (e.g. Arch Linux, some Alpine images, slim containers). - // See 1Panel-dev/1Panel#12605. - if _, err := exec.LookPath(name); err == nil { - return true - } - // Fall back to shelling out for environments where PATH inside the - // agent process differs from the user's interactive shell PATH (the - // previous behaviour, preserved for compatibility). - stdout, err := RunDefaultWithStdoutBashCf("which %s", name) - if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) { - return false - } - return true -} - -func ExecWithStreamOutput(command string, outputCallback func(string)) error { - cmd := exec.Command("bash", "-c", command) - - stdout, err := cmd.StdoutPipe() - if err != nil { - return fmt.Errorf("failed to get stdout: %w", err) - } - - stderr, err := cmd.StderrPipe() - if err != nil { - return fmt.Errorf("failed to get stderr: %w", err) - } - - if err := cmd.Start(); err != nil { - return fmt.Errorf("failed to start command: %w", err) - } - - go streamReader(stdout, outputCallback) - go streamReader(stderr, outputCallback) - - if err := cmd.Wait(); err != nil { - return fmt.Errorf("command finished with error: %w", err) - } - return nil -} - -func streamReader(reader io.ReadCloser, callback func(string)) { - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - callback(scanner.Text()) - } + _, err := exec.LookPath(name) + return err == nil } diff --git a/core/utils/cmd/cmdx.go b/core/utils/cmd/cmdx.go index 71be68afa..3784e0b17 100644 --- a/core/utils/cmd/cmdx.go +++ b/core/utils/cmd/cmdx.go @@ -5,10 +5,12 @@ import ( "context" "errors" "fmt" + "io" "log" "os" "os/exec" "strings" + "sync" "syscall" "time" @@ -17,10 +19,12 @@ import ( "github.com/1Panel-dev/1Panel/core/constant" ) +const maxStreamOutputCapture = 64 * 1024 + type CommandHelper struct { workDir string outputFile string - scriptPath string + env []string timeout time.Duration taskItem *task.Task logger *log.Logger @@ -29,6 +33,46 @@ type CommandHelper struct { type Option func(*CommandHelper) +type PipeCommand struct { + Name string + Args []string + Env []string + Dir string + Stdin io.Reader +} + +type lockedBuffer struct { + mu sync.Mutex + buf bytes.Buffer + limit int + truncated int +} + +func (b *lockedBuffer) Write(p []byte) (int, error) { + b.mu.Lock() + defer b.mu.Unlock() + if b.limit > 0 && b.buf.Len() >= b.limit { + b.truncated += len(p) + return len(p), nil + } + if b.limit > 0 && b.buf.Len()+len(p) > b.limit { + keep := b.limit - b.buf.Len() + _, _ = b.buf.Write(p[:keep]) + b.truncated += len(p) - keep + return len(p), nil + } + return b.buf.Write(p) +} + +func (b *lockedBuffer) String() string { + b.mu.Lock() + defer b.mu.Unlock() + if b.truncated == 0 { + return b.buf.String() + } + return fmt.Sprintf("%s\n... truncated %d bytes ...", b.buf.String(), b.truncated) +} + func NewCommandMgr(opts ...Option) *CommandHelper { s := &CommandHelper{} for _, opt := range opts { @@ -37,49 +81,160 @@ func NewCommandMgr(opts ...Option) *CommandHelper { return s } -func RunDefaultBashC(command string) error { - mgr := NewCommandMgr() - return mgr.RunBashC(command) -} -func RunDefaultBashCf(command string, arg ...interface{}) error { - mgr := NewCommandMgr() - return mgr.RunBashCf(command, arg...) -} -func RunDefaultWithStdoutBashC(command string) (string, error) { - mgr := NewCommandMgr(WithTimeout(20 * time.Second)) - return mgr.RunWithStdoutBashC(command) -} -func RunDefaultWithStdoutBashCf(command string, arg ...interface{}) (string, error) { - mgr := NewCommandMgr(WithTimeout(20 * time.Second)) - return mgr.RunWithStdoutBashCf(command, arg...) -} - func (c *CommandHelper) Run(name string, arg ...string) error { _, err := c.run(name, arg...) return err } -func (c *CommandHelper) RunBashCWithArgs(arg ...string) error { - arg = append([]string{"-c"}, arg...) - _, err := c.run("bash", arg...) - return err -} -func (c *CommandHelper) RunBashC(command string) error { - _, err := c.run("bash", "-c", command) - return err -} -func (c *CommandHelper) RunBashCf(command string, arg ...interface{}) error { - _, err := c.run("bash", "-c", fmt.Sprintf(command, arg...)) - return err -} func (c *CommandHelper) RunWithStdout(name string, arg ...string) (string, error) { return c.run(name, arg...) } -func (c *CommandHelper) RunWithStdoutBashC(command string) (string, error) { - return c.run("bash", "-c", command) + +func (c *CommandHelper) RunPipe(commands ...PipeCommand) (string, error) { + if len(commands) == 0 { + return "", nil + } + + ctx, cancel := c.pipeContext() + if cancel != nil { + defer cancel() + } + + cmds := c.buildPipeCommands(ctx, commands) + customWriter := &CustomWriter{taskItem: c.taskItem} + var outputFile *os.File + limitOutputCapture := c.taskItem != nil || c.logger != nil || len(c.outputFile) != 0 + stdout, stderr := &lockedBuffer{}, &lockedBuffer{} + if limitOutputCapture { + stdout.limit = maxStreamOutputCapture + stderr.limit = maxStreamOutputCapture + } + if commands[0].Stdin != nil { + cmds[0].Stdin = commands[0].Stdin + } + var pipeStderr io.Writer = stderr + var lastStdout io.Writer = stdout + var lastStderr io.Writer = stderr + var streamWriter io.Writer + var streamClosers []io.Closer + if c.taskItem != nil { + streamWriter = customWriter + } else if c.logger != nil { + streamWriter = c.logger.Writer() + if closer, ok := streamWriter.(io.Closer); ok { + streamClosers = append(streamClosers, closer) + } + } else if len(c.outputFile) != 0 { + file, err := os.OpenFile(c.outputFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm) + if err != nil { + return "", err + } + outputFile = file + lastStdout = outputFile + } + if streamWriter != nil { + pipeStderr = io.MultiWriter(stderr, streamWriter) + lastStdout = io.MultiWriter(stdout, streamWriter) + lastStderr = io.MultiWriter(stderr, streamWriter) + } + defer func() { + if c.taskItem != nil { + customWriter.Flush() + } + for _, closer := range streamClosers { + _ = closer.Close() + } + if outputFile != nil { + _ = outputFile.Close() + } + }() + if err := connectPipeCommands(cmds, lastStdout, lastStderr, pipeStderr); err != nil { + return "", err + } + + if err := startPipeCommands(cmds); err != nil { + return handleErrString(stdout.String(), stderr.String(), c.IgnoreExist1, err) + } + + runErr := waitPipeCommands(ctx, cmds) + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return "", buserr.New("ErrCmdTimeout") + } + if runErr != nil { + return handleErrString(stdout.String(), stderr.String(), c.IgnoreExist1, runErr) + } + return stdout.String(), nil } -func (c *CommandHelper) RunWithStdoutBashCf(command string, arg ...interface{}) (string, error) { - return c.run("bash", "-c", fmt.Sprintf(command, arg...)) + +func (c *CommandHelper) pipeContext() (context.Context, context.CancelFunc) { + ctx := context.Background() + if c.timeout == 0 { + return ctx, nil + } + return context.WithTimeout(ctx, c.timeout) +} + +func (c *CommandHelper) buildPipeCommands(ctx context.Context, commands []PipeCommand) []*exec.Cmd { + cmds := make([]*exec.Cmd, 0, len(commands)) + for _, item := range commands { + cmdItem := exec.CommandContext(ctx, item.Name, item.Args...) + cmdItem.Env = append(os.Environ(), c.env...) + cmdItem.Env = append(cmdItem.Env, item.Env...) + cmdItem.Dir = c.workDir + if item.Dir != "" { + cmdItem.Dir = item.Dir + } + cmdItem.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + cmds = append(cmds, cmdItem) + } + return cmds +} + +func connectPipeCommands(cmds []*exec.Cmd, stdout, stderr, pipeStderr io.Writer) error { + for i := 0; i < len(cmds)-1; i++ { + pipe, err := cmds[i].StdoutPipe() + if err != nil { + return err + } + cmds[i+1].Stdin = pipe + cmds[i].Stderr = pipeStderr + } + last := cmds[len(cmds)-1] + last.Stdout = stdout + last.Stderr = stderr + return nil +} + +func startPipeCommands(cmds []*exec.Cmd) error { + for i := len(cmds) - 1; i >= 0; i-- { + if err := cmds[i].Start(); err != nil { + killStarted(cmds[i+1:]) + return err + } + } + return nil +} + +func waitPipeCommands(ctx context.Context, cmds []*exec.Cmd) error { + done := make(chan error, 1) + go func() { + var runErr error + for _, item := range cmds { + if err := item.Wait(); err != nil && runErr == nil { + runErr = err + } + } + done <- runErr + }() + select { + case runErr := <-done: + return runErr + case <-ctx.Done(): + killProcessGroups(cmds) + return <-done + } } func (c *CommandHelper) run(name string, arg ...string) (string, error) { @@ -97,12 +252,21 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) { customWriter := &CustomWriter{taskItem: c.taskItem} var stdout, stderr bytes.Buffer + var loggerClosers []io.Closer if c.taskItem != nil { cmd.Stdout = customWriter cmd.Stderr = customWriter } else if c.logger != nil { - cmd.Stdout = c.logger.Writer() - cmd.Stderr = c.logger.Writer() + stdoutWriter := c.logger.Writer() + stderrWriter := c.logger.Writer() + if closer, ok := stdoutWriter.(io.Closer); ok { + loggerClosers = append(loggerClosers, closer) + } + if closer, ok := stderrWriter.(io.Closer); ok { + loggerClosers = append(loggerClosers, closer) + } + cmd.Stdout = stdoutWriter + cmd.Stderr = stderrWriter } else if len(c.outputFile) != 0 { file, err := os.OpenFile(c.outputFile, os.O_WRONLY|os.O_CREATE, constant.FilePerm) if err != nil { @@ -111,19 +275,21 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) { defer file.Close() cmd.Stdout = file cmd.Stderr = file - } else if len(c.scriptPath) != 0 { - cmd.Stdout = &stdout - cmd.Stderr = &stderr - cmd = exec.Command("bash", c.scriptPath) } else { cmd.Stdout = &stdout cmd.Stderr = &stderr } env := os.Environ() + env = append(env, c.env...) cmd.Env = env if len(c.workDir) != 0 { cmd.Dir = c.workDir } + defer func() { + for _, closer := range loggerClosers { + _ = closer.Close() + } + }() if c.timeout != 0 { err := cmd.Run() @@ -140,12 +306,32 @@ func (c *CommandHelper) run(name string, arg ...string) (string, error) { } err := cmd.Run() + if c.taskItem != nil { + customWriter.Flush() + } if err != nil { return handleErr(stdout, stderr, c.IgnoreExist1, err) } return stdout.String(), nil } +func killStarted(cmds []*exec.Cmd) { + killProcessGroups(cmds) + for _, item := range cmds { + if item.Process != nil { + _ = item.Wait() + } + } +} + +func killProcessGroups(cmds []*exec.Cmd) { + for _, item := range cmds { + if item.Process != nil { + _ = syscall.Kill(-item.Process.Pid, syscall.SIGKILL) + } + } +} + func WithOutputFile(outputFile string) Option { return func(s *CommandHelper) { s.outputFile = outputFile @@ -171,9 +357,9 @@ func WithWorkDir(workDir string) Option { s.workDir = workDir } } -func WithScriptPath(scriptPath string) Option { +func WithEnv(env ...string) Option { return func(s *CommandHelper) { - s.scriptPath = scriptPath + s.env = append(s.env, env...) } } func WithIgnoreExist1() Option { @@ -207,6 +393,10 @@ func (cw *CustomWriter) Flush() { } func handleErr(stdout, stderr bytes.Buffer, ignoreExist1 bool, err error) (string, error) { + return handleErrString(stdout.String(), stderr.String(), ignoreExist1, err) +} + +func handleErrString(stdout, stderr string, ignoreExist1 bool, err error) (string, error) { var exitError *exec.ExitError if ignoreExist1 && errors.As(err, &exitError) { if status, ok := exitError.Sys().(syscall.WaitStatus); ok { @@ -215,16 +405,16 @@ func handleErr(stdout, stderr bytes.Buffer, ignoreExist1 bool, err error) (strin } } } - errMsg := "" - if len(stderr.String()) != 0 { - errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + outItem := stdout + errItem := stderr + if len(errItem) != 0 && len(outItem) != 0 { + return outItem, fmt.Errorf("stdout: %s; stderr: %s, err: %v", outItem, errItem, err) } - if len(stdout.String()) != 0 { - if len(errMsg) != 0 { - errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) - } else { - errMsg = fmt.Sprintf("stdout: %s", stdout.String()) - } + if len(errItem) != 0 { + return outItem, fmt.Errorf("stderr: %s, err: %v", errItem, err) } - return errMsg, err + if len(outItem) != 0 { + return outItem, fmt.Errorf("stdout: %s, err: %v", outItem, err) + } + return "", err } diff --git a/core/utils/common/common.go b/core/utils/common/common.go index 3376c07ff..46a821419 100644 --- a/core/utils/common/common.go +++ b/core/utils/common/common.go @@ -147,7 +147,7 @@ func UniqueUints(items []uint) []uint { } func LoadArch() (string, error) { - std, err := cmd.RunDefaultWithStdoutBashC("uname -a") + std, err := cmd.NewCommandMgr().RunWithStdout("uname", "-a") if err != nil { return "", fmt.Errorf("std: %s, err: %s", std, err.Error()) } @@ -245,25 +245,6 @@ func HandleIPList(content string) ([]string, error) { return res, nil } -func LoadParams(param string) string { - stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param) - if err != nil { - panic(err) - } - info := strings.ReplaceAll(stdout, "\n", "") - if len(info) == 0 || info == `""` { - panic(fmt.Sprintf("error `%s` find in /usr/local/bin/1pctl", param)) - } - return info -} -func LoadParamsWithoutPanic(param string) string { - stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param) - if err != nil { - return "" - } - return strings.ReplaceAll(stdout, "\n", "") -} - func GetRealClientIP(c *gin.Context) string { addr := c.Request.RemoteAddr if ip, _, err := net.SplitHostPort(addr); err == nil { diff --git a/core/utils/common/time.go b/core/utils/common/time.go index bc6fa6407..3483834a3 100644 --- a/core/utils/common/time.go +++ b/core/utils/common/time.go @@ -13,18 +13,21 @@ func LoadTimeZoneByCmd() string { if _, err := time.LoadLocation(loc); err != nil { loc = "Asia/Shanghai" } - std, err := cmd.RunDefaultWithStdoutBashC("timedatectl | grep 'Time zone'") + std, err := cmd.NewCommandMgr().RunWithStdout("timedatectl") if err != nil { return loc } - fields := strings.Fields(string(std)) - if len(fields) != 5 { - return loc + for _, line := range strings.Split(std, "\n") { + fields := strings.Fields(line) + if len(fields) != 5 || fields[0] != "Time" || fields[1] != "zone:" { + continue + } + if _, err := time.LoadLocation(fields[2]); err != nil { + return loc + } + return fields[2] } - if _, err := time.LoadLocation(fields[2]); err != nil { - return loc - } - return fields[2] + return loc } func LoadExpiredLocation() *time.Location { diff --git a/core/utils/controller/manager/common.go b/core/utils/controller/manager/common.go index 89f762089..eb5809df9 100644 --- a/core/utils/controller/manager/common.go +++ b/core/utils/controller/manager/common.go @@ -21,7 +21,7 @@ func handlerErr(out string, err error) error { func run(client *ssh.SSHClient, name string, args ...string) (string, error) { if client == nil { - return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second)).RunWithStdoutBashCf("LANGUAGE=en_US:en %s %s", name, strings.Join(args, " ")) + return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second), cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout(name, args...) } return client.Runf("LANGUAGE=en_US:en %s %s", name, strings.Join(args, " ")) } diff --git a/core/utils/controller/manager/openrc.go b/core/utils/controller/manager/openrc.go index ceb5b1fba..b0636e1c3 100644 --- a/core/utils/controller/manager/openrc.go +++ b/core/utils/controller/manager/openrc.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/1Panel-dev/1Panel/core/utils/cmd" "github.com/1Panel-dev/1Panel/core/utils/ssh" @@ -22,18 +23,11 @@ func (s *Openrc) Name() string { return "openrc" } func (s *Openrc) IsActive(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) - if err != nil { - return false, err - } - return out == "active\n", nil + _, err := cmd.NewCommandMgr().RunWithStdout("service", serviceName, "status") + return err == nil, nil } func (s *Openrc) IsEnable(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) - if err != nil { - return false, err - } - return out == "enabled\n", nil + return isSysvServiceEnabled(serviceName) } func (s *Openrc) IsExist(serviceName string) (bool, error) { if _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)); err != nil { @@ -62,3 +56,27 @@ func (s *Openrc) Operate(operate, serviceName string) error { func (s *Openrc) Reload() error { return nil } + +func isSysvServiceEnabled(serviceName string) (bool, error) { + entries, err := os.ReadDir("/etc") + if err != nil { + return false, err + } + for _, entry := range entries { + name := entry.Name() + if !entry.IsDir() || !strings.HasPrefix(name, "rc") || !strings.HasSuffix(name, ".d") { + continue + } + items, err := os.ReadDir(filepath.Join("/etc", name)) + if err != nil { + continue + } + for _, item := range items { + itemName := item.Name() + if strings.HasPrefix(itemName, "S") && strings.HasSuffix(itemName, serviceName) { + return true, nil + } + } + } + return false, nil +} diff --git a/core/utils/controller/manager/sysvinit.go b/core/utils/controller/manager/sysvinit.go index 74274a5ef..8486751fa 100644 --- a/core/utils/controller/manager/sysvinit.go +++ b/core/utils/controller/manager/sysvinit.go @@ -22,18 +22,11 @@ func (s *Sysvinit) Name() string { return "sysvinit" } func (s *Sysvinit) IsActive(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) - if err != nil { - return false, err - } - return out == "active\n", nil + _, err := cmd.NewCommandMgr().RunWithStdout("service", serviceName, "status") + return err == nil, nil } func (s *Sysvinit) IsEnable(serviceName string) (bool, error) { - out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) - if err != nil { - return false, err - } - return out == "enabled\n", nil + return isSysvServiceEnabled(serviceName) } func (s *Sysvinit) IsExist(serviceName string) (bool, error) { if _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)); err != nil { diff --git a/core/utils/ctl_conf/ctl_conf.go b/core/utils/ctl_conf/ctl_conf.go new file mode 100644 index 000000000..6b5848b6a --- /dev/null +++ b/core/utils/ctl_conf/ctl_conf.go @@ -0,0 +1,88 @@ +package ctl_conf + +import ( + "fmt" + "os" + "strings" +) + +const defaultFile = "/usr/local/bin/1pctl" + +func Load(key string) string { + info, err := LoadFromFile(defaultFile, key) + if err != nil { + panic(err) + } + if len(info) == 0 || info == `""` { + panic(fmt.Sprintf("error `%s` find in %s", key, defaultFile)) + } + return info +} + +func LoadWithoutPanic(key string) string { + info, err := LoadFromFile(defaultFile, key) + if err != nil { + return "" + } + return info +} + +func LoadFromFile(filePath, key string) (string, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + prefix := key + "=" + for _, line := range strings.Split(string(data), "\n") { + if strings.HasPrefix(line, prefix) { + return strings.TrimSpace(strings.TrimPrefix(line, prefix)), nil + } + } + return "", fmt.Errorf("error `%s` find in %s", key, filePath) +} + +func UpdateInFile(filePath, key, value string) error { + return rewriteFile(filePath, func(line string) (string, bool) { + if strings.HasPrefix(line, key+"=") { + return key + "=" + value, true + } + return line, true + }) +} + +func RemoveValueFromFile(filePath, key, value string) error { + target := key + "=" + value + return rewriteFile(filePath, func(line string) (string, bool) { + return line, line != target + }) +} + +func rewriteFile(filePath string, rewrite func(string) (string, bool)) error { + info, err := os.Stat(filePath) + if err != nil { + return err + } + data, err := os.ReadFile(filePath) + if err != nil { + return err + } + content := rewriteLines(string(data), rewrite) + return os.WriteFile(filePath, []byte(content), info.Mode().Perm()) +} + +func rewriteLines(data string, rewrite func(string) (string, bool)) string { + hasTrailingNewline := strings.HasSuffix(data, "\n") + lines := strings.Split(strings.TrimSuffix(data, "\n"), "\n") + rewritten := make([]string, 0, len(lines)) + for _, line := range lines { + newLine, keep := rewrite(line) + if keep { + rewritten = append(rewritten, newLine) + } + } + content := strings.Join(rewritten, "\n") + if hasTrailingNewline { + content += "\n" + } + return content +} diff --git a/core/utils/files/files.go b/core/utils/files/files.go index 4f9363f89..01147ac74 100644 --- a/core/utils/files/files.go +++ b/core/utils/files/files.go @@ -61,13 +61,15 @@ func CopyItem(isDir, withName bool, src, dst string) error { _ = os.MkdirAll(dst, srcInfo.Mode()) } } - cmdStr := fmt.Sprintf(`cp -rf %s %s`, src, dst+"/") + cmdArgs := []string{"-rf", "--", src, dst + "/"} + cmdText := fmt.Sprintf("cp -rf -- %s %s", src, dst+"/") if !isDir { - cmdStr = fmt.Sprintf(`cp -f %s %s`, src, dst+"/") + cmdArgs = []string{"-f", "--", src, dst + "/"} + cmdText = fmt.Sprintf("cp -f -- %s %s", src, dst+"/") } - stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(60 * time.Second)).RunWithStdoutBashC(cmdStr) + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(60*time.Second)).RunWithStdout("cp", cmdArgs...) if err != nil { - return fmt.Errorf("handle %s failed, stdout: %s, err: %v", cmdStr, stdout, err) + return fmt.Errorf("handle %s failed, stdout: %s, err: %v", cmdText, stdout, err) } return nil } @@ -82,10 +84,11 @@ func CopyFileWithRename(src, dst string) error { _ = os.MkdirAll(path.Dir(dst), srcInfo.Mode()) } } - if err := cmd.RunDefaultBashCf("cp -f %s %s.tmp", src, dst); err != nil { + cmdMgr := cmd.NewCommandMgr() + if err := cmdMgr.Run("cp", "-f", "--", src, dst+".tmp"); err != nil { return fmt.Errorf("handle cp file failed, err: %v", err) } - if err = cmd.RunDefaultBashCf("mv %s.tmp %s", dst, dst); err != nil { + if err = cmdMgr.Run("mv", "--", dst+".tmp", dst); err != nil { return err } return nil @@ -98,9 +101,33 @@ func HandleTar(sourceDir, targetDir, name, exclusionRules string, secret string) } } + targetFile := path.Join(targetDir, name) + excludeText, excludeArgs := buildTarExcludeArgs(exclusionRules) + tarPathText, tarPathArgs := buildTarPathArgs(sourceDir) + if len(secret) != 0 { + logTarEncryptCommand(targetFile, excludeText, tarPathText, secret) + stdout, err := runTarEncrypt(targetFile, excludeArgs, tarPathArgs, secret) + if err != nil && len(stdout) != 0 { + global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) + return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) + } + return nil + } + + global.LOG.Debug(fmt.Sprintf("tar -zcf %s %s %s", targetFile, excludeText, tarPathText)) + stdout, err := runTar(targetFile, excludeArgs, tarPathArgs) + if err != nil && len(stdout) != 0 { + global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) + return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) + } + return nil +} + +func buildTarExcludeArgs(exclusionRules string) (string, []string) { exMap := make(map[string]struct{}) excludes := strings.Split(exclusionRules, ",") excludeRules := "" + excludeArgs := []string{} for _, exclude := range excludes { if len(exclude) == 0 { continue @@ -109,39 +136,40 @@ func HandleTar(sourceDir, targetDir, name, exclusionRules string, secret string) continue } excludeRules += fmt.Sprintf(" --exclude '%s'", exclude) + excludeArgs = append(excludeArgs, "--exclude", exclude) exMap[exclude] = struct{}{} } - path := "" + return excludeRules, excludeArgs +} + +func buildTarPathArgs(sourceDir string) (string, []string) { + tarPath := "" + tarPathArgs := []string{} if strings.Contains(sourceDir, "/") { itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "") aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")] if len(aheadDir) == 0 { aheadDir = "/" } - path += fmt.Sprintf("-C %s %s", aheadDir, itemDir) + tarPath += fmt.Sprintf("-C %s %s", aheadDir, itemDir) + tarPathArgs = append(tarPathArgs, "-C", aheadDir, itemDir) } else { - path = sourceDir + tarPath = sourceDir + tarPathArgs = append(tarPathArgs, sourceDir) } + return tarPath, tarPathArgs +} - commands := "" +func logTarEncryptCommand(targetFile, excludeRules, tarPath, secret string) { + extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out" + command := fmt.Sprintf("tar -zcf %s %s %s %s", " -"+excludeRules, tarPath, extraCmd, targetFile) + global.LOG.Debug(strings.ReplaceAll(command, fmt.Sprintf(" '%s' ", secret), " ****** ")) +} - if len(secret) != 0 { - extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out" - commands = fmt.Sprintf("tar -zcf %s %s %s %s", " -"+excludeRules, path, extraCmd, targetDir+"/"+name) - global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) - } else { - commands = fmt.Sprintf("tar -zcf %s %s %s", targetDir+"/"+name, excludeRules, path) - global.LOG.Debug(commands) - } - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(24*time.Hour), cmd.WithIgnoreExist1()) - stdout, err := cmdMgr.RunWithStdoutBashC(commands) - if err != nil { - if len(stdout) != 0 { - global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) - return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) - } - } - return nil +func runTar(targetFile string, excludeArgs, tarPathArgs []string) (string, error) { + tarArgs := append([]string{"-zcf", targetFile}, excludeArgs...) + tarArgs = append(tarArgs, tarPathArgs...) + return cmd.NewCommandMgr(cmd.WithTimeout(24*time.Hour), cmd.WithIgnoreExist1()).RunWithStdout("tar", tarArgs...) } func HandleUnTar(sourceFile, targetDir string, secret string) error { @@ -150,18 +178,18 @@ func HandleUnTar(sourceFile, targetDir string, secret string) error { return err } } - commands := "" if len(secret) != 0 { - extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + sourceFile + " | " - commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, targetDir+" > /dev/null 2>&1") - global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) - } else { - commands = fmt.Sprintf("tar zxvf '%s' -C '%s'", sourceFile, targetDir) - global.LOG.Debug(commands) + logTarDecryptCommand(sourceFile, targetDir, secret) + stdout, err := runTarDecrypt(sourceFile, targetDir, secret) + if err != nil { + global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err) + return errors.New(stdout) + } + return nil } - cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(24 * time.Hour)) - stdout, err := cmdMgr.RunWithStdoutBashC(commands) + global.LOG.Debug(fmt.Sprintf("tar zxvf '%s' -C '%s'", sourceFile, targetDir)) + stdout, err := runUnTar(sourceFile, targetDir) if err != nil { global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err) return errors.New(stdout) @@ -169,6 +197,32 @@ func HandleUnTar(sourceFile, targetDir string, secret string) error { return nil } +func logTarDecryptCommand(sourceFile, targetDir, secret string) { + extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + sourceFile + " | " + command := fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, targetDir+" > /dev/null 2>&1") + global.LOG.Debug(strings.ReplaceAll(command, fmt.Sprintf(" '%s' ", secret), " ****** ")) +} + +func runUnTar(sourceFile, targetDir string) (string, error) { + return cmd.NewCommandMgr(cmd.WithTimeout(24*time.Hour)).RunWithStdout("tar", "zxf", sourceFile, "-C", targetDir) +} + +func runTarEncrypt(targetFile string, excludeArgs, tarPathArgs []string, secret string) (string, error) { + tarArgs := append([]string{"-zcf", "-"}, excludeArgs...) + tarArgs = append(tarArgs, tarPathArgs...) + return cmd.NewCommandMgr(cmd.WithTimeout(24*time.Hour)).RunPipe( + cmd.PipeCommand{Name: "tar", Args: tarArgs}, + cmd.PipeCommand{Name: "openssl", Args: []string{"enc", "-aes-256-cbc", "-salt", "-pass", "env:BACKUP_SECRET", "-out", targetFile}, Env: []string{"BACKUP_SECRET=" + secret}}, + ) +} + +func runTarDecrypt(sourceFile, targetDir, secret string) (string, error) { + return cmd.NewCommandMgr(cmd.WithTimeout(24*time.Hour)).RunPipe( + cmd.PipeCommand{Name: "openssl", Args: []string{"enc", "-d", "-aes-256-cbc", "-pass", "env:BACKUP_SECRET", "-in", sourceFile}, Env: []string{"BACKUP_SECRET=" + secret}}, + cmd.PipeCommand{Name: "tar", Args: []string{"-zxf", "-", "-C", targetDir}}, + ) +} + func DownloadFile(url, dst string) error { resp, err := req_helper.HandleGet(url) if err != nil { diff --git a/core/utils/firewall/firewall.go b/core/utils/firewall/firewall.go index d87cb3810..364457a6c 100644 --- a/core/utils/firewall/firewall.go +++ b/core/utils/firewall/firewall.go @@ -2,6 +2,7 @@ package firewall import ( "fmt" + "strings" "github.com/1Panel-dev/1Panel/core/utils/cmd" ) @@ -9,7 +10,7 @@ import ( func UpdatePort(oldPort, newPort string) error { firewalld := cmd.Which("firewalld") if firewalld { - status, _ := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en firewall-cmd --state") + status, _ := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout("firewall-cmd", "--state") isRunning := status == "running\n" if isRunning { return firewallUpdatePort(oldPort, newPort) @@ -20,8 +21,8 @@ func UpdatePort(oldPort, newPort string) error { if !ufw { return nil } - status, _ := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en ufw status | grep Status") - isRuning := status == "Status: active\n" + status, _ := cmd.NewCommandMgr(cmd.WithEnv("LANGUAGE=en_US:en")).RunWithStdout("ufw", "status") + isRuning := strings.Contains(status, "Status: active") if isRuning { return ufwUpdatePort(oldPort, newPort) } @@ -29,22 +30,22 @@ func UpdatePort(oldPort, newPort string) error { } func firewallUpdatePort(oldPort, newPort string) error { - stdout, err := cmd.RunDefaultWithStdoutBashCf("firewall-cmd --zone=public --add-port=%s/tcp --permanent", newPort) + stdout, err := cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--add-port="+newPort+"/tcp", "--permanent") if err != nil { return fmt.Errorf("add (port: %s/tcp) failed, err: %s", newPort, stdout) } - _, _ = cmd.RunDefaultWithStdoutBashCf("firewall-cmd --zone=public --remove-port=%s/tcp --permanent", oldPort) - _, _ = cmd.RunDefaultWithStdoutBashC("firewall-cmd --reload") + _, _ = cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--zone=public", "--remove-port="+oldPort+"/tcp", "--permanent") + _, _ = cmd.NewCommandMgr().RunWithStdout("firewall-cmd", "--reload") return nil } func ufwUpdatePort(oldPort, newPort string) error { - stdout, err := cmd.RunDefaultWithStdoutBashCf("ufw allow %s", newPort) + stdout, err := cmd.NewCommandMgr().RunWithStdout("ufw", "allow", newPort) if err != nil { return fmt.Errorf("add (port: %s/tcp) failed, err: %s", newPort, stdout) } - _, _ = cmd.RunDefaultWithStdoutBashCf("ufw delete allow %s", oldPort) + _, _ = cmd.NewCommandMgr().RunWithStdout("ufw", "delete", "allow", oldPort) return nil }