diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ec8c315..6fb08a7 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -5,35 +5,25 @@ - - - - - - - - - - - - - + + + + + + - + + + - - - - - - - - - - - + + + + + + - + + + + + + @@ -101,7 +97,7 @@ diff --git a/agent-wdd/build/agent-wdd_linux_amd64 b/agent-wdd/build/agent-wdd_linux_amd64 index 4ffe4cf..f92f1b1 100644 Binary files a/agent-wdd/build/agent-wdd_linux_amd64 and b/agent-wdd/build/agent-wdd_linux_amd64 differ diff --git a/agent-wdd/cert_manager_wdd/AcmeClient.go b/agent-wdd/cert_manager_wdd/AcmeClient.go index 875c0d6..939d12f 100644 --- a/agent-wdd/cert_manager_wdd/AcmeClient.go +++ b/agent-wdd/cert_manager_wdd/AcmeClient.go @@ -366,9 +366,3 @@ func createCSR(privateKey crypto.Signer, domain string) ([]byte, error) { return csrDER, nil } - -// pemEncode 将DER格式数据编码为PEM格式 -func pemEncode(typ string, der []byte) []byte { - block := &pem.Block{Type: typ, Bytes: der} - return pem.EncodeToMemory(block) -} diff --git a/agent-wdd/cloudflare/Cloudflare.go b/agent-wdd/cloudflare/CloudflareAPI.go similarity index 90% rename from agent-wdd/cloudflare/Cloudflare.go rename to agent-wdd/cloudflare/CloudflareAPI.go index f92d148..118eb44 100644 --- a/agent-wdd/cloudflare/Cloudflare.go +++ b/agent-wdd/cloudflare/CloudflareAPI.go @@ -21,10 +21,11 @@ const ( // CloudflareClient represents a client for interacting with Cloudflare API type CloudflareClient struct { - apiToken string - baseURL string - client *http.Client - userAgent string + apiToken string + baseURL string + client *http.Client + userAgent string + dnsNameToIDMap map[string]string // 域名到ID的映射 } var ( @@ -151,3 +152,14 @@ func (c *CloudflareClient) doRequest(method, url string, body interface{}) (*Res log.Debug("Request completed successfully") return &cfResp, nil } + +// GetDNSNameToIDMap 获取域名到ID的映射 +func (c *CloudflareClient) GetDNSNameToIDMap(zoneID string) map[string]string { + + // 如果dnsNameToIDMap为空,则获取所有域名到ID的映射 + if c.dnsNameToIDMap == nil { + c.ListDNSRecords(zoneID, nil) + } + + return c.dnsNameToIDMap +} diff --git a/agent-wdd/cloudflare/Cloudflare_test.go b/agent-wdd/cloudflare/CloudflareAPI_test.go similarity index 100% rename from agent-wdd/cloudflare/Cloudflare_test.go rename to agent-wdd/cloudflare/CloudflareAPI_test.go diff --git a/agent-wdd/cloudflare/DNS.go b/agent-wdd/cloudflare/DNS.go index 724a542..10e859e 100644 --- a/agent-wdd/cloudflare/DNS.go +++ b/agent-wdd/cloudflare/DNS.go @@ -110,6 +110,12 @@ func (c *CloudflareClient) ListDNSRecords(zoneID string, filter *DNSRecordFilter return nil, fmt.Errorf("failed to unmarshal DNS records: %w", err) } + // 将域名和ID的映射关系存储到dnsNameToIDMap中 + c.dnsNameToIDMap = make(map[string]string, len(records)) + for _, record := range records { + c.dnsNameToIDMap[record.Name] = record.ID + } + log.Info("Successfully retrieved %d DNS records for zone ID: %s", len(records), zoneID) return records, nil } diff --git a/agent-wdd/cloudflare/Zone.go b/agent-wdd/cloudflare/Zone.go index 5caa18e..4f7c5be 100644 --- a/agent-wdd/cloudflare/Zone.go +++ b/agent-wdd/cloudflare/Zone.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "agent-wdd/log" ) @@ -179,6 +180,23 @@ func (c *CloudflareClient) GetZone(identifier string) (*Zone, error) { if isID { endpoint = fmt.Sprintf("%s/zones/%s", c.baseURL, identifier) } else { + + // tc.hk.107421.xyz ipv6.t2.107421.xyz 转换为 107421.xyz + // 寻找.的个数 如果大于 从后往前找到 第二个 . 的位置 + dotCount := strings.Count(identifier, ".") + if dotCount > 1 { + // 从后往前找到 第二个 . 的位置 + lastDotIndex := strings.LastIndex(identifier, ".") + secondLastDotIndex := strings.LastIndex(identifier[:lastDotIndex], ".") + identifier = identifier[secondLastDotIndex+1:] + } else if dotCount == 0 { + // 没有. 报错 + log.Error("未找到域名 %s 的ZoneID", identifier) + return nil, fmt.Errorf("no zone found with name: %s", identifier) + } + + log.Debug("转换后的域名: %s", identifier) + // List zones with name filter zones, err := c.ListZones(&ZoneFilter{Name: identifier}) if err != nil { diff --git a/agent-wdd/cmd/Acme.go b/agent-wdd/cmd/Acme.go index 6e3a9e1..bb93abc 100644 --- a/agent-wdd/cmd/Acme.go +++ b/agent-wdd/cmd/Acme.go @@ -40,7 +40,7 @@ func addAcmeSubcommands(cmd *cobra.Command) { "/tmp/acme.sh", ) if !ok { - log.Error("下载acme.sh失败", err) + log.Error("下载acme.sh失败 %v", err) return } @@ -105,7 +105,7 @@ func addAcmeSubcommands(cmd *cobra.Command) { // 执行命令 ok, output := op.SingleLineCommandExecutor([]string{"/root/.acme.sh/acme.sh", "--list"}) if !ok { - log.Error("列出acme全部的证书失败", output) + log.Error("列出acme全部的证书失败 %v", output) return } diff --git a/agent-wdd/cmd/Base.go b/agent-wdd/cmd/Base.go index 5f5f98d..162a35d 100644 --- a/agent-wdd/cmd/Base.go +++ b/agent-wdd/cmd/Base.go @@ -2,7 +2,7 @@ package cmd import ( "agent-wdd/cmd/beans" - "agent-wdd/config" + "agent-wdd/host_info" "agent-wdd/log" "agent-wdd/op" "agent-wdd/utils" @@ -96,13 +96,13 @@ func addBaseSubcommands(cmd *cobra.Command) { log.Info("Selinux 关闭!") // 如果configCache的OS为空,则收集OS信息 - if config.ConfigCache.Agent.OS.Hostname == "" { + if host_info.ConfigCache.Agent.OS.Hostname == "" { log.Warning("ConfigCache OS is nil") - config.ConfigCache.Agent.OS.Gather() - config.ConfigCache.Agent.OS.SaveConfig() + host_info.ConfigCache.Agent.OS.Gather() + host_info.ConfigCache.Agent.OS.SaveConfig() } - os := config.ConfigCache.Agent.OS + os := host_info.ConfigCache.Agent.OS if os.IsUbuntuType { log.Info("Ubuntu 系统,跳过关闭selinux!") return @@ -234,7 +234,7 @@ func addBaseSubcommands(cmd *cobra.Command) { log.Info("Common tool installation!") // Whether It can connect to internet - if config.CanConnectInternet() <= 1 { + if host_info.CanConnectInternet() <= 1 { log.Error("服务器无法连接互联网,无法执行tools") return } @@ -244,7 +244,7 @@ func addBaseSubcommands(cmd *cobra.Command) { packOperator := op.AgentPackOperator packOperator.PackageInit() - os := config.ConfigCache.Agent.OS + os := host_info.ConfigCache.Agent.OS if os.IsUbuntuType { packOperator.Install(ubuntuCommonTools) } else { @@ -299,7 +299,7 @@ func addHarborSubcommands(harborCmd *cobra.Command) { utils.UnzipFile(harborLocalInstallPath, "/root/wdd/") // 获取本机的内网IPv4地址 - configCache := config.ConfigCache + configCache := host_info.ConfigCache if len(configCache.Agent.Network.Interfaces) == 0 { log.Error("没有获取到本机的ipv4地址,无法安装harbor! 请执行 info all !") } @@ -596,7 +596,7 @@ func addDockerSubcommands(cmd *cobra.Command) { packOperator := op.AgentPackOperator packOperator.PackageInit() - configCache := config.ConfigCache + configCache := host_info.ConfigCache if configCache.Agent.OS.IsUbuntuType { // 安装apt-transport-https ca-certificates curl gnupg software-properties-common 依赖部分 packOperator.Install([]string{"apt-transport-https", "ca-certificates", "curl", "gnupg", "software-properties-common"}) @@ -886,7 +886,7 @@ func addDockerSubcommands(cmd *cobra.Command) { } // 获取当前内网IP - ip := config.ConfigCache.Agent.Network.Interfaces[0].IPv4 + ip := host_info.ConfigCache.Agent.Network.Interfaces[0].IPv4 if ip == "" { log.Error("获取当前内网IP失败, 无法进行docker config配置!") return @@ -956,7 +956,7 @@ func addDockerComposeSubcommands(cmd *cobra.Command) { log.Info("安装 Docker Compose 版本: %s", version) // 检查是否可以连接互联网 - if config.CanConnectInternet() <= 1 { + if host_info.CanConnectInternet() <= 1 { log.Error("服务器无法连接互联网,无法在线安装 Docker Compose") return } diff --git a/agent-wdd/cmd/CertManager.go b/agent-wdd/cmd/CertManager.go index 2fb4486..81bf616 100644 --- a/agent-wdd/cmd/CertManager.go +++ b/agent-wdd/cmd/CertManager.go @@ -11,28 +11,17 @@ import ( "github.com/spf13/cobra" ) -var ( +const ( // 证书管理命令选项 - cfAPIToken string - certDir string - emailAddr string - caServer string - daysRenewal int + cfAPIToken = "T7LxBemfe8SNGWkT9uz2XIc1e22ifAbBv_POJvDP" + certDir = "/root/wdd/cert_manager_wdd/" + emailAddr = "wdd@gmail.com" + caServer = "zerossl" + daysRenewal = 30 ) // 初始化证书管理配置 func initCertManagerConfig() *cert_manager_wdd.CertManager { - // 如果未指定API令牌,提示用户 - if cfAPIToken == "" { - log.Error("未指定Cloudflare API令牌,请使用--token参数设置") - os.Exit(1) - } - - // 如果未指定邮箱地址,使用默认值 - if emailAddr == "" { - emailAddr = "cert@example.com" - log.Warning("未指定邮箱地址,使用默认值: %s", emailAddr) - } // 创建证书管理器 certManager := cert_manager_wdd.NewCertManager(certDir, cfAPIToken, emailAddr) @@ -59,12 +48,6 @@ func initCertManagerConfig() *cert_manager_wdd.CertManager { // 添加证书管理命令 func addCertManagerSubcommands(cmd *cobra.Command) { - // 全局标志 - cmd.PersistentFlags().StringVar(&cfAPIToken, "token", "", "Cloudflare API令牌") - cmd.PersistentFlags().StringVar(&certDir, "cert-dir", "", "证书保存目录") - cmd.PersistentFlags().StringVar(&emailAddr, "email", "", "申请证书使用的邮箱") - cmd.PersistentFlags().StringVar(&caServer, "ca", "letsencrypt", "CA服务器 (letsencrypt, zerossl)") - cmd.PersistentFlags().IntVar(&daysRenewal, "days", 30, "证书更新阈值天数") // 申请证书命令 applyCmd := &cobra.Command{ diff --git a/agent-wdd/cmd/Cloudflare.go b/agent-wdd/cmd/Cloudflare.go new file mode 100644 index 0000000..99f424c --- /dev/null +++ b/agent-wdd/cmd/Cloudflare.go @@ -0,0 +1,723 @@ +package cmd + +import ( + "agent-wdd/cloudflare" + "agent-wdd/log" + "bufio" + "fmt" + "os" + "strconv" + "strings" + "text/tabwriter" + + "github.com/spf13/cobra" +) + +const ( + // Cloudflare API设置选项 + cfAPITokenDefault = "T7LxBemfe8SNGWkT9uz2XIc1e22ifAbBv_POJvDP" // 默认的API令牌,实际应用中应从配置文件或环境变量获取 +) + +// DNS记录类型常量 +const ( + DNSTypeA = "A" + DNSTypeAAAA = "AAAA" + DNSTypeCNAME = "CNAME" + DNSTypeTXT = "TXT" + DNSTypeMX = "MX" + DNSTypeSRV = "SRV" +) + +// 初始化Cloudflare客户端 +func initCloudflareClient() *cloudflare.CloudflareClient { + log.Debug("初始化Cloudflare客户端") + return cloudflare.GetInstance(cfAPITokenDefault) +} + +// 询问用户确认 +func askForConfirmation(prompt string) bool { + reader := bufio.NewReader(os.Stdin) + for { + fmt.Printf("%s [y/n]: ", prompt) + response, err := reader.ReadString('\n') + if err != nil { + log.Error("读取用户输入失败: %v", err) + return false + } + + response = strings.ToLower(strings.TrimSpace(response)) + if response == "y" || response == "yes" { + return true + } else if response == "n" || response == "no" { + return false + } + } +} + +// 添加Cloudflare命令 +func addCloudflareSubcommands(cmd *cobra.Command) { + // 域名管理命令 + domainCmd := &cobra.Command{ + Use: "domain", + Short: "Cloudflare域名管理", + Long: "管理Cloudflare上的域名(查询域名列表、查看域名详情等)", + } + + // 域名列表命令 + listDomainsCmd := &cobra.Command{ + Use: "list", + Short: "列出所有域名", + Run: func(cmd *cobra.Command, args []string) { + log.Info("获取域名列表") + client := initCloudflareClient() + zones, err := client.ListZones(nil) + if err != nil { + log.Error("获取域名列表失败: %v", err) + os.Exit(1) + } + + if len(zones) == 0 { + log.Info("未找到任何域名") + return + } + + printDomainsList(zones) + }, + } + + // 查看域名详情命令 + getDomainCmd := &cobra.Command{ + Use: "get [域名]", + Short: "获取域名详情", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + domain := args[0] + log.Info("获取域名详情: %s", domain) + client := initCloudflareClient() + zone, err := client.GetZone(domain) + if err != nil { + log.Error("获取域名详情失败: %v", err) + os.Exit(1) + } + + printDomainDetails(zone) + }, + } + + // DNS记录管理命令 + dnsCmd := &cobra.Command{ + Use: "dns", + Short: "DNS记录管理", + Long: "管理Cloudflare上的DNS记录(查询、创建、更新、删除)", + } + + // 列出DNS记录命令 + listDNSCmd := &cobra.Command{ + Use: "list [域名]", + Short: "列出域名的所有DNS记录", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + domain := args[0] + log.Info("获取域名 %s 的DNS记录", domain) + client := initCloudflareClient() + + // 先获取zone ID + zone, err := client.GetZone(domain) + if err != nil { + log.Error("获取域名信息失败: %v", err) + os.Exit(1) + } + + records, err := client.ListDNSRecords(zone.ID, nil) + if err != nil { + log.Error("获取DNS记录失败: %v", err) + os.Exit(1) + } + + if len(records) == 0 { + log.Info("未找到任何DNS记录") + return + } + + printDNSRecordsList(records, domain) + }, + } + + // 创建DNS记录命令 + createDNSCmd := &cobra.Command{ + Use: "create [域名]", + Short: "创建DNS记录", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + domain := args[0] + log.Info("为域名 %s 创建DNS记录", domain) + client := initCloudflareClient() + + // 先获取zone ID + zone, err := client.GetZone(domain) + if err != nil { + log.Error("获取域名信息失败: %v", err) + os.Exit(1) + } + + // 交互式收集DNS记录信息 + record, err := collectDNSRecordInfo(domain) + if err != nil { + log.Error("收集DNS记录信息失败: %v", err) + os.Exit(1) + } + + // 确认信息 + fmt.Println("\n即将创建以下DNS记录:") + fmt.Printf("名称: %s\n", record.Name) + fmt.Printf("类型: %s\n", record.Type) + fmt.Printf("内容: %s\n", record.Content) + fmt.Printf("TTL: %d\n", record.TTL) + if record.Priority > 0 { + fmt.Printf("优先级: %d\n", record.Priority) + } + fmt.Printf("代理状态: %v\n", record.Proxied) + + if !askForConfirmation("确认创建此DNS记录?") { + log.Info("已取消创建DNS记录") + return + } + + // 创建DNS记录 + createdRecord, err := client.CreateDNSRecord(zone.ID, record) + if err != nil { + log.Error("创建DNS记录失败: %v", err) + os.Exit(1) + } + + log.Info("DNS记录创建成功") + printDNSRecordDetails(createdRecord, domain) + }, + } + + // 更新DNS记录命令 + updateDNSCmd := &cobra.Command{ + Use: "update [域名]", + Short: "更新DNS记录", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + domain := args[0] + log.Info("更新域名 %s 的DNS记录", domain) + client := initCloudflareClient() + + // 先获取zone ID + zone, err := client.GetZone(domain) + if err != nil { + log.Error("获取域名信息失败: %v", err) + os.Exit(1) + } + + // 获取域名到ID的映射 + dnsNameToIDMap := client.GetDNSNameToIDMap(zone.ID) + if len(dnsNameToIDMap) == 0 { + log.Error("未找到ZoneID %s Zone %s 的任何DNS记录", zone.ID, zone.Name) + os.Exit(1) + } + + // recordID 为空,则获取所有DNS记录 + recordID := dnsNameToIDMap[domain] + if recordID == "" { + log.Error("未找到zoneID %s Zone %s 域名 %s 的DNS记录", zone.ID, zone.Name, domain) + os.Exit(1) + } + + // 获取当前记录 + currentRecord, err := client.GetDNSRecord(zone.ID, recordID) + if err != nil { + log.Error("获取DNS记录失败: %v", err) + os.Exit(1) + } + + // 显示当前记录 + fmt.Println("当前DNS记录:") + printDNSRecordDetails(currentRecord, domain) + + // 交互式收集更新后的DNS记录信息 + updatedRecord, err := updateDNSRecordInfo(*currentRecord) + if err != nil { + log.Error("收集DNS记录更新信息失败: %v", err) + os.Exit(1) + } + + // 确认信息 + fmt.Println("\n即将更新为以下DNS记录:") + fmt.Printf("名称: %s\n", updatedRecord.Name) + fmt.Printf("类型: %s\n", updatedRecord.Type) + fmt.Printf("内容: %s\n", updatedRecord.Content) + fmt.Printf("TTL: %d\n", updatedRecord.TTL) + if updatedRecord.Priority > 0 { + fmt.Printf("优先级: %d\n", updatedRecord.Priority) + } + fmt.Printf("代理状态: %v\n", updatedRecord.Proxied) + + if !askForConfirmation("确认更新此DNS记录?") { + log.Info("已取消更新DNS记录") + return + } + + // 更新DNS记录 + updatedRecordResult, err := client.UpdateDNSRecord(zone.ID, recordID, updatedRecord) + if err != nil { + log.Error("更新DNS记录失败: %v", err) + os.Exit(1) + } + + log.Info("DNS记录更新成功") + printDNSRecordDetails(updatedRecordResult, domain) + }, + } + + // 删除DNS记录命令 + deleteDNSCmd := &cobra.Command{ + Use: "delete [域名]", + Short: "删除DNS记录", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + domain := args[0] + log.Info("删除域名 %s 的DNS记录", domain) + client := initCloudflareClient() + + // 先获取zone ID + zone, err := client.GetZone(domain) + if err != nil { + log.Error("获取域名信息失败: %v", err) + os.Exit(1) + } + + // 获取域名到ID的映射 + dnsNameToIDMap := client.GetDNSNameToIDMap(zone.ID) + if len(dnsNameToIDMap) == 0 { + log.Error("未找到ZoneID %s Zone %s 的任何DNS记录", zone.ID, zone.Name) + os.Exit(1) + } + + // recordID 为空,则获取所有DNS记录 + recordID := dnsNameToIDMap[domain] + if recordID == "" { + log.Error("未找到zoneID %s Zone %s 域名 %s 的DNS记录", zone.ID, zone.Name, domain) + os.Exit(1) + } + + // 获取要删除的记录信息 + record, err := client.GetDNSRecord(zone.ID, recordID) + if err != nil { + log.Error("获取DNS记录信息失败: %v", err) + os.Exit(1) + } + + // 显示要删除的记录 + fmt.Println("即将删除以下DNS记录:") + printDNSRecordDetails(record, domain) + + // 请求确认 + if !askForConfirmation("确认删除此DNS记录? 此操作不可恢复!") { + log.Info("已取消删除DNS记录") + return + } + + // 二次确认 + fmt.Println("\n⚠️ 警告: 删除DNS记录可能会影响网站访问、邮件服务等功能") + if !askForConfirmation("再次确认删除此DNS记录?") { + log.Info("已取消删除DNS记录") + return + } + + // 删除DNS记录 + success, err := client.DeleteDNSRecord(zone.ID, recordID) + if err != nil { + log.Error("删除DNS记录失败: %v", err) + os.Exit(1) + } + + if success { + log.Info("DNS记录删除成功") + } else { + log.Error("DNS记录删除失败") + os.Exit(1) + } + }, + } + + // 添加域名管理子命令 + domainCmd.AddCommand(listDomainsCmd, getDomainCmd) + + // 添加DNS管理子命令 + dnsCmd.AddCommand(listDNSCmd, createDNSCmd, updateDNSCmd, deleteDNSCmd) + + // 将所有主要命令添加到父命令 + cmd.AddCommand(domainCmd, dnsCmd) +} + +// 打印域名列表 +func printDomainsList(zones []cloudflare.Zone) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + fmt.Fprintln(w, "域名\t状态\t名称服务器\t创建时间") + fmt.Fprintln(w, "----\t----\t---------\t--------") + + for _, zone := range zones { + nameServers := strings.Join(zone.NameServers, ", ") + createdOn := zone.CreatedOn[:10] // 仅显示日期部分 + status := zone.Status + if zone.Paused { + status += " (已暂停)" + } + + fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", + zone.Name, + status, + nameServers, + createdOn) + } + + w.Flush() +} + +// 打印域名详情 +func printDomainDetails(zone *cloudflare.Zone) { + fmt.Printf("================== 域名详情 ==================\n") + fmt.Printf("域名: %s\n", zone.Name) + fmt.Printf("ID: %s\n", zone.ID) + fmt.Printf("状态: %s\n", zone.Status) + fmt.Printf("暂停: %v\n", zone.Paused) + fmt.Printf("类型: %s\n", zone.Type) + fmt.Printf("开发模式: %d\n", zone.DevelopmentMode) + fmt.Printf("名称服务器: %s\n", strings.Join(zone.NameServers, ", ")) + fmt.Printf("原始名称服务器: %s\n", strings.Join(zone.OriginalNameServers, ", ")) + fmt.Printf("修改时间: %s\n", zone.ModifiedOn) + fmt.Printf("创建时间: %s\n", zone.CreatedOn) + fmt.Printf("激活时间: %s\n", zone.ActivatedOn) + if zone.Account.Name != "" { + fmt.Printf("账户: %s (%s)\n", zone.Account.Name, zone.Account.ID) + } + fmt.Printf("==================================================\n") +} + +// 打印DNS记录列表 +func printDNSRecordsList(records []cloudflare.DNSRecord, domain string) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + fmt.Fprintf(w, "域名: %s\n\n", domain) + fmt.Fprintln(w, "名称\t类型\t内容\tTTL\t代理\t优先级\t备注") + fmt.Fprintln(w, "----\t----\t----\t---\t----\t-----\t----") + + for _, record := range records { + ttl := "自动" + if record.TTL > 1 { + ttl = strconv.Itoa(record.TTL) + } + + priority := "-" + if record.Priority > 0 { + priority = strconv.Itoa(record.Priority) + } + + proxied := "否" + if record.Proxied { + proxied = "是" + } + + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", + record.Name, + record.Type, + record.Content, + ttl, + proxied, + priority, + record.Comment) + } + + w.Flush() +} + +// 打印DNS记录详情 +func printDNSRecordDetails(record *cloudflare.DNSRecord, domain string) { + fmt.Printf("================ DNS记录详情 ================\n") + fmt.Printf("域名: %s\n", domain) + fmt.Printf("ID: %s\n", record.ID) + fmt.Printf("名称: %s\n", record.Name) + fmt.Printf("类型: %s\n", record.Type) + fmt.Printf("内容: %s\n", record.Content) + + ttl := "自动" + if record.TTL > 1 { + ttl = strconv.Itoa(record.TTL) + } + fmt.Printf("TTL: %s\n", ttl) + + fmt.Printf("代理: %v\n", record.Proxied) + + if record.Priority > 0 { + fmt.Printf("优先级: %d\n", record.Priority) + } + + if record.Comment != "" { + fmt.Printf("备注: %s\n", record.Comment) + } + + if record.CreatedOn != "" { + fmt.Printf("创建时间: %s\n", record.CreatedOn) + } + + if record.ModifiedOn != "" { + fmt.Printf("修改时间: %s\n", record.ModifiedOn) + } + fmt.Printf("==============================================\n") +} + +// 交互式收集DNS记录信息 +func collectDNSRecordInfo(domain string) (cloudflare.DNSRecord, error) { + reader := bufio.NewReader(os.Stdin) + record := cloudflare.DNSRecord{ + Proxied: false, // 默认不代理 + TTL: 1, // 默认自动TTL + } + + // 获取记录名称 + fmt.Print("请输入记录名称 (例如: www): ") + name, err := reader.ReadString('\n') + if err != nil { + return record, fmt.Errorf("读取输入失败: %w", err) + } + name = strings.TrimSpace(name) + + // 如果用户仅输入@,则使用根域名 + if name == "@" { + record.Name = domain + } else if name != "" { + // 否则添加域名后缀 + record.Name = name + "." + domain + } else { + return record, fmt.Errorf("记录名称不能为空") + } + + // 获取记录类型 + fmt.Printf("请选择记录类型:\n") + fmt.Printf("1) A (IPv4地址)\n") + fmt.Printf("2) AAAA (IPv6地址)\n") + fmt.Printf("3) CNAME (别名)\n") + fmt.Printf("4) TXT (文本记录)\n") + fmt.Printf("5) MX (邮件服务器)\n") + fmt.Printf("6) SRV (服务记录)\n") + fmt.Print("请输入选项 (1-6): ") + typeChoice, err := reader.ReadString('\n') + if err != nil { + return record, fmt.Errorf("读取输入失败: %w", err) + } + typeChoice = strings.TrimSpace(typeChoice) + + switch typeChoice { + case "1": + record.Type = DNSTypeA + case "2": + record.Type = DNSTypeAAAA + case "3": + record.Type = DNSTypeCNAME + case "4": + record.Type = DNSTypeTXT + case "5": + record.Type = DNSTypeMX + case "6": + record.Type = DNSTypeSRV + default: + return record, fmt.Errorf("无效的记录类型选择") + } + + // 获取记录内容 + fmt.Printf("请输入记录内容 (%s): ", getContentHint(record.Type)) + content, err := reader.ReadString('\n') + if err != nil { + return record, fmt.Errorf("读取输入失败: %w", err) + } + content = strings.TrimSpace(content) + if content == "" { + return record, fmt.Errorf("记录内容不能为空") + } + record.Content = content + + // 对于MX记录,获取优先级 + if record.Type == DNSTypeMX { + fmt.Print("请输入优先级 (1-100,默认10): ") + priorityStr, err := reader.ReadString('\n') + if err != nil { + return record, fmt.Errorf("读取输入失败: %w", err) + } + priorityStr = strings.TrimSpace(priorityStr) + + priority := 10 // 默认优先级 + if priorityStr != "" { + p, err := strconv.Atoi(priorityStr) + if err != nil { + return record, fmt.Errorf("无效的优先级: %w", err) + } + if p < 1 || p > 100 { + return record, fmt.Errorf("优先级必须在1到100之间") + } + priority = p + } + record.Priority = priority + } + + // 获取TTL + fmt.Print("请输入TTL (1表示自动,最小值120,建议值1800或3600): ") + ttlStr, err := reader.ReadString('\n') + if err != nil { + return record, fmt.Errorf("读取输入失败: %w", err) + } + ttlStr = strings.TrimSpace(ttlStr) + + if ttlStr != "" { + ttl, err := strconv.Atoi(ttlStr) + if err != nil { + return record, fmt.Errorf("无效的TTL: %w", err) + } + if ttl != 1 && ttl < 120 { + return record, fmt.Errorf("TTL不能小于120") + } + record.TTL = ttl + } + + // 对于A,AAAA或CNAME记录,询问是否代理 + if record.Type == DNSTypeA || record.Type == DNSTypeAAAA || record.Type == DNSTypeCNAME { + fmt.Print("是否通过Cloudflare代理 (y/n,默认n): ") + proxyStr, err := reader.ReadString('\n') + if err != nil { + return record, fmt.Errorf("读取输入失败: %w", err) + } + proxyStr = strings.ToLower(strings.TrimSpace(proxyStr)) + if proxyStr == "y" || proxyStr == "yes" { + record.Proxied = true + } + } + + // 获取备注(可选) + fmt.Print("请输入备注 (可选): ") + comment, err := reader.ReadString('\n') + if err != nil { + return record, fmt.Errorf("读取输入失败: %w", err) + } + comment = strings.TrimSpace(comment) + if comment != "" { + record.Comment = comment + } + + return record, nil +} + +// 更新DNS记录信息 +func updateDNSRecordInfo(currentRecord cloudflare.DNSRecord) (cloudflare.DNSRecord, error) { + reader := bufio.NewReader(os.Stdin) + updatedRecord := currentRecord + + // 获取记录内容 + fmt.Printf("请输入新的记录内容 (%s) [当前值: %s]: ", getContentHint(currentRecord.Type), currentRecord.Content) + content, err := reader.ReadString('\n') + if err != nil { + return updatedRecord, fmt.Errorf("读取输入失败: %w", err) + } + content = strings.TrimSpace(content) + if content != "" { + updatedRecord.Content = content + } + + // 对于MX记录,获取优先级 + if currentRecord.Type == DNSTypeMX { + fmt.Printf("请输入新的优先级 (1-100) [当前值: %d]: ", currentRecord.Priority) + priorityStr, err := reader.ReadString('\n') + if err != nil { + return updatedRecord, fmt.Errorf("读取输入失败: %w", err) + } + priorityStr = strings.TrimSpace(priorityStr) + + if priorityStr != "" { + p, err := strconv.Atoi(priorityStr) + if err != nil { + return updatedRecord, fmt.Errorf("无效的优先级: %w", err) + } + if p < 1 || p > 100 { + return updatedRecord, fmt.Errorf("优先级必须在1到100之间") + } + updatedRecord.Priority = p + } + } + + // 获取TTL + ttlStr := "自动" + if currentRecord.TTL > 1 { + ttlStr = strconv.Itoa(currentRecord.TTL) + } + fmt.Printf("请输入新的TTL (1表示自动,最小值120,建议值1800或3600) [当前值: %s]: ", ttlStr) + newTTLStr, err := reader.ReadString('\n') + if err != nil { + return updatedRecord, fmt.Errorf("读取输入失败: %w", err) + } + newTTLStr = strings.TrimSpace(newTTLStr) + + if newTTLStr != "" { + ttl, err := strconv.Atoi(newTTLStr) + if err != nil { + return updatedRecord, fmt.Errorf("无效的TTL: %w", err) + } + if ttl != 1 && ttl < 120 { + return updatedRecord, fmt.Errorf("TTL不能小于120") + } + updatedRecord.TTL = ttl + } + + // 对于A,AAAA或CNAME记录,询问是否代理 + if currentRecord.Type == DNSTypeA || currentRecord.Type == DNSTypeAAAA || currentRecord.Type == DNSTypeCNAME { + proxyStr := "n" + if currentRecord.Proxied { + proxyStr = "y" + } + fmt.Printf("是否通过Cloudflare代理 (y/n) [当前值: %s]: ", proxyStr) + newProxyStr, err := reader.ReadString('\n') + if err != nil { + return updatedRecord, fmt.Errorf("读取输入失败: %w", err) + } + newProxyStr = strings.ToLower(strings.TrimSpace(newProxyStr)) + if newProxyStr != "" { + if newProxyStr == "y" || newProxyStr == "yes" { + updatedRecord.Proxied = true + } else if newProxyStr == "n" || newProxyStr == "no" { + updatedRecord.Proxied = false + } + } + } + + // 获取备注(可选) + fmt.Printf("请输入新的备注 (可选) [当前值: %s]: ", currentRecord.Comment) + comment, err := reader.ReadString('\n') + if err != nil { + return updatedRecord, fmt.Errorf("读取输入失败: %w", err) + } + comment = strings.TrimSpace(comment) + if comment != "" { + updatedRecord.Comment = comment + } + + return updatedRecord, nil +} + +// 根据记录类型获取内容提示 +func getContentHint(recordType string) string { + switch recordType { + case DNSTypeA: + return "IPv4地址,例如: 192.168.1.1" + case DNSTypeAAAA: + return "IPv6地址,例如: 2001:db8::1" + case DNSTypeCNAME: + return "域名,例如: example.com" + case DNSTypeTXT: + return "文本内容" + case DNSTypeMX: + return "邮件服务器域名,例如: mail.example.com" + case DNSTypeSRV: + return "SRV记录,例如: 10 5 5060 sip.example.com" + default: + return "记录内容" + } +} diff --git a/agent-wdd/cmd/Info.go b/agent-wdd/cmd/Info.go index 90f106a..fe219b6 100644 --- a/agent-wdd/cmd/Info.go +++ b/agent-wdd/cmd/Info.go @@ -1,7 +1,7 @@ package cmd import ( - "agent-wdd/config" + "agent-wdd/host_info" "github.com/spf13/cobra" ) @@ -13,7 +13,7 @@ func addInfoSubcommands(cmd *cobra.Command) { Use: "os", Short: "主机操作系统相关的信息", Run: func(cmd *cobra.Command, args []string) { - os := config.ConfigCache.Agent.OS + os := host_info.ConfigCache.Agent.OS os.Gather() os.SaveConfig() @@ -25,7 +25,7 @@ func addInfoSubcommands(cmd *cobra.Command) { Use: "network", Short: "主机Network相关的信息", Run: func(cmd *cobra.Command, args []string) { - network := config.ConfigCache.Agent.Network + network := host_info.ConfigCache.Agent.Network network.Gather() network.SaveConfig() }, @@ -36,7 +36,7 @@ func addInfoSubcommands(cmd *cobra.Command) { Use: "cpu", Short: "主机cpu相关的信息", Run: func(cmd *cobra.Command, args []string) { - cpu := config.ConfigCache.Agent.CPU + cpu := host_info.ConfigCache.Agent.CPU cpu.Gather() cpu.Save() }, @@ -47,9 +47,9 @@ func addInfoSubcommands(cmd *cobra.Command) { Use: "mem", Short: "主机memory相关的信息", Run: func(cmd *cobra.Command, args []string) { - mem := config.ConfigCache.Agent.Mem + mem := host_info.ConfigCache.Agent.Mem mem.Gather() - swap := config.ConfigCache.Agent.Swap + swap := host_info.ConfigCache.Agent.Swap swap.Gather() mem.SaveConfig() @@ -63,10 +63,10 @@ func addInfoSubcommands(cmd *cobra.Command) { Use: "disk", Short: "主机disk相关的信息", Run: func(cmd *cobra.Command, args []string) { - //disks := config.ConfigCache.Agent.Disks - config.DiskListGather() + //disks := host_info.ConfigCache.Agent.Disks + host_info.DiskListGather() - config.DiskListSaveConfig() + host_info.DiskListSaveConfig() }, } @@ -83,7 +83,7 @@ func addInfoSubcommands(cmd *cobra.Command) { disk.Run(cmd, args) // 归一化 - config.ConfigCache.NormalizeConfig() + host_info.ConfigCache.NormalizeConfig() }, } diff --git a/agent-wdd/cmd/Proxy.go b/agent-wdd/cmd/Proxy.go index 9b2ddb1..37abf35 100644 --- a/agent-wdd/cmd/Proxy.go +++ b/agent-wdd/cmd/Proxy.go @@ -2,7 +2,7 @@ package cmd import ( "agent-wdd/cmd/xray" - "agent-wdd/config" + "agent-wdd/host_info" "agent-wdd/log" "agent-wdd/op" "agent-wdd/utils" @@ -229,7 +229,7 @@ func installVmess(port string) (bool, []string) { result.Reset() // 清空全局buffer以备后续使用 // 输出 client的配置 - configCache := config.ConfigCache + configCache := host_info.ConfigCache // 获取服务器节点名称 serverNodeName := configCache.Agent.OS.Hostname diff --git a/agent-wdd/cmd/Zsh.go b/agent-wdd/cmd/Zsh.go index 8a49c23..01c253f 100644 --- a/agent-wdd/cmd/Zsh.go +++ b/agent-wdd/cmd/Zsh.go @@ -1,7 +1,7 @@ package cmd import ( - "agent-wdd/config" + "agent-wdd/host_info" "agent-wdd/log" "agent-wdd/op" "agent-wdd/utils" @@ -16,14 +16,14 @@ func addZshSubcommands(cmd *cobra.Command) { // 初始化安装命令 utils.CreateFolder("/root/wdd") - if config.CanConnectInternet() <= 1 { + if host_info.CanConnectInternet() <= 1 { log.Error("无法连接互联网,无法安装zsh") return } // 判定config - if config.ConfigCache.Agent.Network.Public.IPv4 == "" { - network := config.ConfigCache.Agent.Network + if host_info.ConfigCache.Agent.Network.Public.IPv4 == "" { + network := host_info.ConfigCache.Agent.Network network.Gather() network.SaveConfig() } @@ -41,7 +41,7 @@ func addZshSubcommands(cmd *cobra.Command) { ohMyZshInstallUrl := "" - if config.ConfigCache.Agent.Network.Internet == 7 { + if host_info.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" @@ -65,7 +65,7 @@ func addZshSubcommands(cmd *cobra.Command) { "/root/wdd/zsh-install.sh", } - if config.ConfigCache.Agent.Network.Internet == 7 { + if host_info.ConfigCache.Agent.Network.Internet == 7 { op.SingleLineCommandExecutor([]string{ "export", "REMOTE=https://gitea.107421.xyz/zeaslity/ohmyzsh.git", @@ -106,7 +106,7 @@ func addZshSubcommands(cmd *cobra.Command) { } // 下载插件 - if config.ConfigCache.Agent.Network.Internet == 7 { + if host_info.ConfigCache.Agent.Network.Internet == 7 { // 执行硬编码命令, 安装插件 for _, command := range pluginsHardCodeUrl { op.SingleLineCommandExecutor([]string{"git", "clone", command[0], command[1]}) diff --git a/agent-wdd/cmd/root.go b/agent-wdd/cmd/root.go index 71d3227..3c43024 100644 --- a/agent-wdd/cmd/root.go +++ b/agent-wdd/cmd/root.go @@ -1,7 +1,7 @@ package cmd import ( - "agent-wdd/config" + "agent-wdd/host_info" "fmt" "io" "os" @@ -26,7 +26,7 @@ var rootCmd = &cobra.Command{ func init() { // 初始化配置 - cobra.OnInitialize(config.InitConfig) + cobra.OnInitialize(host_info.InitConfig) } func Execute() { @@ -111,6 +111,9 @@ func Execute() { } addCertManagerSubcommands(certCmd) + // 添加Cloudflare命令 + addCloudflareSubcommands(rootCmd) + helpCmd := &cobra.Command{ Use: "help", Short: "帮助信息", diff --git a/agent-wdd/host_info/Config.go b/agent-wdd/host_info/Config.go index 51c730f..2aa4380 100644 --- a/agent-wdd/host_info/Config.go +++ b/agent-wdd/host_info/Config.go @@ -127,13 +127,13 @@ func InitConfig() { v.SetConfigType("yaml") if err := v.ReadInConfig(); err != nil { - log.Error("读取配置文件失败: %w", err) + log.Error("读取配置文件失败: %v", err) panic(err) } if err := v.Unmarshal(&ConfigCache); err != nil { - log.Error("解析配置失败: %w", err) + log.Error("解析配置失败: %v", err) panic(err) } @@ -150,11 +150,11 @@ func SaveConfig() { data, err := yaml.Marshal(&ConfigCache) if err != nil { - log.Error("YAML序列化失败: %w", err) + log.Error("YAML序列化失败: %v", err) } if err := os.WriteFile(WddConfigFilePath, data, 0644); err != nil { - log.Error("写入文件失败: %w", err) + log.Error("写入文件失败: %v", err) } } diff --git a/agent-wdd/log/CallerLog.go b/agent-wdd/log/CallerLog.go index 5eefc6f..66eafde 100644 --- a/agent-wdd/log/CallerLog.go +++ b/agent-wdd/log/CallerLog.go @@ -8,6 +8,10 @@ import ( "time" ) +var ( + DebugMode = true +) + const ( colorReset = "\033[0m" colorRed = "\033[31m" @@ -16,15 +20,10 @@ const ( 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...) + if DebugMode { + log("DEBUG", colorBlue, format, args...) + } } func Info(format string, args ...interface{}) { diff --git a/agent-wdd/op/Excutor.go b/agent-wdd/op/Excutor.go index e4f5266..a39f22d 100644 --- a/agent-wdd/op/Excutor.go +++ b/agent-wdd/op/Excutor.go @@ -213,45 +213,3 @@ func RealTimeCommandExecutor(realTimeCommand []string) (ok bool, resultLog []str 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) -} diff --git a/agent-wdd/op/PackageOperator.go b/agent-wdd/op/PackageOperator.go index b0299f5..e0041f4 100644 --- a/agent-wdd/op/PackageOperator.go +++ b/agent-wdd/op/PackageOperator.go @@ -1,7 +1,7 @@ package op import ( - "agent-wdd/config" + "agent-wdd/host_info" "agent-wdd/log" "encoding/json" "fmt" @@ -72,10 +72,10 @@ func (op *PackageOperator) Install(tools []string) bool { func (op *PackageOperator) PackageInit() bool { log.Info("PackageInit !") - // beautifulPrintListWithTitle(config.ConfigCache) + // beautifulPrintListWithTitle(host_info.ConfigCache) // package init - os := config.ConfigCache.Agent.OS + os := host_info.ConfigCache.Agent.OS if os.PackInit { log.Info("PackageInit already done! skip!") @@ -106,7 +106,7 @@ func (op *PackageOperator) PackageInitForce() bool { log.Info("PackageInitForce !") - os := config.ConfigCache.Agent.OS + os := host_info.ConfigCache.Agent.OS if os.IsUbuntuType { ok, resultLog := SingleLineCommandExecutor(aptPackageOperator.initCommand) @@ -143,13 +143,13 @@ func generatePackageOperator() bool { } // 检查是否可以连接互联网 - if config.CanConnectInternet() <= 1 { + if host_info.CanConnectInternet() <= 1 { log.Error("服务器无法连接互联网,无法初始化包管理器") return false } // 检查本机是否存在OS的信息 - os := config.ConfigCache.Agent.OS + os := host_info.ConfigCache.Agent.OS if os.Hostname == "" { os.Gather() os.SaveConfig() @@ -169,7 +169,7 @@ func generatePackageOperator() bool { // GetLatestGithubReleaseVersion 获取GitHub仓库的最新版本 func GetLatestGithubReleaseVersion(repoOwner, repoName string) (string, error) { // 检查是否可以连接互联网 - if config.CanConnectInternet() <= 1 { + if host_info.CanConnectInternet() <= 1 { return "", fmt.Errorf("服务器无法连接互联网,无法获取GitHub最新版本") }