This commit is contained in:
ZhuoQinghui 2024-12-27 16:50:51 +08:00
parent 510af680c4
commit d505213d2c
24 changed files with 1223 additions and 898 deletions

View File

@ -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
}

238
src/acme/helper.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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")

View File

@ -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() {
//
//}

View File

@ -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

View File

@ -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
//}

View File

@ -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)
//}

View File

@ -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")
//}

View File

@ -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)
//}

View File

@ -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")
}

View File

@ -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)
}

View File

@ -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

View File

@ -1,4 +1,4 @@
package handle
package http_handler
import (
"github.com/gin-gonic/gin"

View File

@ -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())
}

View File

@ -1,4 +1,4 @@
package handle
package http_handler
import (
"acme-mana/src/conf"

View File

@ -1,4 +1,4 @@
package handle
package http_handler
import (
"acme-mana/src/server/model"

View File

@ -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)
}
}

View File

@ -0,0 +1 @@
package socket_handler

View File

@ -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)
}

View File

@ -1,3 +1,4 @@
package server
var Instance *HttpServer
var HttpInstance *HttpServer
var SocketInstance *SocketServer

View File

@ -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)
//
//}

114
src/task/task.go Normal file
View File

@ -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)
}

View File

@ -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"`
}