diff --git a/.cursor/rules/agent-wdd-rules.mdc b/.cursor/rules/agent-wdd-rules.mdc index 51b66cc..ec444f8 100644 --- a/.cursor/rules/agent-wdd-rules.mdc +++ b/.cursor/rules/agent-wdd-rules.mdc @@ -1,12 +1,16 @@ --- +description: +globs: +--- +--- description: 构建agent-wdd的特定上下文的规则 -globs: *,go +globs: *.go --- # 你是一个精通golang的编程大师,熟练掌握github.com/spf13/cobra框架,能够构建出非常现代的cli工具 # 整个项目的架构结构如下 -1. base 服务器基础操作 相关的功能存放于@Base.go中 +1. base 服务器基础操作 相关的功能存放于 [Base.go](mdc:agent-wdd/cmd/Base.go) 1. docker docker相关的操作 1. online 使用网络安装特定版本的docker 2. remove 卸载docker @@ -55,6 +59,6 @@ globs: *,go 4. disk disk相关的信息 [Disk.go](mdc:agent-wdd/config/Disk.go) 5. network 网络相关的内容 [Network.go](mdc:agent-wdd/config/Network.go) 6. all 主机全部的信息 -8. version 打印octopus-agent的构建版本信息 +8. version 打印octopus-agent的构建版本信息 9. config octopus-wdd使用的配置文件 文件 1. show diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..9eb725e --- /dev/null +++ b/.cursorignore @@ -0,0 +1,9 @@ +# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv) + +./agent-deploy/ +./message_pusher/ +./port_forwarding/ +./server/ +./server-go/ +./socks_txthinking/ +./source/ diff --git a/.run/ServerApplication.run.xml b/.run/ServerApplication.run.xml index 37c98b0..63468a9 100644 --- a/.run/ServerApplication.run.xml +++ b/.run/ServerApplication.run.xml @@ -1,12 +1,20 @@ - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/agent-wdd/cmd/Base.go b/agent-wdd/cmd/Base.go index 19427df..40d710c 100644 --- a/agent-wdd/cmd/Base.go +++ b/agent-wdd/cmd/Base.go @@ -1,11 +1,13 @@ package cmd import ( + "agent-wdd/cmd/beans" "agent-wdd/config" "agent-wdd/log" "agent-wdd/op" "agent-wdd/utils" "fmt" + "runtime" "github.com/spf13/cobra" ) @@ -18,9 +20,18 @@ var ( "deltarpm", "net-tools", "iputils", "bind-utils", "lsof", "curl", "wget", "vim", "mtr", "htop", } - dockerLocalInstallPath = "/root/wdd/docker-20.10.15.tgz" // 本地安装docker的文件路径 + dockerLocalInstallPath = "/root/wdd/docker-amd64-20.10.15.tgz" // 本地安装docker的文件路径 ) +func init() { + switch runtime.GOARCH { + case "amd64": + dockerLocalInstallPath = "/root/wdd/docker-amd64-20.10.15.tgz" // 本地安装docker的文件路径 + case "arm64": + dockerLocalInstallPath = "/root/wdd/docker-arm64-20.10.15.tgz" // 本地安装docker的文件路径 + } +} + // 添加base子命令 func addBaseSubcommands(cmd *cobra.Command) { // 1.1 docker @@ -88,7 +99,7 @@ func addDockerSubcommands(cmd *cobra.Command) { } // 没有传递参数,使用默认参数 - version := "26.0.7" + version := "20.10.15" log.Info("Installing Docker version: %s", version) // 安装docker @@ -113,6 +124,9 @@ func addDockerSubcommands(cmd *cobra.Command) { Short: "本地安装Docker", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { + + log.Info("Installing Docker from local file: %s", dockerLocalInstallPath) + exist := utils.FileExistAndNotNull(dockerLocalInstallPath) if !exist { log.Error("Docker local install file not found: %s", dockerLocalInstallPath) @@ -120,7 +134,72 @@ func addDockerSubcommands(cmd *cobra.Command) { } // 解压文件 - utils.Unzip(dockerLocalInstallPath, "/root/wdd") + utils.UnzipFile(dockerLocalInstallPath, "/root/wdd") + + // 安装docker + err := utils.MoveFolerToAnother("/root/wdd/docker", "/usr/bin") + if err != nil { + log.Error("Failed to move Docker binaries: %s", err.Error()) + return + } + + // 设置权限 + chmodCmd := []string{"chmod", "777", "-R", "/usr/bin/docker*"} + ok, resultLog := op.SingleLineCommandExecutor(chmodCmd) + if !ok { + log.Error("Failed to set permissions for Docker binaries: %s", resultLog) + return + } + + // 配置并启动Docker服务 + // systemd daemonize docker + utils.AppendOverwriteContentToFile(beans.ContainerdDaemonService, beans.ContainerdServiceFile) + if !utils.FileExistAndNotNull(beans.ContainerdServiceFile) { + log.Error("docker deamon file not exists !") + } + utils.AppendOverwriteContentToFile(beans.DockerSocketDaemonService, beans.DockerSocketFile) + if !utils.FileExistAndNotNull(beans.DockerSocketFile) { + log.Error("docker deamon file not exists !") + } + utils.AppendOverwriteContentToFile(beans.DockerDaemonService, beans.DockerServiceFile) + if !utils.FileExistAndNotNull(beans.DockerServiceFile) { + log.Error("docker deamon file not exists !") + } + log.Info("docker dameon file append success !") + + ok, resultLog = op.SystemdDaemonReload() + if !ok { + log.Error("daemon reload error ! %s", resultLog) + return + } + + op.SingleLineCommandExecutor([]string{"systemctl", "unmask", "containerd"}) + op.SingleLineCommandExecutor([]string{"systemctl", "unmask", "docker.socket"}) + op.SingleLineCommandExecutor([]string{"systemctl", "unmask", "docker"}) + + op.SingleLineCommandExecutor([]string{"groupadd", "docker"}) + op.SingleLineCommandExecutor([]string{"usermod", "-aG", "docker", "root"}) + op.SingleLineCommandExecutor([]string{"newgrp", "docker"}) + + systemdUp, resultLog := op.SystemdUp("containerd") + if !systemdUp { + log.Error("[InstallDockerBastion] - start containerd service error ! %s", resultLog) + return + } + + ok, resultLog = op.SystemdUp("docker.socket") + if !ok { + log.Error("[InstallDockerBastion] - start docker.socket error ! %s", resultLog) + return + } + + ok, resultLog = op.SystemdUp("docker") + if !ok { + log.Error("[InstallDockerBastion] - start docker service error ! %s", resultLog) + return + } + + log.Info("Docker installed successfully from local file!") }, } diff --git a/agent-wdd/cmd/beans/DockerDaemonConfig.go b/agent-wdd/cmd/beans/DockerDaemonConfig.go new file mode 100644 index 0000000..a1ac546 --- /dev/null +++ b/agent-wdd/cmd/beans/DockerDaemonConfig.go @@ -0,0 +1,121 @@ +package beans + +var ContainerdServiceFile = "/lib/systemd/system/containerd.service" +var DockerSocketFile = "/lib/systemd/system/docker.socket" +var DockerServiceFile = "/lib/systemd/system/docker.service" + +var ContainerdDaemonService = ` +# Copyright The containerd Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +[Unit] +Description=containerd container runtime +Documentation=https://containerd.io +After=network.target local-fs.target + +[Service] +ExecStartPre=-/sbin/modprobe overlay +ExecStart=/usr/bin/containerd + +Type=notify +Delegate=yes +KillMode=process +Restart=always +RestartSec=5 +# Having non-zero Limit*s causes performance problems due to accounting overhead +# in the kernel. We recommend using cgroups to do container-local accounting. +LimitNPROC=infinity +LimitCORE=infinity +LimitNOFILE=infinity +# Comment TasksMax if your systemd version does not supports it. +# Only systemd 226 and above support this version. +TasksMax=infinity +OOMScoreAdjust=-999 + +[installPrefix] +WantedBy=multi-user.target +` + +var DockerSocketDaemonService = ` +[Unit] +Description=Docker Socket for the API + +[Socket] +ListenStream=/var/run/docker.sock +SocketMode=0660 +SocketUser=root +SocketGroup=docker + +[installPrefix] +WantedBy=sockets.target +` + +var DockerDaemonService = ` +[Unit] +Description=Docker Application Container Engine +Documentation=https://docs.docker.com +After=network-online.target docker.socket firewalld.service containerd.service +Wants=network-online.target +Requires=docker.socket containerd.service + +[Service] +Type=notify +# the default is not to use systemd for cgroups because the delegate issues still +# exists and systemd currently does not support the cgroup feature set required +# for containers run by docker +ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock +ExecReload=/bin/kill -s HUP $MAINPID +TimeoutSec=0 +RestartSec=2 +Restart=always + +# Note that StartLimit* options were moved from "Service" to "Unit" in systemd 229. +# Both the old, and new location are accepted by systemd 229 and up, so using the old location +# to make them work for either version of systemd. +StartLimitBurst=3 + +# Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230. +# Both the old, and new name are accepted by systemd 230 and up, so using the old name to make +# this option work for either version of systemd. +StartLimitInterval=60s + +# Having non-zero Limit*s causes performance problems due to accounting overhead +# in the kernel. We recommend using cgroups to do container-local accounting. +LimitNOFILE=infinity +LimitNPROC=infinity +LimitCORE=infinity + +# Comment TasksMax if your systemd version does not support it. +# Only systemd 226 and above support this option. +TasksMax=infinity + +# set delegate yes so that systemd does not reset the cgroups of docker containers +Delegate=yes + +# kill only the docker process, not all processes in the cgroup +KillMode=process +OOMScoreAdjust=-500 + +[installPrefix] +WantedBy=multi-user.target +` + +var DockerDeamonConfig = ` +{ + "insecure-registries": [ + "DockerRegisterDomain:8033", + "harbor.wdd.io:8033" + ] +} +` diff --git a/agent-wdd/go.mod b/agent-wdd/go.mod index 54fe5bf..8a38581 100644 --- a/agent-wdd/go.mod +++ b/agent-wdd/go.mod @@ -5,6 +5,7 @@ go 1.23.5 require ( github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -27,5 +28,5 @@ require ( golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + ) diff --git a/agent-wdd/op/Excutor.go b/agent-wdd/op/Excutor.go index 0826df8..f37787d 100644 --- a/agent-wdd/op/Excutor.go +++ b/agent-wdd/op/Excutor.go @@ -9,6 +9,65 @@ import ( "strings" ) +// SingleLineCommandExecutor 执行单行shell命令,返回执行结果和输出内容 +// singleLineCommand: 命令及参数,如 []string{"ls", "-l"} +// 返回值: +// +// bool - 命令是否执行成功(true为成功,false为失败) +// []string - 合并后的标准输出和标准错误内容(按行分割) +func SingleLineCommandExecutor(singleLineCommand []string) (ok bool, resultLog []string) { + + log.Info("[Excutor] - start => %v", singleLineCommand) + + if len(singleLineCommand) == 0 { + return false, nil + } + + // 创建命令实例 + cmd := exec.Command(singleLineCommand[0], singleLineCommand[1:]...) + + // 创建输出缓冲区 + var stdoutBuf, stderrBuf bytes.Buffer + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + + // 执行命令并获取错误信息 + err := cmd.Run() + + // 合并输出结果 + output := mergeOutput(&stdoutBuf, &stderrBuf) + + // 判断执行结果 + return err == nil, output +} + +// 若追求极致性能,可优化为流式合并 +func mergeOutput(stdout, stderr *bytes.Buffer) []string { + combined := make([]string, 0, bytes.Count(stdout.Bytes(), []byte{'\n'})+bytes.Count(stderr.Bytes(), []byte{'\n'})) + combined = append(combined, strings.Split(stdout.String(), "\n")...) + combined = append(combined, strings.Split(stderr.String(), "\n")...) + return combined +} + +// mergeOutput 合并标准输出和标准错误,按行分割 +//func mergeOutput(stdoutBuf, stderrBuf *bytes.Buffer) []string { +// var output []string +// +// // 处理标准输出 +// scanner := bufio.NewScanner(stdoutBuf) +// for scanner.Scan() { +// output = append(output, scanner.Text()) +// } +// +// // 处理标准错误 +// scanner = bufio.NewScanner(stderrBuf) +// for scanner.Scan() { +// output = append(output, scanner.Text()) +// } +// +// return output +//} + // PipeLineCommandExecutor 执行管道命令,返回执行结果和合并后的输出内容 // pipeLineCommand: 管道命令组,如 [][]string{{"ps", "aux"}, {"grep", "nginx"}, {"wc", "-l"}} // 返回值: @@ -102,65 +161,6 @@ func PipeLineCommandExecutor(pipeLineCommand [][]string) (ok bool, resultLog []s return success, output } -// SingleLineCommandExecutor 执行单行shell命令,返回执行结果和输出内容 -// singleLineCommand: 命令及参数,如 []string{"ls", "-l"} -// 返回值: -// -// bool - 命令是否执行成功(true为成功,false为失败) -// []string - 合并后的标准输出和标准错误内容(按行分割) -func SingleLineCommandExecutor(singleLineCommand []string) (ok bool, resultLog []string) { - - log.Info("[Excutor] - start => %v", singleLineCommand) - - if len(singleLineCommand) == 0 { - return false, nil - } - - // 创建命令实例 - cmd := exec.Command(singleLineCommand[0], singleLineCommand[1:]...) - - // 创建输出缓冲区 - var stdoutBuf, stderrBuf bytes.Buffer - cmd.Stdout = &stdoutBuf - cmd.Stderr = &stderrBuf - - // 执行命令并获取错误信息 - err := cmd.Run() - - // 合并输出结果 - output := mergeOutput(&stdoutBuf, &stderrBuf) - - // 判断执行结果 - return err == nil, output -} - -// mergeOutput 合并标准输出和标准错误,按行分割 -//func mergeOutput(stdoutBuf, stderrBuf *bytes.Buffer) []string { -// var output []string -// -// // 处理标准输出 -// scanner := bufio.NewScanner(stdoutBuf) -// for scanner.Scan() { -// output = append(output, scanner.Text()) -// } -// -// // 处理标准错误 -// scanner = bufio.NewScanner(stderrBuf) -// for scanner.Scan() { -// output = append(output, scanner.Text()) -// } -// -// return output -//} - -// 若追求极致性能,可优化为流式合并 -func mergeOutput(stdout, stderr *bytes.Buffer) []string { - combined := make([]string, 0, bytes.Count(stdout.Bytes(), []byte{'\n'})+bytes.Count(stderr.Bytes(), []byte{'\n'})) - combined = append(combined, strings.Split(stdout.String(), "\n")...) - combined = append(combined, strings.Split(stderr.String(), "\n")...) - return combined -} - func main() { // 成功案例 success, output := SingleLineCommandExecutor([]string{"ls", "-l"}) diff --git a/agent-wdd/op/Operator.go b/agent-wdd/op/PackageOperator.go similarity index 91% rename from agent-wdd/op/Operator.go rename to agent-wdd/op/PackageOperator.go index 7316a85..74b04da 100644 --- a/agent-wdd/op/Operator.go +++ b/agent-wdd/op/PackageOperator.go @@ -8,9 +8,9 @@ import ( ) type PackageOperator struct { - installPrefix []string `json:"install" yaml:"install"` // 安装前缀 - removePrefix []string `json:"remove" yaml:"remove"` // 移除前缀 - upgradePrefix []string `json:"upgrade" yaml:"upgrade"` // 升级前缀 + installPrefix []string // 安装前缀 + removePrefix []string // 移除前缀 + upgradePrefix []string // 升级前缀 initCommand []string } diff --git a/agent-wdd/op/SystemdExcutor.go b/agent-wdd/op/SystemdExcutor.go new file mode 100644 index 0000000..c9535c1 --- /dev/null +++ b/agent-wdd/op/SystemdExcutor.go @@ -0,0 +1,81 @@ +package op + +// SystemdUp 启动服务 +func SystemdUp(serviceName string) (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "enable", serviceName}) + if !ok { + return false, resultLog + } + ok, resultLog = SingleLineCommandExecutor([]string{"systemctl", "start", serviceName}) + if !ok { + return false, resultLog + } + return true, resultLog +} + +// SystemdDown 停止服务 +func SystemdDown(serviceName string) (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "disable", serviceName}) + if !ok { + return false, resultLog + } + ok, resultLog = SingleLineCommandExecutor([]string{"systemctl", "stop", serviceName}) + if !ok { + return false, resultLog + } + return true, resultLog +} + +// SystemdStatus 查看服务状态 +func SystemdStatus(serviceName string) (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "status", serviceName}) + if !ok { + return false, resultLog + } + return true, resultLog +} + +// SystemdRestart 重启服务 +func SystemdRestart(serviceName string) (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "restart", serviceName}) + if !ok { + return false, resultLog + } + return true, resultLog +} + +// SystemdReload 重新加载服务 +func SystemdReload(serviceName string) (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "reload", serviceName}) + if !ok { + return false, resultLog + } + return true, resultLog +} + +// SystemdEnable 启用服务 +func SystemdEnable(serviceName string) (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "enable", serviceName}) + if !ok { + return false, resultLog + } + return true, resultLog +} + +// SystemdDisable 禁用服务 +func SystemdDisable(serviceName string) (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "disable", serviceName}) + if !ok { + return false, resultLog + } + return true, resultLog +} + +// SystemdDaemonReload 重新加载服务 +func SystemdDaemonReload() (bool, []string) { + ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "daemon-reload"}) + if !ok { + return false, resultLog + } + return true, resultLog +} diff --git a/agent-wdd/utils/FileUtils.go b/agent-wdd/utils/FileUtils.go index aa6874b..865325a 100644 --- a/agent-wdd/utils/FileUtils.go +++ b/agent-wdd/utils/FileUtils.go @@ -283,7 +283,7 @@ func ReadAllContentFromFile(fileFullPath string) (result []string) { return result } -// MoveFolerToAnother 将源文件夹中除了子文件夹外的所有文件移动到目标文件夹 +// MoveFolerToAnother 将源文件夹的所有文件递归移动到目标文件夹 func MoveFolerToAnother(srcDir, dstDir string) error { // 读取源文件夹中的所有条目 entries, err := os.ReadDir(srcDir) @@ -291,24 +291,36 @@ func MoveFolerToAnother(srcDir, dstDir string) error { return fmt.Errorf("读取源文件夹失败: %w", err) } + // 创建目标文件夹(如果不存在) + if err := os.MkdirAll(dstDir, os.ModePerm); err != nil { + return fmt.Errorf("创建目标文件夹失败: %w", err) + } + // 遍历所有条目 for _, entry := range entries { - // 跳过子文件夹 - if entry.IsDir() { - continue - } - - // 构造源文件路径和目标文件路径 srcPath := filepath.Join(srcDir, entry.Name()) dstPath := filepath.Join(dstDir, entry.Name()) - // 移动文件 - if err := os.Rename(srcPath, dstPath); err != nil { - return fmt.Errorf("移动文件失败: %w", err) + // 如果是文件夹,递归处理 + if entry.IsDir() { + if err := MoveFolerToAnother(srcPath, dstPath); err != nil { + return fmt.Errorf("递归移动文件夹失败: %w", err) + } + } else { + // 移动文件 + if err := os.Rename(srcPath, dstPath); err != nil { + return fmt.Errorf("移动文件失败: %w", err) + } } } + // 删除源文件夹 + if err := os.RemoveAll(srcDir); err != nil { + return fmt.Errorf("删除源文件夹失败: %w", err) + } + return nil + } // GetCurrentUserFolder 获取运行环境当前用户的根目录 diff --git a/agent-wdd/utils/ZipUtils.go b/agent-wdd/utils/ZipUtils.go new file mode 100644 index 0000000..4030ca4 --- /dev/null +++ b/agent-wdd/utils/ZipUtils.go @@ -0,0 +1,150 @@ +package utils + +import ( + "agent-wdd/log" + "archive/tar" + "archive/zip" + "compress/gzip" + "io" + "os" + "path/filepath" +) + +// UnzipFile 解压文件, 支持zip和tar.gz +func UnzipFile(zipFile string, targetFolder string) { + // 检查文件扩展名以确定解压缩方法 + fileExt := filepath.Ext(zipFile) + switch fileExt { + case ".zip": + // 使用标准库中的archive/zip包进行解压缩 + zipReader, err := zip.OpenReader(zipFile) + if err != nil { + log.Error("无法打开ZIP文件: %s, 错误: %s", zipFile, err.Error()) + return + } + defer zipReader.Close() + + // 创建目标文件夹 + if err := os.MkdirAll(targetFolder, 0755); err != nil { + log.Error("无法创建目标文件夹: %s, 错误: %s", targetFolder, err.Error()) + return + } + + // 遍历ZIP文件中的所有文件 + for _, file := range zipReader.File { + zippedFile, err := file.Open() + if err != nil { + log.Error("无法打开ZIP中的文件: %s, 错误: %s", file.Name, err.Error()) + continue + } + defer zippedFile.Close() + + // 构建目标文件路径 + targetFilePath := filepath.Join(targetFolder, file.Name) + + // 如果是目录,则创建目录 + if file.FileInfo().IsDir() { + if err := os.MkdirAll(targetFilePath, file.Mode()); err != nil { + log.Error("无法创建目录: %s, 错误: %s", targetFilePath, err.Error()) + continue + } + } else { + // 如果是文件,则创建文件并写入内容 + if err := os.MkdirAll(filepath.Dir(targetFilePath), 0755); err != nil { + log.Error("无法创建文件的父目录: %s, 错误: %s", filepath.Dir(targetFilePath), err.Error()) + continue + } + targetFile, err := os.OpenFile(targetFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode()) + if err != nil { + log.Error("无法创建目标文件: %s, 错误: %s", targetFilePath, err.Error()) + continue + } + defer targetFile.Close() + + if _, err := io.Copy(targetFile, zippedFile); err != nil { + log.Error("无法写入文件内容: %s, 错误: %s", targetFilePath, err.Error()) + continue + } + } + } + + case ".tar", ".tar.gz", ".tgz": + // 使用标准库中的archive/tar包进行解压缩 + var file *os.File + var err error + if fileExt == ".tar.gz" || fileExt == ".tgz" { + file, err = os.Open(zipFile) + if err != nil { + log.Error("无法打开TAR.GZ文件: %s, 错误: %s", zipFile, err.Error()) + return + } + defer file.Close() + + gzipReader, err := gzip.NewReader(file) + if err != nil { + log.Error("无法创建GZIP读取器: %s, 错误: %s", zipFile, err.Error()) + return + } + defer gzipReader.Close() + + tarReader := tar.NewReader(gzipReader) + if err := extractTar(tarReader, targetFolder); err != nil { + log.Error("解压TAR.GZ文件时出错: %s, 错误: %s", zipFile, err.Error()) + return + } + } else { + file, err = os.Open(zipFile) + if err != nil { + log.Error("无法打开TAR文件: %s, 错误: %s", zipFile, err.Error()) + return + } + defer file.Close() + + tarReader := tar.NewReader(file) + if err := extractTar(tarReader, targetFolder); err != nil { + log.Error("解压TAR文件时出错: %s, 错误: %s", zipFile, err.Error()) + return + } + } + + default: + log.Error("不支持的文件类型: %s", fileExt) + return + } +} + +// 辅助函数,用于解压TAR文件 +func extractTar(tarReader *tar.Reader, targetFolder string) error { + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + if err != nil { + return err + } + + targetFilePath := filepath.Join(targetFolder, header.Name) + + switch header.Typeflag { + case tar.TypeDir: + if err := os.MkdirAll(targetFilePath, 0755); err != nil { + return err + } + case tar.TypeReg: + if err := os.MkdirAll(filepath.Dir(targetFilePath), 0755); err != nil { + return err + } + file, err := os.OpenFile(targetFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(header.Mode)) + if err != nil { + return err + } + defer file.Close() + + if _, err := io.Copy(file, tarReader); err != nil { + return err + } + } + } + return nil +}