diff --git a/agent-wdd/a_run/one-build-and-run.ps1 b/agent-wdd/a_run/one-build-and-run.ps1 new file mode 100644 index 0000000..d531168 --- /dev/null +++ b/agent-wdd/a_run/one-build-and-run.ps1 @@ -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 +} \ No newline at end of file diff --git a/agent-wdd/a_run/one-build-and-upload.ps1 b/agent-wdd/a_run/one-build-and-upload.ps1 new file mode 100644 index 0000000..9ab9edf --- /dev/null +++ b/agent-wdd/a_run/one-build-and-upload.ps1 @@ -0,0 +1,33 @@ + +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\WddSuperAgent\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\WddSuperAgent\agent-wdd\a_run" + +# 删除上面存在的旧的内容 +mc.exe rm oracle-seoul-2/seoul-2/agent-wdd_linux_amd64 + + +# 上传文件 +mc.exe cp C:\Users\wddsh\Documents\IdeaProjects\WddSuperAgent\agent-wdd\a_run\build\agent-wdd_linux_amd64 oracle-seoul-2/seoul-2/ + + + + + + + + + diff --git a/agent-wdd/a_run/readme_help.txt b/agent-wdd/a_run/readme_help.txt new file mode 100644 index 0000000..3c0c9af --- /dev/null +++ b/agent-wdd/a_run/readme_help.txt @@ -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相关的内容 \ No newline at end of file diff --git a/agent-wdd/a_run/run_test.sh b/agent-wdd/a_run/run_test.sh new file mode 100644 index 0000000..468f888 --- /dev/null +++ b/agent-wdd/a_run/run_test.sh @@ -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 + + diff --git a/agent-wdd/a_run/test-shell.sh b/agent-wdd/a_run/test-shell.sh new file mode 100644 index 0000000..ccec991 --- /dev/null +++ b/agent-wdd/a_run/test-shell.sh @@ -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 diff --git a/agent-wdd/host_info/CPU.go b/agent-wdd/host_info/CPU.go new file mode 100644 index 0000000..dda9e45 --- /dev/null +++ b/agent-wdd/host_info/CPU.go @@ -0,0 +1,177 @@ +package host_info + +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 +} diff --git a/agent-wdd/host_info/Config.go b/agent-wdd/host_info/Config.go new file mode 100644 index 0000000..51c730f --- /dev/null +++ b/agent-wdd/host_info/Config.go @@ -0,0 +1,206 @@ +package host_info + +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() +} diff --git a/agent-wdd/host_info/Disk.go b/agent-wdd/host_info/Disk.go new file mode 100644 index 0000000..6fc6db0 --- /dev/null +++ b/agent-wdd/host_info/Disk.go @@ -0,0 +1,114 @@ +package host_info + +import ( + "agent-wdd/log" + "bufio" + "os" + "path/filepath" + "strings" +) + +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() +} diff --git a/agent-wdd/host_info/Memory.go b/agent-wdd/host_info/Memory.go new file mode 100644 index 0000000..1f1e6e9 --- /dev/null +++ b/agent-wdd/host_info/Memory.go @@ -0,0 +1,140 @@ +package host_info + +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() +} diff --git a/agent-wdd/host_info/Network.go b/agent-wdd/host_info/Network.go new file mode 100644 index 0000000..dd33902 --- /dev/null +++ b/agent-wdd/host_info/Network.go @@ -0,0 +1,273 @@ +package host_info + +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, 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) + } + } +} diff --git a/agent-wdd/host_info/OS.go b/agent-wdd/host_info/OS.go new file mode 100644 index 0000000..b65fef4 --- /dev/null +++ b/agent-wdd/host_info/OS.go @@ -0,0 +1,117 @@ +package host_info + +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" + } +}