初始化项目

This commit is contained in:
zeaslity
2025-03-27 16:09:20 +08:00
parent e09a32d1e8
commit fc2d585489
709 changed files with 516391 additions and 0 deletions

8
agent-wdd/Makefile Normal file
View File

@@ -0,0 +1,8 @@
.PHONY: build-linux
build-linux: ## Install dependencies
@C:\Users\wddsh\go\bin\gox.exe -osarch="linux/amd64" -output "build/agent-wdd_{{.OS}}_{{.Arch}}"

View File

@@ -0,0 +1,42 @@
timestamp: 2025-02-10 15:19:30
agent:
hostname: Shanghai-amd64-01
network:
# 2代表能上外网 1代表能上国内网 0代表无法访问网络
internet: 2
# 服务器公网信息
public:
ipv4: 42.192.52.227
ipv6: xxx
country: CN
city: Shanghai
asn: Oracle
interfaces:
- name: eth0
ipv4: 192.168.233.5
mac: asdasdasd
cpu:
cores: 4
brand: Intel Xeon Gold 5118
mhz: 2400
mem:
size: 4G
type: ddr4
speed: 2400
swap:
on: false
size: 2G
disk:
- path: /
size: 50G
usage: 12G
percent: 12/50
vg: ubuntuvg
lv: datalv

Binary file not shown.

10
agent-wdd/cmd/Acme.go Normal file
View File

@@ -0,0 +1,10 @@
package cmd
import (
"github.com/spf13/cobra"
)
// addAcmeSubcommands acme的相关任务
func addAcmeSubcommands(cmd *cobra.Command) {
}

1087
agent-wdd/cmd/Base.go Normal file

File diff suppressed because it is too large Load Diff

30
agent-wdd/cmd/Config.go Normal file
View File

@@ -0,0 +1,30 @@
package cmd
import (
"agent-wdd/config"
"agent-wdd/log"
"agent-wdd/utils"
"github.com/spf13/cobra"
)
func addConfigSubcommands(cmd *cobra.Command) {
showConfigCmd := &cobra.Command{
Use: "show",
Short: "显示agent运行配置",
Run: func(cmd *cobra.Command, args []string) {
log.Info("显示agent运行配置")
content := utils.ReadAllContentFromFile(config.WddConfigFilePath)
utils.BeautifulPrintListWithTitle(content, "agent运行配置")
log.Info("显示agent运行配置完成")
},
}
cmd.AddCommand(showConfigCmd)
}

122
agent-wdd/cmd/Download.go Normal file
View File

@@ -0,0 +1,122 @@
package cmd
import (
"agent-wdd/log"
"agent-wdd/utils"
"context"
"fmt"
"net"
"net/http"
"net/url"
"time"
"github.com/spf13/cobra"
"golang.org/x/net/proxy"
)
// 示例添加download子命令
func addDownloadSubcommands(cmd *cobra.Command) {
proxyCmd := &cobra.Command{
Use: "proxy [proxyUrl] [url] [dest]",
Short: "使用代理下载 socks5://[username]:[password]@ip:port http://[username]:[password]@ip:port",
Args: cobra.ExactArgs(3),
Run: func(cmd *cobra.Command, args []string) {
proxyURL := args[0]
fileURL := args[1]
destPath := args[2]
log.Info("Downloading using proxy: %s -> from %s to %s\n", proxyURL, fileURL, destPath)
// 创建带代理的HTTP客户端
client, err := createProxyClient(proxyURL)
if err != nil {
log.Error("创建代理客户端失败: %v", err)
}
// 执行下载
downloadOk, resultLog := utils.DownloadFileWithClient(client, fileURL, destPath)
if !downloadOk {
log.Error("下载失败: %v", resultLog)
} else {
log.Info("文件下载完成")
}
},
}
cmd.Run = func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
log.Error("请输入下载地址")
return
} else if len(args) == 1 {
timeString := utils.CurrentTimeString()
args = append(args, "./"+timeString+".txt")
}
log.Info("Downloading without proxy: %s -> %s\n", args[0], args[1])
downloadOk, resultLog := utils.DownloadFile(args[0], args[1])
if !downloadOk {
log.Error("下载失败: %s", resultLog)
} else {
log.Info("下载成功: %s", args[1])
}
}
cmd.AddCommand(proxyCmd)
}
// 创建带代理的HTTP客户端
func createProxyClient(proxyURL string) (*http.Client, error) {
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return nil, fmt.Errorf("无效的代理URL: %w", err)
}
switch parsedURL.Scheme {
case "socks5":
return createSocks5Client(parsedURL)
case "http", "https":
return createHTTPClient(parsedURL), nil
default:
return nil, fmt.Errorf("不支持的代理协议: %s", parsedURL.Scheme)
}
}
// 创建SOCKS5代理客户端
func createSocks5Client(proxyURL *url.URL) (*http.Client, error) {
var auth *proxy.Auth
if proxyURL.User != nil {
password, _ := proxyURL.User.Password()
auth = &proxy.Auth{
User: proxyURL.User.Username(),
Password: password,
}
}
dialer, err := proxy.SOCKS5("tcp", proxyURL.Host, auth, proxy.Direct)
if err != nil {
return nil, fmt.Errorf("创建SOCKS5拨号器失败: %w", err)
}
return &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.Dial(network, addr)
},
},
Timeout: 10 * time.Second,
}, nil
}
// 创建HTTP代理客户端
func createHTTPClient(proxyURL *url.URL) *http.Client {
return &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyURL(proxyURL),
},
Timeout: 30 * time.Minute,
}
}

98
agent-wdd/cmd/Info.go Normal file
View File

@@ -0,0 +1,98 @@
package cmd
import (
"agent-wdd/config"
"github.com/spf13/cobra"
)
func addInfoSubcommands(cmd *cobra.Command) {
// 操作系统
os := &cobra.Command{
Use: "os",
Short: "主机操作系统相关的信息",
Run: func(cmd *cobra.Command, args []string) {
os := config.ConfigCache.Agent.OS
os.Gather()
os.SaveConfig()
},
}
// network
network := &cobra.Command{
Use: "network",
Short: "主机Network相关的信息",
Run: func(cmd *cobra.Command, args []string) {
network := config.ConfigCache.Agent.Network
network.Gather()
network.SaveConfig()
},
}
// cpu
cpu := &cobra.Command{
Use: "cpu",
Short: "主机cpu相关的信息",
Run: func(cmd *cobra.Command, args []string) {
cpu := config.ConfigCache.Agent.CPU
cpu.Gather()
cpu.Save()
},
}
// memory
memory := &cobra.Command{
Use: "mem",
Short: "主机memory相关的信息",
Run: func(cmd *cobra.Command, args []string) {
mem := config.ConfigCache.Agent.Mem
mem.Gather()
swap := config.ConfigCache.Agent.Swap
swap.Gather()
mem.SaveConfig()
swap.SaveConfig()
},
}
// disk
disk := &cobra.Command{
Use: "disk",
Short: "主机disk相关的信息",
Run: func(cmd *cobra.Command, args []string) {
//disks := config.ConfigCache.Agent.Disks
config.DiskListGather()
config.DiskListSaveConfig()
},
}
// all全部的info
all := &cobra.Command{
Use: "all",
Short: "主机全部相关的信息",
Run: func(cmd *cobra.Command, args []string) {
// 执行所有info
cpu.Run(cmd, args)
os.Run(cmd, args)
memory.Run(cmd, args)
network.Run(cmd, args)
disk.Run(cmd, args)
// 归一化
config.ConfigCache.NormalizeConfig()
},
}
cmd.AddCommand(
all,
os,
cpu,
memory,
network,
disk,
)
}

310
agent-wdd/cmd/Proxy.go Normal file
View File

@@ -0,0 +1,310 @@
package cmd
import (
"agent-wdd/cmd/xray"
"agent-wdd/config"
"agent-wdd/log"
"agent-wdd/op"
"agent-wdd/utils"
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"strings"
"text/template"
"github.com/spf13/cobra"
)
// 添加proxy子命令
func addProxySubcommands(cmd *cobra.Command) {
xrayCmd := &cobra.Command{
Use: "xray",
Short: "Xray相关操作",
}
xrayCmd.AddCommand(
&cobra.Command{
Use: "install",
Short: "安装Xray",
Run: func(cmd *cobra.Command, args []string) {
log.Info("Installing Xray...")
installXray()
},
},
&cobra.Command{
Use: "remove",
Short: "移除Xray",
Run: func(cmd *cobra.Command, args []string) {
log.Info("Removing Xray...")
removeXray()
},
},
// 其他xray子命令...
)
vmessCmd := &cobra.Command{
Use: "vmess [port]",
Short: "VMESS代理安装",
Run: func(cmd *cobra.Command, args []string) {
log.Info("Setting up VMESS proxy...")
var port string
if len(args) == 0 {
log.Info("no port provided, using default port 443")
port = "443"
} else {
port = args[0]
}
// 执行info all命令 获得归一化的名字 高耦合方案 不推荐
// for _, command := range cmd.Parent().Parent().Commands() {
// if command.Name() == "info" {
// command.Run(command, []string{"all"})
// }
// }
// 2. 调用 info all 命令
if err := executeInfoAll(cmd); err != nil {
fmt.Printf("Error executing info all: %v\n", err)
}
installVmess(port)
},
}
vlessCmd := &cobra.Command{
Use: "vless [port]",
Short: "VLESS代理安装",
Run: func(cmd *cobra.Command, args []string) {
log.Info("Setting up VLESS proxy...")
// var port string
// if len(args) == 0 {
// log.Info("no port provided, using default port 443")
// port = "443"
// }
},
}
cmd.AddCommand(
xrayCmd,
vmessCmd,
vlessCmd,
// 其他proxy子命令...
)
}
// 移除Xray
func removeXray() {
}
func executeInfoAll(cmd *cobra.Command) any {
// 获取根命令
rootCmd := cmd.Root()
// 创建临时上下文
infoCmd, _, err := rootCmd.Find([]string{"info", "all"})
if err != nil {
return fmt.Errorf("command not found: %w", err)
}
// 克隆命令避免污染原始配置
clonedCmd := cloneCommand(infoCmd)
// 重置命令状态
clonedCmd.SetArgs([]string{})
clonedCmd.SilenceErrors = true
clonedCmd.SilenceUsage = true
// 执行命令
return clonedCmd.Execute()
}
// 深度克隆命令的工具函数
func cloneCommand(cmd *cobra.Command) *cobra.Command {
cloned := &cobra.Command{
Use: cmd.Use,
Run: cmd.Run,
PreRun: cmd.PreRun,
PostRun: cmd.PostRun,
}
cloned.Flags().AddFlagSet(cmd.Flags())
return cloned
}
// VmessConfig 定义模板数据模型
type VmessConfig struct {
PORT string
UUID string
}
type VmessClientConfig struct {
ServerNodeName string
ServerNodeAddress string
VmessConfig
}
// 安装VMESS代理 最简单快速的模式
func installVmess(port string) (bool, []string) {
// 检查是否安装了xray
if !op.CommandExistsByPath("xray") {
log.Error("Xray is not installed, please install xray first")
return false, []string{"Xray is not installed, please install xray first"}
}
// 构建配置
// 创建新模板并解析
tmpl, err := template.New("vmessConfig").Parse(xray.VmessServerTemplate)
if err != nil {
return false, []string{err.Error()}
}
// 准备模板数据
// 执行 xray uuid 拿到返回值
ok, resultLog := op.SingleLineCommandExecutor([]string{"xray", "uuid"})
if !ok {
return false, resultLog
}
if len(resultLog) == 0 {
return false, []string{
"xray uuid generate error ! cant not get the uuid",
}
}
uuid := resultLog[0]
vmessConfig := VmessConfig{
PORT: port,
UUID: uuid,
}
// 执行模板渲染
var result bytes.Buffer
if err := tmpl.Execute(&result, vmessConfig); err != nil {
return false, []string{
err.Error(),
}
}
// 将result写入到 /usr/local/etc/xray/config.json 中
// 直接访问底层字节切片避免拷贝
data := result.Bytes()
// 快速验证JSON格式
if !json.Valid(data) {
return false, []string{fmt.Sprintf("Invalid JSON format: %s", data)}
}
// 使用适当权限创建文件并写入O_WRONLY|O_CREATE|O_TRUNC, 0644
filename := "/usr/local/etc/xray/config.json"
if err := os.WriteFile(filename, data, 0644); err != nil {
return false, []string{err.Error()}
}
fmt.Println("xray config file is written to ", filename)
// 移除encode自动添加的换行重新缩进格式化
var prettyJSON bytes.Buffer
if err := json.Indent(&prettyJSON, bytes.TrimSpace(data), "", " "); err != nil {
fmt.Printf("JSON indent error: %v\n", err)
}
fmt.Println(prettyJSON.String())
fmt.Println()
// 重启Xray
op.SystemdRestart("xray")
ok, resultLog = op.SystemIsRunning("xray")
if !ok {
log.Error("Xray service is not running => %s Maybe the config file is not valid", resultLog)
return false, resultLog
}
result.Reset() // 清空全局buffer以备后续使用
// 输出 client的配置
configCache := config.ConfigCache
// 获取服务器节点名称
serverNodeName := configCache.Agent.OS.Hostname
vmessClientConfig := VmessClientConfig{
ServerNodeName: serverNodeName,
ServerNodeAddress: configCache.Agent.Network.Public.IPv4,
VmessConfig: vmessConfig,
}
// 执行模板渲染
clientTmpl, err := template.New("vmessClientConfig").Parse(xray.VmessClientTemplate)
if err != nil {
return false, []string{err.Error()}
}
if err := clientTmpl.Execute(&result, vmessClientConfig); err != nil {
return false, []string{
err.Error(),
}
}
lines := strings.Split(string(result.Bytes()), "\n")
// for index, line := range lines {
// fmt.Println(index, line)
// }
// 转换为字节切片并进行Base64编码
v2rayNGConfig := "vmess://" + base64.StdEncoding.EncodeToString([]byte(lines[2]))
// 将result 打印的终端
fmt.Println("Clash client config is below: ")
fmt.Println()
fmt.Println(lines[1])
fmt.Println()
fmt.Println("V2rayNG config is below: ")
fmt.Println()
fmt.Println(v2rayNGConfig)
fmt.Println()
return true, []string{"Xray Configuration file written successfully"}
}
// 安装Xray 最新版本 主机可以联网才可以
func installXray() {
installScriptUrl := "https://gitea.107421.xyz/zeaslity/Xray-install/raw/branch/main/install-release.sh"
// 下载安装脚本
ok, err := utils.DownloadFile(installScriptUrl, "/tmp/install-release.sh")
if !ok {
log.Error("Download Xray install script failed ! from %s error is %s", installScriptUrl, err)
return
}
// 添加执行权限
os.Chmod("/tmp/install-release.sh", 0777)
// 执行安装脚本
ok, resultLog := op.SingleLineCommandExecutor([]string{"/bin/bash", "/tmp/install-release.sh", "-u", "root", "install"})
if !ok {
log.Error("Install Xray failed ! error is %s", resultLog)
return
}
op.SystemdUp("xray")
op.SystemdEnable("xray")
// 默认服务已经启动! 检查
ok, resultLog = op.SystemIsRunning("xray")
if !ok {
log.Error("Xray service is not running => %s", resultLog)
return
}
// 安装成功
log.Info("Xray installed successfully")
}

