package main import ( "acme-client/src" "bufio" "encoding/json" "fmt" "github.com/go-acme/lego/v4/log" "io" "net/http" "os" "strconv" "strings" ) func main() { // 读取server参数,如果没有提示添加 config := src.GetClientConfig() server := config.Server if server == "" { log.Println("请输入服务端地址; 如: http://acme.server.com:8080") reader := bufio.NewReader(os.Stdin) serverAddr, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } serverAddr = strings.Trim(serverAddr, "\r\n") config.Server = serverAddr src.WriteConfig() } // 读取服务器RSA公钥, 没有提示添加 rsaPublicKey := config.RsaPublicKey if rsaPublicKey == "" { log.Println("请输入服务端下发的RAS公钥;") reader := bufio.NewReader(os.Stdin) rsaPublicKeyContent, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } rsaPublicKeyContent = strings.Trim(rsaPublicKeyContent, "\r\n") config.RsaPublicKey = rsaPublicKeyContent src.WriteConfig() } // 获取指令, 如果没有则提示添加 args := os.Args if len(args) < 2 { log.Println("请输入命令; 如: add") log.Println("或者输入 help 查看帮助") return } onCommand() } func onCommand() { command := os.Args[1] switch command { case "help": showHelp() case "list": showList() case "add": addConf() case "del": delConf() case "edit": editConf() case "get": getCert() case "-s": onServerCommand() default: log.Fatalf("不支持的指令: %s\n您可以使用 help 查看帮助", command) } } // 帮助 func showHelp() { fmt.Printf("help\t查看帮助\n") fmt.Printf("list\t查看配置列表\n") fmt.Printf("add\t添加配置\n") fmt.Printf("del\t删除配置\n") fmt.Printf("edit\t修改配置\n") fmt.Printf("get\t从获取端获取证书,会自动判断有效期\n") fmt.Printf("\t[-f]\t从获取端获取证书,不判断有效期\n") fmt.Printf("-s list\t查看服务端已配置的Domain名称\n") } // 显示配置列表 func showList() { domains := src.GetClientConfig().Domains if len(domains) == 0 { fmt.Println("暂无配置; 您可以使用 add 命令添加配置") return } for i := range domains { domain := domains[i] fmt.Printf("%d. %s\n", i, domain.Name) fmt.Printf(" - 证书文件: %s\n", domain.CertFile) fmt.Printf(" - 密钥文件: %s\n", domain.KeyFile) fmt.Printf(" - 详情文件: %s\n", domain.InfoFile) fmt.Println() } } // 添加配置 func addConf() { domain := &src.Domain{} name := scanConf("请输入服务端已配置名称;可以通过 -s list 查看", "名称不能为空") domain.Name = name domain.CertFile = scanConf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem", "证书文件路径不能为空") domain.KeyFile = scanConf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem", "私钥文件路径不能为空") domain.InfoFile = scanConf("请输入详情文件存放全路径;如: /data/cert/info.json", "详情文件路径不能为空") config := src.GetClientConfig() config.Domains = append(config.Domains, *domain) src.WriteConfig() fmt.Println("添加成功") showList() } // 删除配置 func delConf() { fmt.Println("当前配置: ") showList() indexInt, _ := selectDomain("请输入要删除的序号") config := src.GetClientConfig() config.Domains = append(config.Domains[:indexInt], config.Domains[indexInt+1:]...) src.WriteConfig() fmt.Println("删除成功") showList() } // 修改配置 func editConf() { fmt.Println("当前配置") showList() _, domain := selectDomain("请输入要修改的序号") msg := fmt.Sprintf("请输入服务端已配置名称;可以通过 -s list 查看\n当前配置: %s\n", domain.Name) domain.Name = scanConf(msg, "名称不能为空") certFile := fmt.Sprintf("请输入证书文件存放全路径;如: /data/cert/fullchain.pem\n当前配置: %s\n", domain.CertFile) domain.CertFile = scanConf(certFile, "证书文件路径不能为空") msg = fmt.Sprintf("请输入私钥文件存放全路径;如: /data/cert/privkey.pem\n当前配置: %s\n", domain.KeyFile) domain.KeyFile = scanConf(msg, "私钥文件路径不能为空") msg = fmt.Sprintf("请输入详情文件存放全路径;如: /data/cert/info.json\n当前配置: %s\n", domain.InfoFile) domain.InfoFile = scanConf(msg, "详情文件路径不能为空") src.WriteConfig() fmt.Println("修改成功") showList() } func getCert() { _, domain := selectDomain("请选择要获取证书的配置编号") args := os.Args isDoGet := len(args) >= 3 && args[3] == "-f" if !isDoGet { infoFile := domain.InfoFile os.Stat(infoFile) //info, err := os.ReadFile(infoFile) //keyFile := domain.KeyFile } //config := src.GetClientConfig() //domains := config.Domains //config.FindDomain(args[2]) } // 服务端命令 func onServerCommand() { args := os.Args if len(args) < 3 { log.Fatal("参数错误, 请检查") } command := args[2] switch command { case "list": showServerList() default: log.Fatal("参数错误, 请检查") } } // 从服务端获取域名列表 func showServerList() { server := src.GetClientConfig().Server token, encryptToken := src.GenToken() url := server + "/api/v1/domain/list?token=" + encryptToken resp, err := http.Get(url) if err != nil { log.Fatal("获取服务端数据失败, 请检查 server 地址是否正确") } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { log.Fatal(err) } result := src.Result{} err = json.Unmarshal(body, &result) if err != nil { log.Fatal(err) } if result.Code != 200 { log.Fatal("读取数据出错; ", result.Msg) } data := result.Data text := src.DecryptByToken(token, data) d := &[]src.SDomain{} err = json.Unmarshal([]byte(text), d) if err != nil { log.Fatal(err) } for i := range *d { domain := (*d)[i] fmt.Printf("- %s\n", domain.Name) fmt.Printf("\t认证域名: [ ") for j := range domain.Host { host := domain.Host[j] fmt.Printf("%s ", host) } fmt.Printf("]\n") } } // 读取用户输入 func scanConf(msg string, errMsg string) string { for { log.Println(msg) reader := bufio.NewReader(os.Stdin) name, err := reader.ReadString('\n') if err != nil { fmt.Println("读取失败;", err) continue } name = strings.Trim(name, "\r\n") if name == "" { fmt.Println(errMsg) continue } return name } } // 选择域名 func selectDomain(msg string) (int, *src.Domain) { index := scanConf(msg, "序号不能为空") indexInt, err := strconv.Atoi(index) if err != nil { log.Fatal("序号错误") } config := src.GetClientConfig() if indexInt >= len(config.Domains) || indexInt < 0 { log.Fatal("序号超出范围") } return indexInt, &config.Domains[indexInt] }