diff --git a/main.go b/main.go index 2e1fed8..221bbf2 100644 --- a/main.go +++ b/main.go @@ -4,8 +4,4 @@ import "acme-mana/src" func main() { src.Start() - //test.TestParseCert() - //test.TestValidExist() - //test.TestParseCertInfo() - } diff --git a/src/acme-client.go b/src/acme-client.go index 0f81e27..893b418 100644 --- a/src/acme-client.go +++ b/src/acme-client.go @@ -27,12 +27,12 @@ func Apply(domain Domain) { log.Fatal(err) } - acmeUser := AcmeUser{ + acmeUser := &AcmeUser{ Email: email, key: privateKey, } - config := lego.NewConfig(&acmeUser) + config := lego.NewConfig(acmeUser) client, err := lego.NewClient(config) if err != nil { diff --git a/src/command.go b/src/command.go index 4c2746f..f4be69e 100644 --- a/src/command.go +++ b/src/command.go @@ -6,7 +6,7 @@ import ( "os" ) -// InitSocket /* +// InitSocket 初始化 socket 文件 func InitSocket() { log.Println("Start listen command") // 删除旧的 socket 文件 diff --git a/src/config.go b/src/config.go index 3cd3618..6a2731d 100644 --- a/src/config.go +++ b/src/config.go @@ -2,19 +2,22 @@ package src import ( "acme-mana/src/crypto" + "bufio" + "fmt" "github.com/go-acme/lego/v4/log" "github.com/go-acme/lego/v4/platform/config/env" "gopkg.in/yaml.v3" "os" + "strings" ) -func ReadConfig() AppConfig { +func ReadConfig() *AppConfig { InitConfig() file, err := os.ReadFile(GetEnvConf().ConfFile) if err != nil { log.Fatal(err) } - var conf AppConfig + var conf *AppConfig err = yaml.Unmarshal(file, &conf) if err != nil { log.Fatal(err) @@ -31,7 +34,7 @@ func InitConfig() { // 配置文件不存在,则创建一个 log.Infof("配置文件不存在,自动创建默认配置文件") // 生成默认配置 - conf := defaultConf() + conf := readNewConf() // 创建一个默认的配置文件 file, err := os.Create(GetEnvConf().ConfFile) if err != nil { @@ -50,6 +53,121 @@ func InitConfig() { } +func readNewConf() *AppConfig { + conf := defaultConf() + log.Println("无配置文件, 生成配置文件") + + conf.CertDir = scanConfDefault("请输入证书保存目录; 默认为 cert", "cert") + isGenMsg := "是否需要自动生成用于数据传输加密的RSA密钥对? \n请输入yes(Y)/no(N) 默认为 yes" + isGenErrMsg := "请输入yes(Y)/no(N)" + isGenValues := []string{"yes", "no", "Y", "N"} + isGenEncrypt := scanConfDefaultCheck(isGenMsg, "yes", isGenValues, isGenErrMsg) + if isGenEncrypt == "no" || isGenEncrypt == "N" { + conf.Encrypt.PriKey = scanConf("请输入RSA私钥", "请输入RSA私钥") + conf.Encrypt.PubKey = scanConf("请输入RSA公钥", "请输入RSA公钥") + } + + codes := []string{"alidns", "tencentcloud", "cloudflare"} + msg := fmt.Sprintf("请输入DNS提供商; 当前支持的: %s", strings.Join(codes, ",")) + errMsg := fmt.Sprintf("不支持的DNS提供商; 当前支持的: %s", strings.Join(codes, ",")) + conf.Use = scanConfDefaultCheck(msg, "", codes, errMsg) + switch conf.Use { + case "alidns": + fmt.Printf("阿里云DNS配置帮助页: \n%s\n", "https://go-acme.github.io/lego/dns/alidns/index.html") + fmt.Printf("阿里云令牌获取方式:\n%s\n", "https://usercenter.console.aliyun.com/#/manage/ak") + fmt.Printf("阿里云SDK客户端项目地址:\n%s\n", "https://github.com/aliyun/alibaba-cloud-sdk-go?tab=readme-ov-file") + conf.Provider.Ali.RegionID = scanConf("请输入阿里云Region ID", "请输入阿里云Region ID") + conf.Provider.Ali.APIKey = scanConf("请输入阿里云API Key", "请输入阿里云API Key") + conf.Provider.Ali.SecretKey = scanConf("请输入阿里云Secret Key", "请输入阿里云Secret Key") + case "tencentcloud": + fmt.Printf("腾讯云DNS配置帮助页: \n%s\n", "https://go-acme.github.io/lego/dns/tencentcloud/index.html") + fmt.Printf("腾讯云令牌获取方式: \n%s\n", "https://console.cloud.tencent.com/cam/capi") + fmt.Printf("腾讯云SDK客户端项目地址: \n%s\n", "https://github.com/tencentcloud/tencentcloud-sdk-go?tab=readme-ov-file") + conf.Provider.Tencent.SecretId = scanConf("请输入腾讯云Secret Id", "请输入腾讯云Secret Id") + conf.Provider.Tencent.SecretKey = scanConf("请输入腾讯云Secret Key", "请输入腾讯云Secret Key") + case "cloudflare": + fmt.Printf("Cloudflare DNS配置帮助页: \n%s\n", "https://go-acme.github.io/lego/dns/tencentcloud/index.html") + fmt.Printf("Cloudflare 令牌获取方式: \n%s\n", "https://blog.cloudflare.com/zh-cn/api-tokens-general-availability/") + fmt.Printf("Cloudflare SDK客户端项目地址: \n%s\n", "https://github.com/cloudflare/cloudflare-go") + conf.Provider.CloudFlare.Token = scanConf("请输入CloudFlare DNS API Token", "请输入CloudFlare Token") + } + isAddDomainMsg := "是否需要添加证书获取配置?\n您可以在此处通过控制台交互添加;也可以在配置文件创建后,直接修改配置文件.\n请输入yes(Y)/no(N) 默认为 yes" + isAddDomain := scanConfDefaultCheck(isAddDomainMsg, "yes", isGenValues, isGenErrMsg) + if isAddDomain == "no" || isAddDomain == "N" { + fmt.Printf("您可以通过手动修改%s文件, 调整证书获取配置.", GetEnvConf().ConfFile) + conf.Domains = []Domain{} + return conf + } + conf.Domains = []Domain{} + for { + name := scanConf("请输入配置名称", "配置名称不能为空") + email := scanConf("请输入邮箱", "邮箱不能为空") + host := scanConf("请输入主机名;支持泛解析;\n支持多个域名,多个域名用,(英文逗号)分割;\n如: example.com,*.example.com\n", "主机名不能为空") + // 将host通过,分割为数组 + hosts := strings.Split(host, ",") + conf.Domains = append(conf.Domains, Domain{ + Name: name, + Email: email, + Host: hosts, + }) + isAddNextDomainMsg := "是否需要继续添加证书获取配置?\n请输入yes(Y)/no(N) 默认为 yes" + isAddNextDomain := scanConfDefaultCheck(isAddNextDomainMsg, "yes", isGenValues, isGenErrMsg) + if isAddNextDomain == "no" || isAddNextDomain == "N" { + fmt.Printf("您后续可以通过手动修改%s文件, 调整证书获取配置.", GetEnvConf().ConfFile) + return conf + } + } +} + +// 读取用户输入 +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 scanConfDefault(msg string, defaultContent 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 == "" { + return defaultContent + } + return name + } +} + +func scanConfDefaultCheck(msg string, defaultContent string, values []string, errMsg string) string { + for { + content := scanConfDefault(msg, defaultContent) + // 判断内容是否在values中 + for _, value := range values { + if value == content { + return content + } + } + log.Println(errMsg) + } +} + func defaultConf() *AppConfig { //priKey, pubKey, err := GenRsa() priKey, pubKey, err := crypto.GenRSA() @@ -69,6 +187,9 @@ func defaultConf() *AppConfig { SecretId: "secret_id", SecretKey: "secret_key", }, + CloudFlare: CloudFlareProvider{ + Token: "token", + }, }, Domains: []Domain{ { @@ -97,8 +218,9 @@ type AppConfig struct { } type AppProvider struct { - Ali AliProvider - Tencent TencentProvider + Ali AliProvider + Tencent TencentProvider + CloudFlare CloudFlareProvider } type AliProvider struct { RegionID string @@ -109,6 +231,9 @@ type TencentProvider struct { SecretId string SecretKey string } +type CloudFlareProvider struct { + Token string +} type Domain struct { Name string Email string @@ -130,8 +255,8 @@ func (conf AppConfig) FindDomain(name string) *Domain { const ENV_CONF_FILE = "ACME_MANA_CONF_FILE" -func InitRuntimeConf() EnvConf { - return EnvConf{ +func InitRuntimeConf() *EnvConf { + return &EnvConf{ ConfFile: env.GetOrDefaultString(ENV_CONF_FILE, "config.yml"), } } diff --git a/src/daemon.go b/src/daemon.go index a61a39d..a2ba087 100644 --- a/src/daemon.go +++ b/src/daemon.go @@ -18,7 +18,6 @@ var stderr *os.File // Start 启动/* func Start() { initLog() - log.Println("Run Acme Mana...") args := os.Args if len(args) <= 1 { //daemonStart() @@ -79,6 +78,7 @@ func initLog() { 守护进程启动 */ func daemonStart() { + GetAppConfig() isDaemon := os.Getenv("GO_DAEMON") log.Println("Run Daemon, DAEMON Is " + isDaemon) if isDaemon != "1" { @@ -200,31 +200,15 @@ func showPubkey() { log.Println(key) } -/* -守护进程接收名称 -*/ +// 守护进程接收命令 func daemonCommand() { log.Println("Sending command...") - //pid, err := readPID() - //if err != nil { - // log.Fatalf("Failed to send command: %v", err) - //} - // - //_, err = os.FindProcess(pid) - //if err != nil { - // log.Fatalf("Failed to find process: %v", err) - //} - //if len(os.Args) < 3 { - // log.Fatalf("No command specified") - //} command := os.Args[2] sendCommand(command) } -/* -发送命令 -*/ +// 发送命令 func sendCommand(command string) { conn, err := net.Dial("unix", SocketFile) if err != nil { @@ -245,16 +229,8 @@ func sendCommand(command string) { log.Printf("Sending command '%s' to daemon with PID: %d", command, 0) } -/* -* -业务进程执行任务 -*/ +// 业务进程执行任务 func doTask() { - // 示例:每隔10秒打印一条日志 - //for { - // log.Println("Daemon is running...") - // time.Sleep(10 * time.Second) - //} // 监听主进程下发的指令 go InitSocket() diff --git a/src/util.go b/src/util.go deleted file mode 100644 index 4f6e8de..0000000 --- a/src/util.go +++ /dev/null @@ -1,25 +0,0 @@ -package src - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/base64" -) - -func GenRsa() (priKey string, pubKey string, err error) { - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - return "", "", err - } - publicKey := &privateKey.PublicKey - publicKeyBytes := x509.MarshalPKCS1PublicKey(publicKey) - pubKey = base64.StdEncoding.EncodeToString(publicKeyBytes) - privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) - if err != nil { - return "", "", err - } - priKey = base64.StdEncoding.EncodeToString(privateKeyBytes) - err = nil - return -} diff --git a/src/variable.go b/src/variable.go index ffce274..2743e7b 100644 --- a/src/variable.go +++ b/src/variable.go @@ -7,15 +7,15 @@ const CertFileName = "fullchain.pem" const KeyFileName = "privkey.pem" const CertInfoFileName = "info.json" -var appConfig AppConfig = ReadConfig() +var appConfig *AppConfig = ReadConfig() -func GetAppConfig() AppConfig { +func GetAppConfig() *AppConfig { return appConfig } -var envConf EnvConf = InitRuntimeConf() +var envConf *EnvConf = InitRuntimeConf() -func GetEnvConf() EnvConf { +func GetEnvConf() *EnvConf { return envConf }