7
agent-wdd/cmd/WddHost.go Normal file
View File

@@ -0,0 +1,7 @@
package cmd
import "github.com/spf13/cobra"
func addWddSubcommands(cmd *cobra.Command) {
}

152
agent-wdd/cmd/Zsh.go Normal file
View File

@@ -0,0 +1,152 @@
package cmd
import (
"agent-wdd/config"
"agent-wdd/log"
"agent-wdd/op"
"agent-wdd/utils"
"github.com/spf13/cobra"
)
func addZshSubcommands(cmd *cobra.Command) {
cmd.Run = func(cmd *cobra.Command, args []string) {
log.Info("安装zsh")
// 初始化安装命令
utils.CreateFolder("/root/wdd")
if config.CanConnectInternet() <= 1 {
log.Error("无法连接互联网无法安装zsh")
return
}
// 判定config
if config.ConfigCache.Agent.Network.Public.IPv4 == "" {
network := config.ConfigCache.Agent.Network
network.Gather()
network.SaveConfig()
}
// 下载并安装zsh和git
ok := op.AgentPackOperator.Install([]string{"git", "zsh"})
if !ok {
log.Error("安装git和zsh失败, 无法继续后面的的安装!")
return
}
// 清理残留
utils.RemoveFolderComplete("/root/.oh-my-zsh")
utils.RemoveFolderComplete("/root/wdd/zsh-install.sh")
ohMyZshInstallUrl := ""
if config.ConfigCache.Agent.Network.Internet == 7 {
ohMyZshInstallUrl = "https://gitea.107421.xyz/zeaslity/ohmyzsh/raw/branch/master/tools/install.sh"
} else {
ohMyZshInstallUrl = "https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh"
}
downloadOk, resultLog := utils.DownloadFile(ohMyZshInstallUrl, "/root/wdd/zsh-install.sh")
if !downloadOk {
log.Error("下载zsh安装脚本失败: %v", resultLog)
return
}
op.SingleLineCommandExecutor([]string{
"chmod",
"+x",
"/root/wdd/zsh-install.sh",
})
installZshCommand := []string{
"sh",
"-c",
"/root/wdd/zsh-install.sh",
}
if config.ConfigCache.Agent.Network.Internet == 7 {
op.SingleLineCommandExecutor([]string{
"export",
"REMOTE=https://gitea.107421.xyz/zeaslity/ohmyzsh.git",
})
}
// 执行 oh-my-zsh 安装命令
op.RealTimeCommandExecutor(installZshCommand)
log.Info("安装zsh完成, 开始安装插件!")
pluginsHardCodeUrl := [][]string{
{
"https://gitee.com/wangl-cc/zsh-autosuggestions.git",
"/root/.oh-my-zsh/plugins/zsh-autosuggestions",
},
{
"https://gitee.com/xiaoqqya/zsh-syntax-highlighting.git",
"/root/.oh-my-zsh/plugins/zsh-syntax-highlighting",
},
}
pluginsHardCodeOutsideUrl := [][]string{
{
"https://github.com/zsh-users/zsh-autosuggestions",
"/root/.oh-my-zsh/plugins/zsh-autosuggestions",
},
{
"https://github.com/zsh-users/zsh-syntax-highlighting.git",
"/root/.oh-my-zsh/plugins/zsh-syntax-highlighting",
},
}
pluginsListUrl := []string{
"https://gitea.107421.xyz/zeaslity/ohmyzsh/src/branch/master/plugins/command-not-found/command-not-found.plugin.zsh",
"https://gitea.107421.xyz/zeaslity/ohmyzsh/src/branch/master/plugins/autojump/autojump.plugin.zsh",
"https://gitea.107421.xyz/zeaslity/ohmyzsh/src/branch/master/plugins/themes/themes.plugin.zsh",
}
// 下载插件
if config.ConfigCache.Agent.Network.Internet == 7 {
// 执行硬编码命令, 安装插件
for _, command := range pluginsHardCodeUrl {
op.SingleLineCommandExecutor([]string{"git", "clone", command[0], command[1]})
}
} else {
// 执行硬编码命令, 安装插件 国外
for _, command := range pluginsHardCodeOutsideUrl {
op.SingleLineCommandExecutor([]string{"git", "clone", command[0], command[1]})
}
}
// 下载插件
for _, url := range pluginsListUrl {
downloadOk, resultLog := utils.DownloadFile(url, "/root/.oh-my-zsh/plugins/")
if !downloadOk {
log.Error("下载插件失败: %v", resultLog)
}
}
// 修改ZSH配置
utils.FindAndReplaceContentInFile("robbyrussell", "agnoster", "/root/.zshrc")
utils.FindAndReplaceContentInFile("# DISABLE_AUTO_UPDATE=\"true\"", "DISABLE_AUTO_UPDATE=\"true\"", "/root/.zshrc")
utils.FindAndReplaceContentInFile("plugins=(git)", "plugins=(git zsh-autosuggestions zsh-syntax-highlighting command-not-found z themes)", "/root/.zshrc")
//
op.SingleLineCommandExecutor([]string{
"source",
"/root/.zshrc",
})
op.SingleLineCommandExecutor([]string{
"chsh",
"-s",
"/bin/zsh",
})
op.SingleLineCommandExecutor([]string{
"zsh",
})
log.Info("Zsh安装完成")
}
}

View File

