Compare commits

2 Commits

Author SHA1 Message Date
cnbugs 3ce7c99bfe fix: resolve merge conflict in services/acme.go (debug logging + TrimSpace) 2026-07-25 00:39:35 +08:00
Your Name 9bae6b1d9b youhua 2026-07-25 00:09:24 +08:00
17 changed files with 340 additions and 19 deletions
+2
View File
@@ -0,0 +1,2 @@
PORT=8080
ADMIN_USER=admin
Executable
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+3 -1
View File
@@ -35,7 +35,9 @@ func CredentialTypeMeta(ctype CredentialType) []CredentialField {
} }
case CredentialCloudflare: case CredentialCloudflare:
return []CredentialField{ return []CredentialField{
{Key: "cf_api_token", Label: "API Token", Type: "password", Required: true}, {Key: "cf_api_token", Label: "API Token", Type: "password", Required: false},
{Key: "cf_email", Label: "Email (Global API Key 方式)", Type: "text", Required: false},
{Key: "cf_api_key", Label: "Global API Key", Type: "password", Required: false},
} }
case CredentialDNSPod: case CredentialDNSPod:
return []CredentialField{ return []CredentialField{
+37 -6
View File
@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv" "strconv"
"strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@@ -67,6 +68,11 @@ func (h *CredentialHandler) CreateCredential(c *gin.Context) {
return return
} }
// Trim whitespace from all values
for k, v := range testData {
testData[k] = strings.TrimSpace(v)
}
// Validate fields for the provider type // Validate fields for the provider type
fields := config.CredentialTypeMeta(req.Type) fields := config.CredentialTypeMeta(req.Type)
for _, f := range fields { for _, f := range fields {
@@ -78,10 +84,23 @@ func (h *CredentialHandler) CreateCredential(c *gin.Context) {
} }
} }
// Cloudflare: require either API Token or Email+Key
if req.Type == config.CredentialCloudflare {
hasToken := testData["cf_api_token"] != ""
hasEmailKey := testData["cf_email"] != "" && testData["cf_api_key"] != ""
if !hasToken && !hasEmailKey {
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写 API Token,或者同时填写 Email 和 Global API Key"})
return
}
}
// Re-serialize trimmed data
trimmedData, _ := json.Marshal(testData)
cred := &config.Credential{ cred := &config.Credential{
Name: req.Name, Name: req.Name,
Type: req.Type, Type: req.Type,
Data: req.Data, Data: string(trimmedData),
} }
if err := config.CredStore.Create(cred); err != nil { if err := config.CredStore.Create(cred); err != nil {
@@ -122,16 +141,28 @@ func (h *CredentialHandler) UpdateCredential(c *gin.Context) {
existing.Type = req.Type existing.Type = req.Type
} }
if req.Data != "" { if req.Data != "" {
// If it's the masked placeholder, keep existing data // If it's the masked placeholder, keep existing data unchanged
if req.Data == "********" { if req.Data != "********" {
existing.Data = existing.Data
} else {
var testData map[string]string var testData map[string]string
if err := json.Unmarshal([]byte(req.Data), &testData); err != nil { if err := json.Unmarshal([]byte(req.Data), &testData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "data must be valid JSON"}) c.JSON(http.StatusBadRequest, gin.H{"error": "data must be valid JSON"})
return return
} }
existing.Data = req.Data // Trim whitespace from all values
for k, v := range testData {
testData[k] = strings.TrimSpace(v)
}
// Cloudflare: require either API Token or Email+Key
if existing.Type == config.CredentialCloudflare {
hasToken := testData["cf_api_token"] != ""
hasEmailKey := testData["cf_email"] != "" && testData["cf_api_key"] != ""
if !hasToken && !hasEmailKey {
c.JSON(http.StatusBadRequest, gin.H{"error": "请填写 API Token,或者同时填写 Email 和 Global API Key"})
return
}
}
trimmedData, _ := json.Marshal(testData)
existing.Data = string(trimmedData)
} }
} }
+16 -8
View File
@@ -52,6 +52,8 @@ type DNSConfig struct {
// Cloudflare // Cloudflare
CFAPIToken string `json:"cf_api_token,omitempty"` CFAPIToken string `json:"cf_api_token,omitempty"`
CFEmail string `json:"cf_email,omitempty"`
CFAPIKey string `json:"cf_api_key,omitempty"`
// DNSPod // DNSPod
DNSPodID string `json:"dnspod_id,omitempty"` DNSPodID string `json:"dnspod_id,omitempty"`
@@ -84,8 +86,8 @@ func GetACMECertificate(cert *config.Certificate, cfg *config.Config) error {
// Use custom DNS timeout for better reliability // Use custom DNS timeout for better reliability
if err := client.Challenge.SetDNS01Provider(provider, if err := client.Challenge.SetDNS01Provider(provider,
dns01.AddRecursiveNameservers(dns01.ParseNameservers([]string{"8.8.8.8:53", "1.1.1.1:53"})), dns01.AddRecursiveNameservers(dns01.ParseNameservers([]string{"223.5.5.5:53", "119.29.29.29:53", "8.8.8.8:53"})),
dns01.AddDNSTimeout(15*time.Second), dns01.AddDNSTimeout(120*time.Second),
); err != nil { ); err != nil {
return fmt.Errorf("failed to set DNS-01 provider: %v", err) return fmt.Errorf("failed to set DNS-01 provider: %v", err)
} }
@@ -155,8 +157,8 @@ func RenewCertificate(cert *config.Certificate, cfg *config.Config) error {
// Use custom DNS timeout for better reliability // Use custom DNS timeout for better reliability
if err := client.Challenge.SetDNS01Provider(provider, if err := client.Challenge.SetDNS01Provider(provider,
dns01.AddRecursiveNameservers(dns01.ParseNameservers([]string{"8.8.8.8:53", "1.1.1.1:53"})), dns01.AddRecursiveNameservers(dns01.ParseNameservers([]string{"223.5.5.5:53", "119.29.29.29:53", "8.8.8.8:53"})),
dns01.AddDNSTimeout(15*time.Second), dns01.AddDNSTimeout(120*time.Second),
); err != nil { ); err != nil {
return fmt.Errorf("failed to set DNS-01 provider: %v", err) return fmt.Errorf("failed to set DNS-01 provider: %v", err)
} }
@@ -327,13 +329,19 @@ func getDNSProvider(cert *config.Certificate) (challenge.Provider, error) {
case "cloudflare": case "cloudflare":
cfg := cloudflareprov.NewDefaultConfig() cfg := cloudflareprov.NewDefaultConfig()
if dnsCfg.CFAPIToken != "" { token := strings.TrimSpace(dnsCfg.CFAPIToken)
prefix := dnsCfg.CFAPIToken if token != "" {
prefix := token
if len(prefix) > 8 { if len(prefix) > 8 {
prefix = prefix[:8] prefix = prefix[:8]
} }
log.Printf("Cloudflare token length: %d, prefix: %s, has_****: %v", len(dnsCfg.CFAPIToken), prefix, strings.Contains(dnsCfg.CFAPIToken, "****")) log.Printf("Cloudflare token length: %d, prefix: %s, has_****: %v", len(token), prefix, strings.Contains(token, "****"))
cfg.AuthToken = dnsCfg.CFAPIToken cfg.AuthToken = token
}
// Also support email + Global API Key auth
if dnsCfg.CFEmail != "" && dnsCfg.CFAPIKey != "" {
cfg.AuthEmail = strings.TrimSpace(dnsCfg.CFEmail)
cfg.AuthKey = strings.TrimSpace(dnsCfg.CFAPIKey)
} }
provider, err := cloudflareprov.NewDNSProviderConfig(cfg) provider, err := cloudflareprov.NewDNSProviderConfig(cfg)
if err != nil { if err != nil {
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIAvBPT8bw9RlsWdzY+Ga/bZcaGoRC1o9KQylxwMt2xpwoAoGCCqGSM49
AwEHoUQDQgAEtsNoIrvUk9RE8/tXhzlGTqOZH2oQjxrrlyvnVV7wj2YWo2Q5diNp
oL1EDmDWVBMBO6enEgGkU3OtNyqCmTFBKA==
-----END EC PRIVATE KEY-----
@@ -0,0 +1,6 @@
{
"body": {
"status": "valid"
},
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/3565382145"
}
@@ -0,0 +1,5 @@
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIF9G4xzfZxxH/8Zp+EabrGlDlH8s5ftlLFXqd5di4wIYoAoGCCqGSM49
AwEHoUQDQgAEo4RIUksl7FCK3+QnlsUSWEBSWuVPwWWsqGeQxXdGu8qqiNGtypXE
ESu0EWaXjQcbZE476xH67DDmtHsEcz0q8g==
-----END EC PRIVATE KEY-----
@@ -0,0 +1,6 @@
{
"body": {
"status": "valid"
},
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/3565389605"
}
+36
View File
@@ -0,0 +1,36 @@
[
{
"id": 3,
"created_at": "2026-07-25T00:06:36.635806194+08:00",
"updated_at": "2026-07-25T00:08:43.014181606+08:00",
"domain": "harbor.yunwei.blog",
"email": "a@qq.com",
"provider": "letsencrypt",
"challenge_type": "dns",
"dns_provider": "cloudflare",
"dns_config": "{\"cf_api_token\":\"cfut_VLxUPTOTAWBwTmY8WM4uuZYQW0iztWSdXEWhQYB348f7e25a\"}",
"credential_id": 1,
"status": "error",
"error_message": "failed to obtain certificate: error: one or more domains had a problem:\n[harbor.yunwei.blog] propagation: time limit exceeded: last error: authoritative nameservers: NS blakely.ns.cloudflare.com.:53 did not return the expected TXT record [fqdn: _acme-challenge.harbor.yunwei.blog., value: iLtFsQrgk5mSpKlWk69-KKxf0kBHCUK4PivtBRC-vNI]: \n",
"auto_renew": true,
"renew_days": 30
},
{
"id": 1,
"created_at": "2026-07-24T23:10:52.210274514+08:00",
"updated_at": "2026-07-25T00:02:51.1655217+08:00",
"domain": "*.k8s.yunwei.blog",
"email": "a@qq.com",
"provider": "letsencrypt",
"challenge_type": "dns",
"dns_provider": "cloudflare",
"dns_config": "{\"cf_api_key\":\"6eabf25cb487d39f166c3ff4dc3145b5c5fb3\",\"cf_email\":\"mrtom0506@gmail.com\"}",
"credential_id": 1,
"status": "active",
"cert_url": "https://acme-v02.api.letsencrypt.org/acme/cert/06f366c19b0ae2a6bfdedfbfcdecbc5a0f4a",
"expires_at": "2026-10-22T15:04:18Z",
"last_renewed_at": "2026-07-25T00:02:51.165423851+08:00",
"auto_renew": true,
"renew_days": 30
}
]
@@ -0,0 +1,64 @@
-----BEGIN CERTIFICATE-----
MIIE2jCCAsKgAwIBAgIQTr0klH4k05SALYSlL9WzGTANBgkqhkiG9w0BAQsFADAu
MQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZUjAe
Fw0yNTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYw
FAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZUjIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDZ0LxwBppqh84luqMerV/eeL/fXQ7mLQQv1Lnp
WKZbyvGpx6wh6AfnslAnF6ewTkcHA+gSOoBvm3Dfm06AuGiF+KRut4fAcowqnAQQ
CW98+QPP/eOv/wug7Iyk4NkOxf2I6g2f55T6nJoOTLFcukeRq80JGQEYan+dPFr9
OGUgQK2hGKgNkW87pappsOAuUJcroYhRt5uUis4qaZireiseu32gzDJNBAiKtsvd
6HX4v25bpkRNcS/B/Gtc9kVbUpD+2PLPxdei3Tim55k4tfAEXwD2qyiPTxrTNq6l
N+AMr5g2c1dNqkOTwjxeV6L5lpP1rGiYvLnRaPlOqyZRPW+5AgMBAAGjge4wgesw
DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBIGA1UdEwEB/wQI
MAYBAf8CAQAwHQYDVR0OBBYEFEAVLSZ57TIgnt+ach3WMh+BDIEMMB8GA1UdIwQY
MBaAFN7nW2DQIm1AKH0/DQH+pLVStFGUMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEF
BQcwAoYWaHR0cDovL3lyLmkubGVuY3Iub3JnLzATBgNVHSAEDDAKMAgGBmeBDAEC
ATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veXIuYy5sZW5jci5vcmcvMA0GCSqG
SIb3DQEBCwUAA4ICAQB0ZUQWZ9/Yn9COEpo+JfecMnB0h0vwDm/M66IqXqw3LoaL
mx9lZvRTeDIS67PUeI3yCA2W6PKRD0/FE/G57lOmS+Xy5AaaL00ICGOqjNcCaMWW
8o8nevHOd4i4lqgtznE/28QwlcdJyF8yBiWHpnyjhEpmNWJURgOCOg2xpwRMBCsj
MScqYPtOhBeuYQvSwAEeTML2Ukh6uGuX4E14q65Ja8cdjF5bAldnP1eE4FBaAwsZ
G2fOqqrKV03Y85Nw2btedP1AtliQuJZs/Jo/gXxXdc7LrH3McgnpnbTiAncX7yES
hP6kzQejllqMCIt52HOjxDGWafS7Xw+DKwqmH+Eqy8dcbOuag/1AYlQoKNVK3F5q
Hh6tEDiMqQcLIibGKteE6iHo4A/bIScbzrhXUYuism42ZYzmc48FMVIH3qy4L84E
TdAH2gtxw0PAhvRVXp8HP7wfngpzsN/8xOTpeRSbM4+Qbc56G6+Bifmv6sk1ieQb
NA3wJdl4DDUuQSV8hBgx6zoI1ZSGORprDFux7c6rhc77QZMSRrEgomBeklervEve
86ylWmZ3WWHV6RLMi8xNvjd71r4EPIGgY7BZU/VPBkq+uA7Gb6mbJnFgV43uh3xy
LRFgxIAphIukwTGSMZZR+AI+Qnp0BYTWovHXozOf3H8r6hozEoT02JHn0AeTfA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
@@ -0,0 +1,93 @@
-----BEGIN CERTIFICATE-----
MIIE/TCCA+WgAwIBAgISBvNmwZsK4qa/3t+/zey8Wg9KMA0GCSqGSIb3DQEBCwUA
MDMxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQD
EwNZUjIwHhcNMjYwNzI0MTUwNDE5WhcNMjYxMDIyMTUwNDE4WjAcMRowGAYDVQQD
DBEqLms4cy55dW53ZWkuYmxvZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
ggEBANMWZF5U34pLTRMceClLMSZhlGasXb6/25sO6aRUJ8gPIT/pKydq2f2tyXTd
GSD7XljUYJLDHzlEKQykzfFmVqwGoEEMLiycwZvynnIqhG+xKb3v7cKC6ZYRu8HJ
kzfn654owO7zUAPgBJj0OZxKz7xNf4ezclzX4F6rY+rg0L/B7U8hHSwuoyJclSvX
8bdy/AQ0TGS3+DPU6JKO1teub3yJtgh+s/37Pag99msL6nR/0Yy/aAdUn/9g8jLz
QotNIFNy4kD8D209Z2BmUHkssQUt+9Q9QShfO09M/fCRUeq5WU5OPpDku7cBS83X
/nfE6G4oL1+ho2a7xVwfz4bk9LcCAwEAAaOCAiAwggIcMA4GA1UdDwEB/wQEAwIF
oDATBgNVHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRi
t3Gdgnf4n0DVXGx/A6TPpo5W/DAfBgNVHSMEGDAWgBRAFS0mee0yIJ7fmnId1jIf
gQyBDDAzBggrBgEFBQcBAQQnMCUwIwYIKwYBBQUHMAKGF2h0dHA6Ly95cjIuaS5s
ZW5jci5vcmcvMBwGA1UdEQQVMBOCESouazhzLnl1bndlaS5ibG9nMBMGA1UdIAQM
MAowCAYGZ4EMAQIBMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly95cjIuYy5sZW5j
ci5vcmcvNzUuY3JsMIIBDQYKKwYBBAHWeQIEAgSB/gSB+wD5AHYA1219ENGn9XfC
x+lf1wC/+YLJM1pl4dCzAXMXwMjFaXcAAAGflN14PgAABAMARzBFAiAvOKW3dS0K
rOjEFyssS6V75ZIQzDFFu7VhUXuvpI/iUAIhAIq95wCyjIan2CFVVGMbeVsIITtF
MIG7yKUO/pFJoufvAH8AbP5QGUOoXqkWvFLRM+TcyR7xQRx9JYQg0XOAnhgY6zoA
AAGflN15ewAIAAAFABc0C34EAwBIMEYCIQCuzaieKwTF6J6fImxKxDk7Rt/xxDGM
sBTDNxke3FM0XgIhAPZgnXt1Y1ikndEY77Rf13C5iN1iIVcr0YrQxO6wmVIDMA0G
CSqGSIb3DQEBCwUAA4IBAQBDXJJa77KhUucsvKuMm+GKSJb5uDQIlh7JEiD/Hijx
2Nor+Yz2SlE4feht5Zt7cDRjacm51SjjySFJ4hqntZGHpCsNYF+VJaE93fFSSVVd
YjqG22XWYcXImChoJ3jseTwLff5m8Pm8umA2Z/TnvpwiQZf08ccLxQ3Qdb5W4GDL
9qzDrkRT1PL1M0WdNeWc+0wRlmYq12dJirn6mGxTQedyfQMqhchX8iGkZ33KiNd/
kcgbURVttvw60hEjPbcURz2Rwl9dZz0qGPdG9bTpnpPtyBHNolNMfRRm5SuPDqQh
X1Wyfhmu3yj2P6SokmVH+lXaMd1vGbZhvLvz1TUou5iG
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIE2jCCAsKgAwIBAgIQTr0klH4k05SALYSlL9WzGTANBgkqhkiG9w0BAQsFADAu
MQswCQYDVQQGEwJVUzENMAsGA1UEChMESVNSRzEQMA4GA1UEAxMHUm9vdCBZUjAe
Fw0yNTA5MDMwMDAwMDBaFw0yODA5MDIyMzU5NTlaMDMxCzAJBgNVBAYTAlVTMRYw
FAYDVQQKEw1MZXQncyBFbmNyeXB0MQwwCgYDVQQDEwNZUjIwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQDZ0LxwBppqh84luqMerV/eeL/fXQ7mLQQv1Lnp
WKZbyvGpx6wh6AfnslAnF6ewTkcHA+gSOoBvm3Dfm06AuGiF+KRut4fAcowqnAQQ
CW98+QPP/eOv/wug7Iyk4NkOxf2I6g2f55T6nJoOTLFcukeRq80JGQEYan+dPFr9
OGUgQK2hGKgNkW87pappsOAuUJcroYhRt5uUis4qaZireiseu32gzDJNBAiKtsvd
6HX4v25bpkRNcS/B/Gtc9kVbUpD+2PLPxdei3Tim55k4tfAEXwD2qyiPTxrTNq6l
N+AMr5g2c1dNqkOTwjxeV6L5lpP1rGiYvLnRaPlOqyZRPW+5AgMBAAGjge4wgesw
DgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMBMBIGA1UdEwEB/wQI
MAYBAf8CAQAwHQYDVR0OBBYEFEAVLSZ57TIgnt+ach3WMh+BDIEMMB8GA1UdIwQY
MBaAFN7nW2DQIm1AKH0/DQH+pLVStFGUMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEF
BQcwAoYWaHR0cDovL3lyLmkubGVuY3Iub3JnLzATBgNVHSAEDDAKMAgGBmeBDAEC
ATAnBgNVHR8EIDAeMBygGqAYhhZodHRwOi8veXIuYy5sZW5jci5vcmcvMA0GCSqG
SIb3DQEBCwUAA4ICAQB0ZUQWZ9/Yn9COEpo+JfecMnB0h0vwDm/M66IqXqw3LoaL
mx9lZvRTeDIS67PUeI3yCA2W6PKRD0/FE/G57lOmS+Xy5AaaL00ICGOqjNcCaMWW
8o8nevHOd4i4lqgtznE/28QwlcdJyF8yBiWHpnyjhEpmNWJURgOCOg2xpwRMBCsj
MScqYPtOhBeuYQvSwAEeTML2Ukh6uGuX4E14q65Ja8cdjF5bAldnP1eE4FBaAwsZ
G2fOqqrKV03Y85Nw2btedP1AtliQuJZs/Jo/gXxXdc7LrH3McgnpnbTiAncX7yES
hP6kzQejllqMCIt52HOjxDGWafS7Xw+DKwqmH+Eqy8dcbOuag/1AYlQoKNVK3F5q
Hh6tEDiMqQcLIibGKteE6iHo4A/bIScbzrhXUYuism42ZYzmc48FMVIH3qy4L84E
TdAH2gtxw0PAhvRVXp8HP7wfngpzsN/8xOTpeRSbM4+Qbc56G6+Bifmv6sk1ieQb
NA3wJdl4DDUuQSV8hBgx6zoI1ZSGORprDFux7c6rhc77QZMSRrEgomBeklervEve
86ylWmZ3WWHV6RLMi8xNvjd71r4EPIGgY7BZU/VPBkq+uA7Gb6mbJnFgV43uh3xy
LRFgxIAphIukwTGSMZZR+AI+Qnp0BYTWovHXozOf3H8r6hozEoT02JHn0AeTfA==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
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
-----END CERTIFICATE-----
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA0xZkXlTfiktNExx4KUsxJmGUZqxdvr/bmw7ppFQnyA8hP+kr
J2rZ/a3JdN0ZIPteWNRgksMfOUQpDKTN8WZWrAagQQwuLJzBm/KeciqEb7Epve/t
woLplhG7wcmTN+frnijA7vNQA+AEmPQ5nErPvE1/h7NyXNfgXqtj6uDQv8HtTyEd
LC6jIlyVK9fxt3L8BDRMZLf4M9Toko7W165vfIm2CH6z/fs9qD32awvqdH/RjL9o
B1Sf/2DyMvNCi00gU3LiQPwPbT1nYGZQeSyxBS371D1BKF87T0z98JFR6rlZTk4+
kOS7twFLzdf+d8TobigvX6GjZrvFXB/PhuT0twIDAQABAoIBABTfEe2B1tiFuYk6
N2dd1+5Dl7jPMjW6IqyiPNfSulw9orv/AhVTq1o55MF5WxK3yAxgc2wPfvbalnR5
lyNXSxqXTSEhYy9mEH6q7p+7NH0Wt1ngb1HcP9xmxHnUxImylMl+nhUCWdcIP2wg
OdIKU5/6ybFRPO2RKs4xhB+lOv66sAsZzMjLl94ufKg9CC/yXxaoP00wDi/aIUPR
SLrf7f5pOipeYTtKeahFlYe4tgtTABTCyJi6sh/lhznFFkHav3a10n9cl1DLoMsv
pjq62ZsNLlZBoca+jvCquWI2MDUvr9eMWhydX/wkha8QPyRlJb7nUbB6mXKFBRb3
KuHXuoECgYEA8ZbzhllP81+O7HgfC5yTyh0NrvST7vKMRFjHiR3OY93Zv2G6AH/o
jd9wLorgZdu1N4wkJPlpnif2/gAtZP6D2tODI9sC71fW3ZYt3F1pZHQfcf0h+qRi
TO4I+rL9ZngtrPgE7NlpV2lqC1Sjog4l/L9UB9wHWL1+TFj3KGCHoRsCgYEA362s
9isIYP6aB+IdYr9uuC7rmCqio32i/Mz083zaAZJkZ6ex6XiNwBxFVZJ9x3Cco88w
iqim8Z7a0XXrylAQpYVqo6U4EkfaJkf9nMjqUWPo36t+MpbwEIs8B8lJ3lspf83n
Rz+4NSba0HNIBuVXuZZK9yqflqBziFdgNGJPkJUCgYA1PcofYpFvFjEeRf65hWFA
mpSIaRgtAcEN0u7udeYlNiAlwDjIoWez1HkZmLuJVxkhy72vdFPlafNR2OIoLrsK
P7DX9w03iAkvPkiPFvsCC2FesqWJYi7Tu2W5MjcMHRIb23Dibat8exvGsXl6Xlna
0KOJM+Femiw6USTu/g8F3wKBgQCSfWL+YZZ+N6BXM149ctQnags/wAR+AGVDHVP7
NzRrvR1myWv6VMq5G4rJDUS03mqn2aSX2vZi3b+/WHRRLniIfsUlYapERqx3T8Sn
7umO3lkAS4c9yJ52p0lsbAalypx2KDqk7fVNUiISTL3KTl9SmzyDfXXBJ1oi296U
0CH9tQKBgA4dX9/5GoWWZ3/5dRny3lb4z8AQQFoCj2fTKIN5usNUzsAOUMZp+p24
h/BzmUtcGUlKNPwg4LYjndIuK4Eembuyaiif5OdaDWWePacsP5pudIYlibXUyZqb
Ad3K4WIp0pVjzqt7HGXwDluBS3y1YApX/kky1o3b4vxoh8me8AX4
-----END RSA PRIVATE KEY-----
+20
View File
@@ -0,0 +1,20 @@
[
{
"id": 1,
"name": "cloudflare_yunweiblog",
"type": "cloudflare",
"data": "{\"cf_api_token\":\"cfut_VLxUPTOTAWBwTmY8WM4uuZYQW0iztWSdXEWhQYB348f7e25a\"}",
"masked": "{\"cf_api_token\":\"cfut****\"}",
"created_at": "2026-07-24T23:08:11.019112775+08:00",
"updated_at": "2026-07-24T23:08:11.019112775+08:00"
},
{
"id": 3,
"name": "cloudflare_globalcloudflare_global",
"type": "cloudflare",
"data": "{\"cf_api_key\":\"6eabf25cb487d39f166c3ff4dc3145b5c5fb3\",\"cf_email\":\"mrtom0506@gmail.com\"}",
"masked": "{\"cf_api_key\":\"6eab****\",\"cf_email\":\"mrto****\"}",
"created_at": "2026-07-24T23:52:19.826553941+08:00",
"updated_at": "2026-07-24T23:52:19.826553941+08:00"
}
]
Symlink
+1
View File
@@ -0,0 +1 @@
frontend/dist
+19 -4
View File
@@ -81,7 +81,14 @@
<!-- Cloudflare Config --> <!-- Cloudflare Config -->
<template v-if="form.dns_provider === 'cloudflare'"> <template v-if="form.dns_provider === 'cloudflare'">
<el-form-item label="API Token"> <el-form-item label="API Token">
<el-input v-model="manualConfig.cf_api_token" type="password" show-password placeholder="Cloudflare API Token" /> <el-input v-model="manualConfig.cf_api_token" type="password" show-password placeholder="Cloudflare API Token(推荐)" />
<div class="form-tip">需要 Zone:Read + DNS:Edit 权限。也可留空,改用下方 Email + Global API Key 方式</div>
</el-form-item>
<el-form-item label="Email">
<el-input v-model="manualConfig.cf_email" placeholder="Cloudflare 账号邮箱(Global API Key 方式)" />
</el-form-item>
<el-form-item label="Global API Key">
<el-input v-model="manualConfig.cf_api_key" type="password" show-password placeholder="Global API Key(与 Email 配合使用)" />
</el-form-item> </el-form-item>
</template> </template>
@@ -162,6 +169,8 @@ const manualConfig = reactive({
ali_key: '', ali_key: '',
ali_secret: '', ali_secret: '',
cf_api_token: '', cf_api_token: '',
cf_email: '',
cf_api_key: '',
dnspod_id: '', dnspod_id: '',
dnspod_key: '', dnspod_key: '',
}) })
@@ -220,6 +229,8 @@ const buildDnsConfigJson = (): string => {
case 'cloudflare': case 'cloudflare':
return JSON.stringify({ return JSON.stringify({
cf_api_token: manualConfig.cf_api_token, cf_api_token: manualConfig.cf_api_token,
cf_email: manualConfig.cf_email,
cf_api_key: manualConfig.cf_api_key,
}) })
case 'dnspod': case 'dnspod':
return JSON.stringify({ return JSON.stringify({
@@ -246,9 +257,13 @@ const handleSubmit = async () => {
ElMessage.warning('请填写阿里云 AccessKey') ElMessage.warning('请填写阿里云 AccessKey')
return return
} }
if (form.dns_provider === 'cloudflare' && !manualConfig.cf_api_token) { if (form.dns_provider === 'cloudflare') {
ElMessage.warning('请填写 Cloudflare API Token') const hasToken = !!manualConfig.cf_api_token
return const hasEmailKey = !!manualConfig.cf_email && !!manualConfig.cf_api_key
if (!hasToken && !hasEmailKey) {
ElMessage.warning('请填写 Cloudflare API Token或者同时填写 Email Global API Key')
return
}
} }
if (form.dns_provider === 'dnspod' && (!manualConfig.dnspod_id || !manualConfig.dnspod_key)) { if (form.dns_provider === 'dnspod' && (!manualConfig.dnspod_id || !manualConfig.dnspod_key)) {
ElMessage.warning('请填写 DNSPod 凭证') ElMessage.warning('请填写 DNSPod 凭证')