Add files via upload

This commit is contained in:
zhangch-dev
2025-05-06 18:51:53 +08:00
committed by GitHub
parent 0ccc0620f3
commit e7b5ac85e1
52 changed files with 7266 additions and 0 deletions

135
backend/app/api/access.go Normal file
View 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
View 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
View 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
}

View 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
View 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, "发送成功")
}

View 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, "正在重启...")
}

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

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

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

View 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 = &registration.Resource{}
if err := json.Unmarshal(regBytes, reg); err != nil {
return nil, err
}
}
return &MyUser{
Email: email,
key: privateKey,
Registration: reg,
}, nil
}

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

View 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
}
// ========================================================

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

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

File diff suppressed because one or more lines are too long

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

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

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

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

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

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

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

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

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

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

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

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

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

View 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]
}

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

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

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

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

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

View 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

File diff suppressed because it is too large Load Diff

181
backend/public/utils.go Normal file
View 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
}

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

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

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

View 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
View 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
View 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
View 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" "$@"