@@ -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
[Install]
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
[Install]
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
[Install]
WantedBy=multi-user.target
`
var DockerDeamonConfig = `
{
"insecure-registries": [
"DockerRegisterDomain:8033",
"harbor.wdd.io:8033"
]
}
`

View File

@@ -0,0 +1,54 @@
package beans
// HarborYamlConfig 是harbor的配置文件
var HarborYamlConfig = `
hostname: HarborHostName
http:
port: 8033
harbor_admin_password: V2ryStr@ngPss
database:
password: V2ryStr@ngPss
max_idle_conns: 50
max_open_conns: 1000
conn_max_lifetime: 3600
conn_max_idle_time: 3600
data_volume: /var/lib/docker/harbor-data
jobservice:
max_job_workers: 10
job_loggers:
- STD_OUTPUT
- FILE
logger_sweeper_duration: 3
notification:
webhook_job_max_retry: 10
webhook_job_http_client_timeout: 10
log:
level: warning
local:
rotate_count: 50
rotate_size: 200M
location: /var/log/harbor
cache:
enabled: false
expire_hours: 24
_version: 2.9.0
proxy:
http_proxy:
https_proxy:
no_proxy:
components:
- core
- jobservice
- trivy
`

View File

@@ -0,0 +1,203 @@
package beans
var Ed25519PrivateKey = `-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDk8R4KXGgDa5H2r8HrqW1klShoSISV20sLiXZPZPfeLwAAAJCIan+LiGp/
iwAAAAtzc2gtZWQyNTUxOQAAACDk8R4KXGgDa5H2r8HrqW1klShoSISV20sLiXZPZPfeLw
AAAEDhnul+q0TNTgrO9kfmGsFhtn/rGRIrmhFostjem/QlZuTxHgpcaANrkfavweupbWSV
KGhIhJXbSwuJdk9k994vAAAADHdkZEBjbWlpLmNvbQE=
-----END OPENSSH PRIVATE KEY-----
`
var Ed25519PublicKey = `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOTxHgpcaANrkfavweupbWSVKGhIhJXbSwuJdk9k994v wdd@cmii.com
`
var DefaultSshdConfig = `
# OCTOPUS AGENT DEFAULT SSHD CONFIG - WDD
# This is the sshd server system-wide configuration file. See
# sshd_config(5) for more information.
# This sshd was compiled with PATH=/usr/bin:/bin:/usr/sbin:/sbin
# The strategy used for options in the default sshd_config shipped with
# OpenSSH is to specify options with their default value where
# possible, but leave them commented. Uncommented options override the
# default value.
#Include /etc/ssh/sshd_config.d/*.conf
Port 22
Port 22333
AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::
#HostKey /etc/ssh/ssh_host_rsa_key
#HostKey /etc/ssh/ssh_host_ecdsa_key
#HostKey /etc/ssh/ssh_host_ed25519_key
# Ciphers and keying
#RekeyLimit default none
# Logging
#SyslogFacility AUTH
#LogLevel INFO
# Authentication:
#LoginGraceTime 2m
#PermitRootLogin prohibit-password
#StrictModes yes
#MaxAuthTries 6
#MaxSessions 10
#PubkeyAuthentication yes
# Expect .ssh/authorized_keys2 to be disregarded by default in future.
#AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2
#AuthorizedPrincipalsFile none
#AuthorizedKeysCommand none
#AuthorizedKeysCommandUser nobody
# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts
#HostbasedAuthentication no
# Change to yes if you don't trust ~/.ssh/known_hosts for
# HostbasedAuthentication
#IgnoreUserKnownHosts no
# Don't read the user's ~/.rhosts and ~/.shosts files
#IgnoreRhosts yes
# To disable tunneled clear text passwords, change to no here!
#PasswordAuthentication yes
PermitEmptyPasswords no
# Change to yes to enable challenge-response passwords (beware issues with
# some PAM modules and threads)
ChallengeResponseAuthentication no
# Kerberos options
#KerberosAuthentication no
#KerberosOrLocalPasswd yes
#KerberosTicketCleanup yes
#KerberosGetAFSToken no
# GSSAPI options
#GSSAPIAuthentication no
#GSSAPICleanupCredentials yes
#GSSAPIStrictAcceptorCheck yes
#GSSAPIKeyExchange no
# Set this to 'yes' to enable PAM authentication, account processing,
# and session processing. If this is enabled, PAM authentication will
# be allowed through the ChallengeResponseAuthentication and
# PasswordAuthentication. Depending on your PAM configuration,
# PAM authentication via ChallengeResponseAuthentication may bypass
# the setting of "PermitRootLogin without-password".
# If you just want the PAM account and session checks to run without
# PAM authentication, then enable this but set PasswordAuthentication
# and ChallengeResponseAuthentication to 'no'.
UsePAM yes
AllowAgentForwarding yes
AllowTcpForwarding yes
#GatewayPorts no
X11Forwarding yes
#X11DisplayOffset 10
#X11UseLocalhost yes
#PermitTTY yes
PrintMotd no
#PrintLastLog yes
TCPKeepAlive yes
#PermitUserEnvironment no
#Compression delayed
#ClientAliveInterval 0
#ClientAliveCountMax 3
#UseDNS no
#PidFile /var/run/sshd.pid
#MaxStartups 10:30:100
#PermitTunnel no
#ChrootDirectory none
#VersionAddendum none
# no default banner path
#Banner none
# Allow client to pass locale environment variables
AcceptEnv LANG LC_*
# override default of no subsystems
Subsystem sftp /usr/lib/openssh/sftp-server
# Example of overriding settings on a per-user basis
#Match User anoncvs
# X11Forwarding no
# AllowTcpForwarding no
# PermitTTY no
# ForceCommand cvs server
PasswordAuthentication yes
PermitRootLogin yes
StrictModes no
ClientAliveInterval 30
ClientAliveCountMax 60
`
var SysctlConfig = `
# 开启 IPv4 路由转发
net.ipv4.ip_forward = 1
# 禁用 IPv6
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
# 开启 IPv4 转发
net.ipv4.conf.all.forwarding = 1
net.ipv4.conf.default.forwarding = 1
# 开启 IPv4 连接跟踪
net.ipv4.tcp_syncookies = 1
# 开启 IPv4 连接跟踪
net.ipv4.tcp_tw_recycle = 1
# 开启 IPv4 连接跟踪
net.ipv4.tcp_tw_reuse = 1
# 开启 IPv4 连接跟踪
net.ipv4.tcp_fin_timeout = 30
# 开启 IPv4 连接跟踪
net.ipv4.tcp_keepalive_time = 1200
# 开启 IPv4 连接跟踪
net.ipv4.ip_local_port_range = 1024 65535
# 开启 IPv4 连接跟踪
net.ipv4.tcp_max_syn_backlog = 8192
# 开启 IPv4 连接跟踪
net.ipv4.tcp_max_tw_buckets = 5000
# 开启 IPv4 连接跟踪
net.ipv4.tcp_max_orphans = 32768
# 开启 IPv4 连接跟踪
net.ipv4.tcp_synack_retries = 2
# 开启 IPv4 连接跟踪
net.ipv4.tcp_syn_retries = 2
# 开启 IPv4 连接跟踪
net.ipv4.tcp_synflood_protect = 1000
# 开启 IPv4 连接跟踪
net.ipv4.tcp_timestamps = 1
# 开启 IPv4 连接跟踪
net.ipv4.tcp_window_scaling = 1
# 开启 IPv4 连接跟踪
net.ipv4.tcp_rmem = 4096 87380 4194304
`

173
agent-wdd/cmd/root.go Normal file
View File

@@ -0,0 +1,173 @@
package cmd
import (
"agent-wdd/config"
"fmt"
"io"
"os"
"strings"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "wdd",
Short: "wdd应用程序是wdd封装的NB工具",
Long: `使用golang的强大特性加上 WDD 的高超技术打造一个方便、高效、适用于各种LINUX系统的瑞士军刀工具!
尽情享用! 尽情享用! 尽情享用!`,
DisableAutoGenTag: true,
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
DisableNoDescFlag: true,
DisableDescriptions: true,
HiddenDefaultCmd: true,
},
}
func init() {
// 初始化配置
cobra.OnInitialize(config.InitConfig)
}
func Execute() {
// 1. base命令
baseCmd := &cobra.Command{
Use: "base",
Short: "服务器基础操作",
}
addBaseSubcommands(baseCmd)
// 2. zsh命令
zshCmd := &cobra.Command{
Use: "zsh",
Short: "zsh相关的内容",
}
addZshSubcommands(zshCmd)
// 3. proxy命令
proxyCmd := &cobra.Command{
Use: "proxy",
Short: "主机代理相关的内容",
}
addProxySubcommands(proxyCmd)
// 4. acme命令
acmeCmd := &cobra.Command{
Use: "acme",
Short: "acme相关的内容",
}
addAcmeSubcommands(acmeCmd)
// 5. wdd命令
wddCmd := &cobra.Command{
Use: "wdd",
Short: "WDD相关操作",
}
addWddSubcommands(wddCmd)
// 6. security命令
securityCmd := &cobra.Command{
Use: "security",
Short: "安全相关操作",
}
// 7. info命令
infoCmd := &cobra.Command{
Use: "info",
Short: "主机信息",
}
addInfoSubcommands(infoCmd)
// 8. version命令
versionCmd := &cobra.Command{
Use: "version",
Short: "打印版本信息",
Run: func(cmd *cobra.Command, args []string) {
// 实现version逻辑
fmt.Println("版本信息: 1.0.0")
},
}
// 9. config命令
configCmd := &cobra.Command{
Use: "config",
Short: "配置文件管理",
}
addConfigSubcommands(configCmd)
// 10. download命令
downloadCmd := &cobra.Command{
Use: "download",
Short: "文件下载,直接下载 [url] [dest_path]",
}
addDownloadSubcommands(downloadCmd)
helpCmd := &cobra.Command{
Use: "help",
Short: "帮助信息",
Run: func(cmd *cobra.Command, args []string) {
// 在你的命令执行函数中
// cmd.Root().SetHelpFunc(func(c *cobra.Command, s []string) {
// // 直接使用默认帮助模板
// c.Usage()
// })
// 自定义实现 打印全部的命令
printAllCommands(cmd.Root(), os.Stdout)
},
}
// 添加所有根命令
rootCmd.AddCommand(
baseCmd,
zshCmd,
proxyCmd,
acmeCmd,
wddCmd,
securityCmd,
infoCmd,
versionCmd,
configCmd,
downloadCmd,
helpCmd,
)
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
}
}
func printAllCommands(cmd *cobra.Command, writer io.Writer) {
// 打印命令标题
fmt.Fprintf(writer, "All available commands:\n\n")
// 递归打印命令树
var traverse func(*cobra.Command, int)
traverse = func(c *cobra.Command, depth int) {
if c.Hidden || c.Deprecated != "" {
return
}
// 生成命令前缀缩进
indent := strings.Repeat(" ", depth)
// 打印当前命令信息
fmt.Fprintf(writer, "%s%-20s%s\n",
indent,
c.Name(),
strings.TrimSpace(c.Short))
// 递归子命令
for _, subCmd := range c.Commands() {
traverse(subCmd, depth+1)
}
}
// 从根命令开始遍历
for _, c := range cmd.Commands() {
traverse(c, 0)
}
}

View File

@@ -0,0 +1,37 @@
package xray
var VmessServerTemplate = `
{
"log": {
"loglevel": "warning"
},
"inbounds": [
{
"listen": "0.0.0.0",
"port": {{.PORT}},
"protocol": "vmess",
"settings": {
"clients": [
{
"id": "{{.UUID}}"
}
]
},
"streamSettings": {
"network": "tcp"
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
}
]
}
`
var VmessClientTemplate = `
{"type":"vmess","name":"{{.ServerNodeName}}","server":"{{.ServerNodeAddress}}","port":{{.PORT}},"uuid":"{{.UUID}}","alterId":0,"cipher":"auto","network":"tcp"}
{"v":"2","ps":"{{.ServerNodeName}}","add":"{{.ServerNodeAddress}}","port":{{.PORT}},"id":"{{.UUID}}","aid":0,"scy":"auto","net":"tcp"}
`

177
agent-wdd/config/CPU.go Normal file
View File

@@ -0,0 +1,177 @@
package config
import (
"agent-wdd/log"
"bufio"
"bytes"
"io/ioutil"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
)
// Gather 获取CPU相关的信息
func (cpu *CPU) Gather() {
log.Info("Gathering INFO => CPU !")
cpu.getBasicInfo()
cpu.getVirtualizationInfo()
}
func (cpu *CPU) Save() {
log.Info("Saving INFO => CPU !")
ConfigCache.Agent.CPU = *cpu
SaveConfig()
}
func (cpu *CPU) getBasicInfo() {
switch runtime.GOOS {
case "linux":
cpu.getLinuxCPUInfo()
case "windows":
cpu.getWindowsCPUInfo()
case "darwin":
cpu.getDarwinCPUInfo()
default:
// 其他平台处理
}
}
func (cpu *CPU) getLinuxCPUInfo() {
data, err := os.ReadFile("/proc/cpuinfo")
if err != nil {
return
}
contents := string(data)
cpu.Cores = strings.Count(contents, "processor\t:")
// 创建带缓冲的扫描器
scanner := bufio.NewScanner(bytes.NewReader(data))
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "model name") {
cpu.Brand = strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
}
if strings.Contains(line, "cpu MHz") {
cpu.Mhz = strings.TrimSpace(strings.Split(line, ":")[1])
}
}
cpu.Arch = runtime.GOARCH
}
func (cpu *CPU) getWindowsCPUInfo() {
cmd := exec.Command("wmic", "cpu", "get", "Name,NumberOfCores,CurrentClockSpeed", "/VALUE")
output, err := cmd.Output()
if err != nil {
return
}
info := string(output)
lines := strings.Split(info, "\n")
for _, line := range lines {
if strings.HasPrefix(line, "Name=") {
cpu.Brand = strings.TrimSpace(strings.SplitN(line, "=", 2)[1])
}
if strings.HasPrefix(line, "NumberOfCores=") {
cores, _ := strconv.Atoi(strings.SplitN(line, "=", 2)[1])
cpu.Cores = cores
}
if strings.HasPrefix(line, "CurrentClockSpeed=") {
cpu.Mhz = strings.SplitN(line, "=", 2)[1]
}
}
// 架构处理
switch runtime.GOARCH {
case "amd64":
cpu.Arch = "x86_64"
default:
cpu.Arch = runtime.GOARCH
}
}
func (cpu *CPU) getVirtualizationInfo() {
switch runtime.GOOS {
case "linux":
cpu.getLinuxVirtualization()
case "windows":
cpu.getWindowsVirtualization()
}
}
func (cpu *CPU) getLinuxVirtualization() {
// 检查CPU虚拟化支持
data, err := ioutil.ReadFile("/proc/cpuinfo")
if err == nil {
if strings.Contains(string(data), "vmx") || strings.Contains(string(data), "svm") {
cpu.Virt = "supported"
}
}
// 检测是否在虚拟机中
if _, err := os.Stat("/sys/hypervisor/uid"); !os.IsNotExist(err) {
cpu.Virt = "virtualized"
if data, err := ioutil.ReadFile("/sys/hypervisor/type"); err == nil {
cpu.Hypervisor = strings.TrimSpace(string(data))
}
return
}
// 使用systemd-detect-virt
cmd := exec.Command("systemd-detect-virt")
if output, err := cmd.Output(); err == nil {
result := strings.TrimSpace(string(output))
if result != "none" {
cpu.Virt = "virtualized"
cpu.Hypervisor = result
}
}
}
func (cpu *CPU) getWindowsVirtualization() {
cmd := exec.Command("wmic", "computersystem", "get", "model", "/VALUE")
output, err := cmd.Output()
if err != nil {
return
}
model := strings.ToLower(string(output))
switch {
case strings.Contains(model, "virtualbox"):
cpu.Hypervisor = "VirtualBox"
case strings.Contains(model, "vmware"):
cpu.Hypervisor = "VMware"
case strings.Contains(model, "kvm"):
cpu.Hypervisor = "KVM"
case strings.Contains(model, "hyper-v"):
cpu.Hypervisor = "Hyper-V"
case strings.Contains(model, "xen"):
cpu.Hypervisor = "Xen"
}
if cpu.Hypervisor != "" {
cpu.Virt = "virtualized"
}
}
func (cpu *CPU) getDarwinCPUInfo() {
// macOS实现
cmd := exec.Command("sysctl", "-n", "machdep.cpu.brand_string")
if output, err := cmd.Output(); err == nil {
cpu.Brand = strings.TrimSpace(string(output))
}
cmd = exec.Command("sysctl", "-n", "hw.ncpu")
if output, err := cmd.Output(); err == nil {
if cores, err := strconv.Atoi(strings.TrimSpace(string(output))); err == nil {
cpu.Cores = cores
}
}
cpu.Arch = runtime.GOARCH
}

206
agent-wdd/config/Config.go Normal file
View File

@@ -0,0 +1,206 @@
package config
import (
"agent-wdd/log"
"agent-wdd/utils"
"fmt"
"os"
"os/exec"
"runtime"
"strings"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
var WddConfigFilePath = "/usr/local/etc/wdd/agent-wdd-config.yaml"
// ConfigCache 配置缓存
var ConfigCache = &Config{
TimeStamp: "",
ModifiedTimes: 0,
Agent: Agent{
OS: OS{},
Network: Network{},
CPU: CPU{},
Mem: Memory{},
Swap: Swap{},
Disks: []Disk{},
},
}
func init() {
// 根据运行的操作系统不同, 修改 WddConfigFilePath 的位置
if runtime.GOOS == "windows" {
homedir, _ := os.UserHomeDir()
WddConfigFilePath = homedir + "\\agent-wdd\\agent-wdd-config.yaml"
}
}
type Config struct {
TimeStamp string `yaml:"timestamp"`
ModifiedTimes int `yaml:"modifiedTimes"`
Agent Agent `yaml:"agent"`
}
type Agent struct {
OS OS `yaml:"os"`
Network Network `yaml:"network"`
CPU CPU `yaml:"cpu"`
Mem Memory `yaml:"mem"`
Swap Swap `yaml:"swap"`
Disks []Disk `yaml:"disks"`
}
type OS struct {
Hostname string `mapstructure:"hostname" yaml:"hostname" comment:"主机名"`
OsName string `mapstructure:"os_name" yaml:"os_name" comment:"操作系统名称"`
OsFamily string `mapstructure:"os_family" yaml:"os_family" comment:"操作系统家族"`
OsVersion string `mapstructure:"os_version" yaml:"os_version" comment:"操作系统版本"`
OsType string `mapstructure:"os_type" yaml:"os_type" comment:"操作系统类型"`
Kernel string `mapstructure:"kernel" yaml:"kernel" comment:"内核版本"`
Arch string `mapstructure:"arch" yaml:"arch" comment:"架构"`
IsUbuntuType bool `mapstructure:"is_ubuntu_type" yaml:"is_ubuntu_type" comment:"是否是ubuntu类型的操作系统"`
PackInit bool `mapstructure:"pack_init" yaml:"pack_init" comment:"是否初始化ubuntu需要"`
OSReleaseCode string `mapstructure:"os_release_code" yaml:"os_release_code" comment:"主机操作系统的发行版代号, focal之类的"`
}
type Network struct {
Internet int `yaml:"internet" comment:"是否能够上网 外网为9 国内为7 不能上网为1"`
Public PublicInfo `yaml:"public"`
Interfaces []Interface `yaml:"interfaces"`
}
type PublicInfo struct {
IPv4 string `yaml:"ipv4"`
IPv6 string `yaml:"ipv6"`
Country string `yaml:"country"`
City string `yaml:"city"`
ASN string `yaml:"asn"`
Timezone string `yaml:"timezone"`
}
type Interface struct {
Name string `yaml:"name"`
IPv4 string `yaml:"ipv4"`
IPv6 string `yaml:"ipv6"`
MAC string `yaml:"mac"`
MTU int `yaml:"mtu"`
}
type CPU struct {
Cores int `yaml:"cores"`
Brand string `yaml:"brand"`
Mhz string `yaml:"mhz"`
Arch string `yaml:"arch"`
Virt string `yaml:"virt"`
Hypervisor string `yaml:"hypervisor"`
}
type Memory struct {
Size string `yaml:"size"`
Type string `yaml:"type"`
Speed int `yaml:"speed"`
}
type Swap struct {
Open bool `yaml:"open"`
Size string `yaml:"size"`
}
type Disk struct {
Path string `yaml:"path"`
Size string `yaml:"size"`
Usage string `yaml:"usage"`
Percent string `yaml:"percent"`
}
func InitConfig() {
// 检查配置文件是否存在
if !utils.FileOrFolderExists(WddConfigFilePath) {
utils.AppendContentToFile("", WddConfigFilePath)
}
v := viper.New()
v.SetConfigFile(WddConfigFilePath)
v.SetConfigType("yaml")
if err := v.ReadInConfig(); err != nil {
log.Error("读取配置文件失败: %w", err)
panic(err)
}
if err := v.Unmarshal(&ConfigCache); err != nil {
log.Error("解析配置失败: %w", err)
panic(err)
}
}
// 写入配置文件
func SaveConfig() {
// 每次写入新的时间
ConfigCache.TimeStamp = utils.CurrentTimeString()
// 每次增加修改文件的次数计数
ConfigCache.ModifiedTimes += 1
data, err := yaml.Marshal(&ConfigCache)
if err != nil {
log.Error("YAML序列化失败: %w", err)
}
if err := os.WriteFile(WddConfigFilePath, data, 0644); err != nil {
log.Error("写入文件失败: %w", err)
}
}
// 归一化配置-重命名主机名
func (c *Config) NormalizeConfig() {
// 重命名主机名
log.Info("归一化主机配置")
// 重新读取配置
InitConfig()
// 主机名称应该为 City(格式为首字母大写)-amd64-内网IP的最后一段(格式化为2位)-公网IPv4(如果公网IPv4为空则使用内网IPv4; ip的格式为127-0-0-1)
// 获取城市(格式为首字母大写)
city := strings.Title(ConfigCache.Agent.Network.Public.City)
// 获取公网IPv4 ip的格式为127-0-0-1
ipInfo := ConfigCache.Agent.Network.Public.IPv4
if ipInfo == "" {
ipInfo = ConfigCache.Agent.Network.Interfaces[0].IPv4
} else {
// 可以获取到公网IPv4, 修改IP的格式
innerIpv4 := ConfigCache.Agent.Network.Interfaces[0].IPv4
ipSegments := strings.Split(innerIpv4, ".")
prefix := fmt.Sprintf("%02s", ipSegments[len(ipSegments)-1])
ipInfo = prefix + "." + ipInfo
}
ipInfo = strings.ReplaceAll(ipInfo, ".", "-")
// 获取架构
arch := ConfigCache.Agent.CPU.Arch
uniformHostname := city + "-" + arch + "-" + ipInfo
// 重命名主机名
log.Info("重命名主机名: %s", uniformHostname)
cmd := exec.Command("hostnamectl", "set-hostname", uniformHostname)
// 执行命令
cmd.Run()
// 更新配置
ConfigCache.Agent.OS.Hostname = uniformHostname
// 更新配置
SaveConfig()
}

116
agent-wdd/config/Disk.go Normal file
View File

@@ -0,0 +1,116 @@
package config
import (
"agent-wdd/log"
"agent-wdd/utils"
"bufio"
"fmt"
"os"
"path/filepath"
"strings"
"syscall"
)
var CommonDiskPath = []string{
"/",
"/var",
"/var/lib/docker",
"/home",
"/user",
"var/log",
}
func CheckCommonMounts() (map[string]bool, error) {
// 读取/proc/mounts获取所有挂载点
file, err := os.Open("/proc/mounts")
if err != nil {
return nil, err
}
defer file.Close()
mountPoints := make(map[string]bool)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
mountPoint := filepath.Clean(fields[1])
mountPoints[mountPoint] = true
}
if err := scanner.Err(); err != nil {
return nil, err
}
return mountPoints, err
}
func DiskListGather() {
log.Info("Gathering INFO => DISK !")
var diskList []Disk
// 拿到所有挂载点
mountPoints, _ := CheckCommonMounts()
// 常用目录
for _, diskPath := range CommonDiskPath {
// 转换为绝对路径并标准化
absPath, err := filepath.Abs(diskPath)
if err != nil {
// 处理错误,例如路径无效,这里选择跳过
continue
}
cleanPath := filepath.Clean(absPath)
if mountPoints[cleanPath] {
// 挂载点存在,计算使用情况
disk := Disk{Path: cleanPath}
disk.calculateDiskUsage()
diskList = append(diskList, disk)
}
}
// 赋值回去
ConfigCache.Agent.Disks = diskList
//utils.BeautifulPrint(diskList)
}
func (disk *Disk) calculateDiskUsage() {
var stat syscall.Statfs_t
err := syscall.Statfs(disk.Path, &stat)
if err != nil {
log.Error("disk syscall error: %v", err)
disk.Size = "0B"
disk.Usage = "0B"
disk.Percent = "0.00%"
return
}
// 计算存储空间大小
totalBytes := stat.Blocks * uint64(stat.Bsize)
availBytes := stat.Bavail * uint64(stat.Bsize)
usedBytes := totalBytes - availBytes
// 格式化输出
disk.Size = utils.HumanDiskSize(totalBytes)
disk.Usage = utils.HumanDiskSize(usedBytes)
if totalBytes == 0 {
disk.Percent = "0.00%"
} else {
percent := float64(usedBytes) / float64(totalBytes) * 100
disk.Percent = fmt.Sprintf("%.2f%%", percent)
}
}
func DiskListSaveConfig() {
log.Info("Saving INFO => DISK !")
SaveConfig()
}

140
agent-wdd/config/Memory.go Normal file
View File

@@ -0,0 +1,140 @@
package config
import (
"agent-wdd/log"
"agent-wdd/utils"
"os"
"os/exec"
"strconv"
"strings"
)
func (mem *Memory) Gather() {
// 获取内存总大小
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
mem.Size = "0B"
mem.Type = ""
mem.Speed = 0
return
}
lines := strings.Split(string(data), "\n")
//var totalKB uint64
for _, line := range lines {
if strings.HasPrefix(line, "MemTotal:") {
fields := strings.Fields(line)
if len(fields) >= 3 {
kb, err := strconv.ParseUint(fields[1], 10, 64)
if err != nil {
mem.Size = "0B"
} else {
mem.Size = utils.HumanSize(kb * 1024)
}
break
}
}
}
// 获取Type和Speed
mem.Type, mem.Speed = getMemoryTypeAndSpeed()
}
func getMemoryTypeAndSpeed() (string, int) {
cmd := exec.Command("dmidecode", "-t", "memory")
output, err := cmd.CombinedOutput()
if err != nil {
return "", 0
}
var memType string
var speed int
lines := strings.Split(string(output), "\n")
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "Type:") {
parts := strings.SplitN(trimmed, ":", 2)
if len(parts) == 2 {
memType = strings.TrimSpace(parts[1])
// 可能dmidecode返回的类型中有更多细节例如"DDR4"或其他
// 例如,如果类型是"Unknown",可能需要处理
if memType == "Unknown" || memType == "Other" {
memType = ""
}
}
} else if strings.HasPrefix(trimmed, "Speed:") {
parts := strings.SplitN(trimmed, ":", 2)
if len(parts) == 2 {
speedStr := strings.TrimSpace(parts[1])
// 可能的格式如 "2667 MHz" 或 "Unknown"
if speedStr == "Unknown" {
continue
}
speedStr = strings.TrimSuffix(speedStr, " MHz")
s, err := strconv.Atoi(speedStr)
if err == nil {
speed = s
}
}
}
}
return memType, speed
}
func (mem *Memory) SaveConfig() {
log.Info("Saving INFO => MEM !")
ConfigCache.Agent.Mem = *mem
SaveConfig()
}
func (swap *Swap) Gather() {
log.Info("Gathering INFO => SWAP !")
const swapsFile = "/proc/swaps"
data, err := os.ReadFile(swapsFile)
if err != nil {
swap.Open = false
swap.Size = "0B"
return
}
lines := strings.Split(strings.TrimSpace(string(data)), "\n")
if len(lines) < 2 { // 空文件或只有标题行
swap.Open = false
swap.Size = "0B"
return
}
var totalKB uint64
for _, line := range lines[1:] {
line = strings.TrimSpace(line)
if line == "" {
continue
}
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
sizeKB, err := strconv.ParseUint(fields[2], 10, 64)
if err != nil {
continue
}
totalKB += sizeKB
}
if totalKB == 0 {
swap.Open = false
swap.Size = "0B"
} else {
swap.Open = true
swap.Size = utils.HumanSize(totalKB * 1024)
}
}
func (swap *Swap) SaveConfig() {
log.Info("Saving INFO => SWAP !")
ConfigCache.Agent.Swap = *swap
SaveConfig()
}

273
agent-wdd/config/Network.go Normal file
View File

@@ -0,0 +1,273 @@
package config
import (
"agent-wdd/log"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"regexp"
"time"
)
// 能够联网,就大于这个数字 外网9 国内7 不能联网1 未知0
const InternetBaseLine = 1
// 定义响应数据结构体
type IPInfo struct {
IP string `json:"ip"`
City string `json:"city"`
Region string `json:"region"`
Country string `json:"country"`
Loc string `json:"loc"`
Org string `json:"org"`
Postal string `json:"postal"`
Timezone string `json:"timezone"`
}
// Gather 获取网络相关的信息
func (network *Network) Gather() {
log.Info("Gathering INFO => NETWORK !")
// 能够联网
network.Internet = CanConnectInternet()
// 获取公网的相关信息
network.Public = network.Public.GetPublicInfo()
//获取本机网卡相关的内容
network.Interfaces = GetInterfaces()
}
// GetInterfaces 获取本机网卡相关的内容
func GetInterfaces() []Interface {
interfaces := []Interface{}
// 获取所有网卡信息
netInterfaces, err := net.Interfaces()
// log.Info("all network interfaces: %v", netInterfaces)
if err != nil {
log.Error("获取网卡信息失败: %v", err)
return interfaces
}
for _, netInterface := range netInterfaces {
// 过滤掉没有地址的网卡
addrs, err := netInterface.Addrs()
if err != nil || len(addrs) == 0 {
continue
}
// 检查网卡名称是否有效
if !isValidNICName(netInterface.Name) {
continue
}
// 创建 Interface 对象
iface := Interface{
Name: netInterface.Name,
MAC: netInterface.HardwareAddr.String(),
MTU: netInterface.MTU,
}
// 获取 IPv4 和 IPv6 地址
for _, addr := range addrs {
ipNet, ok := addr.(*net.IPNet)
if !ok {
continue
}
if ipNet.IP.To4() != nil {
iface.IPv4 = ipNet.IP.String()
} else if ipNet.IP.To16() != nil {
iface.IPv6 = ipNet.IP.String()
}
}
interfaces = append(interfaces, iface)
}
return interfaces
}
func (network *Network) SaveConfig() {
ConfigCache.Agent.Network = *network
SaveConfig()
}
// CanConnectInternet 判定主机能够上网 外网为9 国内为7 不能上网为1
func CanConnectInternet() int {
// 读取 config 文件,判定能否上网
internetCode := ConfigCache.Agent.Network.Internet
if internetCode == 0 {
// 没有相关的信息,需要重新判定
internetCode = judgeCanConnectInternet()
// 持久化保存
ConfigCache.Agent.Network.Internet = internetCode
}
return internetCode
}
// judgeCanConnectInternet 请求网址判定主机能否上网 请求 www.google.com 如果请求正常 返回9 如果超时三秒 请求baidu.com如果没有错误返回7 如果错误返回1
func judgeCanConnectInternet() int {
client := http.Client{
Timeout: 3 * time.Second,
}
results := make(chan int, 2)
go func() {
_, err := client.Get("https://www.google.com")
if err == nil {
results <- 9
} else {
results <- 1
}
}()
go func() {
_, err := client.Get("https://www.baidu.com")
if err == nil {
results <- 7
} else {
results <- 1
}
}()
maxResult := 1
for i := 0; i < 2; i++ {
result := <-results
if result > maxResult {
maxResult = result
}
}
return maxResult
}
// GetPublicInfo 获取服务器的公网信息
func (p PublicInfo) GetPublicInfo() PublicInfo {
// 无法联网, 假信息
fakePublicInfo := PublicInfo{
IPv4: "1.1.1.1",
IPv6: "2400::1",
Country: "CN",
City: "Shanghai",
ASN: "Wdd Inc",
Timezone: "Asia/Shanghai",
}
if CanConnectInternet() == InternetBaseLine {
// 持久化保存
ConfigCache.Agent.Network.Public = fakePublicInfo
return fakePublicInfo
}
// 可以联网
// 创建带有超时的HTTP客户端
client := &http.Client{
Timeout: 3 * time.Second,
}
// 创建新的请求对象
req, err := http.NewRequest("GET", "https://ipinfo.io", nil)
if err != nil {
log.Error("创建请求失败: %v", err)
return fakePublicInfo
}
// 设置请求头
req.Header.Add("Authorization", "Bearer 6ecb0db9bd8f19")
req.Header.Add("Accept", "application/json")
// 发送请求
resp, err := client.Do(req)
if err != nil {
log.Error("请求PublicInfo失败: %s", err.Error())
return fakePublicInfo
}
defer resp.Body.Close()
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
log.Error("非200状态码: %d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Error("读取响应失败: %v", err)
}
// 解析JSON数据
var info IPInfo
if err := json.Unmarshal(body, &info); err != nil {
log.Error("JSON解析失败: %v", err)
}
// 打印解析结果
// log.Info("IP信息:\n%+v\n", info)
// 保存信息
p.IPv4 = info.IP
p.ASN = info.Org
p.Country = info.Country
p.City = info.City
p.Timezone = info.Timezone
ConfigCache.Agent.Network.Public = p
return p
}
func isValidNICName(name string) bool {
// 定义传统命名规则正则表达式
// eth0, eth1, wlan0, wlan1 等
traditionalPattern := `^(eth|wlan)[0-9]+$`
// 定义现代命名规则正则表达式
// 支持 eno1, ens3, enp0s3, enx<MAC>, wlp2s0 等格式
modernPattern := `^(en|wl)(o[0-9]+|s[0-9]+|p[0-9]+s[0-9]+|x[0-9a-fA-F]{12})$`
// 编译正则表达式
traditionalRegex := regexp.MustCompile(traditionalPattern)
modernRegex := regexp.MustCompile(modernPattern)
// 检查是否匹配传统命名或现代命名规则
return traditionalRegex.MatchString(name) || modernRegex.MatchString(name)
}
func main() {
// 测试用例
testCases := []string{
"eth0", // 传统以太网命名
"wlan1", // 传统无线网卡命名
"eno1", // 板载网卡
"ens3", // 插槽网卡
"enp0s3", // PCI网卡
"enx0a1b2c3d4e5f", // 基于MAC地址的命名
"wlp2s0", // 无线PCI网卡
"eth", // 无效:缺少数字
"enp0", // 无效:不符合现代规则
"abc123", // 无效:完全不匹配
}
for _, tc := range testCases {
if isValidNICName(tc) {
fmt.Printf("%s: 有效网卡名称\n", tc)
} else {
fmt.Printf("%s: 无效网卡名称\n", tc)
}
}
}

117
agent-wdd/config/OS.go Normal file
View File

@@ -0,0 +1,117 @@
package config
import (
"agent-wdd/log"
"bufio"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
)
func (os *OS) SaveConfig() {
log.Info("Saving INFO => OS !")
ConfigCache.Agent.OS = *os
SaveConfig()
}
func (o *OS) Gather() {
log.Info("Gathering INFO => OS !")
// 获取主机名
if hostname, err := os.Hostname(); err == nil {
o.Hostname = hostname
}
o.OsType = "linux" // 固定为linux
o.IsUbuntuType = true // 默认为ubuntu
// 解析系统信息
file, err := os.Open("/etc/os-release")
if err == nil {
defer file.Close()
scanner := bufio.NewScanner(file)
osInfo := make(map[string]string)
for scanner.Scan() {
line := scanner.Text()
if parts := strings.SplitN(line, "=", 2); len(parts) == 2 {
key := parts[0]
value := strings.Trim(parts[1], `"`)
osInfo[key] = value
}
}
//utils.BeautifulPrint(osInfo)
// 获取操作系统名称
if name, ok := osInfo["PRETTY_NAME"]; ok {
o.OsName = name
}
// 获取系统家族
if id, ok := osInfo["ID"]; ok {
o.OsFamily = id
}
// 判定系统是否是ubuntu类型
if o.OsFamily == "ubuntu" || o.OsFamily == "debian" || o.OsFamily == "linuxmint" || o.OsFamily == "elementary" || o.OsFamily == "pop" || o.OsFamily == "mint" || o.OsFamily == "kali" || o.OsFamily == "deepin" || o.OsFamily == "zorin" {
o.IsUbuntuType = true
} else {
// 设置系统为非ubuntus
o.IsUbuntuType = false
}
// 获取系统版本
if version, ok := osInfo["VERSION_ID"]; ok {
o.OsVersion = version
} else {
// 针对RedHat系特殊处理
if o.OsFamily == "centos" || o.OsFamily == "rhel" {
// 针对RedHat系特殊处理
if data, err := os.ReadFile("/etc/redhat-release"); err == nil {
re := regexp.MustCompile(`\d+(\.\d+)+`)
if match := re.FindString(string(data)); match != "" {
o.OsVersion = match
}
}
}
}
}
// 获取内核版本
if out, err := exec.Command("uname", "-r").Output(); err == nil {
o.Kernel = strings.TrimSpace(string(out))
}
// 获取系统架构
o.Arch = runtime.GOARCH
// 获取系统发行版代号
if o.IsUbuntuType {
o.OSReleaseCode = judgeUbuntuReleaseFromOsVersion(o.OsVersion)
} else {
o.OSReleaseCode = "non-ubuntu"
}
}
func judgeUbuntuReleaseFromOsVersion(osVersion string) string {
switch osVersion {
case "16.04":
return "xenial"
case "18.04":
return "bionic"
case "20.04":
return "focal"
case "22.04":
return "jammy"
default:
return "ubuntu-unknown"
}
}

