ソースを参照

添加数据库支持并优化错误处理

新增了 SQLite 数据库支持,用于存储证书和域名记录,并在多个文件中进行了相应的修改。同时,移除了部分 panic 处理逻辑以简化代码。
SongZihuan 3 ヶ月 前
コミット
74560029be

+ 6 - 0
go.mod

@@ -20,11 +20,17 @@ require (
 	github.com/alibabacloud-go/tea-xml v1.1.3 // indirect
 	github.com/aliyun/credentials-go v1.3.10 // indirect
 	github.com/clbanning/mxj/v2 v2.5.5 // indirect
+	github.com/jinzhu/inflection v1.0.0 // indirect
+	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/mattn/go-sqlite3 v1.14.24 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	github.com/tjfoc/gmsm v1.4.1 // indirect
 	golang.org/x/net v0.23.0 // indirect
 	golang.org/x/sys v0.18.0 // indirect
+	golang.org/x/text v0.21.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
+	gorm.io/driver/sqlite v1.5.7 // indirect
+	gorm.io/gorm v1.25.12 // indirect
 )

+ 12 - 0
go.sum

@@ -79,6 +79,10 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
+github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
+github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
@@ -88,6 +92,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
+github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
 github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -193,6 +199,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
+golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -231,5 +239,9 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
 gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
+gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
+gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
+gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

+ 16 - 6
src/aliyun/main.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/config"
+	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/database"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/logger"
 	"os"
 	"strings"
