mirror of
https://gitee.com/mirrors/AllinSSL.git
synced 2026-03-29 08:43:25 +08:00
Add files via upload
This commit is contained in:
135
backend/app/api/access.go
Normal file
135
backend/app/api/access.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"ALLinSSL/backend/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetAccessList(c *gin.Context) {
|
||||
var form struct {
|
||||
Search string `form:"search"`
|
||||
Page int64 `form:"p"`
|
||||
Limit int64 `form:"limit"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
accessList, count, err := access.GetList(form.Search, form.Page, form.Limit)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessData(c, accessList, count)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func GetAllAccess(c *gin.Context) {
|
||||
var form struct {
|
||||
Type string `form:"type"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
accessList, err := access.GetAll(form.Type)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessData(c, accessList, 0)
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func AddAccess(c *gin.Context) {
|
||||
var form struct {
|
||||
Name string `form:"name"`
|
||||
Type string `form:"type"`
|
||||
Config string `form:"config"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.Name = strings.TrimSpace(form.Name)
|
||||
if form.Name == "" {
|
||||
public.FailMsg(c, "名称不能为空")
|
||||
return
|
||||
}
|
||||
if form.Type == "" {
|
||||
public.FailMsg(c, "类型不能为空")
|
||||
return
|
||||
}
|
||||
if form.Config == "" {
|
||||
public.FailMsg(c, "配置不能为空")
|
||||
return
|
||||
}
|
||||
err = access.AddAccess(form.Config, form.Name, form.Type)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "添加成功")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func UpdateAccess(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
Name string `form:"name"`
|
||||
Config string `form:"config"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.Name = strings.TrimSpace(form.Name)
|
||||
if form.Name == "" {
|
||||
public.FailMsg(c, "名称不能为空")
|
||||
return
|
||||
}
|
||||
if form.Config == "" {
|
||||
public.FailMsg(c, "配置不能为空")
|
||||
return
|
||||
}
|
||||
err = access.UpdateAccess(form.ID, form.Config, form.Name)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "修改成功")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func DelAccess(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
if form.ID == "" {
|
||||
public.FailMsg(c, "ID不能为空")
|
||||
return
|
||||
}
|
||||
err = access.DelAccess(form.ID)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "删除成功")
|
||||
return
|
||||
}
|
||||
129
backend/app/api/cert.go
Normal file
129
backend/app/api/cert.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/cert"
|
||||
"ALLinSSL/backend/public"
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetCertList(c *gin.Context) {
|
||||
var form struct {
|
||||
Search string `form:"search"`
|
||||
Page int64 `form:"p"`
|
||||
Limit int64 `form:"limit"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
certList, count, err := cert.GetList(form.Search, form.Page, form.Limit)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessData(c, certList, count)
|
||||
return
|
||||
}
|
||||
|
||||
func UploadCert(c *gin.Context) {
|
||||
var form struct {
|
||||
Key string `form:"key"`
|
||||
Cert string `form:"cert"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.Key = strings.TrimSpace(form.Key)
|
||||
form.Cert = strings.TrimSpace(form.Cert)
|
||||
|
||||
if form.Key == "" {
|
||||
public.FailMsg(c, "名称不能为空")
|
||||
return
|
||||
}
|
||||
if form.Cert == "" {
|
||||
public.FailMsg(c, "类型不能为空")
|
||||
return
|
||||
}
|
||||
err = cert.UploadCert(form.Key, form.Cert)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "添加成功")
|
||||
return
|
||||
}
|
||||
|
||||
func DelCert(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
if form.ID == "" {
|
||||
public.FailMsg(c, "ID不能为空")
|
||||
return
|
||||
}
|
||||
err = cert.DelCert(form.ID)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "删除成功")
|
||||
return
|
||||
}
|
||||
|
||||
func DownloadCert(c *gin.Context) {
|
||||
ID := c.Query("id")
|
||||
|
||||
if ID == "" {
|
||||
public.FailMsg(c, "ID不能为空")
|
||||
return
|
||||
}
|
||||
certData, err := cert.GetCert(ID)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 构建 zip 包(内存中)
|
||||
buf := new(bytes.Buffer)
|
||||
zipWriter := zip.NewWriter(buf)
|
||||
|
||||
for filename, content := range certData {
|
||||
if filename == "cert" || filename == "key" {
|
||||
writer, err := zipWriter.Create(filename + ".pem")
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
_, err = writer.Write([]byte(content))
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// 关闭 zipWriter
|
||||
if err := zipWriter.Close(); err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
// 设置响应头
|
||||
|
||||
zipName := strings.ReplaceAll(certData["domains"], ".", "_")
|
||||
zipName = strings.ReplaceAll(zipName, ",", "-")
|
||||
|
||||
c.Header("Content-Type", "application/zip")
|
||||
c.Header("Content-Disposition", "attachment; filename="+zipName+".zip")
|
||||
c.Data(200, "application/zip", buf.Bytes())
|
||||
return
|
||||
}
|
||||
162
backend/app/api/login.go
Normal file
162
backend/app/api/login.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Sign(c *gin.Context) {
|
||||
var form struct {
|
||||
Username string `form:"username" binding:"required"`
|
||||
Password string `form:"password" binding:"required"`
|
||||
Code string `form:"code"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
// return
|
||||
}
|
||||
form.Username = strings.TrimSpace(form.Username)
|
||||
form.Code = strings.TrimSpace(form.Code)
|
||||
|
||||
// 从数据库拿用户
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
s.Connect()
|
||||
defer s.Close()
|
||||
s.TableName = "users"
|
||||
res, err := s.Where("username=?", []interface{}{form.Username}).Select()
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
session := sessions.Default(c)
|
||||
now := time.Now()
|
||||
|
||||
loginErrCount := session.Get("__loginErrCount")
|
||||
loginErrEnd := session.Get("__loginErrEnd")
|
||||
ErrCount := 0
|
||||
ErrEnd := now
|
||||
// 获取登录错误次数
|
||||
if __loginErrCount, ok := loginErrCount.(int); ok {
|
||||
ErrCount = __loginErrCount
|
||||
}
|
||||
// 获取登录错误时间
|
||||
if __loginErrEnd, ok := loginErrEnd.(time.Time); ok {
|
||||
ErrEnd = __loginErrEnd
|
||||
}
|
||||
|
||||
// fmt.Println(ErrCount, ErrEnd)
|
||||
|
||||
// 判断登录错误次数
|
||||
switch {
|
||||
case ErrCount >= 5:
|
||||
// 登录错误次数超过5次,15分钟内禁止登录
|
||||
if now.Sub(ErrEnd) < 15*time.Minute {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR("登录次数过多,请15分钟后再试"))
|
||||
public.FailMsg(c, "登录次数过多,请15分钟后再试")
|
||||
return
|
||||
}
|
||||
session.Delete("__loginErrEnd")
|
||||
case ErrCount > 0:
|
||||
if form.Code == "" {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR("验证码错误1"))
|
||||
public.FailMsg(c, "验证码错误1")
|
||||
return
|
||||
} else {
|
||||
// 这里添加验证码的逻辑
|
||||
verifyCode := session.Get("_verifyCode")
|
||||
if _verifyCode, ok := verifyCode.(string); ok {
|
||||
if !strings.EqualFold(form.Code, _verifyCode) {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR("验证码错误2"))
|
||||
public.FailMsg(c, "验证码错误2")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR("验证码错误3"))
|
||||
public.FailMsg(c, "验证码错误3")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断用户是否存在
|
||||
if len(res) == 0 {
|
||||
session.Set("__loginErrCount", ErrCount+1)
|
||||
session.Set("__loginErrEnd", now)
|
||||
_ = session.Save()
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR("用户不存在"))
|
||||
// 设置cookie
|
||||
c.SetCookie("must_code", "1", 0, "/", "", false, true)
|
||||
public.FailMsg(c, "用户不存在")
|
||||
return
|
||||
}
|
||||
// 判断密码是否正确
|
||||
// qSalt := "_bt_all_in_ssl"
|
||||
// password := md5.Sum([]byte(form.Password + qSalt))
|
||||
// passwordMd5 := hex.EncodeToString(password[:])
|
||||
// fmt.Println(passwordMd5)
|
||||
salt, ok := res[0]["salt"].(string)
|
||||
if !ok {
|
||||
salt = "_bt_all_in_ssl"
|
||||
}
|
||||
passwd := form.Password + salt
|
||||
// fmt.Println(passwd)
|
||||
keyMd5 := md5.Sum([]byte(passwd))
|
||||
passwdMd5 := hex.EncodeToString(keyMd5[:])
|
||||
// fmt.Println(passwdMd5)
|
||||
|
||||
if res[0]["password"] != passwdMd5 {
|
||||
session.Set("__loginErrCount", ErrCount+1)
|
||||
session.Set("__loginErrEnd", now)
|
||||
_ = session.Save()
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR("密码错误"))
|
||||
// 设置cookie
|
||||
c.SetCookie("must_code", "1", 0, "/", "", false, false)
|
||||
public.FailMsg(c, "密码错误")
|
||||
return
|
||||
}
|
||||
|
||||
// session := sessions.Default(c)
|
||||
session.Set("__loginErrCount", 0)
|
||||
session.Delete("__loginErrEnd")
|
||||
session.Set("login", true)
|
||||
session.Set("__login_key", public.GetSettingIgnoreError("login_key"))
|
||||
_ = session.Save()
|
||||
// c.JSON(http.StatusOK, public.ResOK(0, nil, "登录成功"))
|
||||
// 设置cookie
|
||||
c.SetCookie("must_code", "1", -1, "/", "", false, true)
|
||||
public.SuccessMsg(c, "登录成功")
|
||||
return
|
||||
}
|
||||
|
||||
func GetCode(c *gin.Context) {
|
||||
_, bs64, code, _ := public.GenerateCode()
|
||||
session := sessions.Default(c)
|
||||
|
||||
session.Set("_verifyCode", code)
|
||||
_ = session.Save()
|
||||
public.SuccessData(c, bs64, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func SignOut(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
session.Delete("login")
|
||||
_ = session.Save()
|
||||
// c.JSON(http.StatusOK, public.ResOK(0, nil, "登出成功"))
|
||||
public.SuccessMsg(c, "登出成功")
|
||||
return
|
||||
}
|
||||
20
backend/app/api/overview.go
Normal file
20
backend/app/api/overview.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/overview"
|
||||
"ALLinSSL/backend/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetOverview(c *gin.Context) {
|
||||
// Get the overview data from the database
|
||||
overviewData, err := overview.GetOverviewData()
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Return the overview data as JSON
|
||||
public.SuccessData(c, overviewData, 0)
|
||||
|
||||
}
|
||||
102
backend/app/api/report.go
Normal file
102
backend/app/api/report.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/report"
|
||||
"ALLinSSL/backend/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetReportList(c *gin.Context) {
|
||||
var form struct {
|
||||
Search string `form:"search"`
|
||||
Page int64 `form:"p"`
|
||||
Limit int64 `form:"limit"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
certList, count, err := report.GetList(form.Search, form.Page, form.Limit)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessData(c, certList, count)
|
||||
return
|
||||
}
|
||||
|
||||
func AddReport(c *gin.Context) {
|
||||
var form struct {
|
||||
Name string `form:"name"`
|
||||
Type string `form:"type"`
|
||||
Config string `form:"config"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// fmt.Println(err)
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
err = report.AddReport(form.Type, form.Config, form.Name)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "添加成功")
|
||||
return
|
||||
}
|
||||
|
||||
func UpdReport(c *gin.Context) {
|
||||
var form struct {
|
||||
Id string `form:"id"`
|
||||
Name string `form:"name"`
|
||||
Config string `form:"config"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
err = report.UpdReport(form.Id, form.Config, form.Name)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "修改成功")
|
||||
return
|
||||
}
|
||||
|
||||
func DelReport(c *gin.Context) {
|
||||
var form struct {
|
||||
Id string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
err = report.DelReport(form.Id)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "删除成功")
|
||||
}
|
||||
|
||||
func NotifyTest(c *gin.Context) {
|
||||
var form struct {
|
||||
Id string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
err = report.NotifyTest(form.Id)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "发送成功")
|
||||
}
|
||||
40
backend/app/api/setting.go
Normal file
40
backend/app/api/setting.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/setting"
|
||||
"ALLinSSL/backend/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func GetSetting(c *gin.Context) {
|
||||
data, err := setting.Get()
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessData(c, data, 0)
|
||||
}
|
||||
|
||||
func SaveSetting(c *gin.Context) {
|
||||
var data setting.Setting
|
||||
if err := c.Bind(&data); err != nil {
|
||||
public.FailMsg(c, "参数错误")
|
||||
return
|
||||
}
|
||||
if err := setting.Save(&data); err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "保存成功")
|
||||
|
||||
}
|
||||
|
||||
func Shutdown(c *gin.Context) {
|
||||
setting.Shutdown()
|
||||
public.SuccessMsg(c, "关闭成功")
|
||||
}
|
||||
|
||||
func Restart(c *gin.Context) {
|
||||
setting.Restart()
|
||||
public.SuccessMsg(c, "正在重启...")
|
||||
}
|
||||
131
backend/app/api/siteMonitor.go
Normal file
131
backend/app/api/siteMonitor.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/siteMonitor"
|
||||
"ALLinSSL/backend/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetMonitorList(c *gin.Context) {
|
||||
var form struct {
|
||||
Search string `form:"search"`
|
||||
Page int64 `form:"p"`
|
||||
Limit int64 `form:"limit"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
data, count, err := siteMonitor.GetList(form.Search, form.Page, form.Limit)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
// c.JSON(http.StatusOK, public.ResOK(len(data), data, ""))
|
||||
public.SuccessData(c, data, count)
|
||||
return
|
||||
}
|
||||
|
||||
func AddMonitor(c *gin.Context) {
|
||||
var form struct {
|
||||
Name string `form:"name"`
|
||||
Domain string `form:"domain"`
|
||||
Cycle int `form:"cycle"`
|
||||
ReportType string `form:"report_type"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.Name = strings.TrimSpace(form.Name)
|
||||
form.Domain = strings.TrimSpace(form.Domain)
|
||||
|
||||
err = siteMonitor.AddMonitor(form.Name, form.Domain, form.ReportType, form.Cycle)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
// c.JSON(http.StatusOK, public.ResOK(0, nil, "添加成功"))
|
||||
public.SuccessMsg(c, "添加成功")
|
||||
return
|
||||
}
|
||||
|
||||
func UpdMonitor(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
Name string `form:"name"`
|
||||
Domain string `form:"domain"`
|
||||
Cycle int `form:"cycle"`
|
||||
ReportType string `form:"report_type"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
form.Name = strings.TrimSpace(form.Name)
|
||||
form.Domain = strings.TrimSpace(form.Domain)
|
||||
form.ReportType = strings.TrimSpace(form.ReportType)
|
||||
|
||||
err = siteMonitor.UpdMonitor(form.ID, form.Name, form.Domain, form.ReportType, form.Cycle)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
// c.JSON(http.StatusOK, public.ResOK(0, nil, "修改成功"))
|
||||
public.SuccessMsg(c, "修改成功")
|
||||
return
|
||||
}
|
||||
|
||||
func DelMonitor(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
err = siteMonitor.DelMonitor(form.ID)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
// c.JSON(http.StatusOK, public.ResOK(0, nil, "删除成功"))
|
||||
public.SuccessMsg(c, "删除成功")
|
||||
return
|
||||
}
|
||||
|
||||
func SetMonitor(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
Active int `form:"active"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
err = siteMonitor.SetMonitor(form.ID, form.Active)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
// c.JSON(http.StatusOK, public.ResOK(0, nil, "操作成功"))
|
||||
public.SuccessMsg(c, "操作成功")
|
||||
return
|
||||
}
|
||||
236
backend/app/api/workflow.go
Normal file
236
backend/app/api/workflow.go
Normal file
@@ -0,0 +1,236 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/workflow"
|
||||
"ALLinSSL/backend/public"
|
||||
"github.com/gin-gonic/gin"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetWorkflowList(c *gin.Context) {
|
||||
var form struct {
|
||||
Search string `form:"search"`
|
||||
Page int64 `form:"p"`
|
||||
Limit int64 `form:"limit"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
data, count, err := workflow.GetList(form.Search, form.Page, form.Limit)
|
||||
if err != nil {
|
||||
// c.JSON(http.StatusBadRequest, public.ResERR(err.Error()))
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
// c.JSON(http.StatusOK, public.ResOK(len(data), data, ""))
|
||||
public.SuccessData(c, data, count)
|
||||
return
|
||||
}
|
||||
|
||||
func AddWorkflow(c *gin.Context) {
|
||||
var form struct {
|
||||
Name string `form:"name"`
|
||||
Content string `form:"content"`
|
||||
ExecType string `form:"exec_type"`
|
||||
Active string `form:"active"`
|
||||
ExecTime string `form:"exec_time"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.Name = strings.TrimSpace(form.Name)
|
||||
form.ExecType = strings.TrimSpace(form.ExecType)
|
||||
|
||||
err = workflow.AddWorkflow(form.Name, form.Content, form.ExecType, form.Active, form.ExecTime)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "添加成功")
|
||||
return
|
||||
}
|
||||
|
||||
func DelWorkflow(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
|
||||
err = workflow.DelWorkflow(form.ID)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "删除成功")
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
func UpdWorkflow(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
Name string `form:"name"`
|
||||
Content string `form:"content"`
|
||||
ExecType string `form:"exec_type"`
|
||||
Active string `form:"active"`
|
||||
ExecTime string `form:"exec_time"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
form.Name = strings.TrimSpace(form.Name)
|
||||
form.ExecType = strings.TrimSpace(form.ExecType)
|
||||
|
||||
err = workflow.UpdWorkflow(form.ID, form.Name, form.Content, form.ExecType, form.Active, form.ExecTime)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "修改成功")
|
||||
return
|
||||
}
|
||||
|
||||
func UpdExecType(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
ExecType string `form:"exec_type"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
form.ExecType = strings.TrimSpace(form.ExecType)
|
||||
|
||||
err = workflow.UpdExecType(form.ID, form.ExecType)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "修改成功")
|
||||
return
|
||||
}
|
||||
|
||||
func UpdActive(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
Active string `form:"active"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
form.Active = strings.TrimSpace(form.Active)
|
||||
if form.ID == "" {
|
||||
public.FailMsg(c, "ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
err = workflow.UpdActive(form.ID, form.Active)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "修改成功")
|
||||
return
|
||||
}
|
||||
|
||||
func ExecuteWorkflow(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
|
||||
err = workflow.ExecuteWorkflow(form.ID)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "执行成功")
|
||||
return
|
||||
}
|
||||
|
||||
func StopWorkflow(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
|
||||
err = workflow.StopWorkflow(form.ID)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessMsg(c, "停止成功")
|
||||
return
|
||||
}
|
||||
|
||||
func GetWorkflowHistory(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
Page int64 `form:"p"`
|
||||
Limit int64 `form:"limit"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
|
||||
data, count, err := workflow.GetListWH(form.ID, form.Page, form.Limit)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessData(c, data, count)
|
||||
return
|
||||
}
|
||||
|
||||
func GetExecLog(c *gin.Context) {
|
||||
var form struct {
|
||||
ID string `form:"id"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
form.ID = strings.TrimSpace(form.ID)
|
||||
|
||||
data, err := workflow.GetExecLog(form.ID)
|
||||
if err != nil {
|
||||
public.FailMsg(c, err.Error())
|
||||
return
|
||||
}
|
||||
public.SuccessData(c, data, 0)
|
||||
return
|
||||
}
|
||||
149
backend/internal/access/access.go
Normal file
149
backend/internal/access/access.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetSqlite() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "access"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func GetList(search string, p, limit int64) ([]map[string]any, int, error) {
|
||||
var data []map[string]any
|
||||
var count int64
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
var limits []int64
|
||||
if p >= 0 && limit >= 0 {
|
||||
limits = []int64{0, limit}
|
||||
if p > 1 {
|
||||
limits[0] = (p - 1) * limit
|
||||
limits[1] = p * limit
|
||||
}
|
||||
}
|
||||
if search != "" {
|
||||
count, err = s.Where("name like ? or type like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Count()
|
||||
data, err = s.Where("name like ? or type like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Order("update_time", "desc").Limit(limits).Select()
|
||||
} else {
|
||||
count, err = s.Count()
|
||||
data, err = s.Order("update_time", "desc").Limit(limits).Select()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
ATMap := GetAccessTypeMap("name", "type")
|
||||
for _, v := range data {
|
||||
v["access_type"] = ATMap[v["type"].(string)]
|
||||
}
|
||||
|
||||
return data, int(count), nil
|
||||
}
|
||||
|
||||
func GetAll(Type string) ([]map[string]any, error) {
|
||||
var data []map[string]any
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
ATMap := GetAccessTypeMap("type", "name")
|
||||
|
||||
if Type != "" {
|
||||
if Type == "dns" {
|
||||
TypeL := strings.Join(ATMap["dns"], "','")
|
||||
data, err = s.Where(fmt.Sprintf("type in ('%s')", TypeL), []interface{}{}).Select()
|
||||
} else {
|
||||
Type := strings.Split(strings.TrimPrefix(Type, "-"), "-")[0]
|
||||
data, err = s.Where("type = ?", []interface{}{Type}).Select()
|
||||
}
|
||||
} else {
|
||||
data, err = s.Select()
|
||||
}
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func GetAccess(ID string) (map[string]any, error) {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Where("id = ?", []interface{}{ID}).Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("API授权不存在:%s", ID)
|
||||
}
|
||||
return data[0], nil
|
||||
}
|
||||
|
||||
func AddAccess(config, name, typ string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = s.Insert(map[string]any{
|
||||
"name": name,
|
||||
"type": typ,
|
||||
"config": config,
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateAccess(id, config, name string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = s.Where("id = ?", []interface{}{id}).Update(map[string]any{
|
||||
"name": name,
|
||||
"config": config,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DelAccess(id string) error {
|
||||
s, err := GetSqlite()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
_, err = s.Where("id = ?", []interface{}{id}).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
36
backend/internal/access/accessType.go
Normal file
36
backend/internal/access/accessType.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
)
|
||||
|
||||
func GetSqliteAT() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "access_type"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func GetAccessTypeMap(key, val string) map[string][]string {
|
||||
dataMap := make(map[string][]string)
|
||||
s, err := GetSqliteAT()
|
||||
if err != nil {
|
||||
return dataMap
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Select()
|
||||
if err != nil {
|
||||
return dataMap
|
||||
}
|
||||
for _, row := range data {
|
||||
if dataMap[row[key].(string)] == nil {
|
||||
dataMap[row[key].(string)] = []string{row[val].(string)}
|
||||
} else {
|
||||
dataMap[row[key].(string)] = append(dataMap[row[key].(string)], row[val].(string))
|
||||
}
|
||||
}
|
||||
return dataMap
|
||||
}
|
||||
94
backend/internal/cert/apply/account.go
Normal file
94
backend/internal/cert/apply/account.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package apply
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"time"
|
||||
)
|
||||
|
||||
type MyUser struct {
|
||||
Email string
|
||||
Registration *registration.Resource
|
||||
key crypto.PrivateKey
|
||||
}
|
||||
|
||||
func (u *MyUser) GetEmail() string {
|
||||
return u.Email
|
||||
}
|
||||
|
||||
func (u *MyUser) GetRegistration() *registration.Resource {
|
||||
return u.Registration
|
||||
}
|
||||
|
||||
func (u *MyUser) GetPrivateKey() crypto.PrivateKey {
|
||||
return u.key
|
||||
}
|
||||
|
||||
func SaveUserToDB(db *public.Sqlite, user *MyUser) error {
|
||||
keyBytes, err := x509.MarshalPKCS8PrivateKey(user.key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
regBytes := []byte("")
|
||||
if user.Registration != nil {
|
||||
regBytes, err = json.Marshal(user.Registration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pemBytes := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: keyBytes,
|
||||
})
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = db.Insert(map[string]interface{}{
|
||||
"email": user.Email,
|
||||
"private_key": string(pemBytes),
|
||||
"reg": regBytes,
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
"type": "Let's Encrypt",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func LoadUserFromDB(db *public.Sqlite, email string) (*MyUser, error) {
|
||||
data, err := db.Where(`email=?`, []interface{}{email}).Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
regStr, ok := data[0]["reg"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid reg data")
|
||||
}
|
||||
regBytes := []byte(regStr)
|
||||
privPEM, ok := data[0]["private_key"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid private key data")
|
||||
}
|
||||
privateKey, err := public.ParsePrivateKey([]byte(privPEM))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var reg *registration.Resource
|
||||
if len(regBytes) > 0 {
|
||||
reg = ®istration.Resource{}
|
||||
if err := json.Unmarshal(regBytes, reg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &MyUser{
|
||||
Email: email,
|
||||
key: privateKey,
|
||||
Registration: reg,
|
||||
}, nil
|
||||
}
|
||||
245
backend/internal/cert/apply/apply.go
Normal file
245
backend/internal/cert/apply/apply.go
Normal file
@@ -0,0 +1,245 @@
|
||||
package apply
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"ALLinSSL/backend/internal/cert"
|
||||
"ALLinSSL/backend/public"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-acme/lego/v4/certcrypto"
|
||||
"github.com/go-acme/lego/v4/certificate"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/lego"
|
||||
"github.com/go-acme/lego/v4/providers/dns/alidns"
|
||||
"github.com/go-acme/lego/v4/providers/dns/tencentcloud"
|
||||
"github.com/go-acme/lego/v4/registration"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetSqlite() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "_accounts"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func GetDNSProvider(providerName string, creds map[string]string) (challenge.Provider, error) {
|
||||
switch providerName {
|
||||
case "tencentcloud":
|
||||
config := tencentcloud.NewDefaultConfig()
|
||||
config.SecretID = creds["secret_id"]
|
||||
config.SecretKey = creds["secret_key"]
|
||||
return tencentcloud.NewDNSProviderConfig(config)
|
||||
|
||||
// case "cloudflare":
|
||||
// config := cloudflare.NewDefaultConfig()
|
||||
// config.AuthToken = creds["CLOUDFLARE_API_TOKEN"]
|
||||
// return cloudflare.NewDNSProviderConfig(config)
|
||||
|
||||
case "aliyun":
|
||||
config := alidns.NewDefaultConfig()
|
||||
config.APIKey = creds["access_key"]
|
||||
config.SecretKey = creds["access_secret"]
|
||||
return alidns.NewDNSProviderConfig(config)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName)
|
||||
}
|
||||
}
|
||||
|
||||
func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) {
|
||||
db, err := GetSqlite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
email, ok := cfg["email"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("参数错误:email")
|
||||
}
|
||||
domains, ok := cfg["domains"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("参数错误:domains")
|
||||
}
|
||||
providerStr, ok := cfg["provider"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("参数错误:provider")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return nil, fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
|
||||
// 获取上次申请的证书
|
||||
runId, ok := cfg["_runId"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("参数错误:_runId")
|
||||
}
|
||||
if runId != "" {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "workflow_history"
|
||||
defer s.Close()
|
||||
// 查询 workflowId
|
||||
wh, err := s.Where("id=?", []interface{}{runId}).Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(wh) > 0 {
|
||||
s.TableName = "cert"
|
||||
certs, err := s.Where("workflow_id=?", []interface{}{wh[0]["workflow_id"]}).Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(certs) > 0 {
|
||||
layout := "2006-01-02 15:04:05"
|
||||
var maxDays float64
|
||||
var maxItem map[string]any
|
||||
for i := range certs {
|
||||
endTimeStr, ok := certs[i]["end_time"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
endTime, _ := time.Parse(layout, endTimeStr)
|
||||
diff := endTime.Sub(time.Now()).Hours() / 24
|
||||
if diff > maxDays {
|
||||
maxDays = diff
|
||||
maxItem = certs[i]
|
||||
}
|
||||
}
|
||||
certObj := maxItem
|
||||
// 判断证书是否过期
|
||||
cfgEnd, ok := cfg["end_day"].(int)
|
||||
if !ok || cfgEnd <= 0 {
|
||||
cfgEnd = 30
|
||||
}
|
||||
|
||||
if int(maxDays) > cfgEnd {
|
||||
// 证书未过期,直接返回
|
||||
logger.Debug(fmt.Sprintf("上次证书申请成功,剩余天数:%d 大于%d天,已跳过申请复用此证书", int(maxDays), cfgEnd))
|
||||
return map[string]any{
|
||||
"cert": certObj["cert"],
|
||||
"key": certObj["key"],
|
||||
"issuerCert": certObj["issuer_cert"],
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.Debug("正在申请证书,域名: " + domains)
|
||||
|
||||
user, err := LoadUserFromDB(db, email)
|
||||
if err != nil {
|
||||
logger.Debug("acme账号不存在,注册新账号")
|
||||
privateKey, _ := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||
user = &MyUser{
|
||||
Email: email,
|
||||
key: privateKey,
|
||||
}
|
||||
|
||||
config := lego.NewConfig(user)
|
||||
config.Certificate.KeyType = certcrypto.EC384
|
||||
|
||||
client, err := lego.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Debug("正在注册账号:" + email)
|
||||
reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Registration = reg
|
||||
|
||||
err = SaveUserToDB(db, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logger.Debug("账号注册并保存成功")
|
||||
}
|
||||
|
||||
// 初始化 ACME 客户端
|
||||
client, err := lego.NewClient(lego.NewConfig(user))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 获取 DNS 验证提供者
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// DNS 验证
|
||||
provider, err := GetDNSProvider(providerStr, providerConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建 DNS provider 失败: %v", err)
|
||||
}
|
||||
|
||||
err = client.Challenge.SetDNS01Provider(provider,
|
||||
dns01.WrapPreCheck(func(domain, fqdn, value string, check dns01.PreCheckFunc) (bool, error) {
|
||||
// 跳过预检查
|
||||
return true, nil
|
||||
}),
|
||||
dns01.AddRecursiveNameservers([]string{
|
||||
"8.8.8.8:53",
|
||||
"1.1.1.1:53",
|
||||
}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// fmt.Println(strings.Split(domains, ","))
|
||||
request := certificate.ObtainRequest{
|
||||
Domains: strings.Split(domains, ","),
|
||||
Bundle: true,
|
||||
}
|
||||
certObj, err := client.Certificate.Obtain(request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certStr := string(certObj.Certificate)
|
||||
keyStr := string(certObj.PrivateKey)
|
||||
issuerCertStr := string(certObj.IssuerCertificate)
|
||||
|
||||
// 保存证书和私钥
|
||||
data := map[string]any{
|
||||
"cert": certStr,
|
||||
"key": keyStr,
|
||||
"issuerCert": issuerCertStr,
|
||||
}
|
||||
|
||||
err = cert.SaveCert("workflow", keyStr, certStr, issuerCertStr, runId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
206
backend/internal/cert/cert.go
Normal file
206
backend/internal/cert/cert.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package cert
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetSqlite() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "cert"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func GetList(search string, p, limit int64) ([]map[string]any, int, error) {
|
||||
var data []map[string]any
|
||||
var count int64
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
var limits []int64
|
||||
if p >= 0 && limit >= 0 {
|
||||
limits = []int64{0, limit}
|
||||
if p > 1 {
|
||||
limits[0] = (p - 1) * limit
|
||||
limits[1] = p * limit
|
||||
}
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
count, err = s.Where("domains like ?", []interface{}{"%" + search + "%"}).Count()
|
||||
data, err = s.Where("domains like ?", []interface{}{"%" + search + "%"}).Limit(limits).Order("create_time", "desc").Select()
|
||||
} else {
|
||||
count, err = s.Count()
|
||||
data, err = s.Order("create_time", "desc").Limit(limits).Select()
|
||||
}
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
for _, v := range data {
|
||||
endtime, err := time.Parse("2006-01-02 15:04:05", v["end_time"].(string))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
v["end_day"] = strconv.FormatInt(int64(endtime.Sub(time.Now())/(24*time.Hour)), 10)
|
||||
}
|
||||
return data, int(count), nil
|
||||
}
|
||||
|
||||
func AddCert(source, key, cert, issuer, issuerCert, domains, sha256, historyId, startTime, endTime, endDay string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
workflowId := ""
|
||||
if historyId != "" {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "workflow_history"
|
||||
defer s.Close()
|
||||
// 查询 workflowId
|
||||
wh, err := s.Where("id=?", []interface{}{historyId}).Select()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(wh) > 0 {
|
||||
workflowId = wh[0]["workflow_id"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = s.Insert(map[string]any{
|
||||
"source": source,
|
||||
"key": key,
|
||||
"cert": cert,
|
||||
"issuer": issuer,
|
||||
"issuer_cert": issuerCert,
|
||||
"domains": domains,
|
||||
"sha256": sha256,
|
||||
"history_id": historyId,
|
||||
"workflow_id": workflowId,
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
"start_time": startTime,
|
||||
"end_time": endTime,
|
||||
"end_day": endDay,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveCert(source, key, cert, issuerCert, historyId string) error {
|
||||
if err := public.ValidateSSLCertificate(cert, key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
certObj, err := public.ParseCertificate([]byte(cert))
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析证书失败: %v", err)
|
||||
}
|
||||
// SHA256
|
||||
sha256, err := public.GetSHA256(cert)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取 SHA256 失败: %v", err)
|
||||
}
|
||||
if d, _ := GetCert(sha256); d != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
domainSet := make(map[string]bool)
|
||||
|
||||
if certObj.Subject.CommonName != "" {
|
||||
domainSet[certObj.Subject.CommonName] = true
|
||||
}
|
||||
for _, dns := range certObj.DNSNames {
|
||||
domainSet[dns] = true
|
||||
}
|
||||
|
||||
// 转成切片并拼接成逗号分隔的字符串
|
||||
var domains []string
|
||||
for domain := range domainSet {
|
||||
domains = append(domains, domain)
|
||||
}
|
||||
domainList := strings.Join(domains, ",")
|
||||
|
||||
// 提取 CA 名称(Issuer 的组织名)
|
||||
caName := "UNKNOWN"
|
||||
if len(certObj.Issuer.Organization) > 0 {
|
||||
caName = certObj.Issuer.Organization[0]
|
||||
} else if certObj.Issuer.CommonName != "" {
|
||||
caName = certObj.Issuer.CommonName
|
||||
}
|
||||
// 证书有效期
|
||||
startTime := certObj.NotBefore.Format("2006-01-02 15:04:05")
|
||||
endTime := certObj.NotAfter.Format("2006-01-02 15:04:05")
|
||||
endDay := fmt.Sprintf("%d", int(certObj.NotAfter.Sub(time.Now()).Hours()/24))
|
||||
|
||||
err = AddCert(source, key, cert, caName, issuerCert, domainList, sha256, historyId, startTime, endTime, endDay)
|
||||
if err != nil {
|
||||
return fmt.Errorf("保存证书失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UploadCert(key, cert string) error {
|
||||
err := SaveCert("upload", key, cert, "", "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("保存证书失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DelCert(id string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
_, err = s.Where("id=?", []interface{}{id}).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetCert(id string) (map[string]string, error) {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
res, err := s.Where("id=? or sha256=?", []interface{}{id, id}).Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return nil, fmt.Errorf("证书不存在")
|
||||
}
|
||||
|
||||
data := map[string]string{
|
||||
"domains": res[0]["domains"].(string),
|
||||
"cert": res[0]["cert"].(string),
|
||||
"key": res[0]["key"].(string),
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
31
backend/internal/cert/deploy/1p_test.go
Normal file
31
backend/internal/cert/deploy/1p_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package deploy
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSite(t *testing.T) {
|
||||
cfg := map[string]any{
|
||||
"site_id": "1",
|
||||
"provider_id": "22",
|
||||
"certificate": map[string]any{
|
||||
"key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxIjmAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONh\nWhMT6W+cx0WMC80yCRm5JshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM\n4tz/Zh8a3kVyN4MtWDmV1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1\nz6qqEDcM8FtHoAXAdxQBkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9\ncMY1cCEBxpwQTJiJHbX9LcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MU\nS89+DsixFf3HL+iWjr6yVnQ/mAGVPQ+HD4pwmQIDAQABAoIBAALpcFb59MBZZHJ3\nui9RRi96ig6kPQoRjkjN83pjM+/h/bANMmUOQU5FHBKLwj5uhN5Dpk2fzAnIX2TE\nVgfyNGsYuWLsIM+m6EJfm7pXJwJDr3RCpm+6DIKr1U8TwlR2OhbDi6fOlfH66q79\n2Klq4SXsa0vgfllpTVCDtydFVjwAuQV7Cf6DGRjbNpN3DPLeOC1wYFimNZwudSK0\nf8grWpPFXw2TPaf3TgeBGxwL7GCTYSKT+Eq9USbhG4RArrM9oQt+h7rzaH2bFEdg\n7tOM4KIgV+aw8r0TsYisDG9dfiHfHr5vQnkmWgt/rxAOvHlJ7/64pBVuET1ZF0mB\nP6gu4Y0CgYEAzkwXvfnHI5qx9BVP6e9lGrpWrm0RxCKr2iCCwrOVALbX1yfKCb5L\nrP/jSERMuLt6bIKg/AoVu9ogCTGzntyHTbZXFGg/y5Xoul+1af2arQ1rGZ7A/Im7\nnteZePg2U6UiDRy07F94FF5aL/v97D4BffiSA+0atlgH6tpKyYfY6NsCgYEA8+Ku\nGQqX9kHDd5bbzPhLelNmHVnAjnMaHEhvzVtBA737F10Oqg9wyffqe/i/DvdUSx9r\nafKGUfzB2vVZjz//OpSQ8VhRzDTiyelKLsSTmzOokLBnwayyTxw85o9EDvTNrzfb\nYQbAjmAXWmnv5Xvx1KfvTaKFY3BmHsKYJDzwnJsCgYBK1SVjn2CSVMIqlTSI2nMl\nb+STnzLrn9wQ4uwr7nKlcK34+RD72dCfr67lfwkJldBB3lzBMHNT0jr+us26Waqn\nEPaji3Fgyz9BpAgtq3XZQl3QTFsbAGdTpkegrwEd9G/Wq8whVjw7v0Id193zPUbT\nSEDHNdITxPkSQx8P3bxcMwKBgQDO5EGk5KO9OFTFoqib3RbKku1RgM4lCefgjmKp\n5vvkXMohK8RA6BBahYHZ4U7TN2W+xMyueBsSekVJplFvgG7YFyhOVQovHb42Yz2X\nJxPA2bXp6HxchFBPZDkVrfuiZHIIbm4ghUXcgg/Nl4j3OIoSSNRtG63kiXlYJuRB\n+aB0eQKBgD79VrREpbOMS7HRlDTtfkDN94HY3T4MLErs26z/NLO/dC44tmBJGo2P\ngcQ+p7XxNjpWUnUbEiuz4R3Xgh6ULwuSseWtcQicolPHTkBjnc+6BEpyguZJ+FPZ\nGls3g3LxjGhdPlyd37CaWDvx/Jtjrd4Y9iGkGO2d9fXZD0Hg0ymX\n-----END RSA PRIVATE KEY-----",
|
||||
"cert": "-----BEGIN CERTIFICATE-----\nMIIG5DCCBMygAwIBAgIQBPQGlt81+4RKt3RAFXPvrjANBgkqhkiG9w0BAQsFADBb\nMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg\nSW5jLjElMCMGA1UEAxMcVHJ1c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNTAeFw0y\nNTA0MjIwMDAwMDBaFw0yNTA3MjAyMzU5NTlaMB8xHTAbBgNVBAMTFGFsbGluc3Ns\nLnphY2h5YW5nLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxIjm\nAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONhWhMT6W+cx0WMC80yCRm5\nJshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM4tz/Zh8a3kVyN4MtWDmV\n1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1z6qqEDcM8FtHoAXAdxQB\nkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9cMY1cCEBxpwQTJiJHbX9\nLcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MUS89+DsixFf3HL+iWjr6y\nVnQ/mAGVPQ+HD4pwmQIDAQABo4IC3jCCAtowHwYDVR0jBBgwFoAUtBIopbTAHZ8p\ncWk82RGWSnVpUMAwHQYDVR0OBBYEFHqqdlMVBlcadf7iJLJoLnLZ7h4tMB8GA1Ud\nEQQYMBaCFGFsbGluc3NsLnphY2h5YW5nLmNuMD4GA1UdIAQ3MDUwMwYGZ4EMAQIB\nMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHkGCCsG\nAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t\nMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vVHJ1c3RB\nc2lhRFZUTFNSU0FDQTIwMjUuY3J0MAwGA1UdEwEB/wQCMAAwggF9BgorBgEEAdZ5\nAgQCBIIBbQSCAWkBZwB2ABLxTjS9U3JMhAYZw48/ehP457Vih4icbTAFhOvlhiY6\nAAABll0w/o0AAAQDAEcwRQIgd24jCPm+fbHq3grMIxtvQhzkv7dvYPM/BGjPEsy1\nQ70CIQC5jXADjBh+dH50T+atn3lktBEqQhedOl6cAaP/XXmk6gB2AO08S9boBsKk\nogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABll0w/rUAAAQDAEcwRQIgU2GDVEH1\ns5i/RC1RhqvJjn72PAZOlDtJyLdg29vC9HECIQCj78GATYK5quitLxbn3HvD8BeT\noOz+3tacgyN6+TdvugB1AKRCxQZJYGFUjw/U6pz7ei0mRU2HqX8v30VZ9idPOoRU\nAAABll0w/sYAAAQDAEYwRAIgCvU/iBRPKoJLjmU4edBYObWAO/aJp2mWnfJ4ieAr\nrXsCIBsAppYu28h8YEOl0N9yEeF9G05IMxwkCjZKonQs2SKMMA0GCSqGSIb3DQEB\nCwUAA4ICAQB3wFou51Qvl4apMhencuQUnWF3UpYP49e0WQ72DVT3pYjYsozkSuqb\nQZcwMB6HDoHdFicxvQ/yxKyTu/nw3rXjUWYuSxXYd7lJcQ/R0tR00m6AFeinY4Aq\nq4QqoA+lriK1XqO5MomAL4FbSysT1ow/gaG9pYuXEdT4pr05I/NumjXdkwBRZOd4\nrhol2grKf3y37Qla5hUbbG3ab9nf/csJSWkCoESeXr3MB1oAU/aL9pGSagvMXSKQ\nsFs2cn2Fi8ZmJPJXIP114lgvFuFDO+C1yTNbHap/FufvAKGryfPDuPecCF6FSXej\n+bwg4/BNz5lcHbNo2XXjLgoPg4VE6mG/SQQZQEDBk5DowwMVMvh77t9RBNrHozah\nHGtQz2hCuIX7rZQYnSlvW8T75FhI/Sd+HEfU/iyTIELXBUjypnK2bOJL7+jE7f79\nuljhXlCcP52fGHCjexNBz5gIZr82KVxsfxKuZjfioPkhmWleVNMdMWYJRXu618E6\nNtNjUVsDCuMOOMNs1qScqxOT60MeDZLX+vnC93fdd/t2hLEAWWNNMkWeX2qLCE1q\nGarop9U1mJpiBWkW5cBiqnNIbhuV2fcwFIR8mVT5f1Qcw+WxE2nEjY2h75bKv8T5\n3RBngmaX8PcyLAP2s0/4UyzAnMYfioJBh37VpUYBrdriBkRds/AMZw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFnjCCBIagAwIBAgIQCSYyO0lk42hGFRLe8aXVLDANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0yNTAxMDgwMDAwMDBaFw0zNTAxMDcyMzU5NTlaMFsxCzAJBgNVBAYTAkNO\nMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQD\nExxUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSAyMDI1MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA0fuEmuBIsN6ZZVq+gRobMorOGIilTCIfQrxNpR8FUZ9R\n/GfbiekbiIKphQXEZ7N1uBnn6tXUuZ32zl6jPkZpHzN/Bmgk1BWSIzVc0npMzrWq\n/hrbk5+KddXJdsNpeG1+Q8lc8uVMBrztnxaPb7Rh7yQCsMrcO4hgVaqLJWkVvEfW\nULtoCHQnNaj4IroG6VxQf1oArQ8bPbwpI02lieSahRa78FQuXdoGVeQcrkhtVjZs\nON98vq5fPWZX2LFv7e5J6P9IHbzvOl8yyQjv+2/IOwhNSkaXX3bI+//bqF9XW/p7\n+gsUmHiK5YsvLjmXcvDmoDEGrXMzgX31Zl2nJ+umpRbLjwP8rxYIUsKoEwEdFoto\nAid59UEBJyw/GibwXQ5xTyKD/N6C8SFkr1+myOo4oe1UB+YgvRu6qSxIABo5kYdX\nFodLP4IgoVJdeUFs1Usa6bxYEO6EgMf5lCWt9hGZszvXYZwvyZGq3ogNXM7eKyi2\n20WzJXYMmi9TYFq2Fa95aZe4wki6YhDhhOO1g0sjITGVaB73G+JOCI9yJhv6+REN\nD40ZpboUHE8JNgMVWbG1isAMVCXqiADgXtuC+tmJWPEH9cR6OuJLEpwOzPfgAbnn\n2MRu7Tsdr8jPjTPbD0FxblX1ydW3RG30vwLF5lkTTRkHG9epMgpPMdYP7nY/08MC\nAwEAAaOCAVYwggFSMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLQSKKW0\nwB2fKXFpPNkRlkp1aVDAMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485\nMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\ndgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\ndC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E\naWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov\nL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDARBgNV\nHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAJ4a3svh316GY2+Z7EYx\nmBIsOwjJSnyoEfzx2T699ctLLrvuzS79Mg3pPjxSLlUgyM8UzrFc5tgVU3dZ1sFQ\nI4RM+ysJdvIAX/7Yx1QbooVdKhkdi9X7QN7yVkjqwM3fY3WfQkRTzhIkM7mYIQbR\nr+y2Vkju61BLqh7OCRpPMiudjEpP1kEtRyGs2g0aQpEIqKBzxgitCXSayO1hoO6/\n71ts801OzYlqYW9OQQQ2GCJyFbD6XHDjdpn+bWUxTKWaMY0qedSCbHE3Kl2QEF0C\nynZ7SbC03yR+gKZQDeTXrNP1kk5Qhe7jSXgw+nhbspe0q/M1ZcNCz+sPxeOwdCcC\ngJE=\n-----END CERTIFICATE-----",
|
||||
"issuer": "cert-issuer",
|
||||
},
|
||||
}
|
||||
err := Deploy1panelSite(cfg)
|
||||
println(err)
|
||||
}
|
||||
|
||||
func TestP(t *testing.T) {
|
||||
cfg := map[string]any{
|
||||
"site_id": "1",
|
||||
"provider_id": "22",
|
||||
"certificate": map[string]any{
|
||||
"key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxIjmAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONh\nWhMT6W+cx0WMC80yCRm5JshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM\n4tz/Zh8a3kVyN4MtWDmV1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1\nz6qqEDcM8FtHoAXAdxQBkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9\ncMY1cCEBxpwQTJiJHbX9LcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MU\nS89+DsixFf3HL+iWjr6yVnQ/mAGVPQ+HD4pwmQIDAQABAoIBAALpcFb59MBZZHJ3\nui9RRi96ig6kPQoRjkjN83pjM+/h/bANMmUOQU5FHBKLwj5uhN5Dpk2fzAnIX2TE\nVgfyNGsYuWLsIM+m6EJfm7pXJwJDr3RCpm+6DIKr1U8TwlR2OhbDi6fOlfH66q79\n2Klq4SXsa0vgfllpTVCDtydFVjwAuQV7Cf6DGRjbNpN3DPLeOC1wYFimNZwudSK0\nf8grWpPFXw2TPaf3TgeBGxwL7GCTYSKT+Eq9USbhG4RArrM9oQt+h7rzaH2bFEdg\n7tOM4KIgV+aw8r0TsYisDG9dfiHfHr5vQnkmWgt/rxAOvHlJ7/64pBVuET1ZF0mB\nP6gu4Y0CgYEAzkwXvfnHI5qx9BVP6e9lGrpWrm0RxCKr2iCCwrOVALbX1yfKCb5L\nrP/jSERMuLt6bIKg/AoVu9ogCTGzntyHTbZXFGg/y5Xoul+1af2arQ1rGZ7A/Im7\nnteZePg2U6UiDRy07F94FF5aL/v97D4BffiSA+0atlgH6tpKyYfY6NsCgYEA8+Ku\nGQqX9kHDd5bbzPhLelNmHVnAjnMaHEhvzVtBA737F10Oqg9wyffqe/i/DvdUSx9r\nafKGUfzB2vVZjz//OpSQ8VhRzDTiyelKLsSTmzOokLBnwayyTxw85o9EDvTNrzfb\nYQbAjmAXWmnv5Xvx1KfvTaKFY3BmHsKYJDzwnJsCgYBK1SVjn2CSVMIqlTSI2nMl\nb+STnzLrn9wQ4uwr7nKlcK34+RD72dCfr67lfwkJldBB3lzBMHNT0jr+us26Waqn\nEPaji3Fgyz9BpAgtq3XZQl3QTFsbAGdTpkegrwEd9G/Wq8whVjw7v0Id193zPUbT\nSEDHNdITxPkSQx8P3bxcMwKBgQDO5EGk5KO9OFTFoqib3RbKku1RgM4lCefgjmKp\n5vvkXMohK8RA6BBahYHZ4U7TN2W+xMyueBsSekVJplFvgG7YFyhOVQovHb42Yz2X\nJxPA2bXp6HxchFBPZDkVrfuiZHIIbm4ghUXcgg/Nl4j3OIoSSNRtG63kiXlYJuRB\n+aB0eQKBgD79VrREpbOMS7HRlDTtfkDN94HY3T4MLErs26z/NLO/dC44tmBJGo2P\ngcQ+p7XxNjpWUnUbEiuz4R3Xgh6ULwuSseWtcQicolPHTkBjnc+6BEpyguZJ+FPZ\nGls3g3LxjGhdPlyd37CaWDvx/Jtjrd4Y9iGkGO2d9fXZD0Hg0ymX\n-----END RSA PRIVATE KEY-----",
|
||||
"cert": "-----BEGIN CERTIFICATE-----\nMIIG5DCCBMygAwIBAgIQBPQGlt81+4RKt3RAFXPvrjANBgkqhkiG9w0BAQsFADBb\nMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg\nSW5jLjElMCMGA1UEAxMcVHJ1c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNTAeFw0y\nNTA0MjIwMDAwMDBaFw0yNTA3MjAyMzU5NTlaMB8xHTAbBgNVBAMTFGFsbGluc3Ns\nLnphY2h5YW5nLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxIjm\nAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONhWhMT6W+cx0WMC80yCRm5\nJshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM4tz/Zh8a3kVyN4MtWDmV\n1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1z6qqEDcM8FtHoAXAdxQB\nkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9cMY1cCEBxpwQTJiJHbX9\nLcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MUS89+DsixFf3HL+iWjr6y\nVnQ/mAGVPQ+HD4pwmQIDAQABo4IC3jCCAtowHwYDVR0jBBgwFoAUtBIopbTAHZ8p\ncWk82RGWSnVpUMAwHQYDVR0OBBYEFHqqdlMVBlcadf7iJLJoLnLZ7h4tMB8GA1Ud\nEQQYMBaCFGFsbGluc3NsLnphY2h5YW5nLmNuMD4GA1UdIAQ3MDUwMwYGZ4EMAQIB\nMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHkGCCsG\nAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t\nMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vVHJ1c3RB\nc2lhRFZUTFNSU0FDQTIwMjUuY3J0MAwGA1UdEwEB/wQCMAAwggF9BgorBgEEAdZ5\nAgQCBIIBbQSCAWkBZwB2ABLxTjS9U3JMhAYZw48/ehP457Vih4icbTAFhOvlhiY6\nAAABll0w/o0AAAQDAEcwRQIgd24jCPm+fbHq3grMIxtvQhzkv7dvYPM/BGjPEsy1\nQ70CIQC5jXADjBh+dH50T+atn3lktBEqQhedOl6cAaP/XXmk6gB2AO08S9boBsKk\nogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABll0w/rUAAAQDAEcwRQIgU2GDVEH1\ns5i/RC1RhqvJjn72PAZOlDtJyLdg29vC9HECIQCj78GATYK5quitLxbn3HvD8BeT\noOz+3tacgyN6+TdvugB1AKRCxQZJYGFUjw/U6pz7ei0mRU2HqX8v30VZ9idPOoRU\nAAABll0w/sYAAAQDAEYwRAIgCvU/iBRPKoJLjmU4edBYObWAO/aJp2mWnfJ4ieAr\nrXsCIBsAppYu28h8YEOl0N9yEeF9G05IMxwkCjZKonQs2SKMMA0GCSqGSIb3DQEB\nCwUAA4ICAQB3wFou51Qvl4apMhencuQUnWF3UpYP49e0WQ72DVT3pYjYsozkSuqb\nQZcwMB6HDoHdFicxvQ/yxKyTu/nw3rXjUWYuSxXYd7lJcQ/R0tR00m6AFeinY4Aq\nq4QqoA+lriK1XqO5MomAL4FbSysT1ow/gaG9pYuXEdT4pr05I/NumjXdkwBRZOd4\nrhol2grKf3y37Qla5hUbbG3ab9nf/csJSWkCoESeXr3MB1oAU/aL9pGSagvMXSKQ\nsFs2cn2Fi8ZmJPJXIP114lgvFuFDO+C1yTNbHap/FufvAKGryfPDuPecCF6FSXej\n+bwg4/BNz5lcHbNo2XXjLgoPg4VE6mG/SQQZQEDBk5DowwMVMvh77t9RBNrHozah\nHGtQz2hCuIX7rZQYnSlvW8T75FhI/Sd+HEfU/iyTIELXBUjypnK2bOJL7+jE7f79\nuljhXlCcP52fGHCjexNBz5gIZr82KVxsfxKuZjfioPkhmWleVNMdMWYJRXu618E6\nNtNjUVsDCuMOOMNs1qScqxOT60MeDZLX+vnC93fdd/t2hLEAWWNNMkWeX2qLCE1q\nGarop9U1mJpiBWkW5cBiqnNIbhuV2fcwFIR8mVT5f1Qcw+WxE2nEjY2h75bKv8T5\n3RBngmaX8PcyLAP2s0/4UyzAnMYfioJBh37VpUYBrdriBkRds/AMZw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFnjCCBIagAwIBAgIQCSYyO0lk42hGFRLe8aXVLDANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0yNTAxMDgwMDAwMDBaFw0zNTAxMDcyMzU5NTlaMFsxCzAJBgNVBAYTAkNO\nMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQD\nExxUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSAyMDI1MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA0fuEmuBIsN6ZZVq+gRobMorOGIilTCIfQrxNpR8FUZ9R\n/GfbiekbiIKphQXEZ7N1uBnn6tXUuZ32zl6jPkZpHzN/Bmgk1BWSIzVc0npMzrWq\n/hrbk5+KddXJdsNpeG1+Q8lc8uVMBrztnxaPb7Rh7yQCsMrcO4hgVaqLJWkVvEfW\nULtoCHQnNaj4IroG6VxQf1oArQ8bPbwpI02lieSahRa78FQuXdoGVeQcrkhtVjZs\nON98vq5fPWZX2LFv7e5J6P9IHbzvOl8yyQjv+2/IOwhNSkaXX3bI+//bqF9XW/p7\n+gsUmHiK5YsvLjmXcvDmoDEGrXMzgX31Zl2nJ+umpRbLjwP8rxYIUsKoEwEdFoto\nAid59UEBJyw/GibwXQ5xTyKD/N6C8SFkr1+myOo4oe1UB+YgvRu6qSxIABo5kYdX\nFodLP4IgoVJdeUFs1Usa6bxYEO6EgMf5lCWt9hGZszvXYZwvyZGq3ogNXM7eKyi2\n20WzJXYMmi9TYFq2Fa95aZe4wki6YhDhhOO1g0sjITGVaB73G+JOCI9yJhv6+REN\nD40ZpboUHE8JNgMVWbG1isAMVCXqiADgXtuC+tmJWPEH9cR6OuJLEpwOzPfgAbnn\n2MRu7Tsdr8jPjTPbD0FxblX1ydW3RG30vwLF5lkTTRkHG9epMgpPMdYP7nY/08MC\nAwEAAaOCAVYwggFSMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLQSKKW0\nwB2fKXFpPNkRlkp1aVDAMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485\nMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\ndgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\ndC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E\naWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov\nL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDARBgNV\nHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAJ4a3svh316GY2+Z7EYx\nmBIsOwjJSnyoEfzx2T699ctLLrvuzS79Mg3pPjxSLlUgyM8UzrFc5tgVU3dZ1sFQ\nI4RM+ysJdvIAX/7Yx1QbooVdKhkdi9X7QN7yVkjqwM3fY3WfQkRTzhIkM7mYIQbR\nr+y2Vkju61BLqh7OCRpPMiudjEpP1kEtRyGs2g0aQpEIqKBzxgitCXSayO1hoO6/\n71ts801OzYlqYW9OQQQ2GCJyFbD6XHDjdpn+bWUxTKWaMY0qedSCbHE3Kl2QEF0C\nynZ7SbC03yR+gKZQDeTXrNP1kk5Qhe7jSXgw+nhbspe0q/M1ZcNCz+sPxeOwdCcC\ngJE=\n-----END CERTIFICATE-----",
|
||||
"issuer": "cert-issuer",
|
||||
},
|
||||
}
|
||||
err := Deploy1panel(cfg)
|
||||
println(err)
|
||||
}
|
||||
223
backend/internal/cert/deploy/1panel.go
Normal file
223
backend/internal/cert/deploy/1panel.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateToken(timestamp string, apiKey string) string {
|
||||
tokenMd5 := md5.Sum([]byte("1panel" + apiKey + timestamp))
|
||||
tokenMd5Hex := hex.EncodeToString(tokenMd5[:])
|
||||
return tokenMd5Hex
|
||||
}
|
||||
|
||||
// method provider_id url data
|
||||
|
||||
func Request1panel(data *map[string]any, method, providerID, requestUrl string) (map[string]any, error) {
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||
token := generateToken(timestamp, providerConfig["api_key"])
|
||||
|
||||
// data, requestUrl, method := GetDeploy1PBody(cfg, Type)
|
||||
if requestUrl == "" || data == nil {
|
||||
return nil, fmt.Errorf("不支持的部署类型")
|
||||
}
|
||||
|
||||
// 编码为 JSON
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if providerConfig["url"][len(providerConfig["url"])-1:] != "/" {
|
||||
providerConfig["url"] += "/"
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, providerConfig["url"]+requestUrl, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
// fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36")
|
||||
req.Header.Set("1Panel-Timestamp", timestamp)
|
||||
req.Header.Set("1Panel-Token", token)
|
||||
|
||||
// 自定义 Transport,跳过 SSL 证书验证
|
||||
ignoreSsl := false
|
||||
if providerConfig["ignore_ssl"] == "1" {
|
||||
ignoreSsl = true
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// fmt.Println(err)
|
||||
return nil, fmt.Errorf("请求1panel失败: %v", err)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
|
||||
var res map[string]interface{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("证书部署失败: %v", err)
|
||||
}
|
||||
code, ok := res["code"].(float64)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("证书部署失败")
|
||||
}
|
||||
if code != 200 {
|
||||
msg, ok := res["msg"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("证书部署失败")
|
||||
}
|
||||
return nil, fmt.Errorf("证书部署失败: %s", msg)
|
||||
}
|
||||
return res, nil
|
||||
|
||||
}
|
||||
|
||||
func Deploy1panel(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
// 设置证书
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"cert": certPem,
|
||||
"key": keyPem,
|
||||
"ssl": "enable",
|
||||
"sslType": "import-paste",
|
||||
}
|
||||
_, err := Request1panel(&data, "POST", providerID, "api/v1/settings/ssl/update")
|
||||
if err != nil {
|
||||
return fmt.Errorf("证书部署失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Deploy1panelSite(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
siteId, ok := cfg["site_id"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:site_id")
|
||||
}
|
||||
// 设置证书
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
// 获取网站参数
|
||||
siteData, err := Request1panel(&map[string]any{}, "GET", providerID, fmt.Sprintf("api/v1/websites/%s/https", siteId))
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取网站参数失败: %v", err)
|
||||
}
|
||||
//
|
||||
websiteId, err := strconv.Atoi(siteId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取网站参数失败: %v", err)
|
||||
}
|
||||
|
||||
siteData, ok = siteData["data"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("获取网站参数失败: data")
|
||||
}
|
||||
SSLProtocol, ok := siteData["ssl_protocol"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("获取网站参数失败: data.ssl_protocol")
|
||||
}
|
||||
algorithm, ok := siteData["algorithm"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("获取网站参数失败: data.algorithm")
|
||||
}
|
||||
enable, ok := siteData["enable"].(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("获取网站参数失败: data.enable")
|
||||
}
|
||||
hsts, ok := siteData["hsts"].(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("获取网站参数失败: data.hsts")
|
||||
}
|
||||
httpConfig, ok := siteData["http_config"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("获取网站参数失败: data.http_config")
|
||||
}
|
||||
|
||||
data := map[string]any{
|
||||
"SSLProtocol": SSLProtocol,
|
||||
// "acmeAccountId": siteData["SSL"].(map[string]any)["acmeAccountId"].(float64),
|
||||
"algorithm": algorithm,
|
||||
"certificate": certPem,
|
||||
"privateKey": keyPem,
|
||||
// "certificatePath": "",
|
||||
// "privateKeyPath": "",
|
||||
"enable": enable,
|
||||
"hsts": hsts,
|
||||
"httpConfig": httpConfig,
|
||||
// "importType": "paste",
|
||||
"type": "manual",
|
||||
"websiteId": websiteId,
|
||||
}
|
||||
_, err = Request1panel(&data, "POST", providerID, fmt.Sprintf("api/v1/websites/%s/https", siteId))
|
||||
return nil
|
||||
}
|
||||
41
backend/internal/cert/deploy/ali_test.go
Normal file
41
backend/internal/cert/deploy/ali_test.go
Normal file
File diff suppressed because one or more lines are too long
186
backend/internal/cert/deploy/aliyun.go
Normal file
186
backend/internal/cert/deploy/aliyun.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
aliyuncdn "github.com/alibabacloud-go/cdn-20180510/v6/client"
|
||||
aliyunopenapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||
"github.com/alibabacloud-go/tea/tea"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
)
|
||||
|
||||
func ClientAliCdn(accessKey, accessSecret string) (_result *aliyuncdn.Client, err error) {
|
||||
config := &aliyunopenapi.Config{
|
||||
AccessKeyId: tea.String(accessKey),
|
||||
AccessKeySecret: tea.String(accessSecret),
|
||||
Endpoint: tea.String("cdn.aliyuncs.com"),
|
||||
}
|
||||
client, err := aliyuncdn.NewClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func DeployAliCdn(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
//
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := ClientAliCdn(providerConfig["access_key"], providerConfig["access_secret"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
// 设置证书
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
|
||||
setCdnDomainSSLCertificateRequest := &aliyuncdn.SetCdnDomainSSLCertificateRequest{
|
||||
DomainName: tea.String(domain),
|
||||
SSLProtocol: tea.String("on"),
|
||||
SSLPub: tea.String(strings.TrimSpace(certPem)),
|
||||
SSLPri: tea.String(strings.TrimSpace(keyPem)),
|
||||
}
|
||||
_, err = client.SetCdnDomainSSLCertificate(setCdnDomainSSLCertificateRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ClientOss(accessKeyId, accessKeySecret, region string) (*oss.Client, error) {
|
||||
// 接入点一览 https://api.aliyun.com/product/Oss
|
||||
var endpoint string
|
||||
switch region {
|
||||
case "":
|
||||
endpoint = "oss.aliyuncs.com"
|
||||
case
|
||||
"cn-hzjbp",
|
||||
"cn-hzjbp-a",
|
||||
"cn-hzjbp-b":
|
||||
endpoint = "oss-cn-hzjbp-a-internal.aliyuncs.com"
|
||||
case
|
||||
"cn-shanghai-finance-1",
|
||||
"cn-shenzhen-finance-1",
|
||||
"cn-beijing-finance-1",
|
||||
"cn-north-2-gov-1":
|
||||
endpoint = fmt.Sprintf("oss-%s-internal.aliyuncs.com", region)
|
||||
default:
|
||||
endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
|
||||
}
|
||||
|
||||
client, err := oss.New(endpoint, accessKeyId, accessKeySecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func DeployOss(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
//
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
region, ok := cfg["region"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:region")
|
||||
}
|
||||
|
||||
client, err := ClientOss(providerConfig["access_key"], providerConfig["access_secret"], region)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
bucket, ok := cfg["domain"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:bucket")
|
||||
}
|
||||
// 设置证书
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
|
||||
putBucketCnameWithCertificateRequest := oss.PutBucketCname{
|
||||
Cname: domain,
|
||||
CertificateConfiguration: &oss.CertificateConfiguration{
|
||||
Certificate: certPem,
|
||||
PrivateKey: keyPem,
|
||||
Force: true,
|
||||
},
|
||||
}
|
||||
err = client.PutBucketCnameWithCertificate(bucket, putBucketCnameWithCertificateRequest)
|
||||
return err
|
||||
}
|
||||
31
backend/internal/cert/deploy/bt_test.go
Normal file
31
backend/internal/cert/deploy/bt_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package deploy
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestBTSite(t *testing.T) {
|
||||
cfg := map[string]any{
|
||||
"siteName": "abcd.cn",
|
||||
"provider_id": "19",
|
||||
"certificate": map[string]any{
|
||||
"key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxIjmAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONh\nWhMT6W+cx0WMC80yCRm5JshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM\n4tz/Zh8a3kVyN4MtWDmV1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1\nz6qqEDcM8FtHoAXAdxQBkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9\ncMY1cCEBxpwQTJiJHbX9LcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MU\nS89+DsixFf3HL+iWjr6yVnQ/mAGVPQ+HD4pwmQIDAQABAoIBAALpcFb59MBZZHJ3\nui9RRi96ig6kPQoRjkjN83pjM+/h/bANMmUOQU5FHBKLwj5uhN5Dpk2fzAnIX2TE\nVgfyNGsYuWLsIM+m6EJfm7pXJwJDr3RCpm+6DIKr1U8TwlR2OhbDi6fOlfH66q79\n2Klq4SXsa0vgfllpTVCDtydFVjwAuQV7Cf6DGRjbNpN3DPLeOC1wYFimNZwudSK0\nf8grWpPFXw2TPaf3TgeBGxwL7GCTYSKT+Eq9USbhG4RArrM9oQt+h7rzaH2bFEdg\n7tOM4KIgV+aw8r0TsYisDG9dfiHfHr5vQnkmWgt/rxAOvHlJ7/64pBVuET1ZF0mB\nP6gu4Y0CgYEAzkwXvfnHI5qx9BVP6e9lGrpWrm0RxCKr2iCCwrOVALbX1yfKCb5L\nrP/jSERMuLt6bIKg/AoVu9ogCTGzntyHTbZXFGg/y5Xoul+1af2arQ1rGZ7A/Im7\nnteZePg2U6UiDRy07F94FF5aL/v97D4BffiSA+0atlgH6tpKyYfY6NsCgYEA8+Ku\nGQqX9kHDd5bbzPhLelNmHVnAjnMaHEhvzVtBA737F10Oqg9wyffqe/i/DvdUSx9r\nafKGUfzB2vVZjz//OpSQ8VhRzDTiyelKLsSTmzOokLBnwayyTxw85o9EDvTNrzfb\nYQbAjmAXWmnv5Xvx1KfvTaKFY3BmHsKYJDzwnJsCgYBK1SVjn2CSVMIqlTSI2nMl\nb+STnzLrn9wQ4uwr7nKlcK34+RD72dCfr67lfwkJldBB3lzBMHNT0jr+us26Waqn\nEPaji3Fgyz9BpAgtq3XZQl3QTFsbAGdTpkegrwEd9G/Wq8whVjw7v0Id193zPUbT\nSEDHNdITxPkSQx8P3bxcMwKBgQDO5EGk5KO9OFTFoqib3RbKku1RgM4lCefgjmKp\n5vvkXMohK8RA6BBahYHZ4U7TN2W+xMyueBsSekVJplFvgG7YFyhOVQovHb42Yz2X\nJxPA2bXp6HxchFBPZDkVrfuiZHIIbm4ghUXcgg/Nl4j3OIoSSNRtG63kiXlYJuRB\n+aB0eQKBgD79VrREpbOMS7HRlDTtfkDN94HY3T4MLErs26z/NLO/dC44tmBJGo2P\ngcQ+p7XxNjpWUnUbEiuz4R3Xgh6ULwuSseWtcQicolPHTkBjnc+6BEpyguZJ+FPZ\nGls3g3LxjGhdPlyd37CaWDvx/Jtjrd4Y9iGkGO2d9fXZD0Hg0ymX\n-----END RSA PRIVATE KEY-----",
|
||||
"cert": "-----BEGIN CERTIFICATE-----\nMIIG5DCCBMygAwIBAgIQBPQGlt81+4RKt3RAFXPvrjANBgkqhkiG9w0BAQsFADBb\nMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg\nSW5jLjElMCMGA1UEAxMcVHJ1c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNTAeFw0y\nNTA0MjIwMDAwMDBaFw0yNTA3MjAyMzU5NTlaMB8xHTAbBgNVBAMTFGFsbGluc3Ns\nLnphY2h5YW5nLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxIjm\nAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONhWhMT6W+cx0WMC80yCRm5\nJshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM4tz/Zh8a3kVyN4MtWDmV\n1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1z6qqEDcM8FtHoAXAdxQB\nkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9cMY1cCEBxpwQTJiJHbX9\nLcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MUS89+DsixFf3HL+iWjr6y\nVnQ/mAGVPQ+HD4pwmQIDAQABo4IC3jCCAtowHwYDVR0jBBgwFoAUtBIopbTAHZ8p\ncWk82RGWSnVpUMAwHQYDVR0OBBYEFHqqdlMVBlcadf7iJLJoLnLZ7h4tMB8GA1Ud\nEQQYMBaCFGFsbGluc3NsLnphY2h5YW5nLmNuMD4GA1UdIAQ3MDUwMwYGZ4EMAQIB\nMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHkGCCsG\nAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t\nMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vVHJ1c3RB\nc2lhRFZUTFNSU0FDQTIwMjUuY3J0MAwGA1UdEwEB/wQCMAAwggF9BgorBgEEAdZ5\nAgQCBIIBbQSCAWkBZwB2ABLxTjS9U3JMhAYZw48/ehP457Vih4icbTAFhOvlhiY6\nAAABll0w/o0AAAQDAEcwRQIgd24jCPm+fbHq3grMIxtvQhzkv7dvYPM/BGjPEsy1\nQ70CIQC5jXADjBh+dH50T+atn3lktBEqQhedOl6cAaP/XXmk6gB2AO08S9boBsKk\nogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABll0w/rUAAAQDAEcwRQIgU2GDVEH1\ns5i/RC1RhqvJjn72PAZOlDtJyLdg29vC9HECIQCj78GATYK5quitLxbn3HvD8BeT\noOz+3tacgyN6+TdvugB1AKRCxQZJYGFUjw/U6pz7ei0mRU2HqX8v30VZ9idPOoRU\nAAABll0w/sYAAAQDAEYwRAIgCvU/iBRPKoJLjmU4edBYObWAO/aJp2mWnfJ4ieAr\nrXsCIBsAppYu28h8YEOl0N9yEeF9G05IMxwkCjZKonQs2SKMMA0GCSqGSIb3DQEB\nCwUAA4ICAQB3wFou51Qvl4apMhencuQUnWF3UpYP49e0WQ72DVT3pYjYsozkSuqb\nQZcwMB6HDoHdFicxvQ/yxKyTu/nw3rXjUWYuSxXYd7lJcQ/R0tR00m6AFeinY4Aq\nq4QqoA+lriK1XqO5MomAL4FbSysT1ow/gaG9pYuXEdT4pr05I/NumjXdkwBRZOd4\nrhol2grKf3y37Qla5hUbbG3ab9nf/csJSWkCoESeXr3MB1oAU/aL9pGSagvMXSKQ\nsFs2cn2Fi8ZmJPJXIP114lgvFuFDO+C1yTNbHap/FufvAKGryfPDuPecCF6FSXej\n+bwg4/BNz5lcHbNo2XXjLgoPg4VE6mG/SQQZQEDBk5DowwMVMvh77t9RBNrHozah\nHGtQz2hCuIX7rZQYnSlvW8T75FhI/Sd+HEfU/iyTIELXBUjypnK2bOJL7+jE7f79\nuljhXlCcP52fGHCjexNBz5gIZr82KVxsfxKuZjfioPkhmWleVNMdMWYJRXu618E6\nNtNjUVsDCuMOOMNs1qScqxOT60MeDZLX+vnC93fdd/t2hLEAWWNNMkWeX2qLCE1q\nGarop9U1mJpiBWkW5cBiqnNIbhuV2fcwFIR8mVT5f1Qcw+WxE2nEjY2h75bKv8T5\n3RBngmaX8PcyLAP2s0/4UyzAnMYfioJBh37VpUYBrdriBkRds/AMZw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFnjCCBIagAwIBAgIQCSYyO0lk42hGFRLe8aXVLDANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0yNTAxMDgwMDAwMDBaFw0zNTAxMDcyMzU5NTlaMFsxCzAJBgNVBAYTAkNO\nMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQD\nExxUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSAyMDI1MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA0fuEmuBIsN6ZZVq+gRobMorOGIilTCIfQrxNpR8FUZ9R\n/GfbiekbiIKphQXEZ7N1uBnn6tXUuZ32zl6jPkZpHzN/Bmgk1BWSIzVc0npMzrWq\n/hrbk5+KddXJdsNpeG1+Q8lc8uVMBrztnxaPb7Rh7yQCsMrcO4hgVaqLJWkVvEfW\nULtoCHQnNaj4IroG6VxQf1oArQ8bPbwpI02lieSahRa78FQuXdoGVeQcrkhtVjZs\nON98vq5fPWZX2LFv7e5J6P9IHbzvOl8yyQjv+2/IOwhNSkaXX3bI+//bqF9XW/p7\n+gsUmHiK5YsvLjmXcvDmoDEGrXMzgX31Zl2nJ+umpRbLjwP8rxYIUsKoEwEdFoto\nAid59UEBJyw/GibwXQ5xTyKD/N6C8SFkr1+myOo4oe1UB+YgvRu6qSxIABo5kYdX\nFodLP4IgoVJdeUFs1Usa6bxYEO6EgMf5lCWt9hGZszvXYZwvyZGq3ogNXM7eKyi2\n20WzJXYMmi9TYFq2Fa95aZe4wki6YhDhhOO1g0sjITGVaB73G+JOCI9yJhv6+REN\nD40ZpboUHE8JNgMVWbG1isAMVCXqiADgXtuC+tmJWPEH9cR6OuJLEpwOzPfgAbnn\n2MRu7Tsdr8jPjTPbD0FxblX1ydW3RG30vwLF5lkTTRkHG9epMgpPMdYP7nY/08MC\nAwEAAaOCAVYwggFSMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLQSKKW0\nwB2fKXFpPNkRlkp1aVDAMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485\nMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\ndgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\ndC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E\naWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov\nL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDARBgNV\nHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAJ4a3svh316GY2+Z7EYx\nmBIsOwjJSnyoEfzx2T699ctLLrvuzS79Mg3pPjxSLlUgyM8UzrFc5tgVU3dZ1sFQ\nI4RM+ysJdvIAX/7Yx1QbooVdKhkdi9X7QN7yVkjqwM3fY3WfQkRTzhIkM7mYIQbR\nr+y2Vkju61BLqh7OCRpPMiudjEpP1kEtRyGs2g0aQpEIqKBzxgitCXSayO1hoO6/\n71ts801OzYlqYW9OQQQ2GCJyFbD6XHDjdpn+bWUxTKWaMY0qedSCbHE3Kl2QEF0C\nynZ7SbC03yR+gKZQDeTXrNP1kk5Qhe7jSXgw+nhbspe0q/M1ZcNCz+sPxeOwdCcC\ngJE=\n-----END CERTIFICATE-----",
|
||||
"issuer": "cert-issuer",
|
||||
},
|
||||
}
|
||||
err := DeployBtSite(cfg)
|
||||
println(err)
|
||||
}
|
||||
|
||||
func TestBTP(t *testing.T) {
|
||||
cfg := map[string]any{
|
||||
"site_id": "1",
|
||||
"provider_id": "19",
|
||||
"certificate": map[string]any{
|
||||
"key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxIjmAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONh\nWhMT6W+cx0WMC80yCRm5JshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM\n4tz/Zh8a3kVyN4MtWDmV1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1\nz6qqEDcM8FtHoAXAdxQBkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9\ncMY1cCEBxpwQTJiJHbX9LcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MU\nS89+DsixFf3HL+iWjr6yVnQ/mAGVPQ+HD4pwmQIDAQABAoIBAALpcFb59MBZZHJ3\nui9RRi96ig6kPQoRjkjN83pjM+/h/bANMmUOQU5FHBKLwj5uhN5Dpk2fzAnIX2TE\nVgfyNGsYuWLsIM+m6EJfm7pXJwJDr3RCpm+6DIKr1U8TwlR2OhbDi6fOlfH66q79\n2Klq4SXsa0vgfllpTVCDtydFVjwAuQV7Cf6DGRjbNpN3DPLeOC1wYFimNZwudSK0\nf8grWpPFXw2TPaf3TgeBGxwL7GCTYSKT+Eq9USbhG4RArrM9oQt+h7rzaH2bFEdg\n7tOM4KIgV+aw8r0TsYisDG9dfiHfHr5vQnkmWgt/rxAOvHlJ7/64pBVuET1ZF0mB\nP6gu4Y0CgYEAzkwXvfnHI5qx9BVP6e9lGrpWrm0RxCKr2iCCwrOVALbX1yfKCb5L\nrP/jSERMuLt6bIKg/AoVu9ogCTGzntyHTbZXFGg/y5Xoul+1af2arQ1rGZ7A/Im7\nnteZePg2U6UiDRy07F94FF5aL/v97D4BffiSA+0atlgH6tpKyYfY6NsCgYEA8+Ku\nGQqX9kHDd5bbzPhLelNmHVnAjnMaHEhvzVtBA737F10Oqg9wyffqe/i/DvdUSx9r\nafKGUfzB2vVZjz//OpSQ8VhRzDTiyelKLsSTmzOokLBnwayyTxw85o9EDvTNrzfb\nYQbAjmAXWmnv5Xvx1KfvTaKFY3BmHsKYJDzwnJsCgYBK1SVjn2CSVMIqlTSI2nMl\nb+STnzLrn9wQ4uwr7nKlcK34+RD72dCfr67lfwkJldBB3lzBMHNT0jr+us26Waqn\nEPaji3Fgyz9BpAgtq3XZQl3QTFsbAGdTpkegrwEd9G/Wq8whVjw7v0Id193zPUbT\nSEDHNdITxPkSQx8P3bxcMwKBgQDO5EGk5KO9OFTFoqib3RbKku1RgM4lCefgjmKp\n5vvkXMohK8RA6BBahYHZ4U7TN2W+xMyueBsSekVJplFvgG7YFyhOVQovHb42Yz2X\nJxPA2bXp6HxchFBPZDkVrfuiZHIIbm4ghUXcgg/Nl4j3OIoSSNRtG63kiXlYJuRB\n+aB0eQKBgD79VrREpbOMS7HRlDTtfkDN94HY3T4MLErs26z/NLO/dC44tmBJGo2P\ngcQ+p7XxNjpWUnUbEiuz4R3Xgh6ULwuSseWtcQicolPHTkBjnc+6BEpyguZJ+FPZ\nGls3g3LxjGhdPlyd37CaWDvx/Jtjrd4Y9iGkGO2d9fXZD0Hg0ymX\n-----END RSA PRIVATE KEY-----",
|
||||
"cert": "-----BEGIN CERTIFICATE-----\nMIIG5DCCBMygAwIBAgIQBPQGlt81+4RKt3RAFXPvrjANBgkqhkiG9w0BAQsFADBb\nMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg\nSW5jLjElMCMGA1UEAxMcVHJ1c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNTAeFw0y\nNTA0MjIwMDAwMDBaFw0yNTA3MjAyMzU5NTlaMB8xHTAbBgNVBAMTFGFsbGluc3Ns\nLnphY2h5YW5nLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxIjm\nAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONhWhMT6W+cx0WMC80yCRm5\nJshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM4tz/Zh8a3kVyN4MtWDmV\n1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1z6qqEDcM8FtHoAXAdxQB\nkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9cMY1cCEBxpwQTJiJHbX9\nLcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MUS89+DsixFf3HL+iWjr6y\nVnQ/mAGVPQ+HD4pwmQIDAQABo4IC3jCCAtowHwYDVR0jBBgwFoAUtBIopbTAHZ8p\ncWk82RGWSnVpUMAwHQYDVR0OBBYEFHqqdlMVBlcadf7iJLJoLnLZ7h4tMB8GA1Ud\nEQQYMBaCFGFsbGluc3NsLnphY2h5YW5nLmNuMD4GA1UdIAQ3MDUwMwYGZ4EMAQIB\nMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHkGCCsG\nAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t\nMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vVHJ1c3RB\nc2lhRFZUTFNSU0FDQTIwMjUuY3J0MAwGA1UdEwEB/wQCMAAwggF9BgorBgEEAdZ5\nAgQCBIIBbQSCAWkBZwB2ABLxTjS9U3JMhAYZw48/ehP457Vih4icbTAFhOvlhiY6\nAAABll0w/o0AAAQDAEcwRQIgd24jCPm+fbHq3grMIxtvQhzkv7dvYPM/BGjPEsy1\nQ70CIQC5jXADjBh+dH50T+atn3lktBEqQhedOl6cAaP/XXmk6gB2AO08S9boBsKk\nogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABll0w/rUAAAQDAEcwRQIgU2GDVEH1\ns5i/RC1RhqvJjn72PAZOlDtJyLdg29vC9HECIQCj78GATYK5quitLxbn3HvD8BeT\noOz+3tacgyN6+TdvugB1AKRCxQZJYGFUjw/U6pz7ei0mRU2HqX8v30VZ9idPOoRU\nAAABll0w/sYAAAQDAEYwRAIgCvU/iBRPKoJLjmU4edBYObWAO/aJp2mWnfJ4ieAr\nrXsCIBsAppYu28h8YEOl0N9yEeF9G05IMxwkCjZKonQs2SKMMA0GCSqGSIb3DQEB\nCwUAA4ICAQB3wFou51Qvl4apMhencuQUnWF3UpYP49e0WQ72DVT3pYjYsozkSuqb\nQZcwMB6HDoHdFicxvQ/yxKyTu/nw3rXjUWYuSxXYd7lJcQ/R0tR00m6AFeinY4Aq\nq4QqoA+lriK1XqO5MomAL4FbSysT1ow/gaG9pYuXEdT4pr05I/NumjXdkwBRZOd4\nrhol2grKf3y37Qla5hUbbG3ab9nf/csJSWkCoESeXr3MB1oAU/aL9pGSagvMXSKQ\nsFs2cn2Fi8ZmJPJXIP114lgvFuFDO+C1yTNbHap/FufvAKGryfPDuPecCF6FSXej\n+bwg4/BNz5lcHbNo2XXjLgoPg4VE6mG/SQQZQEDBk5DowwMVMvh77t9RBNrHozah\nHGtQz2hCuIX7rZQYnSlvW8T75FhI/Sd+HEfU/iyTIELXBUjypnK2bOJL7+jE7f79\nuljhXlCcP52fGHCjexNBz5gIZr82KVxsfxKuZjfioPkhmWleVNMdMWYJRXu618E6\nNtNjUVsDCuMOOMNs1qScqxOT60MeDZLX+vnC93fdd/t2hLEAWWNNMkWeX2qLCE1q\nGarop9U1mJpiBWkW5cBiqnNIbhuV2fcwFIR8mVT5f1Qcw+WxE2nEjY2h75bKv8T5\n3RBngmaX8PcyLAP2s0/4UyzAnMYfioJBh37VpUYBrdriBkRds/AMZw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFnjCCBIagAwIBAgIQCSYyO0lk42hGFRLe8aXVLDANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0yNTAxMDgwMDAwMDBaFw0zNTAxMDcyMzU5NTlaMFsxCzAJBgNVBAYTAkNO\nMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQD\nExxUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSAyMDI1MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA0fuEmuBIsN6ZZVq+gRobMorOGIilTCIfQrxNpR8FUZ9R\n/GfbiekbiIKphQXEZ7N1uBnn6tXUuZ32zl6jPkZpHzN/Bmgk1BWSIzVc0npMzrWq\n/hrbk5+KddXJdsNpeG1+Q8lc8uVMBrztnxaPb7Rh7yQCsMrcO4hgVaqLJWkVvEfW\nULtoCHQnNaj4IroG6VxQf1oArQ8bPbwpI02lieSahRa78FQuXdoGVeQcrkhtVjZs\nON98vq5fPWZX2LFv7e5J6P9IHbzvOl8yyQjv+2/IOwhNSkaXX3bI+//bqF9XW/p7\n+gsUmHiK5YsvLjmXcvDmoDEGrXMzgX31Zl2nJ+umpRbLjwP8rxYIUsKoEwEdFoto\nAid59UEBJyw/GibwXQ5xTyKD/N6C8SFkr1+myOo4oe1UB+YgvRu6qSxIABo5kYdX\nFodLP4IgoVJdeUFs1Usa6bxYEO6EgMf5lCWt9hGZszvXYZwvyZGq3ogNXM7eKyi2\n20WzJXYMmi9TYFq2Fa95aZe4wki6YhDhhOO1g0sjITGVaB73G+JOCI9yJhv6+REN\nD40ZpboUHE8JNgMVWbG1isAMVCXqiADgXtuC+tmJWPEH9cR6OuJLEpwOzPfgAbnn\n2MRu7Tsdr8jPjTPbD0FxblX1ydW3RG30vwLF5lkTTRkHG9epMgpPMdYP7nY/08MC\nAwEAAaOCAVYwggFSMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLQSKKW0\nwB2fKXFpPNkRlkp1aVDAMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485\nMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\ndgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\ndC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E\naWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov\nL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDARBgNV\nHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAJ4a3svh316GY2+Z7EYx\nmBIsOwjJSnyoEfzx2T699ctLLrvuzS79Mg3pPjxSLlUgyM8UzrFc5tgVU3dZ1sFQ\nI4RM+ysJdvIAX/7Yx1QbooVdKhkdi9X7QN7yVkjqwM3fY3WfQkRTzhIkM7mYIQbR\nr+y2Vkju61BLqh7OCRpPMiudjEpP1kEtRyGs2g0aQpEIqKBzxgitCXSayO1hoO6/\n71ts801OzYlqYW9OQQQ2GCJyFbD6XHDjdpn+bWUxTKWaMY0qedSCbHE3Kl2QEF0C\nynZ7SbC03yR+gKZQDeTXrNP1kk5Qhe7jSXgw+nhbspe0q/M1ZcNCz+sPxeOwdCcC\ngJE=\n-----END CERTIFICATE-----",
|
||||
"issuer": "cert-issuer",
|
||||
},
|
||||
}
|
||||
err := DeployBt(cfg)
|
||||
println(err)
|
||||
}
|
||||
158
backend/internal/cert/deploy/btpanel.go
Normal file
158
backend/internal/cert/deploy/btpanel.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"crypto/md5"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func generateSignature(timestamp, apiKey string) string {
|
||||
keyMd5 := md5.Sum([]byte(apiKey))
|
||||
keyMd5Hex := strings.ToLower(hex.EncodeToString(keyMd5[:]))
|
||||
|
||||
signMd5 := md5.Sum([]byte(timestamp + keyMd5Hex))
|
||||
signMd5Hex := strings.ToLower(hex.EncodeToString(signMd5[:]))
|
||||
return signMd5Hex
|
||||
}
|
||||
|
||||
func RequestBt(data *url.Values, method, providerID, requestUrl string) (map[string]any, error) {
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timestamp := time.Now().Unix()
|
||||
token := generateSignature(fmt.Sprintf("%d", timestamp), providerConfig["api_key"])
|
||||
if providerConfig["url"][len(providerConfig["url"])-1:] != "/" {
|
||||
providerConfig["url"] += "/"
|
||||
}
|
||||
|
||||
data.Set("request_time", fmt.Sprintf("%d", timestamp))
|
||||
data.Set("request_token", token)
|
||||
|
||||
req, err := http.NewRequest(method, providerConfig["url"]+requestUrl, strings.NewReader(data.Encode()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36")
|
||||
// 自定义 Transport,跳过 SSL 证书验证
|
||||
ignoreSsl := false
|
||||
if providerConfig["ignore_ssl"] == "1" {
|
||||
ignoreSsl = true
|
||||
}
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl},
|
||||
}
|
||||
|
||||
client := &http.Client{Transport: tr}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
// fmt.Println(err)
|
||||
return nil, fmt.Errorf("请求BT失败: %v", err)
|
||||
}
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
|
||||
var res map[string]interface{}
|
||||
err = json.Unmarshal(body, &res)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("返回值解析失败: %v", err)
|
||||
}
|
||||
|
||||
if res["status"] != nil && !res["status"].(bool) {
|
||||
return nil, fmt.Errorf("请求出错: %s", res["msg"].(string))
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func DeployBt(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
// 设置证书
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
data := url.Values{}
|
||||
data.Set("cert_type", "1")
|
||||
data.Set("privateKey", keyPem)
|
||||
data.Set("certPem", certPem)
|
||||
_, err := RequestBt(&data, "POST", providerID, "/config?action=SetPanelSSL")
|
||||
if err != nil {
|
||||
return fmt.Errorf("证书部署失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeployBtSite(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
// 设置证书
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
siteName, ok := cfg["siteName"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:siteName")
|
||||
}
|
||||
data := url.Values{}
|
||||
data.Set("key", keyPem)
|
||||
data.Set("csr", certPem)
|
||||
data.Set("siteName", siteName)
|
||||
_, err := RequestBt(&data, "POST", providerID, "/site?action=SetSSL")
|
||||
if err != nil {
|
||||
return fmt.Errorf("证书部署失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
45
backend/internal/cert/deploy/deploy.go
Normal file
45
backend/internal/cert/deploy/deploy.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func Deploy(cfg map[string]any, logger *public.Logger) error {
|
||||
providerName, ok := cfg["provider"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("provider is not string")
|
||||
}
|
||||
switch providerName {
|
||||
case "btpanel":
|
||||
logger.Debug("部署到宝塔面板...")
|
||||
return DeployBt(cfg)
|
||||
case "btpanel-site":
|
||||
logger.Debug("部署到宝塔面板网站...")
|
||||
return DeployBtSite(cfg)
|
||||
case "tencentcloud-cdn":
|
||||
cfg["resource_type"] = "cdn"
|
||||
logger.Debug("部署到腾讯云CDN...")
|
||||
return DeployToTX(cfg)
|
||||
case "tencentcloud-cos":
|
||||
cfg["resource_type"] = "cos"
|
||||
logger.Debug("部署到腾讯云COS...")
|
||||
return DeployToTX(cfg)
|
||||
case "1panel":
|
||||
logger.Debug("部署到1Panel...")
|
||||
return Deploy1panel(cfg)
|
||||
case "1panel-site":
|
||||
logger.Debug("部署到1Panel网站...")
|
||||
return Deploy1panelSite(cfg)
|
||||
case "ssh":
|
||||
logger.Debug("使用ssh部署到指定路径...")
|
||||
return DeploySSH(cfg)
|
||||
case "aliyun-cdn":
|
||||
logger.Debug("部署到阿里云CDN...")
|
||||
return DeployAliCdn(cfg)
|
||||
// case "aliyun-oss":
|
||||
|
||||
default:
|
||||
return fmt.Errorf("不支持的部署: %s", providerName)
|
||||
}
|
||||
}
|
||||
163
backend/internal/cert/deploy/ssh.go
Normal file
163
backend/internal/cert/deploy/ssh.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"path"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type SSHConfig struct {
|
||||
User string
|
||||
Password string // 可选
|
||||
PrivateKey string // 可选
|
||||
Host string
|
||||
Port string
|
||||
}
|
||||
|
||||
type RemoteFile struct {
|
||||
Path string
|
||||
Content string
|
||||
}
|
||||
|
||||
func buildAuthMethods(password, privateKey string) ([]ssh.AuthMethod, error) {
|
||||
var methods []ssh.AuthMethod
|
||||
|
||||
if privateKey != "" {
|
||||
signer, err := ssh.ParsePrivateKey([]byte(privateKey))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse private key: %v", err)
|
||||
}
|
||||
methods = append(methods, ssh.PublicKeys(signer))
|
||||
}
|
||||
|
||||
if password != "" {
|
||||
methods = append(methods, ssh.Password(password))
|
||||
}
|
||||
|
||||
if len(methods) == 0 {
|
||||
return nil, fmt.Errorf("no authentication methods provided")
|
||||
}
|
||||
|
||||
return methods, nil
|
||||
}
|
||||
|
||||
func writeMultipleFilesViaSSH(config SSHConfig, files []RemoteFile, preCmd, postCmd string) error {
|
||||
addr := fmt.Sprintf("%s:%s", config.Host, config.Port)
|
||||
|
||||
authMethods, err := buildAuthMethods(config.Password, config.PrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sshConfig := &ssh.ClientConfig{
|
||||
User: config.User,
|
||||
Auth: authMethods,
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", addr, sshConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to dial: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
return fmt.Errorf("会话创建失败: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
var script bytes.Buffer
|
||||
|
||||
if preCmd != "" {
|
||||
script.WriteString(preCmd + " && ")
|
||||
}
|
||||
|
||||
for i, file := range files {
|
||||
if i > 0 {
|
||||
script.WriteString(" && ")
|
||||
}
|
||||
|
||||
dirCmd := fmt.Sprintf("mkdir -p $(dirname %q)", file.Path)
|
||||
writeCmd := fmt.Sprintf("printf %%s '%s' > %s", file.Content, file.Path)
|
||||
|
||||
script.WriteString(dirCmd + " && " + writeCmd)
|
||||
}
|
||||
|
||||
if postCmd != "" {
|
||||
script.WriteString(" && " + postCmd)
|
||||
}
|
||||
|
||||
cmd := script.String()
|
||||
|
||||
if err := session.Run(cmd); err != nil {
|
||||
return fmt.Errorf("运行出错: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func DeploySSH(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
// 设置证书
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
dir, ok := cfg["path"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:path")
|
||||
}
|
||||
beforeCmd, ok := cfg["beforeCmd"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:beforeCmd")
|
||||
}
|
||||
afterCmd, ok := cfg["afterCmd"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:afterCmd")
|
||||
}
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig SSHConfig
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 自动创建多级目录
|
||||
files := []RemoteFile{
|
||||
{Path: path.Join(dir, "cert.pem"), Content: certPem},
|
||||
{Path: path.Join(dir, "key.pem"), Content: keyPem},
|
||||
}
|
||||
err = writeMultipleFilesViaSSH(providerConfig, files, beforeCmd, afterCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SSH 部署失败: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
22
backend/internal/cert/deploy/ssh_test.go
Normal file
22
backend/internal/cert/deploy/ssh_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package deploy
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSSH(t *testing.T) {
|
||||
cfg := map[string]any{
|
||||
"path": "/www/ccccc",
|
||||
"beforeCmd": "touch /www/ccccc/xxxxx.txt",
|
||||
"afterCmd": "touch /www/ccccc/cccccc.txt",
|
||||
"provider_id": "23",
|
||||
"certificate": map[string]any{
|
||||
"key": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAxIjmAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONh\nWhMT6W+cx0WMC80yCRm5JshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM\n4tz/Zh8a3kVyN4MtWDmV1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1\nz6qqEDcM8FtHoAXAdxQBkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9\ncMY1cCEBxpwQTJiJHbX9LcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MU\nS89+DsixFf3HL+iWjr6yVnQ/mAGVPQ+HD4pwmQIDAQABAoIBAALpcFb59MBZZHJ3\nui9RRi96ig6kPQoRjkjN83pjM+/h/bANMmUOQU5FHBKLwj5uhN5Dpk2fzAnIX2TE\nVgfyNGsYuWLsIM+m6EJfm7pXJwJDr3RCpm+6DIKr1U8TwlR2OhbDi6fOlfH66q79\n2Klq4SXsa0vgfllpTVCDtydFVjwAuQV7Cf6DGRjbNpN3DPLeOC1wYFimNZwudSK0\nf8grWpPFXw2TPaf3TgeBGxwL7GCTYSKT+Eq9USbhG4RArrM9oQt+h7rzaH2bFEdg\n7tOM4KIgV+aw8r0TsYisDG9dfiHfHr5vQnkmWgt/rxAOvHlJ7/64pBVuET1ZF0mB\nP6gu4Y0CgYEAzkwXvfnHI5qx9BVP6e9lGrpWrm0RxCKr2iCCwrOVALbX1yfKCb5L\nrP/jSERMuLt6bIKg/AoVu9ogCTGzntyHTbZXFGg/y5Xoul+1af2arQ1rGZ7A/Im7\nnteZePg2U6UiDRy07F94FF5aL/v97D4BffiSA+0atlgH6tpKyYfY6NsCgYEA8+Ku\nGQqX9kHDd5bbzPhLelNmHVnAjnMaHEhvzVtBA737F10Oqg9wyffqe/i/DvdUSx9r\nafKGUfzB2vVZjz//OpSQ8VhRzDTiyelKLsSTmzOokLBnwayyTxw85o9EDvTNrzfb\nYQbAjmAXWmnv5Xvx1KfvTaKFY3BmHsKYJDzwnJsCgYBK1SVjn2CSVMIqlTSI2nMl\nb+STnzLrn9wQ4uwr7nKlcK34+RD72dCfr67lfwkJldBB3lzBMHNT0jr+us26Waqn\nEPaji3Fgyz9BpAgtq3XZQl3QTFsbAGdTpkegrwEd9G/Wq8whVjw7v0Id193zPUbT\nSEDHNdITxPkSQx8P3bxcMwKBgQDO5EGk5KO9OFTFoqib3RbKku1RgM4lCefgjmKp\n5vvkXMohK8RA6BBahYHZ4U7TN2W+xMyueBsSekVJplFvgG7YFyhOVQovHb42Yz2X\nJxPA2bXp6HxchFBPZDkVrfuiZHIIbm4ghUXcgg/Nl4j3OIoSSNRtG63kiXlYJuRB\n+aB0eQKBgD79VrREpbOMS7HRlDTtfkDN94HY3T4MLErs26z/NLO/dC44tmBJGo2P\ngcQ+p7XxNjpWUnUbEiuz4R3Xgh6ULwuSseWtcQicolPHTkBjnc+6BEpyguZJ+FPZ\nGls3g3LxjGhdPlyd37CaWDvx/Jtjrd4Y9iGkGO2d9fXZD0Hg0ymX\n-----END RSA PRIVATE KEY-----",
|
||||
"cert": "-----BEGIN CERTIFICATE-----\nMIIG5DCCBMygAwIBAgIQBPQGlt81+4RKt3RAFXPvrjANBgkqhkiG9w0BAQsFADBb\nMQswCQYDVQQGEwJDTjElMCMGA1UEChMcVHJ1c3RBc2lhIFRlY2hub2xvZ2llcywg\nSW5jLjElMCMGA1UEAxMcVHJ1c3RBc2lhIERWIFRMUyBSU0EgQ0EgMjAyNTAeFw0y\nNTA0MjIwMDAwMDBaFw0yNTA3MjAyMzU5NTlaMB8xHTAbBgNVBAMTFGFsbGluc3Ns\nLnphY2h5YW5nLmNuMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxIjm\nAi/paC2OmG7nOqZ+OJx7spDrx7yZiWvn1XgLW/5ODONhWhMT6W+cx0WMC80yCRm5\nJshIIMzmMxN03pRD1h4u1fPNUnJmGtthRZIm3aU7TlSM4tz/Zh8a3kVyN4MtWDmV\n1/1MV8H0YBtT6K2gxZ7Fz/YKhVATdh8Fy+1qEz3gSrw1z6qqEDcM8FtHoAXAdxQB\nkS8xu34SIriwZiN2YlrtL8Qy73j4XiJLh2cc/NPp+mW9cMY1cCEBxpwQTJiJHbX9\nLcEqYgOkkhWIijW2dYlCLaLsnvJw0TCRd6PooR8XK7MUS89+DsixFf3HL+iWjr6y\nVnQ/mAGVPQ+HD4pwmQIDAQABo4IC3jCCAtowHwYDVR0jBBgwFoAUtBIopbTAHZ8p\ncWk82RGWSnVpUMAwHQYDVR0OBBYEFHqqdlMVBlcadf7iJLJoLnLZ7h4tMB8GA1Ud\nEQQYMBaCFGFsbGluc3NsLnphY2h5YW5nLmNuMD4GA1UdIAQ3MDUwMwYGZ4EMAQIB\nMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNV\nHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMHkGCCsG\nAQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t\nMEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vVHJ1c3RB\nc2lhRFZUTFNSU0FDQTIwMjUuY3J0MAwGA1UdEwEB/wQCMAAwggF9BgorBgEEAdZ5\nAgQCBIIBbQSCAWkBZwB2ABLxTjS9U3JMhAYZw48/ehP457Vih4icbTAFhOvlhiY6\nAAABll0w/o0AAAQDAEcwRQIgd24jCPm+fbHq3grMIxtvQhzkv7dvYPM/BGjPEsy1\nQ70CIQC5jXADjBh+dH50T+atn3lktBEqQhedOl6cAaP/XXmk6gB2AO08S9boBsKk\nogBX28sk4jgB31Ev7cSGxXAPIN23Pj/gAAABll0w/rUAAAQDAEcwRQIgU2GDVEH1\ns5i/RC1RhqvJjn72PAZOlDtJyLdg29vC9HECIQCj78GATYK5quitLxbn3HvD8BeT\noOz+3tacgyN6+TdvugB1AKRCxQZJYGFUjw/U6pz7ei0mRU2HqX8v30VZ9idPOoRU\nAAABll0w/sYAAAQDAEYwRAIgCvU/iBRPKoJLjmU4edBYObWAO/aJp2mWnfJ4ieAr\nrXsCIBsAppYu28h8YEOl0N9yEeF9G05IMxwkCjZKonQs2SKMMA0GCSqGSIb3DQEB\nCwUAA4ICAQB3wFou51Qvl4apMhencuQUnWF3UpYP49e0WQ72DVT3pYjYsozkSuqb\nQZcwMB6HDoHdFicxvQ/yxKyTu/nw3rXjUWYuSxXYd7lJcQ/R0tR00m6AFeinY4Aq\nq4QqoA+lriK1XqO5MomAL4FbSysT1ow/gaG9pYuXEdT4pr05I/NumjXdkwBRZOd4\nrhol2grKf3y37Qla5hUbbG3ab9nf/csJSWkCoESeXr3MB1oAU/aL9pGSagvMXSKQ\nsFs2cn2Fi8ZmJPJXIP114lgvFuFDO+C1yTNbHap/FufvAKGryfPDuPecCF6FSXej\n+bwg4/BNz5lcHbNo2XXjLgoPg4VE6mG/SQQZQEDBk5DowwMVMvh77t9RBNrHozah\nHGtQz2hCuIX7rZQYnSlvW8T75FhI/Sd+HEfU/iyTIELXBUjypnK2bOJL7+jE7f79\nuljhXlCcP52fGHCjexNBz5gIZr82KVxsfxKuZjfioPkhmWleVNMdMWYJRXu618E6\nNtNjUVsDCuMOOMNs1qScqxOT60MeDZLX+vnC93fdd/t2hLEAWWNNMkWeX2qLCE1q\nGarop9U1mJpiBWkW5cBiqnNIbhuV2fcwFIR8mVT5f1Qcw+WxE2nEjY2h75bKv8T5\n3RBngmaX8PcyLAP2s0/4UyzAnMYfioJBh37VpUYBrdriBkRds/AMZw==\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nMIIFnjCCBIagAwIBAgIQCSYyO0lk42hGFRLe8aXVLDANBgkqhkiG9w0BAQsFADBh\nMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\nd3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH\nMjAeFw0yNTAxMDgwMDAwMDBaFw0zNTAxMDcyMzU5NTlaMFsxCzAJBgNVBAYTAkNO\nMSUwIwYDVQQKExxUcnVzdEFzaWEgVGVjaG5vbG9naWVzLCBJbmMuMSUwIwYDVQQD\nExxUcnVzdEFzaWEgRFYgVExTIFJTQSBDQSAyMDI1MIICIjANBgkqhkiG9w0BAQEF\nAAOCAg8AMIICCgKCAgEA0fuEmuBIsN6ZZVq+gRobMorOGIilTCIfQrxNpR8FUZ9R\n/GfbiekbiIKphQXEZ7N1uBnn6tXUuZ32zl6jPkZpHzN/Bmgk1BWSIzVc0npMzrWq\n/hrbk5+KddXJdsNpeG1+Q8lc8uVMBrztnxaPb7Rh7yQCsMrcO4hgVaqLJWkVvEfW\nULtoCHQnNaj4IroG6VxQf1oArQ8bPbwpI02lieSahRa78FQuXdoGVeQcrkhtVjZs\nON98vq5fPWZX2LFv7e5J6P9IHbzvOl8yyQjv+2/IOwhNSkaXX3bI+//bqF9XW/p7\n+gsUmHiK5YsvLjmXcvDmoDEGrXMzgX31Zl2nJ+umpRbLjwP8rxYIUsKoEwEdFoto\nAid59UEBJyw/GibwXQ5xTyKD/N6C8SFkr1+myOo4oe1UB+YgvRu6qSxIABo5kYdX\nFodLP4IgoVJdeUFs1Usa6bxYEO6EgMf5lCWt9hGZszvXYZwvyZGq3ogNXM7eKyi2\n20WzJXYMmi9TYFq2Fa95aZe4wki6YhDhhOO1g0sjITGVaB73G+JOCI9yJhv6+REN\nD40ZpboUHE8JNgMVWbG1isAMVCXqiADgXtuC+tmJWPEH9cR6OuJLEpwOzPfgAbnn\n2MRu7Tsdr8jPjTPbD0FxblX1ydW3RG30vwLF5lkTTRkHG9epMgpPMdYP7nY/08MC\nAwEAAaOCAVYwggFSMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLQSKKW0\nwB2fKXFpPNkRlkp1aVDAMB8GA1UdIwQYMBaAFE4iVCAYlebjbuYP+vq5Eu0GF485\nMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw\ndgYIKwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy\ndC5jb20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E\naWdpQ2VydEdsb2JhbFJvb3RHMi5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDov\nL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDARBgNV\nHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQELBQADggEBAJ4a3svh316GY2+Z7EYx\nmBIsOwjJSnyoEfzx2T699ctLLrvuzS79Mg3pPjxSLlUgyM8UzrFc5tgVU3dZ1sFQ\nI4RM+ysJdvIAX/7Yx1QbooVdKhkdi9X7QN7yVkjqwM3fY3WfQkRTzhIkM7mYIQbR\nr+y2Vkju61BLqh7OCRpPMiudjEpP1kEtRyGs2g0aQpEIqKBzxgitCXSayO1hoO6/\n71ts801OzYlqYW9OQQQ2GCJyFbD6XHDjdpn+bWUxTKWaMY0qedSCbHE3Kl2QEF0C\nynZ7SbC03yR+gKZQDeTXrNP1kk5Qhe7jSXgw+nhbspe0q/M1ZcNCz+sPxeOwdCcC\ngJE=\n-----END CERTIFICATE-----",
|
||||
"issuer": "cert-issuer",
|
||||
},
|
||||
}
|
||||
err := DeploySSH(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("DeploySSH failed: %v", err)
|
||||
}
|
||||
// println(err.Error())
|
||||
}
|
||||
134
backend/internal/cert/deploy/tencentcloud.go
Normal file
134
backend/internal/cert/deploy/tencentcloud.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package deploy
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/access"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
ssl "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/ssl/v20191205"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func ClientTencentcloud(SecretId, SecretKey, region string) *ssl.Client {
|
||||
credential := common.NewCredential(
|
||||
SecretId,
|
||||
SecretKey,
|
||||
)
|
||||
// 实例化一个client选项,可选的,没有特殊需求可以跳过
|
||||
cpf := profile.NewClientProfile()
|
||||
cpf.HttpProfile.Endpoint = "ssl.tencentcloudapi.com"
|
||||
// 实例化要请求产品的client对象,clientProfile是可选的
|
||||
client, _ := ssl.NewClient(credential, region, cpf)
|
||||
return client
|
||||
}
|
||||
|
||||
func UploadToTX(client *ssl.Client, key, cert string) (string, error) {
|
||||
request := ssl.NewUploadCertificateRequest()
|
||||
request.CertificatePublicKey = common.StringPtr(cert)
|
||||
request.CertificatePrivateKey = common.StringPtr(key)
|
||||
// 返回的resp是一个UploadCertificateResponse的实例,与请求对象对应
|
||||
response, err := client.UploadCertificate(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
return "", err
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return *response.Response.CertificateId, nil
|
||||
}
|
||||
|
||||
func DeployToTX(cfg map[string]any) error {
|
||||
cert, ok := cfg["certificate"].(map[string]any)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书不存在")
|
||||
}
|
||||
keyPem, ok := cert["key"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:key")
|
||||
}
|
||||
certPem, ok := cert["cert"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("证书错误:cert")
|
||||
}
|
||||
|
||||
var providerID string
|
||||
switch v := cfg["provider_id"].(type) {
|
||||
case float64:
|
||||
providerID = strconv.Itoa(int(v))
|
||||
case string:
|
||||
providerID = v
|
||||
default:
|
||||
return fmt.Errorf("参数错误:provider_id")
|
||||
}
|
||||
//
|
||||
providerData, err := access.GetAccess(providerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
providerConfigStr, ok := providerData["config"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("api配置错误")
|
||||
}
|
||||
// 解析 JSON 配置
|
||||
var providerConfig map[string]string
|
||||
err = json.Unmarshal([]byte(providerConfigStr), &providerConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
region := ""
|
||||
if r, ok := cfg["region"].(string); ok {
|
||||
region = r
|
||||
}
|
||||
client := ClientTencentcloud(providerConfig["secret_id"], providerConfig["secret_key"], region)
|
||||
|
||||
// 上传证书
|
||||
certificateId, err := UploadToTX(client, strings.TrimSpace(keyPem), strings.TrimSpace(certPem))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// fmt.Println(certificateId)
|
||||
|
||||
request := ssl.NewDeployCertificateInstanceRequest()
|
||||
|
||||
request.CertificateId = common.StringPtr(certificateId)
|
||||
if cfg["resource_type"] == "cdn" {
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
request.InstanceIdList = common.StringPtrs([]string{domain})
|
||||
request.ResourceType = common.StringPtr("cdn")
|
||||
}
|
||||
if cfg["resource_type"] == "cos" {
|
||||
// fmt.Println(fmt.Sprintf("%s|%s|%s", cfg["region"].(string), cfg["bucket"].(string), cfg["domain"].(string)))
|
||||
domain, ok := cfg["domain"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:domain")
|
||||
}
|
||||
region, ok := cfg["region"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:region")
|
||||
}
|
||||
bucket, ok := cfg["domain"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("参数错误:bucket")
|
||||
}
|
||||
request.InstanceIdList = common.StringPtrs([]string{fmt.Sprintf("%s|%s|%s", region, bucket, domain)})
|
||||
// request.InstanceIdList = common.StringPtrs([]string{"ap-guangzhou#allinssl-1253163109#allinssl.zachyang.cn"})
|
||||
request.ResourceType = common.StringPtr("cos")
|
||||
}
|
||||
|
||||
// 返回的resp是一个DeployCertificateInstanceResponse的实例,与请求对象对应
|
||||
response, err := client.DeployCertificateInstance(request)
|
||||
if _, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(response.Response.DeployRecordId)
|
||||
return nil
|
||||
}
|
||||
163
backend/internal/overview/overview.go
Normal file
163
backend/internal/overview/overview.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package overview
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/cert"
|
||||
"ALLinSSL/backend/internal/workflow"
|
||||
"ALLinSSL/backend/public"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetWorkflowCount() (map[string]any, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
defer s.Close()
|
||||
workflow, err := s.Query(`select count(*) as count,
|
||||
count(case when active=1 then 1 end ) as active,
|
||||
count(case when last_run_status='fail' then 1 end ) as failure
|
||||
from workflow
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(workflow) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return workflow[0], err
|
||||
}
|
||||
|
||||
func GetCertCount() (map[string]int, error) {
|
||||
s, err := cert.GetSqlite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
result := map[string]int{
|
||||
"count": len(data),
|
||||
"will": 0,
|
||||
"end": 0,
|
||||
}
|
||||
for _, v := range data {
|
||||
endTimeStr, ok := v["end_time"].(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
endTime, err := time.Parse("2006-01-02 15:04:05", endTimeStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if endTime.Before(time.Now()) {
|
||||
result["end"]++
|
||||
} else {
|
||||
if endTime.Sub(time.Now()).Hours() < 24*30 {
|
||||
result["will"]++
|
||||
}
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetSiteMonitorCount() (map[string]any, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
defer s.Close()
|
||||
cert, err := s.Query(`select count(*) as count,
|
||||
count(case when state='异常' then 1 end ) as exception
|
||||
from site_monitor`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(cert) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return cert[0], nil
|
||||
}
|
||||
|
||||
func GetWorkflowHistory() ([]map[string]any, error) {
|
||||
s, err := workflow.GetSqliteObjWH()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Limit([]int64{0, 3}).Order("create_time", "desc").Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.TableName = "workflow"
|
||||
var result []map[string]any
|
||||
for _, v := range data {
|
||||
var (
|
||||
mode string
|
||||
name string
|
||||
state int
|
||||
)
|
||||
switch v["status"] {
|
||||
case "success":
|
||||
state = 1
|
||||
case "fail":
|
||||
state = -1
|
||||
case "running":
|
||||
state = 0
|
||||
}
|
||||
switch v["exec_type"] {
|
||||
case "manual":
|
||||
mode = "手动触发"
|
||||
case "auto":
|
||||
mode = "定时触发"
|
||||
}
|
||||
wk, err := s.Where("id=?", []interface{}{v["workflow_id"]}).Select()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if len(wk) > 0 {
|
||||
name = wk[0]["name"].(string)
|
||||
} else {
|
||||
name = "未知"
|
||||
}
|
||||
|
||||
result = append(result, map[string]any{
|
||||
"name": name,
|
||||
"state": state,
|
||||
"mode": mode,
|
||||
"exec_time": v["create_time"],
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetOverviewData() (map[string]any, error) {
|
||||
workflowCount, err := GetWorkflowCount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certCount, err := GetCertCount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
siteMonitorCount, err := GetSiteMonitorCount()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workflowHistory, err := GetWorkflowHistory()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make(map[string]any)
|
||||
result["workflow"] = workflowCount
|
||||
result["cert"] = certCount
|
||||
result["site_monitor"] = siteMonitorCount
|
||||
result["workflow_history"] = workflowHistory
|
||||
return result, nil
|
||||
}
|
||||
189
backend/internal/report/report.go
Normal file
189
backend/internal/report/report.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/jordan-wright/email"
|
||||
"net/smtp"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetSqlite() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "report"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func GetList(search string, p, limit int64) ([]map[string]any, int, error) {
|
||||
var data []map[string]any
|
||||
var count int64
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
var limits []int64
|
||||
if p >= 0 && limit >= 0 {
|
||||
limits = []int64{0, limit}
|
||||
if p > 1 {
|
||||
limits[0] = (p - 1) * limit
|
||||
limits[1] = p * limit
|
||||
}
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
count, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Count()
|
||||
data, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Limit(limits).Order("update_time", "desc").Select()
|
||||
} else {
|
||||
count, err = s.Count()
|
||||
data, err = s.Order("update_time", "desc").Limit(limits).Select()
|
||||
}
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
return data, int(count), nil
|
||||
}
|
||||
|
||||
func GetReport(id string) (map[string]any, error) {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Where("id=?", []interface{}{id}).Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("没有找到此通知配置")
|
||||
}
|
||||
return data[0], nil
|
||||
|
||||
}
|
||||
|
||||
func AddReport(Type, config, name string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = s.Insert(map[string]interface{}{
|
||||
"name": name,
|
||||
"type": Type,
|
||||
"config": config,
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdReport(id, config, name string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
_, err = s.Where("id=?", []interface{}{id}).Update(map[string]interface{}{
|
||||
"name": name,
|
||||
"config": config,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func DelReport(id string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
_, err = s.Where("id=?", []interface{}{id}).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
func NotifyTest(id string) error {
|
||||
if id == "" {
|
||||
return fmt.Errorf("缺少参数")
|
||||
}
|
||||
providerData, err := GetReport(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params := map[string]any{
|
||||
"provider_id": id,
|
||||
"body": "测试消息通道",
|
||||
"subject": "测试消息通道",
|
||||
}
|
||||
switch providerData["type"] {
|
||||
case "mail":
|
||||
err = NotifyMail(params)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func Notify(params map[string]any) error {
|
||||
if params == nil {
|
||||
return fmt.Errorf("缺少参数")
|
||||
}
|
||||
providerName, ok := params["provider"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("通知类型错误")
|
||||
}
|
||||
switch providerName {
|
||||
case "mail":
|
||||
return NotifyMail(params)
|
||||
// case "btpanel-site":
|
||||
// return NotifyBt(params)
|
||||
default:
|
||||
return fmt.Errorf("不支持的通知类型")
|
||||
}
|
||||
}
|
||||
|
||||
func NotifyMail(params map[string]any) error {
|
||||
|
||||
if params == nil {
|
||||
return fmt.Errorf("缺少参数")
|
||||
}
|
||||
providerID := params["provider_id"].(string)
|
||||
// fmt.Println(providerID)
|
||||
providerData, err := GetReport(providerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configStr := providerData["config"].(string)
|
||||
var config map[string]string
|
||||
err = json.Unmarshal([]byte(configStr), &config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析配置失败: %v", err)
|
||||
}
|
||||
|
||||
e := email.NewEmail()
|
||||
e.From = config["sender"]
|
||||
e.To = []string{config["receiver"]}
|
||||
e.Subject = params["subject"].(string)
|
||||
|
||||
e.Text = []byte(params["body"].(string))
|
||||
|
||||
addr := fmt.Sprintf("%s:%s", config["smtpHost"], config["smtpPort"])
|
||||
|
||||
auth := smtp.PlainAuth("", config["sender"], config["password"], config["smtpHost"])
|
||||
|
||||
// 使用 SSL(通常是 465)
|
||||
if config["smtpPort"] == "465" {
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true, // 开发阶段跳过证书验证,生产建议关闭
|
||||
ServerName: config["smtpHost"],
|
||||
}
|
||||
return e.SendWithTLS(addr, auth, tlsConfig)
|
||||
}
|
||||
|
||||
// 普通明文发送(25端口,非推荐)
|
||||
return e.Send(addr, auth)
|
||||
}
|
||||
17
backend/internal/report/report_test.go
Normal file
17
backend/internal/report/report_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMail(t *testing.T) {
|
||||
config := map[string]any{
|
||||
"provider": "mail",
|
||||
"provider_id": "4",
|
||||
"subject": "执行结束",
|
||||
"body": "执行结束",
|
||||
}
|
||||
err := NotifyMail(config)
|
||||
fmt.Println(err)
|
||||
}
|
||||
182
backend/internal/setting/setting.go
Normal file
182
backend/internal/setting/setting.go
Normal file
@@ -0,0 +1,182 @@
|
||||
package setting
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/joho/godotenv"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
Timeout int `json:"timeout" form:"timeout"`
|
||||
Secure string `json:"secure" form:"secure"`
|
||||
Https string `json:"https" form:"https"`
|
||||
Key string `json:"key" form:"key"`
|
||||
Cert string `json:"cert" form:"cert"`
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
}
|
||||
|
||||
func Get() (Setting, error) {
|
||||
var setting = Setting{
|
||||
Timeout: public.TimeOut,
|
||||
Secure: public.Secure,
|
||||
}
|
||||
|
||||
setting.Https = public.GetSettingIgnoreError("https")
|
||||
key, err := os.ReadFile("data/https/key.pem")
|
||||
if err != nil {
|
||||
key = []byte{}
|
||||
}
|
||||
cert, err := os.ReadFile("data/https/cert.pem")
|
||||
if err != nil {
|
||||
cert = []byte{}
|
||||
}
|
||||
setting.Key = string(key)
|
||||
setting.Cert = string(cert)
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return setting, err
|
||||
}
|
||||
defer s.Close()
|
||||
s.TableName = "users"
|
||||
data, err := s.Select()
|
||||
if err != nil {
|
||||
return setting, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return setting, fmt.Errorf("no users found")
|
||||
}
|
||||
username := data[0]["username"].(string)
|
||||
setting.Username = username
|
||||
return setting, nil
|
||||
}
|
||||
|
||||
func Save(setting *Setting) error {
|
||||
var restart bool
|
||||
var reload bool
|
||||
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
if setting.Username != "" || setting.Password != "" {
|
||||
s.TableName = "users"
|
||||
user, err := s.Where("id=1", []interface{}{}).Select()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(user) == 0 {
|
||||
return fmt.Errorf("no users found")
|
||||
}
|
||||
data := map[string]interface{}{}
|
||||
if setting.Username != "" {
|
||||
data["username"] = setting.Username
|
||||
}
|
||||
|
||||
salt := user[0]["salt"].(string)
|
||||
passwd := setting.Password + salt
|
||||
// fmt.Println(passwd)
|
||||
keyMd5 := md5.Sum([]byte(passwd))
|
||||
passwdMd5 := hex.EncodeToString(keyMd5[:])
|
||||
if setting.Password != "" {
|
||||
data["password"] = passwdMd5
|
||||
}
|
||||
_, err = s.Where("id=1", []interface{}{}).Update(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reload = true
|
||||
}
|
||||
s.TableName = "settings"
|
||||
if setting.Timeout != 0 {
|
||||
s.Where("key = 'timeout'", []interface{}{}).Update(map[string]interface{}{"value": setting.Timeout})
|
||||
public.TimeOut = setting.Timeout
|
||||
}
|
||||
if setting.Secure != "" {
|
||||
s.Where("key = 'secure'", []interface{}{}).Update(map[string]interface{}{"value": setting.Secure})
|
||||
public.TimeOut = setting.Timeout
|
||||
}
|
||||
if setting.Https == "1" {
|
||||
if setting.Key == "" || setting.Cert == "" {
|
||||
return fmt.Errorf("key or cert is empty")
|
||||
}
|
||||
// fmt.Println(setting.Key, setting.Cert)
|
||||
err := public.ValidateSSLCertificate(setting.Cert, setting.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Where("key = 'https'", []interface{}{}).Update(map[string]interface{}{"value": setting.Https})
|
||||
// dir := filepath.Dir("data/https")
|
||||
if err := os.MkdirAll("data/https", os.ModePerm); err != nil {
|
||||
panic("创建目录失败: " + err.Error())
|
||||
}
|
||||
err = os.WriteFile("data/https/key.pem", []byte(setting.Key), 0644)
|
||||
// fmt.Println(err)
|
||||
os.WriteFile("data/https/cert.pem", []byte(setting.Cert), 0644)
|
||||
restart = true
|
||||
}
|
||||
|
||||
if restart {
|
||||
Restart()
|
||||
return nil
|
||||
} else {
|
||||
if reload {
|
||||
s.Where("key = 'login_key'", []interface{}{}).Update(map[string]interface{}{"value": public.GenerateUUID()})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Shutdown() {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
public.ShutdownFunc()
|
||||
}()
|
||||
return
|
||||
}
|
||||
|
||||
func Restart() {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
env, err := godotenv.Read("data/.env")
|
||||
if err != nil {
|
||||
env = map[string]string{
|
||||
"web": "restart",
|
||||
"scheduler": "start",
|
||||
}
|
||||
}
|
||||
pidStr, err := os.ReadFile("data/pid")
|
||||
if err != nil {
|
||||
fmt.Println("Error reading pid file")
|
||||
return
|
||||
}
|
||||
err = godotenv.Write(env, "data/.env")
|
||||
if err != nil {
|
||||
fmt.Println("Error writing to .env file")
|
||||
return
|
||||
}
|
||||
pid, err := strconv.Atoi(string(pidStr))
|
||||
if err != nil {
|
||||
fmt.Println("Error converting pid to int:", err)
|
||||
return
|
||||
}
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
fmt.Println("Error finding process:", err)
|
||||
return
|
||||
}
|
||||
err = process.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
fmt.Println("Error sending signal:", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
return
|
||||
}
|
||||
249
backend/internal/siteMonitor/monitor.go
Normal file
249
backend/internal/siteMonitor/monitor.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package siteMonitor
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SSLInfo 定义结果结构体
|
||||
type SSLInfo struct {
|
||||
Target string
|
||||
HTTPStatus int
|
||||
HTTPStatusText string
|
||||
Domains []string
|
||||
Issuer string
|
||||
NotBefore string
|
||||
NotAfter string
|
||||
DaysRemaining int
|
||||
CertificateOK bool
|
||||
CertificateNote string
|
||||
}
|
||||
|
||||
func GetSqlite() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "site_monitor"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func GetList(search string, p, limit int64) ([]map[string]any, int, error) {
|
||||
var data []map[string]any
|
||||
var count int64
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
var limits []int64
|
||||
if p >= 0 && limit >= 0 {
|
||||
limits = []int64{0, limit}
|
||||
if p > 1 {
|
||||
limits[0] = (p - 1) * limit
|
||||
limits[1] = p * limit
|
||||
}
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
count, err = s.Where("name like ? or site_domain like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Count()
|
||||
data, err = s.Where("name like ? or site_domain like ?", []interface{}{"%" + search + "%", "%" + search + "%"}).Order("update_time", "desc").Limit(limits).Select()
|
||||
} else {
|
||||
count, err = s.Count()
|
||||
data, err = s.Order("update_time", "desc").Limit(limits).Select()
|
||||
}
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
for _, v := range data {
|
||||
v["domain"] = v["site_domain"]
|
||||
}
|
||||
|
||||
return data, int(count), nil
|
||||
}
|
||||
|
||||
func AddMonitor(name, domain, reportType string, cycle int) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
info, err := CheckWebsite(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.Insert(map[string]any{
|
||||
"name": name,
|
||||
"site_domain": domain,
|
||||
"report_type": reportType,
|
||||
"cycle": cycle,
|
||||
"state": info.HTTPStatusText,
|
||||
"ca": info.Issuer,
|
||||
"cert_domain": strings.Join(info.Domains, ","),
|
||||
"end_time": info.NotAfter,
|
||||
"end_day": info.DaysRemaining,
|
||||
"create_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"update_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"last_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"active": 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdMonitor(id, name, domain, reportType string, cycle int) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
info, err := CheckWebsite(domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.Where("id=?", []interface{}{id}).Update(map[string]any{
|
||||
"name": name,
|
||||
"site_domain": domain,
|
||||
"report_type": reportType,
|
||||
"cycle": cycle,
|
||||
"state": info.HTTPStatusText,
|
||||
"ca": info.Issuer,
|
||||
"cert_domain": strings.Join(info.Domains, ","),
|
||||
"end_time": info.NotAfter,
|
||||
"end_day": info.DaysRemaining,
|
||||
"update_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"last_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
"active": 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DelMonitor(id string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
_, err = s.Where("id=?", []interface{}{id}).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetMonitor(id string, active int) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
_, err = s.Where("id=?", []interface{}{id}).Update(map[string]any{
|
||||
"active": active,
|
||||
"update_time": time.Now().Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdInfo(id, domain string, s *public.Sqlite, reportType string) error {
|
||||
info, errCheck := CheckWebsite(domain)
|
||||
now := time.Now()
|
||||
updateData := map[string]any{
|
||||
"state": info.HTTPStatusText,
|
||||
"ca": info.Issuer,
|
||||
"cert_domain": strings.Join(info.Domains, ","),
|
||||
"end_time": info.NotAfter,
|
||||
"end_day": info.DaysRemaining,
|
||||
"last_time": now.Format("2006-01-02 15:04:05"),
|
||||
"except_end_time": now.Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
if errCheck != nil {
|
||||
updateData["state"] = "异常"
|
||||
// return err
|
||||
} else {
|
||||
if info.HTTPStatus != 0 && info.CertificateOK != false {
|
||||
delete(updateData, "except_end_time")
|
||||
} else {
|
||||
errCheck = fmt.Errorf("证书异常")
|
||||
}
|
||||
}
|
||||
_, err := s.Where("id=?", []interface{}{id}).Update(updateData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return errCheck
|
||||
}
|
||||
|
||||
// CheckWebsite 实际检测函数
|
||||
func CheckWebsite(target string) (*SSLInfo, error) {
|
||||
result := &SSLInfo{Target: target}
|
||||
|
||||
// 验证格式是否是 IP 或域名
|
||||
if net.ParseIP(target) == nil {
|
||||
if _, err := net.LookupHost(target); err != nil {
|
||||
return result, fmt.Errorf("无效域名或 IP:%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
hostPort := net.JoinHostPort(target, "443")
|
||||
|
||||
// result := &SSLInfo{Target: target}
|
||||
|
||||
// 1. TLS 连接(先做,否则无 HTTPS 支持直接失败)
|
||||
conn, err := tls.Dial("tcp", hostPort, &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
if err != nil {
|
||||
return result, fmt.Errorf("目标不支持 HTTPS:%v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// 发送 HTTPS 请求检测状态
|
||||
resp, err := http.Get("https://" + target)
|
||||
if err != nil {
|
||||
result.HTTPStatus = 0
|
||||
result.HTTPStatusText = "异常"
|
||||
} else {
|
||||
result.HTTPStatus = resp.StatusCode
|
||||
result.HTTPStatusText = "正常"
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
// 获取证书
|
||||
cert := conn.ConnectionState().PeerCertificates[0]
|
||||
result.Domains = cert.DNSNames
|
||||
result.Issuer = cert.Issuer.CommonName
|
||||
result.NotBefore = cert.NotBefore.Format("2006-01-02 15:04:05")
|
||||
result.NotAfter = cert.NotAfter.Format("2006-01-02 15:04:05")
|
||||
result.DaysRemaining = int(cert.NotAfter.Sub(time.Now()).Hours() / 24)
|
||||
|
||||
now := time.Now()
|
||||
switch {
|
||||
case now.Before(cert.NotBefore):
|
||||
result.CertificateOK = false
|
||||
result.CertificateNote = "尚未生效"
|
||||
case now.After(cert.NotAfter):
|
||||
result.CertificateOK = false
|
||||
result.CertificateNote = "已过期"
|
||||
default:
|
||||
result.CertificateOK = true
|
||||
result.CertificateNote = "有效"
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
21
backend/internal/siteMonitor/monitor_test.go
Normal file
21
backend/internal/siteMonitor/monitor_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package siteMonitor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test(t *testing.T) {
|
||||
site := "bt.cn" // 只传域名或 IP,不要 http://
|
||||
result, err := CheckWebsite(site)
|
||||
if err != nil {
|
||||
fmt.Printf("❌ 检测失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Println(result.HTTPStatusText)
|
||||
fmt.Println(result.Domains)
|
||||
fmt.Println(result.Issuer)
|
||||
fmt.Println(result.NotAfter)
|
||||
// fmt.Println(result.Domains)
|
||||
// fmt.Println(result.Domains)
|
||||
}
|
||||
33
backend/internal/workflow/context.go
Normal file
33
backend/internal/workflow/context.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package workflow
|
||||
|
||||
import "ALLinSSL/backend/public"
|
||||
|
||||
func NewExecutionContext(RunID string) *ExecutionContext {
|
||||
Logger, _ := public.NewLogger(public.GetSettingIgnoreError("workflow_log_path") + RunID + ".log")
|
||||
return &ExecutionContext{
|
||||
Data: make(map[string]any),
|
||||
Status: make(map[string]ExecutionStatus),
|
||||
RunID: RunID,
|
||||
Logger: Logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *ExecutionContext) SetOutput(nodeID string, output any, status ExecutionStatus) {
|
||||
ctx.mu.Lock()
|
||||
defer ctx.mu.Unlock()
|
||||
ctx.Data[nodeID] = output
|
||||
ctx.Status[nodeID] = status
|
||||
}
|
||||
|
||||
func (ctx *ExecutionContext) GetOutput(nodeID string) (any, bool) {
|
||||
ctx.mu.RLock()
|
||||
defer ctx.mu.RUnlock()
|
||||
out, ok := ctx.Data[nodeID]
|
||||
return out, ok
|
||||
}
|
||||
|
||||
func (ctx *ExecutionContext) GetStatus(nodeID string) ExecutionStatus {
|
||||
ctx.mu.RLock()
|
||||
defer ctx.mu.RUnlock()
|
||||
return ctx.Status[nodeID]
|
||||
}
|
||||
107
backend/internal/workflow/executor.go
Normal file
107
backend/internal/workflow/executor.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/cert"
|
||||
certApply "ALLinSSL/backend/internal/cert/apply"
|
||||
certDeploy "ALLinSSL/backend/internal/cert/deploy"
|
||||
"ALLinSSL/backend/internal/report"
|
||||
"ALLinSSL/backend/public"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// var executors map[string]func(map[string]any) (any, error)
|
||||
//
|
||||
// func RegistExector(executorName string, executor func(map[string]any) (any, error)) {
|
||||
// executors[executorName] = executor
|
||||
// }
|
||||
|
||||
func Executors(exec string, params map[string]any) (any, error) {
|
||||
switch exec {
|
||||
case "apply":
|
||||
return apply(params)
|
||||
case "deploy":
|
||||
return deploy(params)
|
||||
case "upload":
|
||||
return upload(params)
|
||||
case "notify":
|
||||
return notify(params)
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func apply(params map[string]any) (any, error) {
|
||||
logger := params["logger"].(*public.Logger)
|
||||
|
||||
logger.Info("=============申请证书=============")
|
||||
certificate, err := certApply.Apply(params, logger)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
logger.Info("=============申请失败=============")
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("=============申请成功=============")
|
||||
return certificate, nil
|
||||
}
|
||||
|
||||
func deploy(params map[string]any) (any, error) {
|
||||
logger := params["logger"].(*public.Logger)
|
||||
logger.Info("=============部署证书=============")
|
||||
certificate := params["certificate"]
|
||||
if certificate == nil {
|
||||
logger.Error("证书不存在")
|
||||
logger.Info("=============部署失败=============")
|
||||
return nil, errors.New("证书不存在")
|
||||
}
|
||||
err := certDeploy.Deploy(params, logger)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
logger.Info("=============部署失败=============")
|
||||
} else {
|
||||
logger.Info("=============部署成功=============")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func upload(params map[string]any) (any, error) {
|
||||
logger := params["logger"].(*public.Logger)
|
||||
logger.Info("=============上传证书=============")
|
||||
|
||||
keyStr, ok := params["key"].(string)
|
||||
if !ok {
|
||||
logger.Error("上传的密钥有误")
|
||||
logger.Info("=============上传失败=============")
|
||||
return nil, errors.New("上传的密钥有误")
|
||||
}
|
||||
certStr, ok := params["cert"].(string)
|
||||
if !ok {
|
||||
logger.Error("上传的证书有误")
|
||||
logger.Info("=============上传失败=============")
|
||||
return nil, errors.New("上传的证书有误")
|
||||
}
|
||||
err := cert.UploadCert(keyStr, certStr)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
logger.Info("=============上传失败=============")
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("=============上传成功=============")
|
||||
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func notify(params map[string]any) (any, error) {
|
||||
// fmt.Println("通知:", params)
|
||||
logger := params["logger"].(*public.Logger)
|
||||
logger.Info("=============发送通知=============")
|
||||
logger.Debug(fmt.Sprintf("发送通知:%s", params["subject"].(string)))
|
||||
err := report.Notify(params)
|
||||
if err != nil {
|
||||
logger.Error(err.Error())
|
||||
logger.Info("=============发送失败=============")
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("=============发送成功=============")
|
||||
return fmt.Sprintf("通知到: %s", params["message"]), nil
|
||||
}
|
||||
49
backend/internal/workflow/models.go
Normal file
49
backend/internal/workflow/models.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ExecutionStatus string
|
||||
|
||||
const (
|
||||
StatusSuccess ExecutionStatus = "success"
|
||||
StatusFailed ExecutionStatus = "fail"
|
||||
)
|
||||
|
||||
type WorkflowNodeParams struct {
|
||||
Name string `json:"name"`
|
||||
FromNodeID string `json:"fromNodeId,omitempty"`
|
||||
}
|
||||
|
||||
type WorkflowNode struct {
|
||||
Id string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
|
||||
Config map[string]any `json:"config"`
|
||||
Inputs []WorkflowNodeParams `json:"inputs"`
|
||||
// Outputs []WorkflowNodeParams `json:"outputs"`
|
||||
|
||||
ChildNode *WorkflowNode `json:"childNode,omitempty"`
|
||||
ConditionNodes []*WorkflowNode `json:"conditionNodes,omitempty"`
|
||||
|
||||
Validated bool `json:"validated"`
|
||||
}
|
||||
|
||||
type ExecutionContext struct {
|
||||
Data map[string]any
|
||||
Status map[string]ExecutionStatus
|
||||
mu sync.RWMutex
|
||||
RunID string
|
||||
Logger *public.Logger
|
||||
}
|
||||
|
||||
type ExecTime struct {
|
||||
Type string `json:"type"`
|
||||
Month int `json:"month,omitempty"`
|
||||
Week int `json:"week,omitempty"`
|
||||
Hour int `json:"hour"`
|
||||
Minute int `json:"minute"`
|
||||
}
|
||||
294
backend/internal/workflow/workflow.go
Normal file
294
backend/internal/workflow/workflow.go
Normal file
@@ -0,0 +1,294 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GetSqlite() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "workflow"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func GetList(search string, p, limit int64) ([]map[string]any, int, error) {
|
||||
var data []map[string]any
|
||||
var count int64
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
var limits []int64
|
||||
if p >= 0 && limit >= 0 {
|
||||
limits = []int64{0, limit}
|
||||
if p > 1 {
|
||||
limits[0] = (p - 1) * limit
|
||||
limits[1] = p * limit
|
||||
}
|
||||
}
|
||||
|
||||
if search != "" {
|
||||
count, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Count()
|
||||
data, err = s.Where("name like ?", []interface{}{"%" + search + "%"}).Order("update_time", "desc").Limit(limits).Select()
|
||||
} else {
|
||||
count, err = s.Count()
|
||||
data, err = s.Order("update_time", "desc").Limit(limits).Select()
|
||||
}
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
return data, int(count), nil
|
||||
}
|
||||
|
||||
func AddWorkflow(name, content, execType, active, execTime string) error {
|
||||
var node WorkflowNode
|
||||
err := json.Unmarshal([]byte(content), &node)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检测到工作流配置有问题:%v", err)
|
||||
}
|
||||
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = s.Insert(map[string]interface{}{
|
||||
"name": name,
|
||||
"content": content,
|
||||
"exec_type": execType,
|
||||
"active": active,
|
||||
"exec_time": execTime,
|
||||
"create_time": now,
|
||||
"update_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func DelWorkflow(id string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
_, err = s.Where("id=?", []interface{}{id}).Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdDb(id string, data map[string]any) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
data["update_time"] = time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = s.Where("id=?", []interface{}{id}).Update(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdWorkflow(id, name, content, execType, active, execTime string) error {
|
||||
var node WorkflowNode
|
||||
err := json.Unmarshal([]byte(content), &node)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检测到工作流配置有问题:%v", err)
|
||||
}
|
||||
err = UpdDb(id, map[string]interface{}{
|
||||
"name": name,
|
||||
"content": content,
|
||||
"exec_type": execType,
|
||||
"active": active,
|
||||
"exec_time": execTime,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdExecType(id, execType string) error {
|
||||
err := UpdDb(id, map[string]interface{}{
|
||||
"exec_type": execType,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdActive(id, active string) error {
|
||||
err := UpdDb(id, map[string]interface{}{
|
||||
"active": active,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExecuteWorkflow(id string) error {
|
||||
s, err := GetSqlite()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Where("id=?", []interface{}{id}).Select()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return fmt.Errorf("workflow not found")
|
||||
}
|
||||
if data[0]["last_run_status"] != nil && data[0]["last_run_status"].(string) == "running" {
|
||||
return fmt.Errorf("工作流正在执行中")
|
||||
}
|
||||
content := data[0]["content"].(string)
|
||||
|
||||
go func(id, c string) {
|
||||
// defer wg.Done()
|
||||
// WorkflowID := strconv.FormatInt(id, 10)
|
||||
RunID, err := AddWorkflowHistory(id, "manual")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx := NewExecutionContext(RunID)
|
||||
defer ctx.Logger.Close()
|
||||
err = RunWorkflow(c, ctx)
|
||||
if err != nil {
|
||||
fmt.Println("执行工作流失败:", err)
|
||||
SetWorkflowStatus(id, RunID, "fail")
|
||||
} else {
|
||||
SetWorkflowStatus(id, RunID, "success")
|
||||
}
|
||||
}(id, content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetWorkflowStatus(id, RunID, status string) {
|
||||
_ = UpdateWorkflowHistory(RunID, status)
|
||||
_ = UpdDb(id, map[string]interface{}{"last_run_status": status})
|
||||
}
|
||||
|
||||
func resolveInputs(inputs []WorkflowNodeParams, ctx *ExecutionContext) map[string]any {
|
||||
resolved := make(map[string]any)
|
||||
for _, input := range inputs {
|
||||
if input.FromNodeID != "" {
|
||||
if val, ok := ctx.GetOutput(input.FromNodeID); ok {
|
||||
switch strings.Split(strings.TrimPrefix(input.FromNodeID, "-"), "-")[0] {
|
||||
case "apply":
|
||||
input.Name = "certificate"
|
||||
case "upload":
|
||||
input.Name = "certificate"
|
||||
}
|
||||
resolved[input.Name] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
func RunNode(node *WorkflowNode, ctx *ExecutionContext) error {
|
||||
// 获取上下文
|
||||
inputs := resolveInputs(node.Inputs, ctx)
|
||||
// 组装参数
|
||||
if node.Config == nil {
|
||||
node.Config = make(map[string]any)
|
||||
}
|
||||
for k, v := range inputs {
|
||||
node.Config[k] = v
|
||||
}
|
||||
node.Config["_runId"] = ctx.RunID
|
||||
node.Config["logger"] = ctx.Logger
|
||||
|
||||
// 执行当前节点
|
||||
result, err := Executors(node.Type, node.Config)
|
||||
|
||||
var status ExecutionStatus
|
||||
if err != nil {
|
||||
status = StatusFailed
|
||||
if node.ChildNode == nil || node.ChildNode.Type != "execute_result_branch" {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
status = StatusSuccess
|
||||
}
|
||||
|
||||
ctx.SetOutput(node.Id, result, status)
|
||||
|
||||
// 普通的并行
|
||||
if node.Type == "branch" {
|
||||
if len(node.ConditionNodes) > 0 {
|
||||
var wg sync.WaitGroup
|
||||
errChan := make(chan error, len(node.ConditionNodes))
|
||||
for _, branch := range node.ConditionNodes {
|
||||
wg.Add(1)
|
||||
go func(node *WorkflowNode) {
|
||||
defer wg.Done()
|
||||
if err = RunNode(node, ctx); err != nil {
|
||||
errChan <- err
|
||||
}
|
||||
}(branch)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errChan)
|
||||
for err := range errChan {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 条件分支
|
||||
if node.Type == "execute_result_branch" {
|
||||
//
|
||||
if len(node.ConditionNodes) > 0 {
|
||||
lastStatus := ctx.GetStatus(node.Config["fromNodeId"].(string))
|
||||
for _, branch := range node.ConditionNodes {
|
||||
if branch.Config["type"] == string(lastStatus) {
|
||||
return RunNode(branch, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if node.ChildNode != nil {
|
||||
return RunNode(node.ChildNode, ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunWorkflow(content string, ctx *ExecutionContext) error {
|
||||
var node WorkflowNode
|
||||
err := json.Unmarshal([]byte(content), &node)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
ctx.Logger.Info("=============开始执行=============")
|
||||
err = RunNode(&node, ctx)
|
||||
// fmt.Println(err)
|
||||
if err != nil {
|
||||
ctx.Logger.Info("=============执行失败=============")
|
||||
return err
|
||||
}
|
||||
ctx.Logger.Info("=============执行完成=============")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
117
backend/internal/workflow/workflow_history.go
Normal file
117
backend/internal/workflow/workflow_history.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package workflow
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetSqliteObjWH 工作流执行历史记录表对象
|
||||
func GetSqliteObjWH() (*public.Sqlite, error) {
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
s.TableName = "workflow_history"
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetListWH 获取工作流执行历史记录列表
|
||||
func GetListWH(id string, p, limit int64) ([]map[string]any, int, error) {
|
||||
var data []map[string]any
|
||||
var count int64
|
||||
s, err := GetSqliteObjWH()
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
var limits []int64
|
||||
if p >= 0 && limit >= 0 {
|
||||
limits = []int64{0, limit}
|
||||
if p > 1 {
|
||||
limits[0] = (p - 1) * limit
|
||||
limits[1] = p * limit
|
||||
}
|
||||
}
|
||||
if id == "" {
|
||||
count, err = s.Count()
|
||||
data, err = s.Limit(limits).Order("create_time", "desc").Select()
|
||||
} else {
|
||||
count, err = s.Where("workflow_id=?", []interface{}{id}).Count()
|
||||
data, err = s.Where("workflow_id=?", []interface{}{id}).Limit(limits).Order("create_time", "desc").Select()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return data, 0, err
|
||||
}
|
||||
return data, int(count), nil
|
||||
}
|
||||
|
||||
// 添加工作流执行历史记录
|
||||
func AddWorkflowHistory(workflowID, execType string) (string, error) {
|
||||
s, err := GetSqliteObjWH()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer s.Close()
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
ID := public.GenerateUUID()
|
||||
_, err = s.Insert(map[string]interface{}{
|
||||
"id": ID,
|
||||
"workflow_id": workflowID,
|
||||
"status": "running",
|
||||
"exec_type": execType,
|
||||
"create_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
_ = UpdDb(workflowID, map[string]interface{}{"last_run_status": "running", "last_run_time": now})
|
||||
return ID, nil
|
||||
}
|
||||
|
||||
// 工作流执行结束
|
||||
func UpdateWorkflowHistory(id, status string) error {
|
||||
s, err := GetSqliteObjWH()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
now := time.Now().Format("2006-01-02 15:04:05")
|
||||
_, err = s.Where("id=?", []interface{}{id}).Update(map[string]interface{}{
|
||||
"status": status,
|
||||
"end_time": now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func StopWorkflow(id string) error {
|
||||
s, err := GetSqliteObjWH()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Where("id=?", []interface{}{id}).Select()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil
|
||||
}
|
||||
SetWorkflowStatus(data[0]["workflow_id"].(string), id, "fail")
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetExecLog(id string) (string, error) {
|
||||
log, err := os.ReadFile(filepath.Join(public.GetSettingIgnoreError("workflow_log_path"), id+".log"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(log), nil
|
||||
}
|
||||
121
backend/middleware/auth.go
Normal file
121
backend/middleware/auth.go
Normal file
@@ -0,0 +1,121 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Html404 = []byte(`<html>
|
||||
<head><title>404 Not Found</title></head>
|
||||
<body bgcolor="white">
|
||||
<center><h1>404 Not Found</h1></center>
|
||||
<hr><center>AllinSSL</center>
|
||||
</body>
|
||||
</html>`)
|
||||
|
||||
func SessionAuthMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
routePath := c.Request.URL.Path
|
||||
method := c.Request.Method
|
||||
paths := strings.Split(strings.TrimPrefix(routePath, "/"), "/")
|
||||
session := sessions.Default(c)
|
||||
now := time.Now()
|
||||
gob.Register(time.Time{})
|
||||
last := session.Get("lastRequestTime")
|
||||
var form struct {
|
||||
Skip string `form:"skip"`
|
||||
}
|
||||
err := c.Bind(&form)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
if form.Skip == "1" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if routePath == public.Secure && session.Get("secure") == nil {
|
||||
// 访问安全入口,设置 session
|
||||
session.Set("secure", true)
|
||||
session.Set("lastRequestTime", now)
|
||||
// 一定要保存 session BEFORE redirect
|
||||
session.Save()
|
||||
// 返回登录页
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
// c.Abort()
|
||||
return
|
||||
} else {
|
||||
if session.Get("secure") == nil || last == nil {
|
||||
c.Data(404, "text/html; charset=utf-8", Html404)
|
||||
c.Abort()
|
||||
return
|
||||
} else {
|
||||
if lastTime, ok := last.(time.Time); ok {
|
||||
if now.Sub(lastTime) >= time.Second*time.Duration(public.TimeOut) {
|
||||
if session.Get("login") == nil {
|
||||
session.Clear()
|
||||
session.Save()
|
||||
c.Data(404, "text/html; charset=utf-8", Html404)
|
||||
c.Abort()
|
||||
return
|
||||
} else {
|
||||
session.Delete("login")
|
||||
session.Set("lastRequestTime", now)
|
||||
session.Save()
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if session.Get("login") == nil {
|
||||
if len(paths) > 0 {
|
||||
if paths[0] == "login" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
if len(paths) > 1 {
|
||||
if paths[1] == "login" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
// 判断是否为静态文件路径
|
||||
if method == "GET" {
|
||||
if len(paths) > 1 && paths[0] == "static" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
}
|
||||
// 返回登录页
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
c.Abort()
|
||||
return
|
||||
} else {
|
||||
if session.Get("__login_key") != public.GetSettingIgnoreError("login_key") {
|
||||
session.Clear()
|
||||
session.Save()
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"message": "登录信息发生变化,请重新登录"})
|
||||
c.Abort()
|
||||
} else {
|
||||
// 访问正常,更新最后请求时间
|
||||
session.Set("lastRequestTime", now)
|
||||
session.Save()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.Data(404, "text/html; charset=utf-8", Html404)
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
37
backend/middleware/log.go
Normal file
37
backend/middleware/log.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func LoggerMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
start := time.Now()
|
||||
c.Next()
|
||||
|
||||
duration := time.Since(start)
|
||||
method := c.Request.Method
|
||||
path := c.Request.URL.Path
|
||||
status := c.Writer.Status()
|
||||
clientIP := c.ClientIP()
|
||||
userAgent := c.Request.UserAgent()
|
||||
respSize := c.Writer.Size() // 响应体字节大小
|
||||
|
||||
msg := fmt.Sprintf(
|
||||
"| %3d | %13v | %15s | %-7s %-30s | UA: %-40s | RespSize: %d bytes",
|
||||
status,
|
||||
duration,
|
||||
clientIP,
|
||||
method,
|
||||
path,
|
||||
userAgent,
|
||||
respSize,
|
||||
)
|
||||
|
||||
public.Info(msg)
|
||||
}
|
||||
}
|
||||
10
backend/middleware/oplog.go
Normal file
10
backend/middleware/oplog.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package middleware
|
||||
|
||||
// import (
|
||||
// "github.com/gin-gonic/gin"
|
||||
// )
|
||||
//
|
||||
// func OpLoggerMiddleware() gin.HandlerFunc {
|
||||
// return func(c *gin.Context) {
|
||||
// }
|
||||
// }
|
||||
223
backend/migrations/init.go
Normal file
223
backend/migrations/init.go
Normal file
@@ -0,0 +1,223 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/public"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func init() {
|
||||
os.MkdirAll("data", os.ModePerm)
|
||||
|
||||
dbPath := "data/data.db"
|
||||
_, _ = filepath.Abs(dbPath)
|
||||
// fmt.Println("数据库路径:", absPath)
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
// fmt.Println("创建数据库失败:", err)
|
||||
return
|
||||
}
|
||||
defer db.Close()
|
||||
// 创建表
|
||||
_, err = db.Exec(`
|
||||
create table IF NOT EXISTS _accounts
|
||||
(
|
||||
id integer not null
|
||||
constraint _accounts_pk
|
||||
primary key autoincrement,
|
||||
private_key TEXT not null,
|
||||
reg TEXT not null,
|
||||
email TEXT not null,
|
||||
create_time TEXT,
|
||||
update_time TEXT,
|
||||
type TEXT
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS access
|
||||
(
|
||||
id integer not null
|
||||
constraint access_pk
|
||||
primary key autoincrement,
|
||||
config TEXT not null,
|
||||
type TEXT not null,
|
||||
create_time TEXT,
|
||||
update_time TEXT,
|
||||
name TEXT not null
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS access_type
|
||||
(
|
||||
id integer not null
|
||||
constraint access_type_pk
|
||||
primary key autoincrement,
|
||||
name TEXT,
|
||||
type TEXT
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS cert
|
||||
(
|
||||
id integer not null
|
||||
constraint cert_pk
|
||||
primary key autoincrement,
|
||||
source TEXT not null,
|
||||
sha256 TEXT,
|
||||
history_id TEXT,
|
||||
key TEXT not null,
|
||||
cert TEXT not null,
|
||||
issuer_cert integer,
|
||||
domains TEXT not null,
|
||||
create_time TEXT,
|
||||
update_time TEXT,
|
||||
issuer TEXT not null,
|
||||
start_time TEXT,
|
||||
end_time TEXT,
|
||||
end_day TEXT,
|
||||
workflow_id TEXT
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS report
|
||||
(
|
||||
id integer not null
|
||||
constraint report_pk
|
||||
primary key autoincrement,
|
||||
type TEXT not null,
|
||||
config TEXT not null,
|
||||
create_time TEXT,
|
||||
update_time TEXT,
|
||||
name TEXT
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS settings
|
||||
(
|
||||
id integer
|
||||
constraint settings_pk
|
||||
primary key,
|
||||
key TEXT,
|
||||
value TEXT,
|
||||
create_time TEXT not null,
|
||||
update_time TEXT not null,
|
||||
active integer not null,
|
||||
type TEXT
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS site_monitor
|
||||
(
|
||||
id integer not null
|
||||
constraint site_monitor_pk
|
||||
primary key autoincrement,
|
||||
name TEXT not null,
|
||||
site_domain TEXT not null,
|
||||
cycle integer not null,
|
||||
report_type TEXT not null,
|
||||
cert_domain TEXT,
|
||||
ca TEXT,
|
||||
active integer,
|
||||
end_time TEXT,
|
||||
end_day TEXT,
|
||||
last_time TEXT,
|
||||
except_end_time TEXT,
|
||||
create_time TEXT,
|
||||
state TEXT,
|
||||
update_time TEXT,
|
||||
repeat_send_gap INTEGER
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS users
|
||||
(
|
||||
id integer not null
|
||||
constraint users_pk
|
||||
primary key autoincrement,
|
||||
username TEXT not null
|
||||
constraint users_pk2
|
||||
unique,
|
||||
password TEXT not null,
|
||||
salt TEXT default '' not null
|
||||
);
|
||||
|
||||
|
||||
create table IF NOT EXISTS workflow
|
||||
(
|
||||
id integer not null
|
||||
constraint workflow_pk
|
||||
primary key autoincrement,
|
||||
name TEXT not null,
|
||||
content TEXT not null,
|
||||
cron TEXT,
|
||||
create_time TEXT,
|
||||
update_time TEXT,
|
||||
active integer,
|
||||
exec_type TEXT,
|
||||
last_run_status TEXT,
|
||||
exec_time TEXT,
|
||||
last_run_time TEXT
|
||||
);
|
||||
|
||||
create table IF NOT EXISTS workflow_history
|
||||
(
|
||||
id TEXT not null
|
||||
constraint work_flow_pk
|
||||
primary key,
|
||||
status TEXT,
|
||||
exec_type TEXT,
|
||||
create_time TEXT,
|
||||
end_time TEXT,
|
||||
workflow_id TEXT not null
|
||||
fail_reason TEXT,
|
||||
);
|
||||
`)
|
||||
insertDefaultData(db, "users", "INSERT INTO users (id, username, password, salt) VALUES (1, 'xxxx', 'xxxxxxx', '&*ghs^&%dag');")
|
||||
insertDefaultData(db, "access_type", `
|
||||
INSERT INTO access_type (name, type) VALUES ('aliyun', 'dns');
|
||||
INSERT INTO access_type (name, type) VALUES ('tencentcloud', 'dns');
|
||||
INSERT INTO access_type (name, type) VALUES ('aliyun', 'host');
|
||||
INSERT INTO access_type (name, type) VALUES ('tencentcloud', 'host');
|
||||
INSERT INTO access_type (name, type) VALUES ('ssh', 'host');
|
||||
INSERT INTO access_type (name, type) VALUES ('btpanel', 'host');
|
||||
INSERT INTO access_type (name, type) VALUES ('1panel', 'host');`)
|
||||
|
||||
uuidStr := public.GenerateUUID()
|
||||
randomStr := public.RandomString(8)
|
||||
|
||||
port, err := public.GetFreePort()
|
||||
if err != nil {
|
||||
port = 20773
|
||||
}
|
||||
|
||||
Isql := fmt.Sprintf(
|
||||
`INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('log_path', 'logs/ALLinSSL.log', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);
|
||||
INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'workflow_log_path', 'logs/workflows/', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);
|
||||
INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'timeout', '3600', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);
|
||||
INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'https', '0', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);
|
||||
INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ( 'login_key', '%s', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);
|
||||
INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('session_key', '%s', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);
|
||||
INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('secure', '/%s', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);
|
||||
INSERT INTO settings (key, value, create_time, update_time, active, type) VALUES ('port', '%d', '2025-04-15 15:58', '2025-04-15 15:58', 1, null);`, uuidStr, uuidStr, randomStr, port)
|
||||
|
||||
insertDefaultData(db, "settings", Isql)
|
||||
}
|
||||
|
||||
func insertDefaultData(db *sql.DB, table, insertSQL string) {
|
||||
// 查询用户表中现有的记录数
|
||||
var count int
|
||||
err := db.QueryRow("SELECT COUNT(*) FROM " + table).Scan(&count)
|
||||
if err != nil {
|
||||
// fmt.Println("检查数据行数失败:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 如果表为空,则插入默认数据
|
||||
if count == 0 {
|
||||
// fmt.Println("表为空,插入默认数据...")
|
||||
_, err = db.Exec(insertSQL)
|
||||
if err != nil {
|
||||
// fmt.Println("插入数据失败:", err)
|
||||
return
|
||||
}
|
||||
// fmt.Println("默认数据插入成功。")
|
||||
// } else {
|
||||
// fmt.Println("表已有数据,跳过插入。")
|
||||
}
|
||||
}
|
||||
139
backend/public/cert.go
Normal file
139
backend/public/cert.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/ed25519"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// **解析 PEM 格式的证书**
|
||||
func ParseCertificate(certPEM []byte) (*x509.Certificate, error) {
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("无法解析证书 PEM")
|
||||
}
|
||||
return x509.ParseCertificate(block.Bytes)
|
||||
}
|
||||
|
||||
// **解析 PEM 格式的私钥**
|
||||
func ParsePrivateKey(keyPEM []byte) (crypto.PrivateKey, error) {
|
||||
block, _ := pem.Decode(keyPEM)
|
||||
if block == nil {
|
||||
return nil, fmt.Errorf("无法解析私钥 PEM")
|
||||
}
|
||||
|
||||
// **尝试解析不同类型的私钥**
|
||||
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParseECPrivateKey(block.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParsePKCS8PrivateKey(block.Bytes); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
return nil, fmt.Errorf("无法识别的私钥格式")
|
||||
}
|
||||
|
||||
// **检查证书是否过期**
|
||||
func CheckCertificateExpiration(cert *x509.Certificate) error {
|
||||
now := time.Now()
|
||||
if now.Before(cert.NotBefore) {
|
||||
return fmt.Errorf("证书尚未生效,有效期开始于: %v", cert.NotBefore)
|
||||
}
|
||||
if now.After(cert.NotAfter) {
|
||||
return fmt.Errorf("证书已过期,有效期截止到: %v", cert.NotAfter)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// **检查证书是否与私钥匹配**
|
||||
func VerifyCertificateAndKey(cert *x509.Certificate, privateKey crypto.PrivateKey) error {
|
||||
messageARR := sha256.Sum256([]byte("test message"))
|
||||
message := messageARR[:]
|
||||
var signature []byte
|
||||
var err error
|
||||
|
||||
// **用私钥签名数据**
|
||||
switch key := privateKey.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
signature, err = rsa.SignPKCS1v15(nil, key, crypto.SHA256, message)
|
||||
case *ecdsa.PrivateKey:
|
||||
signature, err = key.Sign(nil, message, crypto.SHA256)
|
||||
case ed25519.PrivateKey:
|
||||
signature = ed25519.Sign(key, message)
|
||||
default:
|
||||
err = errors.New("不支持的私钥类型")
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("签名失败: %v", err)
|
||||
}
|
||||
|
||||
// **使用公钥验证签名**
|
||||
switch pub := cert.PublicKey.(type) {
|
||||
case *rsa.PublicKey:
|
||||
err = rsa.VerifyPKCS1v15(pub, crypto.SHA256, message, signature)
|
||||
case *ecdsa.PublicKey:
|
||||
ok := ecdsa.VerifyASN1(pub, message, signature)
|
||||
if !ok {
|
||||
err = fmt.Errorf("ECDSA 签名验证失败")
|
||||
}
|
||||
case ed25519.PublicKey:
|
||||
if !ed25519.Verify(pub, message, signature) {
|
||||
err = fmt.Errorf("Ed25519 签名验证失败")
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("不支持的公钥类型: %T", pub)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// **主验证函数**
|
||||
func ValidateSSLCertificate(certStr, keyStr string) error {
|
||||
certPEM, keyPEM := []byte(certStr), []byte(keyStr)
|
||||
// **解析证书和私钥**
|
||||
cert, err := ParseCertificate(certPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析证书失败: %v", err)
|
||||
}
|
||||
privateKey, err := ParsePrivateKey(keyPEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析私钥失败: %v", err)
|
||||
}
|
||||
|
||||
// **检查证书有效期**
|
||||
if err := CheckCertificateExpiration(cert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// **检查证书和私钥是否匹配**
|
||||
if err := VerifyCertificateAndKey(cert, privateKey); err != nil {
|
||||
return fmt.Errorf("证书与私钥不匹配: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 获取sha256
|
||||
func GetSHA256(certStr string) (string, error) {
|
||||
certPEM := []byte(certStr)
|
||||
block, _ := pem.Decode(certPEM)
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("无法解析证书 PEM")
|
||||
}
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("解析证书失败: %v", err)
|
||||
}
|
||||
|
||||
sha256Hash := sha256.Sum256(cert.Raw)
|
||||
return hex.EncodeToString(sha256Hash[:]), nil
|
||||
}
|
||||
44
backend/public/config.go
Normal file
44
backend/public/config.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package public
|
||||
|
||||
import "strconv"
|
||||
|
||||
var Port = GetSettingIgnoreError("port")
|
||||
var Secure = GetSettingIgnoreError("secure")
|
||||
var SessionKey = GetSettingIgnoreError("session_key")
|
||||
var LogPath = GetSettingIgnoreError("log_path")
|
||||
var TimeOut = func() int {
|
||||
settingStr := GetSettingIgnoreError("timeout")
|
||||
setting, err := strconv.Atoi(settingStr)
|
||||
if err != nil {
|
||||
return 300
|
||||
}
|
||||
return setting
|
||||
}()
|
||||
var ShutdownFunc func()
|
||||
|
||||
func ReloadConfig() {
|
||||
Port = GetSettingIgnoreError("port")
|
||||
Secure = GetSettingIgnoreError("secure")
|
||||
SessionKey = GetSettingIgnoreError("session_key")
|
||||
LogPath = GetSettingIgnoreError("log_path")
|
||||
|
||||
settingStr := GetSettingIgnoreError("timeout")
|
||||
setting, err := strconv.Atoi(settingStr)
|
||||
if err != nil {
|
||||
TimeOut = 300
|
||||
} else {
|
||||
TimeOut = setting
|
||||
}
|
||||
ShutdownFunc = nil
|
||||
|
||||
}
|
||||
|
||||
// OpLog 操作日志
|
||||
type OpLog struct {
|
||||
OpType string `db:"op_type"`
|
||||
OpUser string `db:"op_user"`
|
||||
OpTime string `db:"op_time"`
|
||||
OpDetail string `db:"op_detail"`
|
||||
OpResult string `db:"op_result"`
|
||||
IP string `db:"ip"`
|
||||
}
|
||||
112
backend/public/logger.go
Normal file
112
backend/public/logger.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
infoLogger *log.Logger
|
||||
errorLogger *log.Logger
|
||||
warnLogger *log.Logger
|
||||
logFile *os.File
|
||||
)
|
||||
|
||||
// InitLogger 初始化日志器(仅写入文件)
|
||||
func InitLogger(logPath string) {
|
||||
// 确保日志目录存在
|
||||
dir := filepath.Dir(logPath)
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
panic("创建日志目录失败: " + err.Error())
|
||||
}
|
||||
|
||||
var err error
|
||||
logFile, err = os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
panic("无法打开日志文件: " + err.Error())
|
||||
}
|
||||
|
||||
infoLogger = log.New(logFile, "[INFO] ", log.LstdFlags|log.Lshortfile)
|
||||
errorLogger = log.New(logFile, "[ERROR] ", log.LstdFlags|log.Lshortfile)
|
||||
warnLogger = log.New(logFile, "[WARN] ", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
|
||||
// Info 输出 Info 级别日志
|
||||
func Info(msg string) {
|
||||
infoLogger.Println(msg)
|
||||
}
|
||||
|
||||
// Error 输出 Error 级别日志
|
||||
func Error(msg string) {
|
||||
errorLogger.Println(msg)
|
||||
}
|
||||
|
||||
// Warn 输出 Warn 级别日志
|
||||
func Warn(msg string) {
|
||||
warnLogger.Println(msg)
|
||||
}
|
||||
|
||||
// CloseLogger 关闭日志文件(建议在程序退出前调用)
|
||||
func CloseLogger() {
|
||||
if logFile != nil {
|
||||
logFile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
filePath string
|
||||
logger *log.Logger
|
||||
file *os.File
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewLogger(filePath string) (*Logger, error) {
|
||||
// 确保日志目录存在
|
||||
dir := filepath.Dir(filePath)
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
panic("创建日志目录失败: " + err.Error())
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := log.New(file, "", 0) // 不设置前缀,我们手动格式化
|
||||
return &Logger{
|
||||
filePath: filePath,
|
||||
logger: l,
|
||||
file: file,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Close 关闭日志文件
|
||||
func (l *Logger) Close() {
|
||||
l.file.Close()
|
||||
}
|
||||
|
||||
// write 写日志,内部使用锁保证线程安全
|
||||
func (l *Logger) write(level string, msg string) {
|
||||
l.mutex.Lock()
|
||||
defer l.mutex.Unlock()
|
||||
|
||||
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||
logLine := "[" + level + "] " + timestamp + " - " + msg
|
||||
l.logger.Println(logLine)
|
||||
}
|
||||
|
||||
// Info 输出 info 级别日志
|
||||
func (l *Logger) Info(msg string) {
|
||||
l.write("INFO", msg)
|
||||
}
|
||||
|
||||
// Error 输出 error 级别日志
|
||||
func (l *Logger) Error(msg string) {
|
||||
l.write("ERROR", msg)
|
||||
}
|
||||
func (l *Logger) Debug(msg string) {
|
||||
l.write("Debug", msg)
|
||||
}
|
||||
44
backend/public/response.go
Normal file
44
backend/public/response.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package public
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Count int `json:"count"`
|
||||
Data any `json:"data"`
|
||||
Message string `json:"message"`
|
||||
Status bool `json:"status"`
|
||||
}
|
||||
|
||||
func SuccessMsg(c *gin.Context, msg string) {
|
||||
c.JSON(200, Response{
|
||||
Code: 200,
|
||||
Count: 0,
|
||||
Data: nil,
|
||||
Message: msg,
|
||||
Status: true,
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
func SuccessData(c *gin.Context, data interface{}, count int) {
|
||||
c.JSON(200, Response{
|
||||
Code: 200,
|
||||
Count: count,
|
||||
Data: data,
|
||||
Message: "success",
|
||||
Status: true,
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
func FailMsg(c *gin.Context, msg string) {
|
||||
c.JSON(500, Response{
|
||||
Code: 500,
|
||||
Count: 0,
|
||||
Data: nil,
|
||||
Message: msg,
|
||||
Status: false,
|
||||
})
|
||||
c.Abort()
|
||||
}
|
||||
1094
backend/public/sqlite.go
Normal file
1094
backend/public/sqlite.go
Normal file
File diff suppressed because it is too large
Load Diff
181
backend/public/utils.go
Normal file
181
backend/public/utils.go
Normal file
@@ -0,0 +1,181 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"io"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const defaultCharset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
// GetSettingIgnoreError 获取系统配置-忽略错误
|
||||
func GetSettingIgnoreError(key string) string {
|
||||
s, err := NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
s.Connect()
|
||||
defer s.Close()
|
||||
s.TableName = "settings"
|
||||
res, err := s.Where("key=?", []interface{}{key}).Select()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
if len(res) == 0 {
|
||||
return ""
|
||||
}
|
||||
setting, ok := res[0]["value"].(string)
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return setting
|
||||
}
|
||||
|
||||
func UpdateSetting(key, val string) error {
|
||||
s, err := NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Connect()
|
||||
defer s.Close()
|
||||
s.TableName = "settings"
|
||||
_, err = s.Where("key=?", []interface{}{key}).Update(map[string]any{"value": val})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSettingsFromType(typ string) ([]map[string]any, error) {
|
||||
db := "data/data.db"
|
||||
s, err := NewSqlite(db, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.Connect()
|
||||
defer s.Close()
|
||||
s.TableName = "settings"
|
||||
res, err := s.Where("type=?", []interface{}{typ}).Select()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetFreePort 获取一个可用的随机端口
|
||||
func GetFreePort() (int, error) {
|
||||
// 端口为 0,表示让系统自动分配一个可用端口
|
||||
ln, err := net.Listen("tcp", "localhost:0")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
addr := ln.Addr().String()
|
||||
// 提取端口号
|
||||
parts := strings.Split(addr, ":")
|
||||
if len(parts) < 2 {
|
||||
return 0, fmt.Errorf("invalid address: %s", addr)
|
||||
}
|
||||
|
||||
var port int
|
||||
fmt.Sscanf(parts[len(parts)-1], "%d", &port)
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// RandomString 生成指定长度的随机字符串
|
||||
func RandomString(length int) string {
|
||||
if str, err := RandomStringWithCharset(length, defaultCharset); err != nil {
|
||||
return "allinssl"
|
||||
} else {
|
||||
return str
|
||||
}
|
||||
}
|
||||
|
||||
// RandomStringWithCharset 使用指定字符集生成随机字符串
|
||||
func RandomStringWithCharset(length int, charset string) (string, error) {
|
||||
result := make([]byte, length)
|
||||
charsetLen := big.NewInt(int64(len(charset)))
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
num, err := rand.Int(rand.Reader, charsetLen)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result[i] = charset[num.Int64()]
|
||||
}
|
||||
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// GenerateUUID 生成 UUID
|
||||
func GenerateUUID() string {
|
||||
// 生成一个新的 UUID
|
||||
uuidStr := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
|
||||
// 返回 UUID 的字符串表示
|
||||
return uuidStr
|
||||
}
|
||||
|
||||
func GetLocalIP() (string, error) {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagUp == 0 {
|
||||
continue // 接口未启用
|
||||
}
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue // 忽略回环地址
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
// 只返回 IPv4 内网地址
|
||||
if ip != nil && ip.To4() != nil && !ip.IsLoopback() {
|
||||
return ip.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("没有找到内网 IP")
|
||||
}
|
||||
|
||||
func GetPublicIP() (string, error) {
|
||||
resp, err := http.Get("https://www.bt.cn/Api/getIpAddress")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("HTTP状态错误: %v", resp.Status)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
return string(body), nil
|
||||
}
|
||||
35
backend/public/validate_code.go
Normal file
35
backend/public/validate_code.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package public
|
||||
|
||||
import (
|
||||
"github.com/mojocn/base64Captcha"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
var codeDefaultDriver = base64Captcha.NewDriverString(
|
||||
1000,
|
||||
1200,
|
||||
0,
|
||||
base64Captcha.OptionShowSlimeLine,
|
||||
4,
|
||||
"23456789abcdefghjkmnpqrstuvwxyz",
|
||||
&color.RGBA{
|
||||
R: 225,
|
||||
G: 225,
|
||||
B: 200,
|
||||
A: 255,
|
||||
},
|
||||
nil,
|
||||
[]string{"wqy-microhei.ttc", "RitaSmith.ttf"},
|
||||
)
|
||||
|
||||
// GenerateCode 生成图形化字符串验证码
|
||||
func GenerateCode() (string, string, string, error) {
|
||||
// 生成默认数字的driver
|
||||
codeId, content, _ := codeDefaultDriver.GenerateIdQuestionAnswer() // 生成验证码和随机id
|
||||
item, err := codeDefaultDriver.DrawCaptcha(content) // 生成验证码图片
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
b64s := item.EncodeB64string()
|
||||
return codeId, b64s, content, nil
|
||||
}
|
||||
86
backend/route/route.go
Normal file
86
backend/route/route.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/app/api"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Register(r *gin.Engine) {
|
||||
v1 := r.Group("/v1")
|
||||
|
||||
login := v1.Group("/login")
|
||||
{
|
||||
login.POST("/sign", api.Sign)
|
||||
login.POST("/sign-out", api.SignOut)
|
||||
login.GET("/get_code", api.GetCode)
|
||||
}
|
||||
siteMonitor := v1.Group("/siteMonitor")
|
||||
{
|
||||
siteMonitor.POST("/get_list", api.GetMonitorList)
|
||||
siteMonitor.POST("/add_site_monitor", api.AddMonitor)
|
||||
siteMonitor.POST("/upd_site_monitor", api.UpdMonitor)
|
||||
siteMonitor.POST("/del_site_monitor", api.DelMonitor)
|
||||
siteMonitor.POST("/set_site_monitor", api.SetMonitor)
|
||||
}
|
||||
workflow := v1.Group("/workflow")
|
||||
{
|
||||
workflow.POST("/get_list", api.GetWorkflowList)
|
||||
workflow.POST("/add_workflow", api.AddWorkflow)
|
||||
workflow.POST("/del_workflow", api.DelWorkflow)
|
||||
workflow.POST("/upd_workflow", api.UpdWorkflow)
|
||||
workflow.POST("/exec_type", api.UpdExecType)
|
||||
workflow.POST("/active", api.UpdActive)
|
||||
workflow.POST("/execute_workflow", api.ExecuteWorkflow)
|
||||
workflow.POST("/get_workflow_history", api.GetWorkflowHistory)
|
||||
workflow.POST("/get_exec_log", api.GetExecLog)
|
||||
workflow.POST("/stop", api.StopWorkflow)
|
||||
}
|
||||
access := v1.Group("/access")
|
||||
{
|
||||
access.POST("/get_list", api.GetAccessList)
|
||||
access.POST("/add_access", api.AddAccess)
|
||||
access.POST("/del_access", api.DelAccess)
|
||||
access.POST("/upd_access", api.UpdateAccess)
|
||||
access.POST("/get_all", api.GetAllAccess)
|
||||
}
|
||||
cert := v1.Group("/cert")
|
||||
{
|
||||
cert.POST("/get_list", api.GetCertList)
|
||||
cert.POST("/upload_cert", api.UploadCert)
|
||||
cert.POST("/del_cert", api.DelCert)
|
||||
cert.GET("/download", api.DownloadCert)
|
||||
}
|
||||
report := v1.Group("/report")
|
||||
{
|
||||
report.POST("/get_list", api.GetReportList)
|
||||
report.POST("/add_report", api.AddReport)
|
||||
report.POST("/del_report", api.DelReport)
|
||||
report.POST("/upd_report", api.UpdReport)
|
||||
report.POST("/notify_test", api.NotifyTest)
|
||||
}
|
||||
setting := v1.Group("/setting")
|
||||
{
|
||||
setting.POST("/get_setting", api.GetSetting)
|
||||
setting.POST("/save_setting", api.SaveSetting)
|
||||
setting.POST("/shutdown", api.Shutdown)
|
||||
setting.POST("/restart", api.Restart)
|
||||
}
|
||||
overview := v1.Group("/overview")
|
||||
{
|
||||
overview.POST("/get_overviews", api.GetOverview)
|
||||
}
|
||||
|
||||
// 1. 提供静态文件服务
|
||||
r.StaticFS("/static", http.Dir("./frontend/static")) // 静态资源路径
|
||||
r.StaticFS("/auto-deploy/static", http.Dir("./frontend/static")) // 静态资源路径
|
||||
|
||||
// 3. 前端路由托管:匹配所有其他路由并返回 index.html
|
||||
r.NoRoute(func(c *gin.Context) {
|
||||
c.File("./frontend/index.html")
|
||||
})
|
||||
// v2 := r.Group("/v2")
|
||||
// {
|
||||
// v2.POST("/submit")
|
||||
// }
|
||||
}
|
||||
129
backend/scheduler/scheduler.go
Normal file
129
backend/scheduler/scheduler.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 你的任务列表
|
||||
var funcs = []func(){
|
||||
SiteMonitor,
|
||||
RunWorkflows,
|
||||
}
|
||||
|
||||
// Scheduler 控制器
|
||||
type Scheduler struct {
|
||||
mu sync.Mutex
|
||||
ctx context.Context
|
||||
cancelFunc context.CancelFunc
|
||||
running bool
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// 启动调度器(在 goroutine 中运行)
|
||||
func (s *Scheduler) Start() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if s.running {
|
||||
return
|
||||
}
|
||||
|
||||
s.ctx, s.cancelFunc = context.WithCancel(context.Background())
|
||||
s.running = true
|
||||
s.wg.Add(1)
|
||||
|
||||
go s.loop() // goroutine 中运行任务调度
|
||||
}
|
||||
|
||||
// 停止调度器
|
||||
func (s *Scheduler) Stop() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if !s.running {
|
||||
return
|
||||
}
|
||||
|
||||
s.cancelFunc() // 取消上下文
|
||||
s.wg.Wait() // 等待 goroutine 完成退出
|
||||
s.running = false // 标记为未运行
|
||||
}
|
||||
|
||||
// 重启调度器
|
||||
func (s *Scheduler) Restart() {
|
||||
s.Stop()
|
||||
time.Sleep(500 * time.Millisecond) // 可选,避免 race
|
||||
s.Start()
|
||||
}
|
||||
|
||||
// 调度主循环(内部)
|
||||
func (s *Scheduler) loop() {
|
||||
defer s.wg.Done()
|
||||
|
||||
for {
|
||||
// fmt.Println("Scheduler loop")
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
return // 外部关闭信号,退出
|
||||
default:
|
||||
start := time.Now()
|
||||
|
||||
var taskWg sync.WaitGroup
|
||||
taskWg.Add(len(funcs))
|
||||
|
||||
for _, f := range funcs {
|
||||
go func(fn func()) {
|
||||
defer taskWg.Done()
|
||||
fn()
|
||||
}(f)
|
||||
}
|
||||
taskWg.Wait()
|
||||
|
||||
// 间隔控制
|
||||
elapsed := time.Since(start)
|
||||
if elapsed < 10*time.Second {
|
||||
select {
|
||||
case <-time.After(10*time.Second - elapsed):
|
||||
case <-s.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// package scheduler
|
||||
//
|
||||
// import (
|
||||
// "sync"
|
||||
// "time"
|
||||
// )
|
||||
//
|
||||
// var funcs = []func(){
|
||||
// SiteMonitor,
|
||||
// RunWorkflows,
|
||||
// }
|
||||
//
|
||||
// func Scheduler() {
|
||||
// for {
|
||||
// start := time.Now()
|
||||
//
|
||||
// var wg sync.WaitGroup
|
||||
// wg.Add(len(funcs))
|
||||
//
|
||||
// for _, f := range funcs {
|
||||
// go func(fn func()) {
|
||||
// defer wg.Done()
|
||||
// fn()
|
||||
// }(f)
|
||||
// }
|
||||
// wg.Wait()
|
||||
// // 保证每轮间隔至少10秒
|
||||
// elapsed := time.Since(start)
|
||||
// if elapsed < 10*time.Second {
|
||||
// time.Sleep(10*time.Second - elapsed)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
90
backend/scheduler/site_monitor.go
Normal file
90
backend/scheduler/site_monitor.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/internal/report"
|
||||
"ALLinSSL/backend/internal/siteMonitor"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
func SiteMonitor() {
|
||||
s, err := siteMonitor.GetSqlite()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Select()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
now := time.Now()
|
||||
loc := now.Location()
|
||||
var wg sync.WaitGroup
|
||||
for _, v := range data {
|
||||
if v["active"].(int64) == 1 {
|
||||
lastTimeStr := v["last_time"].(string)
|
||||
lastTime, err := time.ParseInLocation("2006-01-02 15:04:05", lastTimeStr, loc)
|
||||
if err != nil {
|
||||
// fmt.Println(err)
|
||||
continue
|
||||
}
|
||||
if now.Sub(lastTime).Minutes() >= float64(v["cycle"].(int64)) {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
Err := siteMonitor.UpdInfo(fmt.Sprintf("%d", v["id"].(int64)), v["site_domain"].(string), s, v["report_type"].(string))
|
||||
|
||||
path := fmt.Sprintf("data/site_monitor/%d", v["id"].(int64))
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return
|
||||
}
|
||||
errCount := 0
|
||||
file, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
errCount = 0
|
||||
}
|
||||
errCount, err = strconv.Atoi(string(file))
|
||||
if err != nil {
|
||||
errCount = 0
|
||||
}
|
||||
|
||||
// 此处应该发送错误邮件
|
||||
if Err != nil {
|
||||
errCount += 1
|
||||
os.WriteFile(path, []byte(strconv.Itoa(errCount)), os.ModePerm)
|
||||
repeatSendGap, ok := v["repeat_send_gap"].(int64)
|
||||
if !ok {
|
||||
repeatSendGap = 10
|
||||
}
|
||||
reportType, ok := v["report_type"].(string)
|
||||
if ok && errCount >= int(repeatSendGap) {
|
||||
s.TableName = "report"
|
||||
rdata, err := s.Where("type=?", []interface{}{reportType}).Select()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(rdata) <= 0 {
|
||||
return
|
||||
}
|
||||
_ = report.Notify(map[string]any{
|
||||
"provider": reportType,
|
||||
"provider_id": strconv.FormatInt(rdata[0]["id"].(int64), 10),
|
||||
"body": fmt.Sprintf("检测到域名为%s的网站出现异常,请保持关注!\n检测时间:%s", v["site_domain"].(string), now.Format("2006-01-02 15:04:05")),
|
||||
"subject": "ALLinSSL网站监控通知",
|
||||
})
|
||||
os.Remove(path)
|
||||
}
|
||||
} else {
|
||||
os.Remove(path)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
104
backend/scheduler/workflow.go
Normal file
104
backend/scheduler/workflow.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
wf "ALLinSSL/backend/internal/workflow"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ExecTime struct {
|
||||
Type string `json:"type"` // "day", "week", "month"
|
||||
Month int `json:"month,omitempty"` // 每月几号 type="month"时必填
|
||||
Week int `json:"week,omitempty"` // 星期几 type="week"时必填
|
||||
Hour int `json:"hour"` // 几点 必填
|
||||
Minute int `json:"minute"` // 几分 必填
|
||||
}
|
||||
|
||||
func RunWorkflows() {
|
||||
s, err := wf.GetSqlite()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
data, err := s.Select()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
// 遍历工作流
|
||||
var wg sync.WaitGroup
|
||||
for _, workflow := range data {
|
||||
if workflow["exec_type"].(string) != "auto" {
|
||||
// fmt.Println("不是自动执行的工作流")
|
||||
continue
|
||||
}
|
||||
if workflow["active"].(int64) == 0 {
|
||||
// 1: 启用
|
||||
// 0: 禁用
|
||||
// fmt.Println("工作流未启用")
|
||||
continue
|
||||
}
|
||||
if workflow["last_run_status"] != nil && workflow["last_run_status"].(string) == "running" {
|
||||
// fmt.Println("工作流正在运行")
|
||||
continue
|
||||
}
|
||||
// if workflow["last"]
|
||||
if workflow["last_run_time"] != nil && now.Format("2006-01-02 15:04") == workflow["last_run_time"].(string)[0:16] {
|
||||
// fmt.Println("工作流已执行过")
|
||||
continue
|
||||
}
|
||||
// 判断是否到执行时间
|
||||
var execTime ExecTime
|
||||
execTimeStr := ""
|
||||
if et, ok := workflow["exec_time"].(string); ok {
|
||||
execTimeStr = et
|
||||
}
|
||||
err := json.Unmarshal([]byte(execTimeStr), &execTime)
|
||||
if err != nil {
|
||||
// fmt.Println("解析执行时间失败:", err)
|
||||
continue
|
||||
}
|
||||
if execTime.Minute != now.Minute() || execTime.Hour != now.Hour() {
|
||||
// fmt.Println("不在执行时间内1")
|
||||
continue
|
||||
}
|
||||
|
||||
if execTime.Type == "week" && execTime.Week != int(now.Weekday()) {
|
||||
// fmt.Println("不在执行时间内2")
|
||||
continue
|
||||
}
|
||||
if execTime.Type == "month" && execTime.Month != now.Day() {
|
||||
// fmt.Println("不在执行时间内3")
|
||||
continue
|
||||
}
|
||||
if content, ok := workflow["content"].(string); !ok {
|
||||
// fmt.Println("工作流内容为空")
|
||||
continue
|
||||
} else {
|
||||
wg.Add(1)
|
||||
go func(id int64, c string) {
|
||||
defer wg.Done()
|
||||
WorkflowID := strconv.FormatInt(id, 10)
|
||||
RunID, err := wf.AddWorkflowHistory(WorkflowID, "auto")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ctx := wf.NewExecutionContext(RunID)
|
||||
defer ctx.Logger.Close()
|
||||
err = wf.RunWorkflow(c, ctx)
|
||||
if err != nil {
|
||||
fmt.Println("执行工作流失败:", err)
|
||||
wf.SetWorkflowStatus(WorkflowID, RunID, "fail")
|
||||
} else {
|
||||
wf.SetWorkflowStatus(WorkflowID, RunID, "success")
|
||||
}
|
||||
}(workflow["id"].(int64), content)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
76
backend/server/server.go
Normal file
76
backend/server/server.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"ALLinSSL/backend/middleware"
|
||||
"ALLinSSL/backend/public"
|
||||
"ALLinSSL/backend/route"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/gob"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/memstore"
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Run() error {
|
||||
public.ReloadConfig()
|
||||
public.InitLogger(public.LogPath)
|
||||
defer public.CloseLogger()
|
||||
r := gin.Default()
|
||||
|
||||
store := memstore.NewStore([]byte("secret")) // 只在内存中,不持久化
|
||||
r.Use(sessions.Sessions(public.SessionKey, store))
|
||||
r.Use(middleware.LoggerMiddleware())
|
||||
r.Use(gzip.Gzip(gzip.DefaultCompression))
|
||||
gob.Register(time.Time{})
|
||||
r.Use(middleware.SessionAuthMiddleware())
|
||||
// r.Use(middleware.OpLoggerMiddleware())
|
||||
route.Register(r)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
public.ShutdownFunc = cancel
|
||||
err := RunServer(ctx, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func RunServer(ctx context.Context, r *gin.Engine) error {
|
||||
|
||||
// 初始化http服务
|
||||
srv := &http.Server{
|
||||
Addr: ":" + public.Port,
|
||||
Handler: r,
|
||||
}
|
||||
errchan := make(chan error, 1)
|
||||
if public.GetSettingIgnoreError("https") == "1" {
|
||||
srv.TLSConfig = &tls.Config{
|
||||
MinVersion: tls.VersionTLS12, // 推荐设置最低 TLS 版本
|
||||
}
|
||||
go func() {
|
||||
defer close(errchan)
|
||||
err := srv.ListenAndServeTLS("data/https/cert.pem", "data/https/key.pem")
|
||||
if err != nil {
|
||||
errchan <- err
|
||||
}
|
||||
}()
|
||||
} else {
|
||||
go func() {
|
||||
defer close(errchan)
|
||||
err := srv.ListenAndServe()
|
||||
if err != nil {
|
||||
errchan <- err
|
||||
}
|
||||
}()
|
||||
}
|
||||
select {
|
||||
case err := <-errchan:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
_ = srv.Shutdown(ctx)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
435
cmd/main.go
Normal file
435
cmd/main.go
Normal file
@@ -0,0 +1,435 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "ALLinSSL/backend/migrations"
|
||||
"ALLinSSL/backend/public"
|
||||
"ALLinSSL/backend/scheduler"
|
||||
"ALLinSSL/backend/server"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/joho/godotenv"
|
||||
ps "github.com/mitchellh/go-ps"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var s = &scheduler.Scheduler{}
|
||||
var pidPath = "data/pid"
|
||||
var envPath = "data/.env"
|
||||
|
||||
var envVars = map[string]string{
|
||||
"web": "start",
|
||||
"scheduler": "start",
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println(`请不要直接运行本程序`)
|
||||
// start()
|
||||
return
|
||||
}
|
||||
env, err := godotenv.Read(envPath)
|
||||
if err == nil {
|
||||
envVars = env
|
||||
}
|
||||
|
||||
cmd := os.Args[1]
|
||||
switch cmd {
|
||||
case "start":
|
||||
mainRun()
|
||||
case "1":
|
||||
fmt.Println("Starting ALLinSSL...")
|
||||
if checkRun() {
|
||||
_ = exec.Command("/bin/bash", "-c", fmt.Sprintf("nohup %s start > /dev/null 2>&1 &", os.Args[0])).Run()
|
||||
fmt.Println("Started ALLinSSL")
|
||||
return
|
||||
}
|
||||
fmt.Println("ALLinSSL is already running")
|
||||
case "2":
|
||||
fmt.Println("Stopping ALLinSSL...")
|
||||
pid, err := os.ReadFile(pidPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading pid file")
|
||||
return
|
||||
}
|
||||
exec.Command("kill", "-9", string(pid)).Run()
|
||||
os.Remove(pidPath)
|
||||
fmt.Println("Stopped ALLinSSL")
|
||||
case "3":
|
||||
fmt.Println("Restarting ALLinSSL...")
|
||||
pid, err := os.ReadFile(pidPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading pid file")
|
||||
}
|
||||
exec.Command("kill", "-9", string(pid)).Run()
|
||||
os.Remove(pidPath)
|
||||
exec.Command("/bin/bash", "-c", fmt.Sprintf("nohup %s start > /dev/null 2>&1 &", os.Args[0])).Run()
|
||||
fmt.Println("Restarted ALLinSSL")
|
||||
case "4":
|
||||
var secure string
|
||||
if len(os.Args) > 2 {
|
||||
secure = os.Args[2]
|
||||
} else {
|
||||
fmt.Print("请输入安全入口: ")
|
||||
fmt.Scanln(&secure)
|
||||
}
|
||||
if len(secure) < 5 {
|
||||
fmt.Println("安全入口至少需要5位")
|
||||
return
|
||||
}
|
||||
if secure[0] != '/' {
|
||||
secure = "/" + secure
|
||||
}
|
||||
err := public.UpdateSetting("secure", secure)
|
||||
if err != nil {
|
||||
fmt.Println("Error updating setting:", err)
|
||||
return
|
||||
}
|
||||
envVars["web"] = "restart"
|
||||
err = control()
|
||||
fmt.Println("安全入口设置成功:", secure)
|
||||
case "5":
|
||||
var input string
|
||||
if len(os.Args) > 2 {
|
||||
input = os.Args[2]
|
||||
} else {
|
||||
fmt.Print("请输入用户名: ")
|
||||
fmt.Scanln(&input)
|
||||
}
|
||||
if len(input) < 5 {
|
||||
fmt.Println("用户名至少需要5位")
|
||||
return
|
||||
}
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
s.TableName = "users"
|
||||
_, err = s.Where("id=1", []interface{}{}).Update(map[string]interface{}{"username": input})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
public.UpdateSetting("login_key", public.GenerateUUID())
|
||||
fmt.Println("用户名设置成功:", input)
|
||||
case "6":
|
||||
var input string
|
||||
if len(os.Args) > 2 {
|
||||
input = os.Args[2]
|
||||
} else {
|
||||
fmt.Print("请输入密码: ")
|
||||
fmt.Scanln(&input)
|
||||
}
|
||||
if len(input) < 8 {
|
||||
fmt.Println("密码至少需要8位")
|
||||
return
|
||||
}
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
s.TableName = "users"
|
||||
user, err := s.Where("id=1", []interface{}{}).Select()
|
||||
if err != nil {
|
||||
fmt.Println("Error selecting user:", err)
|
||||
return
|
||||
}
|
||||
if len(user) == 0 {
|
||||
fmt.Println("No user")
|
||||
return
|
||||
}
|
||||
salt := user[0]["salt"].(string)
|
||||
passwd := input + "_bt_all_in_ssl"
|
||||
keyMd5 := md5.Sum([]byte(passwd))
|
||||
passwdMd5 := hex.EncodeToString(keyMd5[:])
|
||||
passwdMd5 += salt
|
||||
keyMd5 = md5.Sum([]byte(passwdMd5))
|
||||
passwdMd5 = hex.EncodeToString(keyMd5[:])
|
||||
|
||||
_, err = s.Where("id=1", []interface{}{}).Update(map[string]interface{}{"password": passwdMd5})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
public.UpdateSetting("login_key", public.GenerateUUID())
|
||||
|
||||
fmt.Println("密码设置成功:", input)
|
||||
case "7":
|
||||
var input string
|
||||
if len(os.Args) > 2 {
|
||||
input = os.Args[2]
|
||||
} else {
|
||||
fmt.Print("请输入端口号: ")
|
||||
fmt.Scanln(&input)
|
||||
}
|
||||
port, err := strconv.Atoi(input) // 转换成整数
|
||||
if err != nil {
|
||||
fmt.Println("端口号必须是数字!")
|
||||
return
|
||||
}
|
||||
if port < 1 || port > 65535 {
|
||||
fmt.Println("端口号必须在 1 到 65535 之间!")
|
||||
return
|
||||
}
|
||||
err = public.UpdateSetting("port", input)
|
||||
if err != nil {
|
||||
fmt.Println("Error updating setting:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("端口号设置成功:", input)
|
||||
envVars["web"] = "restart"
|
||||
control()
|
||||
case "8":
|
||||
envVars["web"] = "stop"
|
||||
if control() != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("web服务已停止")
|
||||
case "9":
|
||||
envVars["web"] = "start"
|
||||
if control() != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("web服务已启动")
|
||||
case "10":
|
||||
envVars["web"] = "restart"
|
||||
if control() != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("已重启web服务")
|
||||
case "11":
|
||||
envVars["scheduler"] = "stop"
|
||||
if control() != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("后台调度服务已停止")
|
||||
case "12":
|
||||
envVars["scheduler"] = "start"
|
||||
if control() != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("后台调度服务已开启")
|
||||
|
||||
case "13":
|
||||
envVars["scheduler"] = "restart"
|
||||
if control() != nil {
|
||||
return
|
||||
}
|
||||
fmt.Println("已重启后台调度服务")
|
||||
|
||||
case "14":
|
||||
err := public.UpdateSetting("https", "0")
|
||||
if err != nil {
|
||||
fmt.Println("Error updating setting:", err)
|
||||
return
|
||||
}
|
||||
envVars["web"] = "restart"
|
||||
control()
|
||||
case "15":
|
||||
public.ReloadConfig()
|
||||
http := "http"
|
||||
if public.GetSettingIgnoreError("https") == "1" {
|
||||
http = "https"
|
||||
}
|
||||
|
||||
localIp, err := public.GetLocalIP()
|
||||
if err != nil {
|
||||
localIp = "0.0.0.0"
|
||||
}
|
||||
localAddr := fmt.Sprintf("%s://%s:%s%s", http, localIp, public.Port, public.Secure)
|
||||
publicIp, err := public.GetPublicIP()
|
||||
if err != nil {
|
||||
publicIp = "0.0.0.0"
|
||||
}
|
||||
publicAddr := fmt.Sprintf("%s://%s:%s%s", http, publicIp, public.Port, public.Secure)
|
||||
|
||||
s, err := public.NewSqlite("data/data.db", "")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
defer s.Close()
|
||||
s.TableName = "users"
|
||||
user, err := s.Where("id=1", []interface{}{}).Select()
|
||||
if err != nil {
|
||||
fmt.Println("Error selecting user:", err)
|
||||
return
|
||||
}
|
||||
if len(user) == 0 {
|
||||
fmt.Println("No user")
|
||||
return
|
||||
}
|
||||
username, ok := user[0]["username"].(string)
|
||||
if !ok {
|
||||
fmt.Println("Error getting username")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf(`
|
||||
外网面板地址: %s
|
||||
内网面板地址: %s
|
||||
用户名: %s
|
||||
密码: ********
|
||||
`,
|
||||
publicAddr, localAddr, username)
|
||||
default:
|
||||
fmt.Println("无效的命令")
|
||||
}
|
||||
}
|
||||
|
||||
func control() (err error) {
|
||||
pidStr, err := os.ReadFile(pidPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading pid file")
|
||||
return
|
||||
}
|
||||
err = godotenv.Write(envVars, envPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error writing to .env file")
|
||||
return
|
||||
}
|
||||
pid, err := strconv.Atoi(string(pidStr))
|
||||
if err != nil {
|
||||
fmt.Println("Error converting pid to int:", err)
|
||||
return
|
||||
}
|
||||
process, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
fmt.Println("Error finding process:", err)
|
||||
return
|
||||
}
|
||||
err = process.Signal(syscall.SIGHUP)
|
||||
if err != nil {
|
||||
fmt.Println("Error sending signal:", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func mainRun() {
|
||||
if checkRun() {
|
||||
pid := os.Getpid()
|
||||
os.WriteFile(pidPath, []byte(fmt.Sprintf("%d", pid)), 0644)
|
||||
defer os.Remove(pidPath)
|
||||
go func() {
|
||||
fmt.Println("web服务正在运行...")
|
||||
err := server.Run()
|
||||
if err != nil {
|
||||
fmt.Println("web服务在运行时遇到错误:", err)
|
||||
}
|
||||
fmt.Println("web服务已停止")
|
||||
}()
|
||||
fmt.Println("正在启动调度器...")
|
||||
go s.Start()
|
||||
fmt.Println("调度器启动成功")
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, syscall.SIGHUP)
|
||||
for {
|
||||
<-c
|
||||
run()
|
||||
}
|
||||
} else {
|
||||
fmt.Println("服务已经启动")
|
||||
}
|
||||
}
|
||||
|
||||
func checkRun() bool {
|
||||
_, err := os.Stat(pidPath)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
pid, err := os.ReadFile(pidPath)
|
||||
if err != nil {
|
||||
os.Remove(pidPath)
|
||||
return true
|
||||
}
|
||||
pidInt, err := strconv.Atoi(string(pid))
|
||||
if err != nil {
|
||||
fmt.Println("Error converting pid to int")
|
||||
os.Remove(pidPath)
|
||||
return true
|
||||
}
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return isProcessAliveWindows(pidInt)
|
||||
default:
|
||||
return isProcessAliveUnix(pidInt)
|
||||
}
|
||||
}
|
||||
|
||||
// Unix/Linux/macOS 检测
|
||||
func isProcessAliveUnix(pid int) bool {
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
// 发送 0 信号,不会杀死,只是检测
|
||||
err = proc.Signal(syscall.Signal(0))
|
||||
return err != nil
|
||||
}
|
||||
|
||||
// Windows 检测(遍历进程表)
|
||||
func isProcessAliveWindows(pid int) bool {
|
||||
processList, err := ps.Processes()
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
for _, p := range processList {
|
||||
if p.Pid() == pid {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func run() {
|
||||
envVars, err := godotenv.Read(envPath)
|
||||
if err != nil {
|
||||
fmt.Println("Error reading .env file")
|
||||
}
|
||||
switch envVars["web"] {
|
||||
case "start":
|
||||
go func() {
|
||||
fmt.Println("web服务正在运行...")
|
||||
err := server.Run()
|
||||
if err != nil {
|
||||
fmt.Println("web服务在运行时遇到错误:", err)
|
||||
return
|
||||
// errchan <- err
|
||||
}
|
||||
fmt.Println("web服务已停止")
|
||||
}()
|
||||
case "stop":
|
||||
public.ShutdownFunc()
|
||||
fmt.Println("web服务已停止")
|
||||
case "restart":
|
||||
public.ShutdownFunc()
|
||||
go func() {
|
||||
fmt.Println("web服务正在运行...")
|
||||
err := server.Run()
|
||||
if err != nil {
|
||||
fmt.Println("web服务在运行时遇到错误:", err)
|
||||
return
|
||||
}
|
||||
fmt.Println("web服务已停止")
|
||||
}()
|
||||
}
|
||||
switch envVars["scheduler"] {
|
||||
case "start":
|
||||
go s.Start()
|
||||
case "stop":
|
||||
s.Stop()
|
||||
case "restart":
|
||||
s.Restart()
|
||||
}
|
||||
}
|
||||
166
script/allinssl.sh
Normal file
166
script/allinssl.sh
Normal file
@@ -0,0 +1,166 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 设置工作目录
|
||||
WORK_DIR="/www/allinssl"
|
||||
|
||||
# 检查工作目录是否存在
|
||||
if [ ! -d "$WORK_DIR" ]; then
|
||||
echo "目录 $WORK_DIR 不存在,正在创建..."
|
||||
mkdir -p "$WORK_DIR"
|
||||
fi
|
||||
|
||||
# 切换到工作目录
|
||||
cd "$WORK_DIR" || exit
|
||||
|
||||
# 检查二进制文件是否存在
|
||||
BINARY_FILE="allinssl"
|
||||
if [ ! -f "$BINARY_FILE" ]; then
|
||||
echo "二进制文件 $BINARY_FILE 不存在,请确保已编译并放置在 $WORK_DIR 目录下。"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "=========== ALLinSSL 控制台 ==========="
|
||||
echo "1: 启动服务"
|
||||
echo "2: 停止服务"
|
||||
echo "3: 重启服务"
|
||||
echo "4: 修改安全入口"
|
||||
echo "5: 修改用户名"
|
||||
echo "6: 修改密码"
|
||||
echo "7: 修改端口"
|
||||
echo "8: 关闭web服务"
|
||||
echo "9: 开启web服务"
|
||||
echo "10: 重启web服务"
|
||||
echo "11: 关闭后台自动调度"
|
||||
echo "12: 开启后台自动调度"
|
||||
echo "13: 重启后台自动调度"
|
||||
echo "14: 关闭https"
|
||||
echo "15: 获取面板地址"
|
||||
echo "16: 更新ALLinSSL到最新版本(文件覆盖安装)"
|
||||
echo "17: 卸载ALLinSSL"
|
||||
echo "========================================"
|
||||
read -p "请输入操作编号 (1-17): " user_input
|
||||
|
||||
if [[ ! "$user_input" =~ ^([1-9]|1[0-7])$ ]]; then
|
||||
echo "❌ 非法操作编号:$user_input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -- "$user_input"
|
||||
fi
|
||||
|
||||
function update_allinssl() {
|
||||
local url="http://192.168.69.167:8888/down/Kuguq0edGNRA.tar.gz"
|
||||
local target_dir="${WORK_DIR}"
|
||||
local temp_file=$(mktemp)
|
||||
local original_filename temp_file
|
||||
# 创建目录
|
||||
create_directory() {
|
||||
echo -e "${BLUE}${GEAR} Creating directory...${NC}"
|
||||
${SUDO} mkdir -p "$target_dir" || {
|
||||
echo -e "${RED}${CROSS} Error: Failed to create directory $target_dir${NC}"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 下载文件
|
||||
download_file() {
|
||||
echo -e "${BLUE}${DOWNLOAD} Downloading from $url...${NC}"
|
||||
|
||||
# 获取原始文件名(去除URL参数)
|
||||
original_filename=$(basename "$url" | cut -d '?' -f1)
|
||||
[[ -z "$original_filename" ]] && {
|
||||
echo -e "${RED}${CROSS} Error: Cannot determine filename from URL${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
temp_file="${temp_dir}/${original_filename}"
|
||||
|
||||
wget --no-check-certificate -O "$temp_file" "$url" || {
|
||||
echo -e "${RED}${CROSS} Error: Download failed${NC}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo -e "${BLUE}⚙️ 保存文件名: ${original_filename}${NC}"
|
||||
}
|
||||
|
||||
# 解压文件
|
||||
extract_file() {
|
||||
echo -e "${BLUE}${PACKAGE} Extracting to $target_dir...${NC}"
|
||||
case "$temp_file" in
|
||||
*.tar.gz|*.tgz)
|
||||
${SUDO} tar xzf "$temp_file" -C "$target_dir"
|
||||
;;
|
||||
*.zip)
|
||||
${SUDO} unzip -q "$temp_file" -d "$target_dir"
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}${CROSS} 不支持的压缩格式: ${temp_file##*.}${NC}"
|
||||
exit 1
|
||||
;;
|
||||
esac || {
|
||||
echo -e "${RED}${CROSS} 解压失败,请检查文件完整性${NC}"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
set_cloudc() {
|
||||
echo -e "${BLUE}${GEAR} Setting up ALLinSSL...${NC}"
|
||||
chmod 755 "$target_dir/allinssl"
|
||||
chmod +x "$target_dir/allinssl"
|
||||
chmod 755 "$target_dir/allinssl.sh"
|
||||
chmod +x "$target_dir/allinssl.sh"
|
||||
ln -s "$target_dir/allinssl.sh" /usr/bin/allinssl
|
||||
cd $target_dir || exit 1
|
||||
allinssl 3
|
||||
}
|
||||
|
||||
# 清理临时文件
|
||||
cleanup() {
|
||||
rm -f "$temp_file"
|
||||
echo -e "${GREEN}${CLEAN} Temporary files cleaned${NC}"
|
||||
}
|
||||
|
||||
# 执行安装流程
|
||||
if create_directory && download_file && extract_file; then
|
||||
# copy_config
|
||||
set_cloudc
|
||||
cleanup
|
||||
echo -e "${GREEN}${CHECK} Successfully installed to $target_dir${NC}"
|
||||
return 0
|
||||
else
|
||||
cleanup
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 判断特殊操作
|
||||
if [ "$1" == "16" ]; then
|
||||
echo "⚠️ 正在准备执行 ALLinSSL 更新操作..."
|
||||
read -p "是否继续更新?(y/n): " confirm
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
echo "已取消更新操作。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 可在此插入更新逻辑(如下载新版、替换二进制等)
|
||||
update_allinssl
|
||||
echo "✅ 已确认,执行更新操作..."
|
||||
exit 0
|
||||
elif [ "$1" == "17" ]; then
|
||||
echo "⚠️ 正在准备执行 ALLinSSL 卸载操作..."
|
||||
read -p "是否确认卸载 ALLinSSL?这将删除相关组件,此操作不可逆!(y/n): " confirm
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
echo "已取消卸载操作。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 可在此插入卸载逻辑(如删除文件、清除服务等)
|
||||
echo "✅ 已确认,执行卸载操作..."
|
||||
# 删除工作目录
|
||||
rm -rf "$WORK_DIR"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 运行二进制文件
|
||||
"./$BINARY_FILE" "$@"
|
||||
Reference in New Issue
Block a user