33
agent-wdd/go.mod Normal file
View File

@@ -0,0 +1,33 @@
module agent-wdd
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 (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.35.0
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)

83
agent-wdd/go.sum Normal file
View File

@@ -0,0 +1,83 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -0,0 +1,71 @@
package log
import (
"fmt"
"runtime"
"strconv"
"strings"
"time"
)
const (
colorReset = "\033[0m"
colorRed = "\033[31m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
)
func main() {
Debug("Debug message: %s", "connection established")
Info("System %s", "started successfully")
Warning("Disk space at %d%%", 85)
Error("Failed to %s", "load config")
}
func Debug(format string, args ...interface{}) {
log("DEBUG", colorBlue, format, args...)
}
func Info(format string, args ...interface{}) {
log("INFO", colorGreen, format, args...)
}
func Warning(format string, args ...interface{}) {
log("WARNING", colorYellow, format, args...)
}
func Error(format string, args ...interface{}) {
log("ERROR", colorRed, format, args...)
}
func log(level string, color string, format string, args ...interface{}) {
// 获取调用者信息跳过2层调用栈
_, file, line, _ := runtime.Caller(2)
s := strings.Split(file, "ProjectOctopus")[1]
callerInfo := strings.TrimLeft(s, "/") + " "
callerInfo += strconv.FormatInt(int64(line), 10)
timestamp := currentTimeString()
message := fmt.Sprintf(format, args...)
fmt.Printf("%s %s%-7s%s [%s] %s\n",
timestamp,
color,
level,
colorReset,
callerInfo,
message)
}
func currentTimeString() string {
// 加载东八区时区
loc, _ := time.LoadLocation("Asia/Shanghai")
// 获取当前时间并转换为东八区时间
now := time.Now().In(loc)
// 格式化为 "2006-01-02 15:04:05" 的布局
formattedTime := now.Format(time.DateTime)
return formattedTime
}

