Files
WddSuperAgent/agent-wdd/cmd/Cloudflare.go

724 lines
19 KiB
Go
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
// 对于AAAAA或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
}
// 对于AAAAA或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 "记录内容"
}
}