初始化项目
This commit is contained in:
8
agent-wdd/Makefile
Normal file
8
agent-wdd/Makefile
Normal 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}}"
|
||||
|
||||
42
agent-wdd/agent-wdd-config.yaml
Normal file
42
agent-wdd/agent-wdd-config.yaml
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
BIN
agent-wdd/build/agent-wdd_linux_amd64
Normal file
BIN
agent-wdd/build/agent-wdd_linux_amd64
Normal file
Binary file not shown.
10
agent-wdd/cmd/Acme.go
Normal file
10
agent-wdd/cmd/Acme.go
Normal 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
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
30
agent-wdd/cmd/Config.go
Normal 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
122
agent-wdd/cmd/Download.go
Normal 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
98
agent-wdd/cmd/Info.go
Normal 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
310
agent-wdd/cmd/Proxy.go
Normal 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
7
agent-wdd/cmd/WddHost.go
Normal 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
152
agent-wdd/cmd/Zsh.go
Normal 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安装完成")
|
||||
}
|
||||
}
|
||||
121
agent-wdd/cmd/beans/DockerDaemonConfig.go
Normal file
121
agent-wdd/cmd/beans/DockerDaemonConfig.go
Normal 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"
|
||||
]
|
||||
}
|
||||
`
|
||||
54
agent-wdd/cmd/beans/HarborConfig.go
Normal file
54
agent-wdd/cmd/beans/HarborConfig.go
Normal 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
|
||||
`
|
||||
203
agent-wdd/cmd/beans/SshSysConfig.go
Normal file
203
agent-wdd/cmd/beans/SshSysConfig.go
Normal 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
173
agent-wdd/cmd/root.go
Normal 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)
|
||||
}
|
||||
}
|
||||
37
agent-wdd/cmd/xray/vmessTemplate.go
Normal file
37
agent-wdd/cmd/xray/vmessTemplate.go
Normal 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
177
agent-wdd/config/CPU.go
Normal 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
206
agent-wdd/config/Config.go
Normal 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
116
agent-wdd/config/Disk.go
Normal 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
140
agent-wdd/config/Memory.go
Normal 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
273
agent-wdd/config/Network.go
Normal 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
117
agent-wdd/config/OS.go
Normal 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
33
agent-wdd/go.mod
Normal 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
83
agent-wdd/go.sum
Normal 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=
|
||||
71
agent-wdd/log/SimpleLog.go
Normal file
71
agent-wdd/log/SimpleLog.go
Normal 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
14
agent-wdd/main.go
Normal 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()
|
||||
|
||||
}
|
||||
29
agent-wdd/one-build-and-run.ps1
Normal file
29
agent-wdd/one-build-and-run.ps1
Normal 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
257
agent-wdd/op/Excutor.go
Normal 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)
|
||||
}
|
||||
244
agent-wdd/op/PackageOperator.go
Normal file
244
agent-wdd/op/PackageOperator.go
Normal 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()
|
||||
}
|
||||
21
agent-wdd/op/PackageOperator_test.go
Normal file
21
agent-wdd/op/PackageOperator_test.go
Normal 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)
|
||||
}
|
||||
89
agent-wdd/op/SystemdExcutor.go
Normal file
89
agent-wdd/op/SystemdExcutor.go
Normal 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
|
||||
}
|
||||
38
agent-wdd/test/one-click-build-run.ps1
Normal file
38
agent-wdd/test/one-click-build-run.ps1
Normal 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/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
43
agent-wdd/test/readme_help.txt
Normal file
43
agent-wdd/test/readme_help.txt
Normal 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 通用工具安装 利用本机的yum,apt等从网络安装常用的软件
|
||||
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相关的内容
|
||||
67
agent-wdd/test/run_test.sh
Normal file
67
agent-wdd/test/run_test.sh
Normal 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
|
||||
|
||||
|
||||
135
agent-wdd/test/test-shell.sh
Normal file
135
agent-wdd/test/test-shell.sh
Normal 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
|
||||
152
agent-wdd/utils/DownloadUtils.go
Normal file
152
agent-wdd/utils/DownloadUtils.go
Normal 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))
|
||||
}
|
||||
476
agent-wdd/utils/FileUtils.go
Normal file
476
agent-wdd/utils/FileUtils.go
Normal 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
|
||||
}
|
||||
40
agent-wdd/utils/FormatUtils.go
Normal file
40
agent-wdd/utils/FormatUtils.go
Normal 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]
|
||||
}
|
||||
1
agent-wdd/utils/HttpUtils.go
Normal file
1
agent-wdd/utils/HttpUtils.go
Normal file
@@ -0,0 +1 @@
|
||||
package utils
|
||||
1
agent-wdd/utils/ObjectUtils.go
Normal file
1
agent-wdd/utils/ObjectUtils.go
Normal file
@@ -0,0 +1 @@
|
||||
package utils
|
||||
55
agent-wdd/utils/PrintUtils.go
Normal file
55
agent-wdd/utils/PrintUtils.go
Normal 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()
|
||||
}
|
||||
24
agent-wdd/utils/TimeUtils.go
Normal file
24
agent-wdd/utils/TimeUtils.go
Normal 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
152
agent-wdd/utils/ZipUtils.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user