14
agent-wdd/main.go Normal file
View File

@@ -0,0 +1,14 @@
package main
import "agent-wdd/cmd"
// C:\Users\wdd\go\bin\gox.exe -osarch="linux/amd64" -output "build/agent-wdd_{{.OS}}_{{.Arch}}"
// rm -rf agent-wdd_linux_amd64
// chmod +x agent-wdd_linux_amd64 && ./agent-wdd_linux_amd64 version
func main() {
// WDD 启动
cmd.Execute()
}

View File

@@ -0,0 +1,29 @@
try {
$ErrorActionPreference = "Stop"
Write-Host "1. Building binary exec file..." -ForegroundColor Cyan
& "C:\Users\wddsh\go\bin\gox.exe" -osarch="linux/amd64" -output "build/agent-wdd_{{.OS}}_{{.Arch}}"
# 执行远程ssh命令 ssh root@192.168.35.71 "rm /root/agent-wdd_linux_amd64"
Write-Host "2. Cleaning old binary file..." -ForegroundColor Yellow
ssh root@192.168.35.71 "rm -f /root/agent-wdd_linux_amd64 && rm /usr/local/etc/wdd/agent-wdd-config.yaml"
Write-Host "3. Uploading binary exec..." -ForegroundColor Green
scp build/agent-wdd_linux_amd64 root@192.168.35.71:/root/
Write-Host "4. Exec the command ..." -ForegroundColor Blue
Write-Host ""
Write-Host ""
ssh root@192.168.35.71 "chmod +x agent-wdd_linux_amd64 && ./agent-wdd_linux_amd64 help"
Write-Host ""
Write-Host ""
Write-Host "5. Cheak Info Result ..." -ForegroundColor Blue
ssh root@192.168.35.71 "cat /usr/local/etc/wdd/agent-wdd-config.yaml"
} catch {
Write-Host "操作失败: $_" -ForegroundColor Red
exit 1
}

257
agent-wdd/op/Excutor.go Normal file
View File