@@ -22,12 +23,12 @@ func Init() (err error) {
 
 	casClient, err = createCASClient(key, secret)
 	if err != nil {
-		return fmt.Errorf("init alibaba cloud sdk CAS client error: %s\n", err.Error())
+		return fmt.Errorf("init alibaba cloud sdk CAS client error: %s", err.Error())
 	}
 
 	cdnClient, err = createCDNClient(key, secret)
 	if err != nil {
-		return fmt.Errorf("init alibaba cloud sdk CDN client error: %s\n", err.Error())
+		return fmt.Errorf("init alibaba cloud sdk CDN client error: %s", err.Error())
 	}
 
 	return nil
@@ -36,28 +37,37 @@ func Init() (err error) {
 func UpdateCDNHttpsByFilePath(domainList []string, cert string, prikey string) error {
 	certData, err := os.ReadFile(cert)
 	if err != nil {
-		return fmt.Errorf("read cert file error: %s\n", err.Error())
+		return fmt.Errorf("read cert file error: %s", err.Error())
 	}
 
 	privateKeyData, err := os.ReadFile(prikey)
 	if err != nil {
-		return fmt.Errorf("read private key error: %s\n", err.Error())
+		return fmt.Errorf("read private key error: %s", err.Error())
 	}
 
 	return UpdateCDNHttps(domainList, certData, privateKeyData)
 }
 
 func UpdateCDNHttps(domainList []string, certData []byte, privateKeyData []byte) error {
-	certID, certName, err := uploadCert(certData, privateKeyData)
+	certID, certName, subject, err := uploadCert(certData, privateKeyData)
 	if err != nil && errors.Is(err, ErrCertExists) {
 		logger.Warnf("证书已存在, 不在重新更新CDN(%s)", strings.Join(domainList, ", "))
 		return nil
 	} else if err != nil {
-		return fmt.Errorf("aliyun cloud ssl cert/key upload error: %s\n", err.Error())
+		return fmt.Errorf("aliyun cloud ssl cert/key upload error: %s", err.Error())
+	}
+
+	err = database.UpdateCert(certID, certName, subject)
+	if err != nil {
+		logger.Errorf("aliyun cloud ssl cert/key save to sqlite error: %s", err.Error())
 	}
 
 	for _, domain := range domainList {
 		setDomainServerCertificateNotError(domain, certID, certName)
+		err = database.UpdateDomain(certID, certName, subject, domain)
+		if err != nil {
+			logger.Error("aliyun cloud ssl domain save to sqlite error: %s", err.Error())
+		}
 	}
 
 	return nil

+ 15 - 12
src/aliyun/operation.go

@@ -4,6 +4,7 @@ import (
 	"crypto/sha256"
 	"encoding/hex"
 	"errors"
+	"fmt"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/logger"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/utils"
 	cas "github.com/alibabacloud-go/cas-20200407/v3/client"
@@ -13,12 +14,14 @@ import (
 	"strings"
 )
 
-func uploadCert(certData []byte, keyData []byte) (certID int64, name string, err error) {
+func uploadCert(certData []byte, keyData []byte) (certID int64, name string, subject string, err error) {
 	cert, err := utils.ReadCertificate(certData)
 	if err != nil {
-		panic(err)
+		return 0, "", "", fmt.Errorf("read cert error: %s", err.Error())
 	}
 
+	subject = utils.GetCertDomainSubject(cert)
+
 	hash := sha256.Sum224(cert.RawSubjectPublicKeyInfo) // Sum256 太长
 	fingerprint := hex.EncodeToString(hash[:])
 
@@ -41,12 +44,12 @@ func uploadCert(certData []byte, keyData []byte) (certID int64, name string, err
 		var sdkErr *tea.SDKError
 		if errors.As(tryErr, &sdkErr) && tea.StringValue(sdkErr.Code) == "NameRepeat" {
 			logger.Warnf("证书已经存在, 证书名字:%s", fingerprint)
-			return 0, fingerprint, ErrCertExists
+			return 0, fingerprint, subject, ErrCertExists
 		}
-		return 0, fingerprint, tryErr
+		return 0, fingerprint, subject, tryErr
 	}
 	logger.Infof("上传成功, 证书名字:%s, 证书ID:%d, 请求ID:%s", fingerprint, tea.Int64Value(resp.Body.CertId), tea.StringValue(resp.Body.RequestId))
-	return tea.Int64Value(resp.Body.CertId), fingerprint, nil
+	return tea.Int64Value(resp.Body.CertId), fingerprint, subject, nil
 }
 
 func setDomainServerCertificate(domainName string, certID int64, certName string) (err error) {
@@ -80,13 +83,13 @@ func setDomainServerCertificate(domainName string, certID int64, certName string
 
 func setDomainServerCertificateNotError(domainName string, certID int64, certName string) {
 	defer func() {
-		if r := recover(); r != nil {
-			if err, ok := r.(error); ok {
-				logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s) panic: %s", domainName, err.Error())
-			} else {
-				logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s) panic: %v", domainName, r)
-			}
-		}
+		//if r := recover(); r != nil {
+		//	if err, ok := r.(error); ok {
+		//		logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s) panic: %s", domainName, err.Error())
+		//	} else {
+		//		logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s) panic: %v", domainName, r)
+		//	}
+		//}
 	}()
 
 	err := setDomainServerCertificate(domainName, certID, certName)

+ 10 - 0
src/cmd/auto-aliyun-cdn-ssl-cli/version1/main.go

@@ -0,0 +1,10 @@
+package main
+
+import (
+	mainfunc "github.com/SongZihuan/auto-aliyun-cdn-ssl/src/mainfunc/auto-aliyun-cdn-ssl-cli"
+	"os"
+)
+
+func main() {
+	os.Exit(mainfunc.MainV1())
+}

+ 0 - 10
src/cmd/version1/main.go

@@ -1,10 +0,0 @@
-package main
-
-import (
-	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/mainfunc"
-	"os"
-)
-
-func main() {
-	os.Exit(mainfunc.MainV1())
-}

+ 15 - 2
src/config/domainconfg.go

@@ -16,8 +16,10 @@ type DomainListCollection struct {
 }
 
 type DomainListsGroup struct {
-	RootDir    string                 `yaml:"rootrir"`
-	Collection []DomainListCollection `yaml:"collection"`
+	SQLFilePath    string                 `yaml:"sqlfilepath"`
+	ActiveShutdown utils.StringBool       `yaml:"activeshutdown"`
+	RootDir        string                 `yaml:"rootrir"`
+	Collection     []DomainListCollection `yaml:"collection"`
 }
 
 func (d *DomainListsGroup) SetDefault(configPath string) {
@@ -28,6 +30,17 @@ func (d *DomainListsGroup) SetDefault(configPath string) {
 			d.RootDir = path.Dir(configPath)
 		}
 	}
+
+	d.ActiveShutdown.SetDefaultDisable()
+
+	if d.SQLFilePath == "" {
+		if baota.HasBaoTaLetsEncrypt() {
+			d.SQLFilePath = path.Join(configPath, "auto-aliyun-cdn-ssl.db")
+		} else {
+			d.SQLFilePath = "./auto-aliyun-cdn-ssl.db"
+		}
+	}
+
 	return
 }
 

+ 88 - 0
src/database/db.go

@@ -0,0 +1,88 @@
+package database
+
+import (
+	"errors"
+	"fmt"
+	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/logger"
+	"gorm.io/gorm"
+)
+
+func UpdateCert(certID int64, name string, subject string) error {
+	err := db.Transaction(func(tx *gorm.DB) error {
+		var cert CertRecord
+		err := tx.Model(&CertRecord{}).Where("name = ?", name).First(&cert).Error
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			cert = CertRecord{
+				CertID:  certID,
+				Name:    name,
+				Subject: subject,
+			}
+
+			err = tx.Create(&cert).Error
+			if err != nil {
+				return err
+			}
+		} else if err != nil {
+			return err
+		} else if cert.CertID != certID || cert.Name != name || cert.Subject != subject {
+			cert.CertID = certID
+			cert.Name = name
+			cert.Subject = subject
+
+			err = tx.Save(&cert).Error
+			if err != nil {
+				return err
+			}
+
+			err = tx.Model(&DomainRecord{}).Updates(map[string]interface{}{
+				"cert_id": cert.CertID,
+				"name":    cert.Name,
+				"subject": cert.Subject,
+			}).Error
+			if err != nil {
+				logger.Errorf("try update CDN domain SSL record because information does not match, but failed: %s", err.Error())
+			}
+		}
+
+		return nil
+	})
+	if err != nil {
+		return fmt.Errorf("create/update CAS cert record to SQLitte failed: %s", err.Error())
+	}
+
+	return nil
+}
+
+func UpdateDomain(certID int64, name string, subject string, domain string) error {
+	err := db.Transaction(func(tx *gorm.DB) error {
+		var cert CertRecord
+		err := tx.Model(&CertRecord{}).Where("name = ?", name).First(&cert).Error
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return fmt.Errorf("update CDN domain SSL record failed: cert record not found")
+		} else if err != nil {
+			return err
+		} else if cert.CertID != certID || cert.Name != name || cert.Subject != subject {
+			logger.Errorf("Update CDN domain SSL record failed: information does not match (sqlite: cert-id=%d; name=%s; subject=%s) (be given: cert-id=%d; name=%s; subject=%s)", cert.CertID, cert.Name, cert.Subject, certID, name, subject)
+		}
+
+		record := DomainRecord{
+			CertRecordID: cert.ID,
+			CertID:       cert.CertID,
+			Name:         cert.Name,
+			Subject:      cert.Subject,
+			Domain:       domain,
+		}
+
+		err = tx.Save(&record).Error
+		if err != nil {
+			return err
+		}
+
+		return nil
+	})
+	if err != nil {
+		return fmt.Errorf("create/update CDN domain SSL record to SQLite failed: %s", err.Error())
+	}
+
+	return nil
+}

+ 52 - 0
src/database/init.go

@@ -0,0 +1,52 @@
+package database
+
+import (
+	"fmt"
+	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/config"
+	"gorm.io/driver/sqlite"
+	"gorm.io/gorm"
+)
+
+var db *gorm.DB
+
+func InitSQLite() error {
+	if !config.IsReady() {
+		panic("config is not ready")
+	}
+
+	sqlfilepath := config.GetConfig().SQLFilePath
+
+	_db, err := gorm.Open(sqlite.Open(sqlfilepath), &gorm.Config{})
+	if err != nil {
+		return fmt.Errorf("connect to sqlite (%s) failed: %s", sqlfilepath, err)
+	}
+
+	err = _db.AutoMigrate(&CertRecord{}, &DomainRecord{})
+	if err != nil {
+		return fmt.Errorf("migrate sqlite (%s) failed: %s", sqlfilepath, err)
+	}
+
+	db = _db
+	return nil
+}
+
+func CloseSQLite() {
+	if db == nil {
+		return
+	}
+
+	defer func() {
+		db = nil
+	}()
+
+	if !config.IsReady() {
+		panic("config is not ready")
+	}
+
+	if config.GetConfig().ActiveShutdown.IsEnable(false) {
+		// https://github.com/go-gorm/gorm/issues/3145
+		if sqlDB, err := db.DB(); err == nil {
+			_ = sqlDB.Close()
+		}
+	}
+}

+ 27 - 0
src/database/module.go

@@ -0,0 +1,27 @@
+package database
+
+import "gorm.io/gorm"
+
+type CertRecord struct {
+	gorm.Model
+	CertID  int64  `gorm:"column:cert_id;not null;uniqueIndex:unq_idx_cert"`
+	Name    string `gorm:"column:name;type:VARCHAR(100);not null;uniqueIndex:unq_idx_cert"`
+	Subject string `gorm:"column:subject;type:VARCHAR(100);not null;"`
+}
+
+func (*CertRecord) TableName() string {
+	return "cert_record"
+}
+
+type DomainRecord struct {
+	gorm.Model
+	CertRecordID uint   `gorm:"column:cert_record_id;not null;"`
+	CertID       int64  `gorm:"column:cert_id;not null"`
+	Name         string `gorm:"column:name;type:VARCHAR(100);not null"`
+	Subject      string `gorm:"column:subject;type:VARCHAR(100);not null;"`
+	Domain       string `gorm:"type:VARCHAR(100);not null;"` // 允许多次重复记录
+}
+
+func (*DomainRecord) TableName() string {
+	return "domain_record"
+}

+ 8 - 1
src/mainfunc/v1.go → src/mainfunc/auto-aliyun-cdn-ssl-cli/v1.go

@@ -1,9 +1,10 @@
-package mainfunc
+package auto_aliyun_cdn_ssl_cli
 
 import (
 	"errors"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/aliyun"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/config"
+	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/database"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/flagparser"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/logger"
 	"github.com/SongZihuan/auto-aliyun-cdn-ssl/src/server"
@@ -51,6 +52,12 @@ func MainV1() int {
 	logger.Executablef("%s", "ready")
 	logger.Infof("run mode: %s", config.GetConfig().GlobalConfig.GetRunMode())
 
+	err = database.InitSQLite()
+	if err != nil {
+		return utils.ExitByError(err)
+	}
+	defer database.CloseSQLite()
+
 	err = aliyun.Init()
 	if err != nil {
 		return utils.ExitByError(err)

+ 7 - 7
src/server/server.go

@@ -14,13 +14,13 @@ func Server() error {
 	for index, collection := range cfg.Collection {
 		func() {
 			defer func() {
-				if r := recover(); r != nil {
-					if err, ok := r.(error); ok {
-						logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s / %d) panic: %s", strings.Join(collection.Domain, ", "), index, err.Error())
-					} else {
-						logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s / %d) panic: %v", strings.Join(collection.Domain, ", "), index, r)
-					}
-				}
+				//if r := recover(); r != nil {
+				//	if err, ok := r.(error); ok {
+				//		logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s / %d) panic: %s", strings.Join(collection.Domain, ", "), index, err.Error())
+				//	} else {
+				//		logger.Panicf("aliyun update CDN HTTPS by domains/collection (%s / %d) panic: %v", strings.Join(collection.Domain, ", "), index, r)
+				//	}
+				//}
 			}()
 
 			certPath, prikeyPath := collection.GetFilePath()

+ 12 - 0
src/utils/x509.go

@@ -52,3 +52,15 @@ func CheckCertWithTime(cert *x509.Certificate, gracePeriod time.Duration) bool {
 
 	return true
 }
+
+func GetCertDomainSubject(cert *x509.Certificate) string {
+	if cert.Subject.CommonName != "" {
+		return cert.Subject.CommonName // 通用名匹配
+	}
+
+	if len(cert.DNSNames) > 0 {
+		return cert.DNSNames[0]
+	}
+
+	return ""
+}