diff --git a/backend/app/api/setting.go b/backend/app/api/setting.go index f69c8ad..a6bed70 100644 --- a/backend/app/api/setting.go +++ b/backend/app/api/setting.go @@ -4,6 +4,8 @@ import ( "ALLinSSL/backend/internal/setting" "ALLinSSL/backend/public" "github.com/gin-gonic/gin" + "os" + "path/filepath" ) func GetSetting(c *gin.Context) { @@ -47,3 +49,40 @@ func GetVersion(c *gin.Context) { } public.SuccessData(c, data, 0) } + +func DownloadData(c *gin.Context) { + dbPath := "data/data.db" + dbName := filepath.Base(dbPath) + + // 设置响应头,让浏览器下载文件 + c.Header("Content-Type", "application/octet-stream") + c.Header("Content-Disposition", "attachment; filename=\""+dbName+"\"") + c.File(dbPath) +} + +func UploadData(c *gin.Context) { + file, err := c.FormFile("file") + if err != nil { + public.FailMsg(c, "文件上传失败: "+err.Error()) + return + } + // 检查文件类型 + if filepath.Ext(file.Filename) != ".db" { + public.FailMsg(c, "只允许上传 .db 文件") + return + } + // 备份源文件 + // 修改源文件名为 data.db.bak + err = os.Rename("data/data.db", "data/data.db.bak") + if err != nil { + public.FailMsg(c, "备份源文件失败: "+err.Error()) + return + } + + if err := c.SaveUploadedFile(file, "data/data.db"); err != nil { + public.FailMsg(c, "保存文件失败: "+err.Error()) + return + } + + public.SuccessMsg(c, "数据上传成功") +} diff --git a/backend/internal/cert/apply/apply.go b/backend/internal/cert/apply/apply.go index 86e117f..dc89edc 100644 --- a/backend/internal/cert/apply/apply.go +++ b/backend/internal/cert/apply/apply.go @@ -4,7 +4,9 @@ import ( "ALLinSSL/backend/internal/access" "ALLinSSL/backend/internal/cert" "ALLinSSL/backend/internal/cert/apply/lego/jdcloud" + "ALLinSSL/backend/internal/cert/apply/lego/webhook" "ALLinSSL/backend/public" + "crypto/tls" "encoding/json" "fmt" azcorecloud "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" @@ -202,6 +204,10 @@ func GetDNSProvider(providerName string, creds map[string]string, httpClient *ht config.SecretKey = creds["secret_key"] config.PropagationTimeout = maxWait return constellix.NewDNSProviderConfig(config) + case "webhook": + config := webhook.NewConfig(creds) + config.PropagationTimeout = maxWait + return webhook.NewDNSProviderConfig(config) default: return nil, fmt.Errorf("不支持的 DNS Provider: %s", providerName) @@ -495,7 +501,9 @@ func Apply(cfg map[string]any, logger *public.Logger) (map[string]any, error) { } httpClient = &http.Client{ Transport: &http.Transport{ - Proxy: http.ProxyURL(proxyURL), + Proxy: http.ProxyURL(proxyURL), + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableKeepAlives: true, }, Timeout: 30 * time.Second, } diff --git a/backend/internal/cert/apply/lego/webhook/lego.go b/backend/internal/cert/apply/lego/webhook/lego.go new file mode 100644 index 0000000..bca33cc --- /dev/null +++ b/backend/internal/cert/apply/lego/webhook/lego.go @@ -0,0 +1,77 @@ +package webhook + +import ( + "ALLinSSL/backend/public" + "fmt" + "github.com/go-acme/lego/v4/challenge/dns01" + "time" +) + +var configData string + +type Config struct { + WebhookConfig *public.WebhookConfig + + PropagationTimeout time.Duration + PollingInterval time.Duration + TTL int + HTTPTimeout time.Duration +} + +type DNSProvider struct { + config *Config +} + +func NewConfig(WebhookConfigStr map[string]string) *Config { + fmt.Println(WebhookConfigStr) + + WebhookConfig := &public.WebhookConfig{ + Url: WebhookConfigStr["url"], + Data: WebhookConfigStr["data"], + Method: WebhookConfigStr["method"], + Headers: WebhookConfigStr["headers"], + IgnoreSSL: WebhookConfigStr["ignore_ssl"] == "true", + } + fmt.Println(WebhookConfig.Url) + + return &Config{ + WebhookConfig: WebhookConfig, + TTL: 600, + PropagationTimeout: dns01.DefaultPropagationTimeout, + PollingInterval: dns01.DefaultPollingInterval, + HTTPTimeout: 30 * time.Second, + } +} + +func NewDNSProviderConfig(config *Config) (*DNSProvider, error) { + if config == nil { + return nil, fmt.Errorf("配置不能为空") + } + return &DNSProvider{config: config}, nil +} + +func (d *DNSProvider) Timeout() (timeout, interval time.Duration) { + return d.config.PropagationTimeout, d.config.PollingInterval +} + +func (d *DNSProvider) Present(domain, token, keyAuth string) error { + fmt.Println(d.config.WebhookConfig.Url) + configData = d.config.WebhookConfig.Data + return d.send(domain, token, keyAuth, "present") +} + +func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { + d.config.WebhookConfig.Data = configData + return d.send(domain, token, keyAuth, "cleanup") +} + +func (d *DNSProvider) send(domain, token, keyAuth, action string) error { + info := dns01.GetChallengeInfo(domain, keyAuth) + + data, err := public.ReplaceJSONPlaceholders(d.config.WebhookConfig.Data, map[string]interface{}{"domain": info.EffectiveFQDN, "token": token, "keyAuth": info.Value, "action": action}) + if err != nil { + return fmt.Errorf("替换JSON占位符失败: %w", err) + } + d.config.WebhookConfig.Data = data + return d.config.WebhookConfig.Send() +} diff --git a/backend/internal/cert/deploy/1panel.go b/backend/internal/cert/deploy/1panel.go index 26fbca6..ae8ecc2 100644 --- a/backend/internal/cert/deploy/1panel.go +++ b/backend/internal/cert/deploy/1panel.go @@ -91,7 +91,8 @@ func Request1panel(data *map[string]any, method, providerID, requestUrl string) ignoreSsl = true } tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + DisableKeepAlives: true, } client := &http.Client{Transport: tr} @@ -269,7 +270,7 @@ func OnePanelSiteList(providerID string) ([]response.AccessSiteList, error) { if err != nil { return nil, fmt.Errorf("获取网站列表失败 %v", err) } - + var result []response.AccessSiteList sites, ok := siteList["data"].(map[string]any)["items"].([]any) if !ok { diff --git a/backend/internal/cert/deploy/btpanel.go b/backend/internal/cert/deploy/btpanel.go index 72a0d50..5b90469 100644 --- a/backend/internal/cert/deploy/btpanel.go +++ b/backend/internal/cert/deploy/btpanel.go @@ -65,7 +65,8 @@ func RequestBt(data *url.Values, method, providerID, requestUrl string) (map[str ignoreSsl = true } tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + DisableKeepAlives: true, } client := &http.Client{Transport: tr} diff --git a/backend/internal/cert/deploy/btwaf.go b/backend/internal/cert/deploy/btwaf.go index 99145e7..2893a7b 100644 --- a/backend/internal/cert/deploy/btwaf.go +++ b/backend/internal/cert/deploy/btwaf.go @@ -65,7 +65,8 @@ func RequestBtWaf(data *map[string]any, method, providerID, requestUrl string) ( ignoreSsl = true } tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + DisableKeepAlives: true, } client := &http.Client{Transport: tr} @@ -207,4 +208,4 @@ func BtWafAPITest(providerID string) error { return fmt.Errorf("测试请求失败: %v", err) } return nil -} \ No newline at end of file +} diff --git a/backend/internal/cert/deploy/deploy.go b/backend/internal/cert/deploy/deploy.go index ff8245f..49ad7e1 100644 --- a/backend/internal/cert/deploy/deploy.go +++ b/backend/internal/cert/deploy/deploy.go @@ -5,6 +5,7 @@ import ( "ALLinSSL/backend/internal/cert/deploy/doge" "ALLinSSL/backend/internal/cert/deploy/lecdn" "ALLinSSL/backend/internal/cert/deploy/plugin" + "ALLinSSL/backend/internal/cert/deploy/webhook" "ALLinSSL/backend/public" "fmt" ) @@ -106,6 +107,9 @@ func Deploy(cfg map[string]any, logger *public.Logger) error { case "plugin": logger.Debug("使用插件部署...") return plugin.Deploy(cfg, logger) + case "webhook": + logger.Debug("通过Webhook推送证书...") + return webhook.Deploy(cfg) default: return fmt.Errorf("不支持的部署: %s", providerName) } diff --git a/backend/internal/cert/deploy/lecdn/lecdn.go b/backend/internal/cert/deploy/lecdn/lecdn.go index a526317..f888ee1 100644 --- a/backend/internal/cert/deploy/lecdn/lecdn.go +++ b/backend/internal/cert/deploy/lecdn/lecdn.go @@ -53,7 +53,8 @@ func requestLecdn(url, method, token string, params map[string]any, ignoreSsl bo client := &http.Client{ Transport: &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + DisableKeepAlives: true, }, } diff --git a/backend/internal/cert/deploy/safelinewaf.go b/backend/internal/cert/deploy/safelinewaf.go index c91e2a2..6bc90b1 100644 --- a/backend/internal/cert/deploy/safelinewaf.go +++ b/backend/internal/cert/deploy/safelinewaf.go @@ -50,7 +50,8 @@ func RequestSafeLineWaf(data *map[string]any, method, providerID, requestUrl str ignoreSsl = true } tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + TLSClientConfig: &tls.Config{InsecureSkipVerify: ignoreSsl}, + DisableKeepAlives: true, } client := &http.Client{Transport: tr} @@ -211,4 +212,4 @@ func SafeLineAPITest(providerID string) error { return fmt.Errorf("测试请求失败: %v", err) } return nil -} \ No newline at end of file +} diff --git a/backend/internal/cert/deploy/webhook/deploy.go b/backend/internal/cert/deploy/webhook/deploy.go new file mode 100644 index 0000000..e824454 --- /dev/null +++ b/backend/internal/cert/deploy/webhook/deploy.go @@ -0,0 +1,57 @@ +package webhook + +import ( + "ALLinSSL/backend/internal/access" + "ALLinSSL/backend/public" + "encoding/json" + "fmt" + "strconv" +) + +func Deploy(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 public.WebhookConfig + err = json.Unmarshal([]byte(providerConfigStr), &providerConfig) + if err != nil { + return err + } + + certStr, ok := cert["cert"].(string) + if !ok || certStr == "" { + return fmt.Errorf("cert is required and must be a string") + } + keyStr, ok := cert["key"].(string) + if !ok || keyStr == "" { + return fmt.Errorf("key is required and must be a string") + } + + data, err := public.ReplaceJSONPlaceholders(providerConfig.Data, map[string]interface{}{"key": keyStr, "cert": certStr}) + if err != nil { + return fmt.Errorf("替换JSON占位符失败: %w", err) + } + providerConfig.Data = data + + return providerConfig.Send() +} diff --git a/backend/internal/monitor/check.go b/backend/internal/monitor/check.go index d3da881..ac25ebd 100644 --- a/backend/internal/monitor/check.go +++ b/backend/internal/monitor/check.go @@ -69,8 +69,8 @@ func Check(certs []*x509.Certificate, host string, advanceDay int) (result *Cert } result.CommonName = leafCert.Subject.CommonName - result.NotBefore = leafCert.NotBefore.Format("2006-01-02 15:04:05") - result.NotAfter = leafCert.NotAfter.Format("2006-01-02 15:04:05") + result.NotBefore = leafCert.NotBefore.In(time.Local).Format("2006-01-02 15:04:05") + result.NotAfter = leafCert.NotAfter.In(time.Local).Format("2006-01-02 15:04:05") result.DaysLeft = int(leafCert.NotAfter.Sub(time.Now()).Hours() / 24) result.SANs = strings.Join(leafCert.DNSNames, ",") result.SignatureAlgo = leafCert.SignatureAlgorithm.String() @@ -141,8 +141,9 @@ func CheckHttps(target string, advanceDay int) (result *CertInfo, err error) { TLSClientConfig: &tls.Config{ InsecureSkipVerify: true, }, + DisableKeepAlives: true, }, - //Timeout: 5 * time.Second, + Timeout: 30 * time.Second, } // 发送请求 diff --git a/backend/internal/report/webhook.go b/backend/internal/report/webhook.go index 618e5b1..1de4ac6 100644 --- a/backend/internal/report/webhook.go +++ b/backend/internal/report/webhook.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/go-resty/resty/v2" "net/http" - "regexp" "strings" "time" ) @@ -184,7 +183,7 @@ func NotifyWebHook(params map[string]any) error { if err != nil { return fmt.Errorf("解析配置失败: %v", err) } - config.Data, err = ReplaceJSONPlaceholders(config.Data, params) + config.Data, err = public.ReplaceJSONPlaceholders(config.Data, params) if err != nil { return fmt.Errorf("替换JSON占位符失败: %w", err) } @@ -197,16 +196,3 @@ func NotifyWebHook(params map[string]any) error { } return nil } - -func ReplaceJSONPlaceholders(jsonStr string, vars map[string]any) (string, error) { - re := regexp.MustCompile(`__([a-zA-Z0-9_]+)__`) - result := re.ReplaceAllStringFunc(jsonStr, func(match string) string { - key := re.FindStringSubmatch(match)[1] - if val, ok := vars[key]; ok { - return fmt.Sprintf("%v", val) // 将 any 类型转换为字符串 - } - return match // 未匹配到变量则保留原样 - }) - - return result, nil -} diff --git a/backend/internal/report/workwx.go b/backend/internal/report/workwx.go index 6174c96..53abe8e 100644 --- a/backend/internal/report/workwx.go +++ b/backend/internal/report/workwx.go @@ -1,6 +1,7 @@ package report import ( + "ALLinSSL/backend/public" "encoding/json" "fmt" "io" @@ -75,7 +76,7 @@ func NotifyWorkWx(params map[string]any) error { } ` } - msg, err := ReplaceJSONPlaceholders(config["data"], params) + msg, err := public.ReplaceJSONPlaceholders(config["data"], params) if err != nil { return fmt.Errorf("替换JSON占位符失败: %v", err) } diff --git a/backend/internal/setting/setting.go b/backend/internal/setting/setting.go index 8c0997c..b52ea36 100644 --- a/backend/internal/setting/setting.go +++ b/backend/internal/setting/setting.go @@ -16,13 +16,14 @@ import ( ) 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"` + 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"` + PluginPath string `json:"plugin_path" form:"plugin_path"` } func Get() (Setting, error) { @@ -57,6 +58,7 @@ func Get() (Setting, error) { } username := data[0]["username"].(string) setting.Username = username + setting.PluginPath = public.GetSettingIgnoreError("plugin_dir") return setting, nil } @@ -108,6 +110,9 @@ func Save(setting *Setting) error { public.TimeOut = setting.Timeout restart = true } + if setting.PluginPath != "" && setting.PluginPath != public.GetSettingIgnoreError("plugin_dir") { + public.UpdateSetting("plugin_dir", setting.PluginPath) + } if setting.Https != "" { if setting.Https == "1" { if setting.Key == "" || setting.Cert == "" { @@ -192,13 +197,16 @@ func GetVersion() (map[string]string, error) { update := "0" newVersionObj, err := http.Get("https://download.allinssl.com/version.json") if err != nil { - return map[string]string{ - "version": version, - "new_version": version, - "update": update, - "log": "", - "date": "", - }, nil + newVersionObj, err = http.Get("https://node1.allinssl.com/version.json") + if err != nil { + return map[string]string{ + "version": version, + "new_version": version, + "update": update, + "log": "", + "date": "", + }, nil + } } defer newVersionObj.Body.Close() diff --git a/backend/public/webhook.go b/backend/public/webhook.go new file mode 100644 index 0000000..c20051d --- /dev/null +++ b/backend/public/webhook.go @@ -0,0 +1,147 @@ +package public + +import ( + "crypto/tls" + "encoding/json" + "fmt" + "github.com/go-resty/resty/v2" + "net/http" + "regexp" + "strconv" + "strings" + "time" +) + +type WebhookConfig struct { + Url string `json:"url"` + Data string `json:"data,omitempty"` + Method string `json:"method,omitempty"` + Headers string `json:"headers,omitempty"` + IgnoreSSL bool `json:"ignore_ssl,omitempty"` +} + +func (w *WebhookConfig) Send() error { + // 确定HTTP方法 + method := strings.ToUpper(w.Method) + if method == "" { + method = http.MethodPost // 默认使用POST方法 + } + + client := resty.New() + client.SetTimeout(30 * time.Second) + + if w.IgnoreSSL { + client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}) + } + + req := client.R() + + // 设置请求头 + if w.Headers != "" { + reqHeader, err := w.ParseHeaders(w.Headers) + if err != nil { + return fmt.Errorf("解析请求头错误: %w", err) + } + req.Header = reqHeader + } + + switch method { + case http.MethodPost: + { + contentType := req.Header.Get("application/json") + if contentType == "" { + contentType = "application/json" + } + switch contentType { + case "application/json": + req.SetHeader("Content-Type", "application/json") + var reqData interface{} + err := json.Unmarshal([]byte(w.Data), &reqData) + if err != nil { + return fmt.Errorf("webhook数据解析失败err: %w", err) + } + + req.SetBody(reqData) + case "application/x-www-form-urlencoded": + req.SetHeader("Content-Type", "application/x-www-form-urlencoded") + reqData := make(map[string]string) + err := json.Unmarshal([]byte(w.Data), &reqData) + if err != nil { + return fmt.Errorf("webhook数据解析失败err: %w", err) + } + req.SetFormData(reqData) + case "multipart/form-data": + req.SetHeader("Content-Type", "multipart/form-data") + reqData := make(map[string]string) + err := json.Unmarshal([]byte(w.Data), &reqData) + if err != nil { + return fmt.Errorf("webhook数据解析失败err: %w", err) + } + req.SetMultipartFormData(reqData) + } + } + case http.MethodGet: + { + reqData := make(map[string]string) + err := json.Unmarshal([]byte(w.Data), &reqData) + if err != nil { + return fmt.Errorf("webhook数据解析失败err: %w", err) + } + req.SetQueryParams(reqData) + } + default: + return fmt.Errorf("暂不支持的HTTP方法: %s", method) + } + + // 发送请求 + resp, err := req.Execute(method, w.Url) + if err != nil { + return fmt.Errorf("webhook请求失败: %w", err) + } + + // 处理响应 + if resp.IsError() { + return fmt.Errorf("webhook返回错误状态码: %d, msg: %s", resp.StatusCode(), resp.String()) + } + + return nil +} + +func (w *WebhookConfig) ParseHeaders(headerStr string) (http.Header, error) { + headers := make(http.Header) + lines := strings.Split(headerStr, "\n") + + for i, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("解析请求头错误 第%d行: %s", i+1, line) + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + if key == "" || value == "" { + return nil, fmt.Errorf("请求头Key第%d行为空", i+1) + } + canonicalKey := http.CanonicalHeaderKey(key) + headers.Add(canonicalKey, value) + } + + return headers, nil +} + +func ReplaceJSONPlaceholders(jsonStr string, vars map[string]any) (string, error) { + re := regexp.MustCompile(`__([a-zA-Z0-9_]+)__`) + result := re.ReplaceAllStringFunc(jsonStr, func(match string) string { + key := re.FindStringSubmatch(match)[1] + if val, ok := vars[key]; ok { + escaped := strconv.Quote(fmt.Sprintf("%v", val)) // 将 any 类型转换为字符串 + return escaped[1 : len(escaped)-1] + } + return match // 未匹配到变量则保留原样 + }) + + return result, nil +} diff --git a/backend/route/route.go b/backend/route/route.go index 3a3feac..30867c6 100644 --- a/backend/route/route.go +++ b/backend/route/route.go @@ -98,6 +98,8 @@ func Register(r *gin.Engine) { setting.POST("/shutdown", api.Shutdown) setting.POST("/restart", api.Restart) setting.POST("/get_version", api.GetVersion) + setting.GET("/download_data", api.DownloadData) + setting.POST("/upload_data", api.UploadData) } overview := v1.Group("/overview") {