@@ -0,0 +1,257 @@
package op
import (
"agent-wdd/log"
"bufio"
"bytes"
"fmt"
"io"
"os/exec"
"strings"
)
// SingleLineCommandExecutor 执行单行shell命令返回执行结果和输出内容
// singleLineCommand: 命令及参数,如 []string{"ls", "-l"}
// 返回值:
//
// bool - 命令是否执行成功true为成功false为失败
// []string - 合并后的标准输出和标准错误内容(按行分割)
func SingleLineCommandExecutor(singleLineCommand []string) (ok bool, resultLog []string) {
if len(singleLineCommand) == 0 {
return false, nil
}
log.Info("start => %v", strings.Join(singleLineCommand, " "))
// 创建命令实例
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"}}
// 返回值:
//
// bool - 所有命令是否全部执行成功
// []string - 合并后的输出内容中间命令的stderr + 最后一个命令的stdout/stderr
func PipeLineCommandExecutor(pipeLineCommand [][]string) (ok bool, resultLog []string) {
if len(pipeLineCommand) == 0 {
return false, nil
}
// 转换为 管道命令的字符串格式
// [][]string{{"ps", "aux"}, {"grep", "nginx"}, {"wc", "-l"}} 转换为 ps aux | grep nginx | wc -l
pipeLineCommandStr := ""
// 预检所有子命令
for _, cmd := range pipeLineCommand {
if len(cmd) == 0 {
return false, nil
}
pipeLineCommandStr += fmt.Sprintf("%s | ", strings.Join(cmd, " "))
}
pipeLineCommandStr = strings.TrimSuffix(pipeLineCommandStr, " | ")
log.Info("start => %v", pipeLineCommandStr)
// 创建命令组
cmds := make([]*exec.Cmd, len(pipeLineCommand))
for i, args := range pipeLineCommand {
cmds[i] = exec.Command(args[0], args[1:]...)
}
// 建立管道连接
for i := 0; i < len(cmds)-1; i++ {
stdoutPipe, err := cmds[i].StdoutPipe()
if err != nil {
return false, []string{err.Error()}
}
cmds[i+1].Stdin = stdoutPipe
}
// 准备输出捕获
stderrBuffers := make([]bytes.Buffer, len(cmds)) // 所有命令的stderr
var lastStdout bytes.Buffer // 最后一个命令的stdout
// 绑定输出
for i, cmd := range cmds {
cmd.Stderr = &stderrBuffers[i] // 每个命令单独捕获stderr
}
cmds[len(cmds)-1].Stdout = &lastStdout // 仅捕获最后一个命令的stdout
// 启动所有命令
started := make([]*exec.Cmd, 0, len(cmds))
defer func() {
// 异常时清理已启动进程
for _, cmd := range started {
if cmd.Process != nil {
cmd.Process.Kill()
}
}
}()
for _, cmd := range cmds {
if err := cmd.Start(); err != nil {
return false, []string{err.Error()}
}
started = append(started, cmd)
}
// 等待所有命令完成
success := true
for _, cmd := range cmds {
if err := cmd.Wait(); err != nil {
success = false
}
}
// 合并输出内容
output := make([]string, 0)
// 合并中间命令的stderr按命令顺序
for i := 0; i < len(cmds)-1; i++ {
scanner := bufio.NewScanner(&stderrBuffers[i])
for scanner.Scan() {
output = append(output, scanner.Text())
}
}
// 合并最后一个命令的输出stdout在前 + stderr在后
scanner := bufio.NewScanner(&lastStdout)
for scanner.Scan() {
output = append(output, scanner.Text())
}
scanner = bufio.NewScanner(&stderrBuffers[len(cmds)-1])
for scanner.Scan() {
output = append(output, scanner.Text())
}
return success, output
}
// RealTimeCommandExecutor 执行命令,需要实时打印输出执行命令的日志,并返回执行结果和输出内容
// realTimeCommand: 实时命令,如 "docker load -i /root/wdd/harbor/harbor-offline-installer-v2.10.1.tgz"
// 返回值:
//
// bool - 命令是否执行成功true为成功false为失败
// []string - 合并后的标准输出和标准错误内容(按行分割)
func RealTimeCommandExecutor(realTimeCommand []string) (ok bool, resultLog []string) {
if len(realTimeCommand) == 0 {
return false, nil
}
log.Info("start real time command => %v", strings.Join(realTimeCommand, " "))
cmd := exec.Command(realTimeCommand[0], realTimeCommand[1:]...)
stdout, err := cmd.StdoutPipe()
if err != nil {
return false, []string{err.Error()}
}
stderr, err := cmd.StderrPipe()
if err != nil {
return false, []string{err.Error()}
}
if err := cmd.Start(); err != nil {
return false, []string{err.Error()}
}
output := make([]string, 0)
scanner := bufio.NewScanner(io.MultiReader(stdout, stderr))
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
output = append(output, line)
}
if err := cmd.Wait(); err != nil {
return false, append(output, err.Error())
}
return true, output
}
func main() {
// 成功案例
success, output := SingleLineCommandExecutor([]string{"ls", "-l"})
fmt.Println("执行成功:", success)
fmt.Println("输出内容:", output)
// 失败案例(命令存在但参数错误)
success, output = SingleLineCommandExecutor([]string{"ls", "/nonexistent"})
fmt.Println("执行成功:", success)
fmt.Println("输出内容:", output)
// 失败案例(命令不存在)
success, output = SingleLineCommandExecutor([]string{"invalid_command"})
fmt.Println("执行成功:", success)
fmt.Println("输出内容:", output)
// 成功案例(三阶管道)
success, output = PipeLineCommandExecutor([][]string{
{"ps", "aux"},
{"grep", "nginx"},
{"wc", "-l"},
})
fmt.Println("执行结果:", success)
fmt.Println("合并输出:", output)
// 失败案例(命令不存在)
success, output = PipeLineCommandExecutor([][]string{
{"invalid_cmd"},
{"wc", "-l"},
})
fmt.Println("执行结果:", success)
fmt.Println("合并输出:", output)
// 失败案例(参数错误)
success, output = PipeLineCommandExecutor([][]string{
{"ls", "/nonexistent"},
{"grep", "test"},
})
fmt.Println("执行结果:", success)
fmt.Println("合并输出:", output)
}

View File

@@ -0,0 +1,244 @@
package op
import (
"agent-wdd/config"
"agent-wdd/log"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type PackageOperator struct {
installPrefix []string // 安装前缀
removePrefix []string // 移除前缀
upgradePrefix []string // 升级前缀
initCommand []string
}
var (
AgentPackOperator = &PackageOperator{}
aptPackageOperator = &PackageOperator{
installPrefix: []string{
"apt-get", "install", "--allow-downgrades", "-y",
},
removePrefix: []string{
"apt", "remove", "-y",
},
upgradePrefix: []string{},
initCommand: []string{
"apt-get", "update",
},
}
yumPackageOperator = &PackageOperator{
installPrefix: []string{
"yum", "install", "-y",
},
removePrefix: []string{
"yum", "remove", "-y",
},
upgradePrefix: []string{},
initCommand: []string{
"yum", "makecache",
},
}
)
func (op *PackageOperator) Install(tools []string) bool {
// 判定本机的包管理Operator
ok := generatePackageOperator()
if !ok {
log.Error("PackageOperator init failed! 无法执行安装操作 %s", tools)
return false
}
// install seperately
for _, tool := range tools {
ok, result := SingleLineCommandExecutor(append(AgentPackOperator.installPrefix, tool))
if !ok {
log.Error("[install] failed! => %s", tool)
}
// 打印安装的过程内容
beautifulPrintListWithTitle(result, fmt.Sprintf("安装 %s 的过程内容", tool))
}
return true
}
// PackageInit 初始化包管理器, 同样会判定主机是否可以联网如果本机是ubuntu或者debian, 则使用apt, 否则使用yum
func (op *PackageOperator) PackageInit() bool {
log.Info("PackageInit !")
// beautifulPrintListWithTitle(config.ConfigCache)
// package init
os := config.ConfigCache.Agent.OS
if os.PackInit {
log.Info("PackageInit already done! skip!")
return true
}
// 判定本机的包管理Operator
ok := generatePackageOperator()
if !ok {
return false
}
os.PackInit = true
os.SaveConfig()
if os.IsUbuntuType {
ok, resultLog := SingleLineCommandExecutor(aptPackageOperator.initCommand)
if !ok {
log.Error("APT init failed! please check ! %s", resultLog)
}
}
return true
}
func (op *PackageOperator) PackageInitForce() bool {
log.Info("PackageInitForce !")
os := config.ConfigCache.Agent.OS
if os.IsUbuntuType {
ok, resultLog := SingleLineCommandExecutor(aptPackageOperator.initCommand)
if !ok {
log.Error("APT init failed! please check ! %s", resultLog)
}
log.Info("APT init success! %s", resultLog)
}
return true
}
func (op *PackageOperator) Remove(tools []string) {
// 判定本机的包管理Operator
generatePackageOperator()
// install seperately
for _, tool := range tools {
ok, result := SingleLineCommandExecutor(append(AgentPackOperator.removePrefix, tool))
if !ok {
log.Error("[remove] failed! => %s", tool)
beautifulPrintListWithTitle(result, fmt.Sprintf("移除 %s 的过程内容", tool))
}
}
}
func generatePackageOperator() bool {
// cache return
if AgentPackOperator.initCommand != nil {
log.Info("PackageOperator init success! %v", AgentPackOperator.initCommand)
return true
}
// 检查是否可以连接互联网
if config.CanConnectInternet() <= 1 {
log.Error("服务器无法连接互联网,无法初始化包管理器")
return false
}
// 检查本机是否存在OS的信息
os := config.ConfigCache.Agent.OS
if os.Hostname == "" {
os.Gather()
os.SaveConfig()
}
if os.IsUbuntuType {
log.Info("Ubuntu type detected! use apt package operator!")
AgentPackOperator = aptPackageOperator
} else {
log.Info("Other type detected! use yum package operator!")
AgentPackOperator = yumPackageOperator
}
return true
}
// GetLatestGithubReleaseVersion 获取GitHub仓库的最新版本
func GetLatestGithubReleaseVersion(repoOwner, repoName string) (string, error) {
// 检查是否可以连接互联网
if config.CanConnectInternet() <= 1 {
return "", fmt.Errorf("服务器无法连接互联网无法获取GitHub最新版本")
}
// 设置超时时间为10秒的HTTP客户端
client := &http.Client{
Timeout: 10 * time.Second,
}
// 构建GitHub API URL
apiURL := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", repoOwner, repoName)
// 发送GET请求
resp, err := client.Get(apiURL)
if err != nil {
return "", fmt.Errorf("请求GitHub API失败: %v", err)
}
defer resp.Body.Close()
// 检查响应状态码
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("GitHub API返回非200状态码: %d", resp.StatusCode)
}
// 读取响应体
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("读取响应内容失败: %v", err)
}
// 解析JSON响应
var release struct {
TagName string `json:"tag_name"`
}
if err := json.Unmarshal(body, &release); err != nil {
return "", fmt.Errorf("解析JSON响应失败: %v", err)
}
return release.TagName, nil
}
// CommandExists 判定命令是否存在
func CommandExists(command string) bool {
ok, _ := SingleLineCommandExecutor([]string{"command", "-v", command})
return ok
}
func CommandExistsByPath(command string) bool {
// 查询 /usr/bin /usr/local/bin中是否有可执行文件
_, result := SingleLineCommandExecutor([]string{"find", "/usr/bin", "/usr/local/bin", "-name", command})
if result == nil || len(result) == 0 {
return false
}
return true
}
func beautifulPrintListWithTitle(contend []string, title string) {
fmt.Println()
fmt.Println(">>>>>>>> " + title + " <<<<<<<<")
for _, line := range contend {
if line == "EOF" {
// do nothing
continue
}
fmt.Println(line)
}
fmt.Println(">>>>>>>> end <<<<<<<<")
fmt.Println()
}

View File

@@ -0,0 +1,21 @@
package op
import (
"testing"
)
func TestGetLatestGithubReleaseVersion(t *testing.T) {
repoOwner := "docker"
repoName := "compose"
version, err := GetLatestGithubReleaseVersion(repoOwner, repoName)
if err != nil {
t.Fatalf("获取最新版本失败: %v", err)
}
if version == "" {
t.Error("获取的版本号为空")
}
t.Logf("获取到的最新版本号: %s", version)
}

View File

@@ -0,0 +1,89 @@
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
}
func SystemIsRunning(serviceName string) (bool, []string) {
ok, resultLog := SingleLineCommandExecutor([]string{"systemctl", "is-active", serviceName})
if !ok {
return false, resultLog
}
return true, resultLog
}

View File

@@ -0,0 +1,38 @@
set HTTP_PROXY=http://127.0.0.1:7899
set HTTPS_PROXY=http://127.0.0.1:7899
# 设置
# mc.exe alias set oracle-osaka-1 https://axzsapxffbbn.compat.objectstorage.ap-osaka-1.oraclecloud.com b0696c316f87b644b4a13bcb020f095cd147be0b GAIaM/064epLzQcXsRbj2gwlFOrVepjCR23wj2tfJ+A=
# mc.exe ls oracle-osaka-1/osaka
# 设置 韩国oss
# mc.exe alias set oracle-seoul-2 https://cncvl8ro2rbf.compat.objectstorage.ap-seoul-1.oraclecloud.com 9e413c6e66269bc65d7ec951d93ba9c6a9781f6e dkXD7PysjrhsTKfNIbKupUmtxdfOvYCyLXf0MXa4hnU=
# mc.exe ls oracle-seoul-2/seoul-2
# 重新build项目
Set-Location "C:\Users\wddsh\Documents\IdeaProjects\ProjectOctopus\agent-wdd"
& "C:\Users\wddsh\go\bin\gox.exe" -osarch="linux/amd64" -output "build/agent-wdd_{{.OS}}_{{.Arch}}"
Set-Location "C:\Users\wddsh\Documents\IdeaProjects\ProjectOctopus\agent-wdd\test"
# 删除上面存在的旧的内容
mc.exe rm oracle-seoul-2/seoul-2/agent-wdd_linux_amd64
# mc.exe rm oracle-seoul-2/seoul-2/test-shell.sh
# 上传文件
mc.exe cp C:\Users\wddsh\Documents\IdeaProjects\ProjectOctopus\agent-wdd\build\agent-wdd_linux_amd64 oracle-seoul-2/seoul-2/
# mc.exe cp C:\Users\wddsh\Documents\IdeaProjects\ProjectOctopus\agent-wdd\test\test-shell.sh oracle-seoul-2/seoul-2/

View File

