From d505213d2c652c31368d15b3d618b9de9a1e6ab8 Mon Sep 17 00:00:00 2001 From: ZhuoQinghui <1302344380@qq.com> Date: Fri, 27 Dec 2024 16:50:51 +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 --- src/acme-client.go | 173 ------ src/acme/helper.go | 238 +++++++++ src/cmd/cmd_handle/server.go | 126 +++++ src/cmd/handler.go | 4 +- src/command.go | 162 +++--- src/common/variable.go | 11 +- src/config.go | 530 +++++++++---------- src/http.go | 286 +++++----- src/http/handler.go | 19 - src/http/server.go | 46 -- src/server/handle/cert.go | 25 - src/server/http-server.go | 20 +- src/server/http-server_test.go | 18 +- src/server/{handle => http_handler}/auth.go | 2 +- src/server/http_handler/cert.go | 43 ++ src/server/{handle => http_handler}/conf.go | 2 +- src/server/{handle => http_handler}/error.go | 2 +- src/server/socker-server.go | 53 ++ src/server/socket_handler/cert.go | 1 + src/server/socket_handler/handler.go | 47 ++ src/server/variable.go | 3 +- src/task.go | 168 +++--- src/task/task.go | 114 ++++ src/variable.go | 28 - 24 files changed, 1223 insertions(+), 898 deletions(-) create mode 100644 src/acme/helper.go create mode 100644 src/cmd/cmd_handle/server.go delete mode 100644 src/http/handler.go delete mode 100644 src/http/server.go delete mode 100644 src/server/handle/cert.go rename src/server/{handle => http_handler}/auth.go (87%) create mode 100644 src/server/http_handler/cert.go rename src/server/{handle => http_handler}/conf.go (92%) rename src/server/{handle => http_handler}/error.go (97%) create mode 100644 src/server/socker-server.go create mode 100644 src/server/socket_handler/cert.go create mode 100644 src/server/socket_handler/handler.go create mode 100644 src/task/task.go delete mode 100644 src/variable.go diff --git a/src/acme-client.go b/src/acme-client.go index c304936..c6d5f54 100644 --- a/src/acme-client.go +++ b/src/acme-client.go @@ -1,174 +1 @@ package src - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/x509" - "encoding/json" - "encoding/pem" - "github.com/go-acme/lego/v4/certificate" - "github.com/go-acme/lego/v4/challenge" - "github.com/go-acme/lego/v4/lego" - "github.com/go-acme/lego/v4/log" - "github.com/go-acme/lego/v4/providers/dns/alidns" - "github.com/go-acme/lego/v4/registration" - "os" - "path" - "path/filepath" -) - -// Apply 申请证书 -// domain: 申请的域名 -func Apply(domain Domain) { - email, hosts, name := domain.Email, domain.Host, domain.Name - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - log.Fatal(err) - } - - acmeUser := &AcmeUser{ - Email: email, - key: privateKey, - } - - config := lego.NewConfig(acmeUser) - - client, err := lego.NewClient(config) - if err != nil { - log.Fatal(err) - } - - ali := appConfig.Provider.Ali - conf := alidns.NewDefaultConfig() - conf.RegionID = ali.RegionID - conf.APIKey = ali.APIKey - conf.SecretKey = ali.SecretKey - provider, err := alidns.NewDNSProviderConfig(conf) - if err != nil { - log.Fatal(err) - } - - challenge := client.Challenge - err = challenge.SetDNS01Provider(provider) - if err != nil { - return - } - - registrar := client.Registration - reg, err := registrar.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) - if err != nil { - log.Fatal(err) - } - acmeUser.Registration = reg - - request := certificate.ObtainRequest{ - Domains: hosts, - Bundle: true, - } - - cert, err := client.Certificate.Obtain(request) - if err != nil { - log.Fatal(err) - } - - saveCertFile(cert, name) -} - -func getProvider() challenge.Provider { - config := GetAppConfig() - - switch config.Use { - case "alidns": - return getAliProvider() - } - return nil -} -func getAliProvider() challenge.Provider { - ali := GetAppConfig().Provider.Ali - conf := alidns.NewDefaultConfig() - conf.RegionID = ali.RegionID - conf.APIKey = ali.APIKey - conf.SecretKey = ali.SecretKey - provider, err := alidns.NewDNSProviderConfig(conf) - if err != nil { - log.Fatal(err) - } - return provider -} -func getTencentProvider() challenge.Provider { - return nil -} - -// saveCertFile 保存证书文件 -func saveCertFile(cert *certificate.Resource, name string) { - dir := GetAppConfig().CertDir - dir = filepath.Join(dir, name) - _, err := os.Stat(dir) - if os.IsNotExist(err) { - err := os.MkdirAll(dir, 0755) - if err != nil { - log.Infof("创建目录 %s 失败", dir) - log.Fatal(err) - } - log.Infof("创建目录 %s", dir) - } - - certBytes := cert.Certificate - err = os.WriteFile(path.Join(dir, CertFileName), certBytes, 0755) - if err != nil { - log.Fatalf("Failed to save certificate: %v", err) - } - - err = os.WriteFile(path.Join(dir, KeyFileName), cert.PrivateKey, 0755) - if err != nil { - log.Fatalf("Failed to save private key: %v", err) - } - - block, _ := pem.Decode(certBytes) - if block == nil { - log.Fatalf("Failed to decode PEM block") - return - } - - certParse, err := x509.ParseCertificate(block.Bytes) - if err != nil { - log.Fatalf("Failed to parse certificate: %v", err) - } - certInfo := CertInfo{ - Cert: *cert, - Info: *certParse, - } - // 将 cert 转换为json格式并保存到文件中 - certJson, err := json.Marshal(certInfo) - if err != nil { - log.Fatalf("Failed to marshal certificate: %v", err) - } - err = os.WriteFile(path.Join(dir, CertInfoFileName), certJson, 0644) - if err != nil { - log.Fatalf("Failed to save certificate info: %v", err) - } - -} - -type AcmeUser struct { - Email string - Registration *registration.Resource - key crypto.PrivateKey -} - -func (u AcmeUser) GetEmail() string { - return u.Email -} -func (u AcmeUser) GetRegistration() *registration.Resource { - return u.Registration -} -func (u AcmeUser) GetPrivateKey() crypto.PrivateKey { - return u.key -} - -type CertInfo struct { - Cert certificate.Resource - Info x509.Certificate -} diff --git a/src/acme/helper.go b/src/acme/helper.go new file mode 100644 index 0000000..2c0e052 --- /dev/null +++ b/src/acme/helper.go @@ -0,0 +1,238 @@ +package acme + +import ( + "acme-mana/src/common" + "acme-mana/src/conf" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/json" + "encoding/pem" + "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/log" + "github.com/go-acme/lego/v4/providers/dns/alidns" + "github.com/go-acme/lego/v4/registration" + "os" + "path" + "path/filepath" +) + +func Apply(cert *conf.CertConf) { + email := cert.Email + host := cert.Host + name := cert.Name + + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + log.Fatal(err) + } + + acmeUser := &AcmeUser{ + Email: email, + key: privateKey, + } + config := lego.NewConfig(acmeUser) + client, err := lego.NewClient(config) + if err != nil { + log.Fatal(err) + } + + providerName := cert.Provider + p, _ := conf.FindProvider(providerName) + + provider := getProvider(p) + + //var provider challenge.Provider + // + //switch p.Type { + //case "ali": + // conf := alidns.NewDefaultConfig() + // conf.RegionID = p.Conf["RegionID"] + // conf.APIKey = p.Conf["APIKey"] + // conf.SecretKey = p.Conf["SecretKey"] + // provider, err = alidns.NewDNSProviderConfig(conf) + // if err != nil { + // log.Fatal(err) + // } + //} + + chall := client.Challenge + err = chall.SetDNS01Provider(provider) + if err != nil { + return + } + + registrar := client.Registration + reg, err := registrar.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + if err != nil { + log.Fatal(err) + } + acmeUser.Registration = reg + + request := certificate.ObtainRequest{ + Domains: host, + Bundle: true, + } + + res, err := client.Certificate.Obtain(request) + if err != nil { + log.Fatal(err) + } + + saveCertFile(res, cert, name) + +} + +//func Apply1(domain Domain) { +// email, hosts, name := domain.Email, domain.Host, domain.Name +// privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) +// if err != nil { +// log.Fatal(err) +// } +// +// acmeUser := &AcmeUser{ +// Email: email, +// key: privateKey, +// } +// +// config := lego.NewConfig(acmeUser) +// +// client, err := lego.NewClient(config) +// if err != nil { +// log.Fatal(err) +// } +// +// ali := appConfig.Provider.Ali +// conf := alidns.NewDefaultConfig() +// conf.RegionID = ali.RegionID +// conf.APIKey = ali.APIKey +// conf.SecretKey = ali.SecretKey +// provider, err := alidns.NewDNSProviderConfig(conf) +// if err != nil { +// log.Fatal(err) +// } +// +// challenge := client.Challenge +// err = challenge.SetDNS01Provider(provider) +// if err != nil { +// return +// } +// +// registrar := client.Registration +// reg, err := registrar.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) +// if err != nil { +// log.Fatal(err) +// } +// acmeUser.Registration = reg +// +// request := certificate.ObtainRequest{ +// Domains: hosts, +// Bundle: true, +// } +// +// cert, err := client.Certificate.Obtain(request) +// if err != nil { +// log.Fatal(err) +// } +// +// saveCertFile(cert, name) +//} + +func getProvider(p *conf.ProviderConf) challenge.Provider { + + switch p.Type { + case "ali": + return aliProvider(p) + } + return nil +} +func aliProvider(p *conf.ProviderConf) challenge.Provider { + aliConf := alidns.NewDefaultConfig() + aliConf.RegionID = p.Conf["RegionID"] + aliConf.APIKey = p.Conf["APIKey"] + aliConf.SecretKey = p.Conf["SecretKey"] + provider, err := alidns.NewDNSProviderConfig(aliConf) + if err != nil { + log.Fatal(err) + } + return provider +} +func tencentProvider() challenge.Provider { + return nil +} + +// saveCertFile 保存证书文件 +func saveCertFile(cert *certificate.Resource, cerfConf *conf.CertConf, name string) { + dir := cerfConf.Dir + dir = filepath.Join(dir, name) + _, err := os.Stat(dir) + if os.IsNotExist(err) { + err := os.MkdirAll(dir, 0755) + if err != nil { + log.Infof("创建目录 %s 失败", dir) + log.Fatal(err) + } + log.Infof("创建目录 %s", dir) + } + + certBytes := cert.Certificate + err = os.WriteFile(path.Join(dir, common.CertFileName), certBytes, 0755) + if err != nil { + log.Fatalf("Failed to save certificate: %v", err) + } + + err = os.WriteFile(path.Join(dir, common.KeyFileName), cert.PrivateKey, 0755) + if err != nil { + log.Fatalf("Failed to save private key: %v", err) + } + + block, _ := pem.Decode(certBytes) + if block == nil { + log.Fatalf("Failed to decode PEM block") + return + } + + certParse, err := x509.ParseCertificate(block.Bytes) + if err != nil { + log.Fatalf("Failed to parse certificate: %v", err) + } + certInfo := CertInfo{ + Cert: *cert, + Info: *certParse, + } + // 将 cert 转换为json格式并保存到文件中 + certJson, err := json.Marshal(certInfo) + if err != nil { + log.Fatalf("Failed to marshal certificate: %v", err) + } + err = os.WriteFile(path.Join(dir, common.CertInfoFileName), certJson, 0644) + if err != nil { + log.Fatalf("Failed to save certificate info: %v", err) + } + +} + +type AcmeUser struct { + Email string + Registration *registration.Resource + key crypto.PrivateKey +} + +func (u AcmeUser) GetEmail() string { + return u.Email +} +func (u AcmeUser) GetRegistration() *registration.Resource { + return u.Registration +} +func (u AcmeUser) GetPrivateKey() crypto.PrivateKey { + return u.key +} + +type CertInfo struct { + Cert certificate.Resource + Info x509.Certificate +} diff --git a/src/cmd/cmd_handle/server.go b/src/cmd/cmd_handle/server.go new file mode 100644 index 0000000..053726b --- /dev/null +++ b/src/cmd/cmd_handle/server.go @@ -0,0 +1,126 @@ +package cmd_handle + +import ( + "acme-mana/src/common" + "acme-mana/src/server" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "strconv" + "syscall" +) + +var stdout *os.File +var stderr *os.File + +func start() { + initLog() + + isDaemon := os.Getenv("GO_DAEMON") + log.Println("守护进程启动: " + isDaemon) + + if isDaemon != "1" { + daemonStart() + return + } + if isRunning() { + log.Println("守护进程已启动, 跳过执行") + return + } + workPath, err := os.Executable() + if err != nil { + log.Fatalf("无法获取当前工作路径: %v", err) + } + cmd := exec.Cmd{ + Path: workPath, + Args: os.Args, + Dir: filepath.Dir(workPath), + Env: append(os.Environ(), "GO_DAEMON=1"), + //Stdin: os.Stdin, + Stdout: stdout, + Stderr: stderr, + SysProcAttr: &syscall.SysProcAttr{}, + } + log.Println("启动守护进程中...") + err = cmd.Start() + if err != nil { + log.Fatalf("无法启动守护进程: %v", err) + } + err = os.WriteFile(common.PidFile, []byte(strconv.Itoa(cmd.Process.Pid)), 0644) + if err != nil { + log.Fatalf("无法写出PID文件: %v", err) + } + log.Printf("启动进程启动成功, PID: %d", cmd.Process.Pid) + os.Exit(0) +} + +func daemonStart() { + // 启动 HttpServer + server.HttpInstance.Init() + server.HttpInstance.Start() + + // 启动 SocketServer + server.SocketInstance.Start() + + // TODO 启动 AutoRefreshCert +} + +func isRunning() bool { + log.Println("Checking if daemon is running...") + pid, err := readPID() + if err != nil { + return false + } + + process, err := os.FindProcess(pid) + log.Println("Found process:", process) + if err != nil { + log.Println("Failed to find process:", err) + return false + } + return true +} + +// 读取PID文件 +func readPID() (int, error) { + log.Println("Reading PID file...") + data, err := os.ReadFile(common.PidFile) + if err != nil { + log.Println("Failed to read PID file:", err) + return 0, err + } + log.Println("PID file content:", string(data)) + + pid, err := strconv.Atoi(string(data)) + if err != nil { + log.Println("Failed to parse PID:", err) + return 0, err + } + log.Println("PID:", pid) + + return pid, nil +} + +func initLog() { + pwd, err := os.Getwd() + if err != nil { + log.Fatalf("无法获取当前工作路径: %v", err) + } + dir := path.Join(pwd, "log") + _, err = os.Stat(dir) + if os.IsNotExist(err) { + err := os.Mkdir(dir, 0777) + if err != nil { + log.Fatalf("无法创建日志目录: %v", err) + } + } + outFile, err := os.OpenFile(path.Join(dir, "out.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + defer outFile.Close() + stdout = outFile + + errFile, err := os.OpenFile(path.Join(dir, "err.log"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) + defer stderr.Close() + stderr = errFile +} diff --git a/src/cmd/handler.go b/src/cmd/handler.go index 0ef8af1..65e98f5 100644 --- a/src/cmd/handler.go +++ b/src/cmd/handler.go @@ -30,10 +30,10 @@ func editServer(cmd *cobra.Command, args []string) { } func serverState(cmd *cobra.Command, args []string) { - server.Instance.Status() + server.HttpInstance.Status() } func startServer(cmd *cobra.Command, args []string) { - server.Instance.Start() + server.HttpInstance.Start() } func stopServer(cmd *cobra.Command, args []string) { fmt.Println("stop server") diff --git a/src/command.go b/src/command.go index f4be69e..f625330 100644 --- a/src/command.go +++ b/src/command.go @@ -1,83 +1,83 @@ package src -import ( - "log" - "net" - "os" -) - -// InitSocket 初始化 socket 文件 -func InitSocket() { - log.Println("Start listen command") - // 删除旧的 socket 文件 - if _, err := os.Stat(SocketFile); err == nil { - os.Remove(SocketFile) - } - - listener, err := net.Listen("unix", SocketFile) - if err != nil { - log.Fatalf("Failed to listen on socket: %v", err) - } - defer listener.Close() - - for { - log.Println("Waiting for connections...") - conn, err := listener.Accept() - if err != nil { - log.Printf("Failed to accept connection: %v", err) - continue - } - - go handleConnection(conn) - } -} - -/* -* -处理连接 -*/ -func handleConnection(conn net.Conn) { - defer conn.Close() - - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - log.Printf("Failed to read command: %v", err) - return - } - - command := string(buf[:n]) - log.Printf("Received command: %s", command) - - // 在这里处理接收到的命令 - switch command { - case "stop": - onStop() - default: - onCommand(command) - } -} - -/* -* -收到停止命令 -*/ -func onStop() { - log.Println("Stopping daemon...") - os.Remove(PidFile) - log.Println("Remove PID File...") - os.Remove(SocketFile) - log.Println("Remove Socket File...") - os.Exit(0) -} - -/* -收到命令 -*/ -func onCommand(command string) { - -} - -func onConfig() { - -} +//import ( +// "log" +// "net" +// "os" +//) +// +//// InitSocket 初始化 socket 文件 +//func InitSocket() { +// log.Println("Start listen command") +// // 删除旧的 socket 文件 +// if _, err := os.Stat(SocketFile); err == nil { +// os.Remove(SocketFile) +// } +// +// listener, err := net.Listen("unix", SocketFile) +// if err != nil { +// log.Fatalf("Failed to listen on socket: %v", err) +// } +// defer listener.Close() +// +// for { +// log.Println("Waiting for connections...") +// conn, err := listener.Accept() +// if err != nil { +// log.Printf("Failed to accept connection: %v", err) +// continue +// } +// +// go handleConnection(conn) +// } +//} +// +///* +//* +//处理连接 +//*/ +//func handleConnection(conn net.Conn) { +// defer conn.Close() +// +// buf := make([]byte, 1024) +// n, err := conn.Read(buf) +// if err != nil { +// log.Printf("Failed to read command: %v", err) +// return +// } +// +// command := string(buf[:n]) +// log.Printf("Received command: %s", command) +// +// // 在这里处理接收到的命令 +// switch command { +// case "stop": +// onStop() +// default: +// onCommand(command) +// } +//} +// +///* +//* +//收到停止命令 +//*/ +//func onStop() { +// log.Println("Stopping daemon...") +// os.Remove(PidFile) +// log.Println("Remove PID File...") +// os.Remove(SocketFile) +// log.Println("Remove Socket File...") +// os.Exit(0) +//} +// +///* +//收到命令 +//*/ +//func onCommand(command string) { +// +//} +// +//func onConfig() { +// +//} diff --git a/src/common/variable.go b/src/common/variable.go index d77e9ea..94ba834 100644 --- a/src/common/variable.go +++ b/src/common/variable.go @@ -1,5 +1,14 @@ package common -import "github.com/spf13/cobra" +import ( + "github.com/spf13/cobra" +) + +const PidFile = "acme-mana.pid" +const SocketFile = "acme-mana.sock" + +const CertFileName = "fullchain.pem" +const KeyFileName = "privkey.pem" +const CertInfoFileName = "info.json" var RootCmd *cobra.Command diff --git a/src/config.go b/src/config.go index 0b44c59..72d6a92 100644 --- a/src/config.go +++ b/src/config.go @@ -1,267 +1,267 @@ 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 { - if DnsProviderSupports != nil { - return nil - } - InitConfig() - file, err := os.ReadFile(GetEnvConf().ConfFile) - if err != nil { - log.Fatal(err) - } - var conf *AppConfig - err = yaml.Unmarshal(file, &conf) - if err != nil { - log.Fatal(err) - } - return conf -} - -func InitConfig() { - // 判断当前目录下是否存在配置文件 config.yml - _, err := os.Stat(GetEnvConf().ConfFile) - - if os.IsNotExist(err) { - // 配置文件不存在,则创建一个 - log.Infof("配置文件不存在,自动创建默认配置文件") - // 生成默认配置 - conf := readNewConf() - // 创建一个默认的配置文件 - file, err := os.Create(GetEnvConf().ConfFile) - if err != nil { - log.Fatal(err) - } - data, err := yaml.Marshal(conf) - if err != nil { - log.Fatal(err) - } - // 写入配置文件 - _, err = file.Write(data) - if err != nil { - log.Fatal(err) - } - } - -} - -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公钥") - } - - msg := fmt.Sprintf("请输入DNS提供商; 当前支持的: %s", strings.Join(DnsProviderSupports, ",")) - errMsg := fmt.Sprintf("不支持的DNS提供商; 当前支持的: %s", strings.Join(DnsProviderSupports, ",")) - conf.Use = scanConfDefaultCheck(msg, "", DnsProviderSupports, 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() - if err != nil { - log.Fatal(err) - } - return &AppConfig{ - Use: "Ali", - CertDir: "cert", - Provider: AppProvider{ - Ali: AliProvider{ - RegionID: "cn-hangzhou", - APIKey: "api_key", - SecretKey: "secret_key", - }, - Tencent: TencentProvider{ - SecretId: "secret_id", - SecretKey: "secret_key", - }, - CloudFlare: CloudFlareProvider{ - Token: "token", - }, - }, - Domains: []Domain{ - { - Name: "example.com", - Email: "email@example.com", - Host: []string{"www.example.com"}, - }, - }, - Encrypt: Encrypt{ - PriKey: priKey, - PubKey: pubKey, - }, - } -} - -type AppConfig struct { - Use string - - CertDir string - - Provider AppProvider - - Domains []Domain - - Encrypt Encrypt -} - -type AppProvider struct { - Ali AliProvider - Tencent TencentProvider - CloudFlare CloudFlareProvider -} -type AliProvider struct { - RegionID string - APIKey string - SecretKey string -} -type TencentProvider struct { - SecretId string - SecretKey string -} -type CloudFlareProvider struct { - Token string -} -type Domain struct { - Name string - Email string - Host []string -} -type Encrypt struct { - PriKey string - PubKey string -} - -func (conf AppConfig) FindDomain(name string) *Domain { - for _, domain := range conf.Domains { - if domain.Name == name { - return &domain - } - } - return nil -} - -const ENV_CONF_FILE = "ACME_MANA_CONF_FILE" - -func InitRuntimeConf() *EnvConf { - return &EnvConf{ - ConfFile: env.GetOrDefaultString(ENV_CONF_FILE, "config.yml"), - } -} - -type EnvConf struct { - ConfFile string -} +//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 { +// if DnsProviderSupports != nil { +// return nil +// } +// InitConfig() +// file, err := os.ReadFile(GetEnvConf().ConfFile) +// if err != nil { +// log.Fatal(err) +// } +// var conf *AppConfig +// err = yaml.Unmarshal(file, &conf) +// if err != nil { +// log.Fatal(err) +// } +// return conf +//} +// +//func InitConfig() { +// // 判断当前目录下是否存在配置文件 config.yml +// _, err := os.Stat(GetEnvConf().ConfFile) +// +// if os.IsNotExist(err) { +// // 配置文件不存在,则创建一个 +// log.Infof("配置文件不存在,自动创建默认配置文件") +// // 生成默认配置 +// conf := readNewConf() +// // 创建一个默认的配置文件 +// file, err := os.Create(GetEnvConf().ConfFile) +// if err != nil { +// log.Fatal(err) +// } +// data, err := yaml.Marshal(conf) +// if err != nil { +// log.Fatal(err) +// } +// // 写入配置文件 +// _, err = file.Write(data) +// if err != nil { +// log.Fatal(err) +// } +// } +// +//} +// +//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公钥") +// } +// +// msg := fmt.Sprintf("请输入DNS提供商; 当前支持的: %s", strings.Join(DnsProviderSupports, ",")) +// errMsg := fmt.Sprintf("不支持的DNS提供商; 当前支持的: %s", strings.Join(DnsProviderSupports, ",")) +// conf.Use = scanConfDefaultCheck(msg, "", DnsProviderSupports, 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() +// if err != nil { +// log.Fatal(err) +// } +// return &AppConfig{ +// Use: "Ali", +// CertDir: "cert", +// Provider: AppProvider{ +// Ali: AliProvider{ +// RegionID: "cn-hangzhou", +// APIKey: "api_key", +// SecretKey: "secret_key", +// }, +// Tencent: TencentProvider{ +// SecretId: "secret_id", +// SecretKey: "secret_key", +// }, +// CloudFlare: CloudFlareProvider{ +// Token: "token", +// }, +// }, +// Domains: []Domain{ +// { +// Name: "example.com", +// Email: "email@example.com", +// Host: []string{"www.example.com"}, +// }, +// }, +// Encrypt: Encrypt{ +// PriKey: priKey, +// PubKey: pubKey, +// }, +// } +//} +// +//type AppConfig struct { +// Use string +// +// CertDir string +// +// Provider AppProvider +// +// Domains []Domain +// +// Encrypt Encrypt +//} +// +//type AppProvider struct { +// Ali AliProvider +// Tencent TencentProvider +// CloudFlare CloudFlareProvider +//} +//type AliProvider struct { +// RegionID string +// APIKey string +// SecretKey string +//} +//type TencentProvider struct { +// SecretId string +// SecretKey string +//} +//type CloudFlareProvider struct { +// Token string +//} +//type Domain struct { +// Name string +// Email string +// Host []string +//} +//type Encrypt struct { +// PriKey string +// PubKey string +//} +// +//func (conf AppConfig) FindDomain(name string) *Domain { +// for _, domain := range conf.Domains { +// if domain.Name == name { +// return &domain +// } +// } +// return nil +//} +// +//const ENV_CONF_FILE = "ACME_MANA_CONF_FILE" +// +//func InitRuntimeConf() *EnvConf { +// return &EnvConf{ +// ConfFile: env.GetOrDefaultString(ENV_CONF_FILE, "config.yml"), +// } +//} +// +//type EnvConf struct { +// ConfFile string +//} diff --git a/src/http.go b/src/http.go index 13d78f5..44b01de 100644 --- a/src/http.go +++ b/src/http.go @@ -1,145 +1,145 @@ package src -import ( - "acme-mana/src/crypto" - "encoding/base64" - "encoding/hex" - "encoding/json" - "github.com/gin-gonic/gin" - "log" - "os" - "path" - "path/filepath" - "strconv" -) - -func InitHttpServer(host string, port int) { - - log.Println("Start http server, Listen " + strconv.Itoa(port)) - h := gin.Default() - h.GET("/api/v1/refresh", refreshCert) - h.GET("/api/v1/cert", getCert) - h.GET("/api/v1/domain/list", domainList) - err := h.Run(host + ":" + strconv.Itoa(port)) - if err != nil { - return - } -} - -func domainList(c *gin.Context) { - token := getToken(c) - domains := GetAppConfig().Domains - data, err := json.Marshal(domains) - if err != nil { - log.Fatal(err) - } - - encryptData := encryptResult(string(data), token) - c.JSON(200, gin.H{ - "code": 200, - "msg": "success", - "data": encryptData, - }) -} - -func getCert(c *gin.Context) { - name := c.Query("name") - token := getToken(c) - - dir := GetAppConfig().CertDir - dir = filepath.Join(dir, name) - _, err := os.Stat(dir) - if os.IsNotExist(err) { - c.JSON(200, gin.H{ - "code": 500, - "msg": "Name does not exist.", - }) - return - } - crtFilePath := path.Join(dir, CertFileName) - crtContent, err := os.ReadFile(crtFilePath) - if err != nil { - c.JSON(200, gin.H{ - "code": 500, - "msg": "Failed to read crt file.", - }) - return - } - crt := string(crtContent) - - keyFilePath := path.Join(dir, KeyFileName) - keyContent, err := os.ReadFile(keyFilePath) - if err != nil { - c.JSON(200, gin.H{ - "code": 500, - "msg": "Failed to read key file.", - }) - return - } - key := string(keyContent) - - certInfoFilePath := path.Join(dir, CertInfoFileName) - certInfoContent, err := os.ReadFile(certInfoFilePath) - if err != nil { - c.JSON(200, gin.H{ - "code": 500, - "msg": "Failed to read cert info file.", - }) - return - } - certInfo := string(certInfoContent) - - data, err := json.Marshal(&DomainData{ - Fullchain: crt, - Key: key, - Info: certInfo, - }) - if err != nil { - log.Fatal(err) - } - - encryptData := encryptResult(string(data), token) - c.JSON(200, gin.H{ - "code": 200, - "msg": "Success", - "data": encryptData, - }) -} - -func refreshCert(c *gin.Context) { - name := c.Param("name") - domain := GetAppConfig().FindDomain(name) - if domain == nil { - c.JSON(200, gin.H{ - "code": 500, - "msg": "Name does not exist.", - }) - return - } - Apply(*domain) - c.JSON(200, gin.H{ - "code": 200, - "msg": "Success", - }) -} - -func getToken(c *gin.Context) (token string) { - token = c.Query("token") - token = decryptParam(token) - return -} - -func decryptParam(param string) string { - priKey := GetAppConfig().Encrypt.PriKey - tokenBytes, err := hex.DecodeString(param) - tokenPlain, err := crypto.DecryptRSABase64(priKey, tokenBytes) - if err != nil { - log.Fatal(err) - } - return string(tokenPlain) -} - -func encryptResult(content string, token string) string { - result := crypto.EncryptAES([]byte(token), []byte(content)) - return base64.StdEncoding.EncodeToString(result) -} +//import ( +// "acme-mana/src/crypto" +// "encoding/base64" +// "encoding/hex" +// "encoding/json" +// "github.com/gin-gonic/gin" +// "log" +// "os" +// "path" +// "path/filepath" +// "strconv" +//) +// +//func InitHttpServer(host string, port int) { +// +// log.Println("Start http server, Listen " + strconv.Itoa(port)) +// h := gin.Default() +// h.GET("/api/v1/refresh", refreshCert) +// h.GET("/api/v1/cert", getCert) +// h.GET("/api/v1/domain/list", domainList) +// err := h.Run(host + ":" + strconv.Itoa(port)) +// if err != nil { +// return +// } +//} +// +//func domainList(c *gin.Context) { +// token := getToken(c) +// domains := GetAppConfig().Domains +// data, err := json.Marshal(domains) +// if err != nil { +// log.Fatal(err) +// } +// +// encryptData := encryptResult(string(data), token) +// c.JSON(200, gin.H{ +// "code": 200, +// "msg": "success", +// "data": encryptData, +// }) +//} +// +//func getCert(c *gin.Context) { +// name := c.Query("name") +// token := getToken(c) +// +// dir := GetAppConfig().CertDir +// dir = filepath.Join(dir, name) +// _, err := os.Stat(dir) +// if os.IsNotExist(err) { +// c.JSON(200, gin.H{ +// "code": 500, +// "msg": "Name does not exist.", +// }) +// return +// } +// crtFilePath := path.Join(dir, CertFileName) +// crtContent, err := os.ReadFile(crtFilePath) +// if err != nil { +// c.JSON(200, gin.H{ +// "code": 500, +// "msg": "Failed to read crt file.", +// }) +// return +// } +// crt := string(crtContent) +// +// keyFilePath := path.Join(dir, KeyFileName) +// keyContent, err := os.ReadFile(keyFilePath) +// if err != nil { +// c.JSON(200, gin.H{ +// "code": 500, +// "msg": "Failed to read key file.", +// }) +// return +// } +// key := string(keyContent) +// +// certInfoFilePath := path.Join(dir, CertInfoFileName) +// certInfoContent, err := os.ReadFile(certInfoFilePath) +// if err != nil { +// c.JSON(200, gin.H{ +// "code": 500, +// "msg": "Failed to read cert info file.", +// }) +// return +// } +// certInfo := string(certInfoContent) +// +// data, err := json.Marshal(&DomainData{ +// Fullchain: crt, +// Key: key, +// Info: certInfo, +// }) +// if err != nil { +// log.Fatal(err) +// } +// +// encryptData := encryptResult(string(data), token) +// c.JSON(200, gin.H{ +// "code": 200, +// "msg": "Success", +// "data": encryptData, +// }) +//} +// +//func refreshCert(c *gin.Context) { +// name := c.Param("name") +// domain := GetAppConfig().FindDomain(name) +// if domain == nil { +// c.JSON(200, gin.H{ +// "code": 500, +// "msg": "Name does not exist.", +// }) +// return +// } +// Apply(*domain) +// c.JSON(200, gin.H{ +// "code": 200, +// "msg": "Success", +// }) +//} +// +//func getToken(c *gin.Context) (token string) { +// token = c.Query("token") +// token = decryptParam(token) +// return +//} +// +//func decryptParam(param string) string { +// priKey := GetAppConfig().Encrypt.PriKey +// tokenBytes, err := hex.DecodeString(param) +// tokenPlain, err := crypto.DecryptRSABase64(priKey, tokenBytes) +// if err != nil { +// log.Fatal(err) +// } +// return string(tokenPlain) +//} +// +//func encryptResult(content string, token string) string { +// result := crypto.EncryptAES([]byte(token), []byte(content)) +// return base64.StdEncoding.EncodeToString(result) +//} diff --git a/src/http/handler.go b/src/http/handler.go deleted file mode 100644 index b446ae8..0000000 --- a/src/http/handler.go +++ /dev/null @@ -1,19 +0,0 @@ -package http - -// -//import ( -// "fmt" -// "github.com/gin-gonic/gin" -//) -// -//func domainList(context *gin.Context) { -// fmt.Println("domainList") -//} -// -//func getCert(context *gin.Context) { -// fmt.Println("getCert") -//} -// -//func refreshCert(context *gin.Context) { -// fmt.Println("refreshCert") -//} diff --git a/src/http/server.go b/src/http/server.go deleted file mode 100644 index 2508ac2..0000000 --- a/src/http/server.go +++ /dev/null @@ -1,46 +0,0 @@ -package http - -// -//import ( -// "acme-mana/src/common" -// "github.com/gin-gonic/gin" -// "github.com/go-acme/lego/v4/log" -// "strconv" -//) -// -//var service *gin.Engine -//var isRunning bool -// -//func Start() { -// if isRunning { -// return -// } -// go start() -//} -// -//func Stop() { -// -//} -// -//func Status() bool { -// return isRunning -//} -// -//func start() { -// conf := common.AppConf -// server := conf.Server -// service := gin.Default() -// register(service) -// isRunning = true -// err := service.Run(server.Host + ":" + strconv.Itoa(server.Port)) -// if err != nil { -// log.Fatal("http server start error \n", err) -// } -// isRunning = false -//} -// -//func register(service *gin.Engine) { -// service.GET("/api/v1/refresh", refreshCert) -// service.GET("/api/v1/cert", getCert) -// service.GET("/api/v1/domain/list", domainList) -//} diff --git a/src/server/handle/cert.go b/src/server/handle/cert.go deleted file mode 100644 index f19505a..0000000 --- a/src/server/handle/cert.go +++ /dev/null @@ -1,25 +0,0 @@ -package handle - -import ( - "acme-mana/src/conf" - "github.com/gin-gonic/gin" - "log" -) - -var CertHandlerInstance = &CertHandler{} - -type CertHandler struct { -} - -func (h *CertHandler) Get(c *gin.Context) { - log.Println("get cert") - c.JSON(200, conf.Config()) -} - -func (h *CertHandler) Refresh(context *gin.Context) { - log.Fatalln("refresh cert") -} - -func (h *CertHandler) DomainList(context *gin.Context) { - log.Fatalln("domain list") -} diff --git a/src/server/http-server.go b/src/server/http-server.go index 62019d9..930bd7d 100644 --- a/src/server/http-server.go +++ b/src/server/http-server.go @@ -2,7 +2,7 @@ package server import ( "acme-mana/src/conf" - "acme-mana/src/server/handle" + "acme-mana/src/server/http_handler" "context" "errors" "github.com/gin-gonic/gin" @@ -13,12 +13,12 @@ import ( ) type HttpServer struct { - server *http.Server `json:"server"` + server *http.Server status bool engine *gin.Engine } -func (s *HttpServer) InitServer() { +func (s *HttpServer) Init() { config := conf.Config() var serverConf = config.Server s.initServer(serverConf.Host, serverConf.Port) @@ -53,10 +53,10 @@ func (s *HttpServer) Start() { func (s *HttpServer) Stop() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() - err := Instance.server.Shutdown(ctx) + err := HttpInstance.server.Shutdown(ctx) s.status = false if err != nil { - log.Fatalln("Instance Shutdown:", err) + log.Fatalln("HttpInstance Shutdown:", err) } } @@ -67,13 +67,13 @@ func (s *HttpServer) Status() bool { func (s *HttpServer) register() { service := s.engine service.Use(gin.Logger()) - service.Use(handle.GlobalErrorHandler()) + service.Use(http_handler.GlobalErrorHandler()) - certHandler := handle.CertHandlerInstance - certGroup := service.Group("/api/v1/cert", handle.AuthMiddleware()) + certHandler := http_handler.CertHandlerInstance + certGroup := service.Group("/api/v1/cert", http_handler.AuthMiddleware()) certGroup.GET("/", certHandler.Get) - confHandler := handle.ConfHandlerInstance - confGroup := service.Group("/api/v1", handle.AuthMiddleware()) + confHandler := http_handler.ConfHandlerInstance + confGroup := service.Group("/api/v1", http_handler.AuthMiddleware()) confGroup.GET("/conf", confHandler.Get) } diff --git a/src/server/http-server_test.go b/src/server/http-server_test.go index 74e2a37..1b71779 100644 --- a/src/server/http-server_test.go +++ b/src/server/http-server_test.go @@ -29,7 +29,7 @@ func TestHttpServer_InitServer(t *testing.T) { status: tt.fields.status, engine: tt.fields.engine, } - s.InitServer() + s.Init() }) } } @@ -119,22 +119,6 @@ func TestHttpServer_register(t *testing.T) { } } -func TestStatus(t *testing.T) { - tests := []struct { - name string - want bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := Status(); got != tt.want { - t.Errorf("Status() = %v, want %v", got, tt.want) - } - }) - } -} - func TestHttpServer_initServer(t *testing.T) { type fields struct { server *http.Server diff --git a/src/server/handle/auth.go b/src/server/http_handler/auth.go similarity index 87% rename from src/server/handle/auth.go rename to src/server/http_handler/auth.go index 1e0be86..3a4c6ab 100644 --- a/src/server/handle/auth.go +++ b/src/server/http_handler/auth.go @@ -1,4 +1,4 @@ -package handle +package http_handler import ( "github.com/gin-gonic/gin" diff --git a/src/server/http_handler/cert.go b/src/server/http_handler/cert.go new file mode 100644 index 0000000..38c9b50 --- /dev/null +++ b/src/server/http_handler/cert.go @@ -0,0 +1,43 @@ +package http_handler + +import ( + "acme-mana/src/acme" + "acme-mana/src/conf" + "acme-mana/src/server/model" + "github.com/gin-gonic/gin" + "log" +) + +var CertHandlerInstance = &CertHandler{} + +type CertHandler struct { +} + +func (h *CertHandler) List(c *gin.Context) { + log.Println("list cert") + certs := conf.Config().Certs + c.JSON(200, model.SuccessD(certs)) +} + +func (h *CertHandler) Get(c *gin.Context) { + log.Println("get cert") + name := c.Query("name") + if name == "" { + c.Error(model.NewAppError(500, "请输入名称")) + return + } + cert, _ := conf.FindCert(name) + c.JSON(200, model.SuccessD(cert)) +} + +func (h *CertHandler) Refresh(c *gin.Context) { + log.Println("refresh cert") + name := c.Query("name") + if name == "" { + c.Error(model.NewAppError(500, "请输入名称")) + return + } + cert, _ := conf.FindCert(name) + acme.Apply(cert) + c.JSON(200, model.Success()) +} diff --git a/src/server/handle/conf.go b/src/server/http_handler/conf.go similarity index 92% rename from src/server/handle/conf.go rename to src/server/http_handler/conf.go index 277c400..46699d8 100644 --- a/src/server/handle/conf.go +++ b/src/server/http_handler/conf.go @@ -1,4 +1,4 @@ -package handle +package http_handler import ( "acme-mana/src/conf" diff --git a/src/server/handle/error.go b/src/server/http_handler/error.go similarity index 97% rename from src/server/handle/error.go rename to src/server/http_handler/error.go index 12ea9bc..81a6ab6 100644 --- a/src/server/handle/error.go +++ b/src/server/http_handler/error.go @@ -1,4 +1,4 @@ -package handle +package http_handler import ( "acme-mana/src/server/model" diff --git a/src/server/socker-server.go b/src/server/socker-server.go new file mode 100644 index 0000000..7222ae8 --- /dev/null +++ b/src/server/socker-server.go @@ -0,0 +1,53 @@ +package server + +import ( + "acme-mana/src/common" + socker_handler "acme-mana/src/server/socket_handler" + "log" + "net" + "os" +) + +type SocketServer struct { +} + +func (s *SocketServer) InitSocketServer() { + +} + +func (s *SocketServer) Start() { + go start() +} + +func start() { + log.Println("启动指令监听服务...") + // 删除旧的 socket 文件 + if _, err := os.Stat(common.SocketFile); err == nil { + err := os.Remove(common.SocketFile) + if err != nil { + log.Fatalf("无法删除Socket文件: %v\n", err) + } + } + + listener, err := net.Listen("unix", common.SocketFile) + if err != nil { + log.Fatalf("Failed to listen on socket: %v", err) + } + defer func(listener net.Listener) { + err := listener.Close() + if err != nil { + log.Fatalf("无法关闭Socket文件监听: %v", err) + } + }(listener) + + for { + log.Println("等待连接...") + conn, err := listener.Accept() + if err != nil { + log.Printf("无法建立连接: %v", err) + continue + } + + go socker_handler.HandleConnection(conn) + } +} diff --git a/src/server/socket_handler/cert.go b/src/server/socket_handler/cert.go new file mode 100644 index 0000000..29f9402 --- /dev/null +++ b/src/server/socket_handler/cert.go @@ -0,0 +1 @@ +package socket_handler diff --git a/src/server/socket_handler/handler.go b/src/server/socket_handler/handler.go new file mode 100644 index 0000000..8247364 --- /dev/null +++ b/src/server/socket_handler/handler.go @@ -0,0 +1,47 @@ +package socket_handler + +import ( + "acme-mana/src/common" + "log" + "net" + "os" +) + +func HandleConnection(conn net.Conn) { + defer conn.Close() + + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + log.Printf("Failed to read command: %v", err) + return + } + + command := string(buf[:n]) + log.Printf("Received command: %s", command) + + // 在这里处理接收到的命令 + switch command { + case "stop": + onStop() + default: + onCommand(command) + } +} + +/* +收到命令 +*/ +func onCommand(command string) { + +} + +// 收到停止命令 +func onStop() { + log.Println("停止守护进程...") + os.Remove(common.PidFile) + log.Println("删除PID文件...") + os.Remove(common.SocketFile) + log.Println("删除Socket文件...") + os.Exit(0) +} diff --git a/src/server/variable.go b/src/server/variable.go index 61c5b6b..8968dbd 100644 --- a/src/server/variable.go +++ b/src/server/variable.go @@ -1,3 +1,4 @@ package server -var Instance *HttpServer +var HttpInstance *HttpServer +var SocketInstance *SocketServer diff --git a/src/task.go b/src/task.go index 556dfa1..b8b9cc3 100644 --- a/src/task.go +++ b/src/task.go @@ -1,86 +1,86 @@ package src -import ( - "encoding/json" - "log" - "os" - "path" - "time" -) - -var AutoRefreshCertTicker = time.NewTicker(time.Hour) - -func AutoRefreshCert() { - log.Println("Start auto refresh cert") - defer AutoRefreshCertTicker.Stop() - for { - select { - case <-AutoRefreshCertTicker.C: - doRefreshCert() - } - } -} - -func doRefreshCert() { - domains := GetAppConfig().Domains - for _, domain := range domains { - doRefreshCertOnce(domain) - } -} - -func doRefreshCertOnce(domain Domain) { - name := domain.Name - dir := GetAppConfig().CertDir - certDir := path.Join(dir, name) - if !ValidExist(certDir, domain) { - Apply(domain) - } - infoFile := path.Join(certDir, CertInfoFileName) - certInfo := ParseCertInfo(infoFile, domain) - log.Println("Checking if the certificate is expired, Domain: {}", name) - if certInfo.Info.NotAfter.Sub(time.Now()) < 14*24*time.Hour { - log.Println("Apply for a certificate that is about to expire, domain name:", name) - Apply(domain) - } -} - -func ValidExist(certDir string, domain Domain) bool { - _, err := os.Stat(certDir) - if os.IsNotExist(err) { - log.Printf("Applying for a certificate, Domain: %s certificate directory does not exist!", domain.Name) - return false - } - if !ExistFile(certDir, CertFileName) { - log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, CertFileName) - return false - } - if !ExistFile(certDir, KeyFileName) { - log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, KeyFileName) - return false - } - if !ExistFile(certDir, CertInfoFileName) { - log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, CertInfoFileName) - return false - } - return true -} - -func ParseCertInfo(infoFile string, domain Domain) CertInfo { - infoBytes, err := os.ReadFile(infoFile) - if err != nil { - log.Println("Failed to read cert info file, Domain: {}", domain.Name) - } - var certInfo CertInfo - err = json.Unmarshal(infoBytes, &certInfo) - //if err != nil { - // log.Println("Failed to parse cert info file, Domain: {}", domain.Name) - //} - return certInfo -} - -func ExistFile(dir string, fileName string) bool { - f := path.Join(dir, fileName) - _, err := os.Stat(f) - return !os.IsNotExist(err) - -} +//import ( +// "encoding/json" +// "log" +// "os" +// "path" +// "time" +//) +// +//var AutoRefreshCertTicker = time.NewTicker(time.Hour) +// +//func AutoRefreshCert() { +// log.Println("Start auto refresh cert") +// defer AutoRefreshCertTicker.Stop() +// for { +// select { +// case <-AutoRefreshCertTicker.C: +// doRefreshCert() +// } +// } +//} +// +//func doRefreshCert() { +// domains := GetAppConfig().Domains +// for _, domain := range domains { +// doRefreshCertOnce(domain) +// } +//} +// +//func doRefreshCertOnce(domain Domain) { +// name := domain.Name +// dir := GetAppConfig().CertDir +// certDir := path.Join(dir, name) +// if !ValidExist(certDir, domain) { +// Apply(domain) +// } +// infoFile := path.Join(certDir, CertInfoFileName) +// certInfo := ParseCertInfo(infoFile, domain) +// log.Println("Checking if the certificate is expired, Domain: {}", name) +// if certInfo.Info.NotAfter.Sub(time.Now()) < 14*24*time.Hour { +// log.Println("Apply for a certificate that is about to expire, domain name:", name) +// Apply(domain) +// } +//} +// +//func ValidExist(certDir string, domain Domain) bool { +// _, err := os.Stat(certDir) +// if os.IsNotExist(err) { +// log.Printf("Applying for a certificate, Domain: %s certificate directory does not exist!", domain.Name) +// return false +// } +// if !ExistFile(certDir, CertFileName) { +// log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, CertFileName) +// return false +// } +// if !ExistFile(certDir, KeyFileName) { +// log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, KeyFileName) +// return false +// } +// if !ExistFile(certDir, CertInfoFileName) { +// log.Printf("Applying for a certificate, Domain: %s %s does not exist!", domain.Name, CertInfoFileName) +// return false +// } +// return true +//} +// +//func ParseCertInfo(infoFile string, domain Domain) CertInfo { +// infoBytes, err := os.ReadFile(infoFile) +// if err != nil { +// log.Println("Failed to read cert info file, Domain: {}", domain.Name) +// } +// var certInfo CertInfo +// err = json.Unmarshal(infoBytes, &certInfo) +// //if err != nil { +// // log.Println("Failed to parse cert info file, Domain: {}", domain.Name) +// //} +// return certInfo +//} +// +//func ExistFile(dir string, fileName string) bool { +// f := path.Join(dir, fileName) +// _, err := os.Stat(f) +// return !os.IsNotExist(err) +// +//} diff --git a/src/task/task.go b/src/task/task.go new file mode 100644 index 0000000..f460645 --- /dev/null +++ b/src/task/task.go @@ -0,0 +1,114 @@ +package task + +import ( + "acme-mana/src/acme" + "acme-mana/src/common" + "acme-mana/src/conf" + "encoding/json" + "log" + "os" + "path" + "time" +) + +var AutoRefreshCertTicker = time.NewTicker(time.Hour) + +func AutoRefreshCert() { + log.Println("Start auto refresh cert") + defer AutoRefreshCertTicker.Stop() + for { + select { + case <-AutoRefreshCertTicker.C: + doRefreshCert() + } + } +} + +func doRefreshCert() { + config := conf.Config() + certs := config.Certs + + for _, cert := range *certs { + doRefreshCertOne(&cert) + } + + //domains := GetAppConfig().Domains + //for _, domain := range domains { + // doRefreshCertOnce(domain) + //} +} + +func doRefreshCertOne(cert *conf.CertConf) { + name := cert.Name + dir := cert.Dir + certDir := path.Join(dir, name) + if !ValidExist(certDir, cert) { + acme.Apply(cert) + return + } + infoFile := path.Join(certDir, common.CertInfoFileName) + certInfo := ParseCertInfo(infoFile, cert) + log.Println("校验当前证书有效时间, 证书: {}", name) + if certInfo.Info.NotAfter.Sub(time.Now()) < 14*24*time.Hour { + log.Println("申请即将过期的证书: {}", name) + acme.Apply(cert) + } + +} + +//func doRefreshCertOnce(domain Domain) { +// name := domain.Name +// dir := GetAppConfig().CertDir +// certDir := path.Join(dir, name) +// if !ValidExist(certDir, domain) { +// Apply(domain) +// } +// infoFile := path.Join(certDir, CertInfoFileName) +// certInfo := ParseCertInfo(infoFile, domain) +// log.Println("Checking if the certificate is expired, Domain: {}", name) +// if certInfo.Info.NotAfter.Sub(time.Now()) < 14*24*time.Hour { +// log.Println("Apply for a certificate that is about to expire, domain name:", name) +// Apply(domain) +// } +//} + +func ValidExist(certDir string, cert *conf.CertConf) bool { + _, err := os.Stat(certDir) + if os.IsNotExist(err) { + log.Printf("Applying for a certificate, Domain: %s certificate directory does not exist!", cert.Name) + return false + } + if !ExistFile(certDir, common.CertFileName) { + log.Printf("Applying for a certificate, Domain: %s %s does not exist!", cert.Name, common.CertFileName) + return false + } + if !ExistFile(certDir, common.KeyFileName) { + log.Printf("Applying for a certificate, Domain: %s %s does not exist!", cert.Name, common.KeyFileName) + return false + } + if !ExistFile(certDir, common.CertInfoFileName) { + log.Printf("Applying for a certificate, Domain: %s %s does not exist!", cert.Name, common.CertInfoFileName) + return false + } + return true +} + +func ParseCertInfo(infoFile string, cert *conf.CertConf) *acme.CertInfo { + infoBytes, err := os.ReadFile(infoFile) + if err != nil { + log.Println("Failed to read cert info file, Domain: {}", cert.Name) + } + var certInfo acme.CertInfo + err = json.Unmarshal(infoBytes, &certInfo) + //if err != nil { + // log.Println("Failed to parse cert info file, Domain: {}", domain.Name) + //} + return &certInfo +} + +func ExistFile(dir string, fileName string) bool { + f := path.Join(dir, fileName) + _, err := os.Stat(f) + return !os.IsNotExist(err) + +} diff --git a/src/variable.go b/src/variable.go deleted file mode 100644 index c5af8d0..0000000 --- a/src/variable.go +++ /dev/null @@ -1,28 +0,0 @@ -package src - -const PidFile = "acme-mana.pid" -const SocketFile = "acme-mana.sock" - -const CertFileName = "fullchain.pem" -const KeyFileName = "privkey.pem" -const CertInfoFileName = "info.json" - -var DnsProviderSupports = []string{"alidns", "tencentcloud", "cloudflare"} - -var appConfig *AppConfig = ReadConfig() - -func GetAppConfig() *AppConfig { - return appConfig -} - -var envConf *EnvConf = InitRuntimeConf() - -func GetEnvConf() *EnvConf { - return envConf -} - -type DomainData struct { - Fullchain string `json:"fullchain"` - Key string `json:"key"` - Info string `json:"info"` -}