From 5c2a435597dd22838af8d9109fe4c8ba13168ff3 Mon Sep 17 00:00:00 2001
From: ZhuoQinghui <1302344380@qq.com>
Date: Thu, 31 Oct 2024 16:26:55 +0800
Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 main.go            |   4 --
 src/acme-client.go |   4 +-
 src/command.go     |   2 +-
 src/config.go      | 139 ++++++++++++++++++++++++++++++++++++++++++---
 src/daemon.go      |  32 ++---------
 src/util.go        |  25 --------
 src/variable.go    |   8 +--
 7 files changed, 143 insertions(+), 71 deletions(-)
 delete mode 100644 src/util.go

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
 }