@@ -0,0 +1,43 @@
Available commands:
acme acme相关的内容
base 服务器基础操作
docker Docker相关操作
local 本地安装Docker
online 网络安装Docker
remove 卸载Docker
dockercompose Docker Compose相关操作
local 本地安装Docker Compose
online 安装Docker Compose
remove 卸载Docker Compose
version 查看Docker Compose版本
firewall 关闭防火墙
selinux 关闭selinux
ssh 修改ssh配置
config 修改ssh配置 为wdd默认配置!
key 安装默认的ssh-key
port 修改ssh端口
swap 关闭系统的Swap
sysconfig 修改系统的sysconfig
tools 通用工具安装 利用本机的yumapt等从网络安装常用的软件
config 配置文件管理
show 显示agent运行配置
download 文件下载,直接下载 [url] [dest_path]
proxy 使用代理下载 socks5://[username]:[password]@ip:port http://[username]:[password]@ip:port
help 帮助信息
help Help about any command
info 打印主机详细信息
all 主机全部相关的信息
cpu 主机cpu相关的信息
disk 主机disk相关的信息
mem 主机memory相关的信息
network 主机Network相关的信息
os 主机操作系统相关的信息
proxy 主机代理相关的内容
vmess 设置VMESS代理
xray Xray相关操作
install 安装Xray
security 安全相关操作
version 打印版本信息
wdd WDD相关操作
zsh zsh相关的内容

View File

@@ -0,0 +1,67 @@
#!/bin/bash
rm -f /usr/local/bin/agent-wdd
rm -f /usr/local/bin/test-shell.sh
wget https://pan.107421.xyz/d/oracle-seoul-2/agent-wdd_linux_amd64 -qO /usr/local/bin/agent-wdd
chmod +x /usr/local/bin/agent-wdd
/usr/local/bin/agent-wdd info all
cat /usr/local/etc/wdd/agent-wdd-config.yaml
/usr/local/bin/agent-wdd base firewall
/usr/local/bin/agent-wdd base ssh config
/usr/local/bin/agent-wdd base ssh key
/usr/local/bin/agent-wdd proxy xray install
/usr/local/bin/agent-wdd proxy vmess 22443
#bash /usr/local/bin/test-shell.sh
/usr/local/bin/agent-wdd info network
/usr/local/bin/agent-wdd info cpu
/usr/local/bin/agent-wdd info mem
/usr/local/bin/agent-wdd info swap
/usr/local/bin/agent-wdd info disks
/usr/local/bin/agent-wdd base docker local
/usr/local/bin/agent-wdd info os
/usr/local/bin/agent-wdd base docker online
/usr/local/bin/agent-wdd info os
/usr/local/bin/agent-wdd zsh
/usr/local/bin/agent-wdd base tools
/usr/local/bin/agent-wdd base swap
/usr/local/bin/agent-wdd base firewall
/usr/local/bin/agent-wdd base selinux
/usr/local/bin/agent-wdd base sysconfig
/usr/local/bin/agent-wdd download https://pan.107421.xyz/d/oracle-osaka-1/docker-amd64-20.10.15.tgz /root/wdd
/usr/local/bin/agent-wdd proxy xray install
/usr/local/bin/agent-wdd proxy vmess 22443
wget https://pan.107421.xyz/d/oracle-seoul-2/test-shell.sh -qO /usr/local/bin/test-shell.sh
chmod +x /usr/local/bin/test-shell.sh

View File

@@ -0,0 +1,135 @@
#!/bin/bash
# set -eo pipefail
# 测试配置
TEST_REPEATS=2
AGENT_BIN="/usr/local/bin/agent-wdd" # 修正路径拼写错误
# 输出颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
# 测试统计
declare -i TOTAL_TESTS=0 PASSED_TESTS=0 FAILED_TESTS=0
# 实用函数
log() {
echo -e "${YELLOW}[INFO] $* ${NC}"
}
pass() {
echo -e "${GREEN}PASS: $* ${NC}"
((PASSED_TESTS++))
((TOTAL_TESTS++))
}
fail() {
echo -e "${RED}FAIL: $* ${NC}"
((FAILED_TESTS++))
((TOTAL_TESTS++))
}
test_command() {
local cmd="$1"
local expected="${2:-0}"
if ! eval "$cmd"; then
local exit_code=$?
[[ $exit_code -eq "$expected" ]] || {
fail "$cmd (exit $exit_code)"
return 1
}
fi
pass "$cmd"
return 0
}
repeat_test() {
local times=$1
local cmd="$2"
local expected="${3:-0}"
for ((i=1; i<=times; i++)); do
test_command "$cmd" "$expected"
done
}
test_base_docker() {
log "\nTesting Docker commands..."
test_command "$AGENT_BIN base docker remove" 0
test_command "$AGENT_BIN base docker online" 0
repeat_test $TEST_REPEATS "$AGENT_BIN base docker local" 0
if docker --version >/dev/null 2>&1; then
pass "docker installation"
else
fail "docker installation"
fi
}
test_base_ssh() {
log "\nTesting SSH commands..."
repeat_test $TEST_REPEATS "$AGENT_BIN base ssh port 2222" 0
test_command "$AGENT_BIN base ssh config" 0
}
test_tools() {
log "\nTesting tools installation..."
local tools=("htop" "tmux" "nano")
for tool in "${tools[@]}"; do
if command -v "$tool" >/dev/null 2>&1; then
pass "$tool already installed"
else
test_command "$AGENT_BIN base tools $tool" 0
fi
done
}
test_info_commands() {
log "\nTesting info commands..."
local commands=(
"$AGENT_BIN info all"
"$AGENT_BIN info cpu"
"$AGENT_BIN info disk"
"$AGENT_BIN info mem"
"$AGENT_BIN info network"
"$AGENT_BIN info os"
)
for cmd in "${commands[@]}"; do
repeat_test $TEST_REPEATS "$cmd" 0
done
}
main() {
# 前置检查
[[ -x "$AGENT_BIN" ]] || {
log "Error: Agent binary not found or not executable: $AGENT_BIN"
exit 1
}
log "Starting tests with $AGENT_BIN..."
# 基本信息测试
test_command "$AGENT_BIN version" 0
test_command "$AGENT_BIN help" 0
# 基础模块测试
test_base_docker
test_base_ssh
test_tools
# 信息模块测试
test_info_commands
log "\nTest Summary:"
echo -e "Total Tests: $TOTAL_TESTS"
echo -e "${GREEN}Passed: $PASSED_TESTS ${NC}"
echo -e "${RED}Failed: $FAILED_TESTS ${NC}"
exit "$FAILED_TESTS"
}
main

View File

@@ -0,0 +1,152 @@
package utils
import (
"agent-wdd/log"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
)
func DownloadFile(url string, path string) (bool, string) {
// 创建HTTP客户端
client := &http.Client{
Timeout: 5 * time.Second,
}
return DownloadFileWithClient(client, url, path)
}
// DownloadFileWithClient 使用http客户端下载文件
func DownloadFileWithClient(client *http.Client, url string, path string) (bool, string) {
// path如果是一个目录则需要获取文件名
// 获取url使用 / 分割最后的一部分
// 如果path是目录则需要获取文件名
if IsDirOrFile(path) {
fileName := strings.Split(url, "/")[len(strings.Split(url, "/"))-1]
path = filepath.Join(path, fileName)
log.Info("path是目录自动获取文件名为 => : %s", path)
}
return downloadWithProgress(client, url, path)
}
// 带进度显示的下载函数
func downloadWithProgress(client *http.Client, url, dest string) (bool, string) {
// 创建目标文件
file, err := os.Create(dest)
if err != nil {
return false, fmt.Sprintf("创建文件失败: %s", err.Error())
}
defer file.Close()
// 发起请求
resp, err := client.Get(url)
if err != nil {
return false, fmt.Sprintf("HTTP请求失败: %s", err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Sprintf("服务器返回错误状态码: %s", resp.Status)
}
// 获取文件大小
size := resp.ContentLength
var downloaded int64
// 不支持下载超过10GB的文件
if size > 10*1024*1024*1024 || size < 0 {
log.Error("文件大小超过10GB或者文件大小未知不支持高级下载方式! 尝试使用wget下载")
return downloadFileByWget(url, dest)
}
// 打印下载信息
fmt.Printf("开始下载: %s 大小: %s\n", url, HumanSizeInt(size))
// 创建带进度跟踪的Reader
progressReader := &progressReader{
Reader: resp.Body,
Reporter: func(r int64) {
downloaded += r
printProgress(downloaded, size)
},
}
// 执行拷贝
if _, err := io.Copy(file, progressReader); err != nil {
return false, fmt.Sprintf("文件拷贝失败: %s", err.Error())
}
fmt.Print("\n") // 保持最后进度显示的完整性
return true, fmt.Sprintf("文件下载成功: %s", dest)
}
// 使用wget下载文件
func downloadFileByWget(url, dest string) (bool, string) {
log.Info("使用wget下载文件: %s", fmt.Sprintf("wget %s -qO %s", url, dest))
cmd := exec.Command("wget", url, "-qO", dest)
_, err := cmd.CombinedOutput()
if err != nil {
return false, fmt.Sprintf("wget下载失败: %s", err.Error())
}
// 检查文件是否存在且不为空
fileInfo, err := os.Stat(dest)
if err != nil || fileInfo.Size() == 0 {
return false, fmt.Sprintf("wget下载失败 文件不存在或为空: %s", dest)
}
return true, fmt.Sprintf("wget下载成功: %s", dest)
}
// 进度跟踪Reader
type progressReader struct {
io.Reader
Reporter func(r int64)
}
func (pr *progressReader) Read(p []byte) (int, error) {
n, err := pr.Reader.Read(p)
if n > 0 {
pr.Reporter(int64(n))
}
return n, err
}
// 打印进度信息
func printProgress(downloaded, total int64) {
const barLength = 40
percent := float64(downloaded) / float64(total) * 100
// 生成进度条
filled := int(barLength * downloaded / total)
bar := fmt.Sprintf("[%s%s]",
strings.Repeat("=", filled),
strings.Repeat(" ", barLength-filled))
// 格式化为人类可读大小
humanSize := func(bytes int64) string {
const unit = 1024
if bytes < unit {
return fmt.Sprintf("%d B", bytes)
}
div, exp := int64(unit), 0
for n := bytes / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB", float64(bytes)/float64(div), "KMGTPE"[exp])
}
fmt.Printf("\r%-45s %6.2f%% %s/%s", bar, percent,
humanSize(downloaded), humanSize(total))
}

View File

@@ -0,0 +1,476 @@
package utils
import (
"agent-wdd/log"
"bufio"
"fmt"
"io"
"os"
"os/user"
"path/filepath"
"strings"
)
// AppendFileToFile 将源文件的内容添加到目标文件
func AppendFileToFile(sourceFile, targetFile string) bool {
// 打开源文件
source, err := os.Open(sourceFile)
if err != nil {
log.Error("[BasicAppendSourceToFile] - error open source file => %s, error is %s", sourceFile, err.Error())
return false
}
defer source.Close()
// 打开目标文件,如果不存在则创建,如果存在则在末尾追加
target, err := os.OpenFile(targetFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Error("[BasicAppendSourceToFile] - error open target file => %s, error is %s", sourceFile, err.Error())
return false
}
defer target.Close()
// 将源文件内容复制到目标文件
_, err = io.Copy(target, source)
if err != nil {
log.Error("[BasicAppendSourceToFile] - Error appending to target file: %s", err.Error())
return false
}
return true
}
// AppendOverwriteContentToFile 向目标文件中写入一些内容,覆盖源文件
func AppendOverwriteContentToFile(content string, targetFile string) bool {
err := os.Remove(targetFile)
if err != nil {
log.Warning("[BasicAppendOverwriteContentToFile] - Error removing file: %s , error is %s", targetFile, err.Error())
}
return AppendContentToFile(content, targetFile)
}
// AppendContentToFile 向目标文件(targetFile 文件的绝对路径)中追加写入一些内容, 如果文件不存在,那么就创建相应的目录及文件
func AppendContentToFile(content string, targetFile string) bool {
// 创建目标文件的目录(递归创建)
dir := filepath.Dir(targetFile)
if err := os.MkdirAll(dir, 0755); err != nil {
return false
}
// 打开文件用于追加。如果文件不存在,将会创建一个新文件。
file, err := os.OpenFile(targetFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Error("[BasicAppendContentToFile] - Error opening file: %s , error is %s", targetFile, err.Error())
return false
}
defer file.Close() // 确保文件最终被关闭
// 写入内容到文件
// 内容非空时执行写入操作
if content != "" {
if _, err := file.WriteString(content); err != nil {
log.Error("[BasicAppendContentToFile] - Error writing to file: %s , error is %s", targetFile, err.Error())
return false
}
}
return true
}
// AppendOverwriteListContentToFile 将一个字符串列表中的内容,一行一行的写入文件中
func AppendOverwriteListContentToFile(contentList []string, targetFile string) bool {
err := os.Remove(targetFile)
if err != nil {
log.Warning("[AppendOverwriteListContentToFile] - Error removing file: %s , error is %s", targetFile, err.Error())
}
// 打开文件用于追加。如果文件不存在,将会创建一个新文件。
file, err := os.OpenFile(targetFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Error("[AppendOverwriteListContentToFile] - Error opening file: %s , error is %s", targetFile, err.Error())
return false
}
defer file.Close() // 确保文件最终被关闭
// 写入内容到文件
for _, contentLine := range contentList {
//bytes, _ := json.Marshal(contentLine)
if _, err := file.WriteString(contentLine + "\n"); err != nil {
log.Error("[AppendOverwriteListContentToFile] - Error writing to file: %s , error is %s", targetFile, err.Error())
return false
}
}
return true
}
// AppendK8sYamlWithSplitLineToFile 专门为k8s的yaml文件设计的在每次写入内容之前先写入一行分隔符
func AppendK8sYamlWithSplitLineToFile(content string, targetFile string) bool {
// 打开文件用于追加。如果文件不存在,将会创建一个新文件。
file, err := os.OpenFile(targetFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Error("[BasicAppendContentToFile] - Error opening file: %s , error is %s", targetFile, err.Error())
return false
}
defer file.Close() // 确保文件最终被关闭
// 写入内容到文件
if _, err := file.WriteString("---"); err != nil {
log.Error("[BasicAppendContentToFile] - Error writing to file: %s , error is %s", targetFile, err.Error())
return false
}
if _, err := file.WriteString(content); err != nil {
log.Error("[BasicAppendContentToFile] - Error writing to file: %s , error is %s", targetFile, err.Error())
return false
}
return true
}
// AppendNullOverWriteToFile 清空一个文件
func AppendNullOverWriteToFile(targetFile string) bool {
// 使用os.O_TRUNC清空文件内容
file, err := os.OpenFile(targetFile, os.O_TRUNC|os.O_WRONLY, 0644)
if err != nil {
log.Error("[AppendNullOverWriteToFile] - Error opening file: %s, error is %s", targetFile, err.Error())
return false
}
defer file.Close() // 确保在函数退出前关闭文件
return true
}
func WordSpaceCompletion(source string, totalLength int) string {
sourceLength := len(source)
if sourceLength >= totalLength {
return source
}
for i := 0; i < totalLength-sourceLength; i++ {
source += " "
}
return source
}
// IsDirOrFile 如果是目录则返回true是文件则返回false
func IsDirOrFile(path string) bool {
info, err := os.Stat(path)
if err != nil {
return false
}
return info.IsDir()
}
// FileExists 文件存在返回true不存在返回false如果文件是一个目录也返回false
func FileExists(fileFullPath string) bool {
info, err := os.Stat(fileFullPath)
if err != nil {
return false
}
return !info.IsDir()
}
// CreateFolder 创建文件夹如果文件夹存在则返回true否则返回false
func CreateFolder(folderName string) bool {
// 递归创建 类似于 mkdir -p folderName
if _, err := os.Stat(folderName); os.IsNotExist(err) {
return os.MkdirAll(folderName, 0755) == nil
}
return true
}
// FileOrFolderExists 文件或者目录是否返回true不存在返回false
func FileOrFolderExists(fileFullPath string) bool {
_, err := os.Stat(fileFullPath)
return !os.IsNotExist(err)
}
// FileExistAndNotNull 文件不为空返回true 文件为空返回false
func FileExistAndNotNull(filename string) bool {
// Check if the file exists
if _, err := os.Stat(filename); os.IsNotExist(err) {
log.Debug("文件 %s 不存在!", filename)
return false
}
// Open the file for reading
file, err := os.Open(filename)
defer file.Close()
if err != nil {
log.Debug("文件 %s 打开有误!", filename)
return false // Handle error according to your needs
}
// Get the file size
info, _ := file.Stat()
size := info.Size()
// Check if the file is not empty
return size > 0
}
// ListAllFileInFolder 列出一个目录中的所有文件返回文件名忽略folder不带全路径
func ListAllFileInFolder(folderName string) ([]string, error) {
return listAllFileInFolderWithFullPath(folderName, false)
}
func ListAllFileInFolderWithFullPath(folderName string) ([]string, error) {
return listAllFileInFolderWithFullPath(folderName, true)
}
func listAllFileInFolderWithFullPath(folderName string, fullPath bool) ([]string, error) {
files := make([]string, 0)
err := filepath.Walk(folderName, func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
if fullPath {
files = append(files, path)
} else {
files = append(files, info.Name())
}
}
return nil
})
if err != nil {
return nil, err
}
return files, nil
}
// RemoveFile 删除文件如果文件不存在则返回true
func RemoveFile(filePath string) bool {
if _, err := os.Stat(filePath); os.IsNotExist(err) {
log.Error("文件不存在: %s", filePath)
return true
}
err := os.Remove(filePath)
if err != nil {
log.Error("Failed to remove file: %s", err.Error())
return false
}
return true
}
func RemoveFolderComplete(folderName string) bool {
err := filepath.Walk(folderName,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() {
return os.Remove(path)
}
return nil
})
if err != nil {
return false
}
err = os.RemoveAll(folderName)
if err != nil {
return false
}
return true
}
func ReadAllContentFromFile(fileFullPath string) (result []string) {
f, err := os.Open(fileFullPath)
if err != nil {
fmt.Println(err)
return result
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
if len(line) > 0 { // ignore empty lines
result = append(result, line)
}
}
if err := scanner.Err(); err != nil {
fmt.Println(err)
return result
}
return result
}
// FindContentInFile 在文件中查找内容如果存在则返回true否则返回false 类似于grep -q content targetFile
func FindContentInFile(content string, targetFile string) bool {
// 读取文件内容
fileContent, err := os.ReadFile(targetFile)
if err != nil {
log.Error("[FindContentInFile] - Error reading file: %s , error is %s", targetFile, err.Error())
return false
}
// 将文件内容按行分割
lines := strings.Split(string(fileContent), "\n")
// 遍历每一行
for _, line := range lines {
if strings.Contains(line, content) {
return true
}
}
return false
}
// FindAndReplaceContentInFile 在文件中查找内容,如果存在,则替换,如果存在多个,则替换全部 类似于sed -i 's/oldContent/newContent/g' targetFile
func FindAndReplaceContentInFile(oldContent string, newContent string, targetFile string) bool {
// 读取文件内容
fileContent, err := os.ReadFile(targetFile)
if err != nil {
log.Error("[FindAndReplaceContentInFile] - Error reading file: %s , error is %s", targetFile, err.Error())
return false
}
// 将文件内容按行分割
lines := strings.Split(string(fileContent), "\n")
// 遍历每一行
for i, line := range lines {
if strings.Contains(line, oldContent) {
lines[i] = strings.Replace(line, oldContent, newContent, -1)
}
}
// 将修改后的内容写回文件
err = os.WriteFile(targetFile, []byte(strings.Join(lines, "\n")), 0644)
if err != nil {
log.Error("[FindAndReplaceContentInFile] - Error writing file: %s , error is %s", targetFile, err.Error())
return false
}
return true
}
// FindAndDeleteContentInFile 在文件中查找内容,如果存在,则删除,如果存在多个,则删除全部 类似于sed -i '/content/d' targetFile
func FindAndDeleteContentInFile(content string, targetFile string) bool {
// 读取文件内容
fileContent, err := os.ReadFile(targetFile)
if err != nil {
log.Error("[FindAndDeleteContentInFile] - Error reading file: %s , error is %s", targetFile, err.Error())
return false
}
// 将文件内容按行分割
lines := strings.Split(string(fileContent), "\n")
// 过滤掉包含指定内容的行
var newLines []string
for _, line := range lines {
if !strings.Contains(line, content) {
newLines = append(newLines, line)
}
}
// 将过滤后的内容写回文件
err = os.WriteFile(targetFile, []byte(strings.Join(newLines, "\n")), 0644)
if err != nil {
log.Error("[FindAndDeleteContentInFile] - Error writing file: %s , error is %s", targetFile, err.Error())
return false
}
return true
}
// MoveFileToAnother 将源文件移动到目标文件
func MoveFileToAnother(srcFile, dstFile string) error {
// 如果dstFile是目录则将srcFile移动到dstFile目录下
if IsDirOrFile(dstFile) {
dstFile = filepath.Join(dstFile, filepath.Base(srcFile))
}
// 如果目标文件存在,则删除目标文件
if FileExists(dstFile) {
err := os.Remove(dstFile)
if err != nil {
return fmt.Errorf("删除目标文件失败: %w", err)
}
}
// 如果源文件不存在,则返回错误
if !FileExists(srcFile) {
return fmt.Errorf("源文件不存在: %s", srcFile)
}
// 如果目标文件夹不存在,则创建目标文件夹
if !FileExists(filepath.Dir(dstFile)) {
err := os.MkdirAll(filepath.Dir(dstFile), os.ModePerm)
if err != nil {
return fmt.Errorf("创建目标文件夹失败: %w", err)
}
}
// 移动文件
return os.Rename(srcFile, dstFile)
}
// MoveFolerToAnother 将源文件夹的所有文件递归移动到目标文件夹
func MoveFolerToAnother(srcDir, dstDir string) error {
// 读取源文件夹中的所有条目
entries, err := os.ReadDir(srcDir)
if err != nil {
return fmt.Errorf("读取源文件夹失败: %w", err)
}
// 创建目标文件夹(如果不存在)
if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
return fmt.Errorf("创建目标文件夹失败: %w", err)
}
// 遍历所有条目
for _, entry := range entries {
srcPath := filepath.Join(srcDir, entry.Name())
dstPath := filepath.Join(dstDir, entry.Name())
// 如果是文件夹,递归处理
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 获取运行环境当前用户的根目录
func GetCurrentUserFolder() string {
usr, err := user.Current()
if err != nil {
fmt.Println(err)
return ""
}
return usr.HomeDir
}

View File

@@ -0,0 +1,40 @@
package utils
import "strconv"
func HumanSize(bytes uint64) string {
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
var unitIndex int
size := float64(bytes)
for size >= 1024 && unitIndex < len(units)-1 {
size /= 1024
unitIndex++
}
if unitIndex == 0 {
return strconv.FormatUint(bytes, 10) + units[unitIndex]
}
return strconv.FormatFloat(size, 'f', 1, 64) + units[unitIndex]
}
func HumanSizeInt(bytes int64) string {
return HumanSize(uint64(bytes))
}
func HumanDiskSize(bytes uint64) string {
units := []string{"B", "KB", "MB", "GB", "TB", "PB"}
var unitIndex int
size := float64(bytes)
for size >= 1000 && unitIndex < len(units)-1 {
size /= 1000
unitIndex++
}
if unitIndex == 0 {
return strconv.FormatUint(bytes, 10) + units[unitIndex]
}
return strconv.FormatFloat(size, 'f', 1, 64) + units[unitIndex]
}

View File

@@ -0,0 +1 @@
package utils

View File

@@ -0,0 +1 @@
package utils

View File

@@ -0,0 +1,55 @@
package utils
import (
"agent-wdd/log"
"encoding/json"
"fmt"
)
func BeautifulPrint(object interface{}) {
bytes, err := json.MarshalIndent(object, "", " ")
if err != nil {
log.Error("[BeautifulPrint] - json marshal error ! => %v", object)
}
fmt.Println()
fmt.Println(string(bytes))
fmt.Println()
}
func BeautifulPrintToString(object interface{}) string {
bytes, err := json.MarshalIndent(object, "", " ")
if err != nil {
log.Error("[BeautifulPrint] - json marshal error ! => %v", object)
}
return string(bytes)
}
func BeautifulPrintWithTitle(contend any, title string) {
fmt.Println()
fmt.Println(">>>>>>>> " + title + " <<<<<<<<")
bytes, _ := json.MarshalIndent(contend, "", " ")
fmt.Println(string(bytes))
fmt.Println(">>>>>>>> end <<<<<<<<")
}
func BeautifulPrintListWithTitle(contend []string, title string) {
fmt.Println()
fmt.Println(">>>>>>>> " + title + " <<<<<<<<")
for _, line := range contend {
fmt.Println(line)
}
fmt.Println(">>>>>>>> end <<<<<<<<")
}
func SplitLinePrint() {
fmt.Println()
fmt.Println()
fmt.Println()
}

View File

@@ -0,0 +1,24 @@
package utils
import (
"fmt"
"time"
)
const defaultTimeString = "2011-11-11 11:11:11"
func CurrentTimeString() string {
// 加载东八区时区
loc, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
fmt.Println("无法加载时区:", err)
return defaultTimeString
}
// 获取当前时间并转换为东八区时间
now := time.Now().In(loc)
// 格式化为 "2006-01-02 15:04:05" 的布局
formattedTime := now.Format(time.DateTime)
return formattedTime
}

152
agent-wdd/utils/ZipUtils.go Normal file
View File

@@ -0,0 +1,152 @@
package utils
import (
"agent-wdd/log"
"archive/tar"
"archive/zip"
"compress/gzip"
"io"
"os"
"path/filepath"
)
// UnzipFile 解压文件, 支持zip tgz tar.gz tar
func UnzipFile(zipFile string, targetFolder string) {
log.Info("解压文件: %s, 到: %s", zipFile, targetFolder)
// 检查文件扩展名以确定解压缩方法
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
}