mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 20:57:19 +08:00
* refactor: 重构部分完成 * fix: 添加.gitkeep * fix: build * fix: lint * fix: lint * chore(deps): Update module github.com/go-playground/validator/v10 to v10.22.1 (#162) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update module gorm.io/gorm to v1.25.12 (#161) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update module golang.org/x/net to v0.29.0 (#159) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * workflow: 更新工作流 * workflow: test new download * feat: merge frontend project * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: fix frontend build * workflow: update to ubuntu-24.04 * workflow: rename build-* * workflow: 修改fetch-depth * chore(deps): Update dependency eslint to v9 (#164) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(frontend): update dependences * chore(frontend): fix lint * chore(frontend): fix lint * workflow: add govulncheck * workflow: disable nilaway * feat: 使用新的压缩解压库 * fix: 测试 * fix: 测试 * fix: 测试 * feat: 添加ntp包 * chore(deps): Lock file maintenance (#168) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update module github.com/go-resty/resty/v2 to v2.15.0 (#167) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update dependency @iconify/json to v2.2.249 (#169) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * feat: 添加限流器 * feat: 调整登录限流 * feat: 证书 * fix: lint * feat: 证书dns * feat: 证书acme账号 * fix: 修改UserID导致的一系列问题 * feat: 低配版任务队列 * feat: 队列完成 * fix: lint * fix: lint * fix: swagger和前端路由 * fix: 去掉ntp测试 * feat: 完成插件接口 * feat: 完成cron * feat: 完成safe * chore(deps): Update dependency vue to v3.5.6 (#170) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update dependency @vueuse/core to v11.1.0 (#171) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update dependency vite to v5.4.6 (#173) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore(deps): Update unocss monorepo to v0.62.4 (#172) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: update renovate config * feat: 新的firewall客户端 * fix: lint * feat: firewall完成 * feat: ssh完成 * feat: 容器完成1/2 * feat: 容器完成 * feat: 文件完成 * feat: systemctl及设置 * fix: windows编译 * fix: session not work * fix: migrate not work * feat: 前端路由 * feat: 初步支持cli --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
204 lines
4.9 KiB
Go
204 lines
4.9 KiB
Go
package acme
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"path/filepath"
|
||
"time"
|
||
|
||
"github.com/libdns/alidns"
|
||
"github.com/libdns/cloudflare"
|
||
"github.com/libdns/dnspod"
|
||
"github.com/libdns/libdns"
|
||
"github.com/libdns/tencentcloud"
|
||
"github.com/mholt/acmez/v2/acme"
|
||
"golang.org/x/net/publicsuffix"
|
||
|
||
"github.com/TheTNB/panel/pkg/shell"
|
||
"github.com/TheTNB/panel/pkg/systemctl"
|
||
)
|
||
|
||
type httpSolver struct {
|
||
conf string
|
||
path string
|
||
}
|
||
|
||
func (s httpSolver) Present(_ context.Context, challenge acme.Challenge) error {
|
||
var err error
|
||
if s.path == "" {
|
||
return nil
|
||
}
|
||
|
||
challengeFilePath := filepath.Join(s.path, challenge.HTTP01ResourcePath())
|
||
if err = os.MkdirAll(filepath.Dir(challengeFilePath), 0755); err != nil {
|
||
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的目录: %w", err)
|
||
}
|
||
|
||
if err = os.WriteFile(challengeFilePath, []byte(challenge.KeyAuthorization), 0644); err != nil {
|
||
return fmt.Errorf("无法在网站目录创建HTTP挑战所需的文件: %w", err)
|
||
}
|
||
|
||
conf := fmt.Sprintf(`location = /.well-known/acme-challenge/%s {
|
||
default_type text/plain;
|
||
return 200 %q;
|
||
}
|
||
`, challenge.Token, challenge.KeyAuthorization)
|
||
if err = os.WriteFile(s.conf, []byte(conf), 0644); err != nil {
|
||
return fmt.Errorf("无法写入OpenResty配置文件: %w", err)
|
||
}
|
||
if err = systemctl.Reload("openresty"); err != nil {
|
||
_, err = shell.Execf("openresty -t")
|
||
return fmt.Errorf("无法重载OpenResty: %w", err)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CleanUp cleans up the HTTP server if it is the last one to finish.
|
||
func (s httpSolver) CleanUp(_ context.Context, challenge acme.Challenge) error {
|
||
if s.path == "" {
|
||
return nil
|
||
}
|
||
|
||
_ = os.Remove(filepath.Join(s.path, challenge.HTTP01ResourcePath()))
|
||
_ = os.WriteFile(s.conf, []byte{}, 0644)
|
||
_ = systemctl.Reload("openresty")
|
||
return nil
|
||
}
|
||
|
||
type dnsSolver struct {
|
||
dns DnsType
|
||
param DNSParam
|
||
records *[]libdns.Record
|
||
}
|
||
|
||
func (s dnsSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||
dnsName := challenge.DNS01TXTRecordName()
|
||
keyAuth := challenge.DNS01KeyAuthorization()
|
||
provider, err := s.getDNSProvider()
|
||
if err != nil {
|
||
return fmt.Errorf("获取DNS提供商失败: %w", err)
|
||
}
|
||
zone, err := publicsuffix.EffectiveTLDPlusOne(dnsName)
|
||
if err != nil {
|
||
return fmt.Errorf("获取域名%q的顶级域失败: %w", dnsName, err)
|
||
}
|
||
|
||
rec := libdns.Record{
|
||
Type: "TXT",
|
||
Name: libdns.RelativeName(dnsName+".", zone+"."),
|
||
Value: keyAuth,
|
||
}
|
||
|
||
results, err := provider.AppendRecords(ctx, zone+".", []libdns.Record{rec})
|
||
if err != nil {
|
||
return fmt.Errorf("域名%q添加临时记录%q失败: %w", zone, dnsName, err)
|
||
}
|
||
if len(results) != 1 {
|
||
return fmt.Errorf("预期添加1条记录,但实际添加了%d条记录", len(results))
|
||
}
|
||
|
||
s.records = &results
|
||
return nil
|
||
}
|
||
|
||
func (s dnsSolver) CleanUp(ctx context.Context, challenge acme.Challenge) error {
|
||
dnsName := challenge.DNS01TXTRecordName()
|
||
provider, err := s.getDNSProvider()
|
||
if err != nil {
|
||
return fmt.Errorf("获取DNS提供商失败: %w", err)
|
||
}
|
||
zone, _ := publicsuffix.EffectiveTLDPlusOne(dnsName)
|
||
|
||
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||
defer cancel()
|
||
|
||
_, _ = provider.DeleteRecords(ctx, zone+".", *s.records)
|
||
return nil
|
||
}
|
||
|
||
func (s dnsSolver) getDNSProvider() (DNSProvider, error) {
|
||
var dns DNSProvider
|
||
|
||
switch s.dns {
|
||
case DnsPod:
|
||
dns = &dnspod.Provider{
|
||
APIToken: s.param.ID + "," + s.param.Token,
|
||
}
|
||
case Tencent:
|
||
dns = &tencentcloud.Provider{
|
||
SecretId: s.param.AccessKey,
|
||
SecretKey: s.param.SecretKey,
|
||
}
|
||
case AliYun:
|
||
dns = &alidns.Provider{
|
||
AccKeyID: s.param.AccessKey,
|
||
AccKeySecret: s.param.SecretKey,
|
||
}
|
||
case CloudFlare:
|
||
dns = &cloudflare.Provider{
|
||
APIToken: s.param.APIkey,
|
||
}
|
||
default:
|
||
return nil, fmt.Errorf("未知的DNS提供商 %q", s.dns)
|
||
}
|
||
|
||
return dns, nil
|
||
}
|
||
|
||
type DnsType string
|
||
|
||
const (
|
||
DnsPod DnsType = "dnspod"
|
||
Tencent DnsType = "tencent"
|
||
AliYun DnsType = "aliyun"
|
||
CloudFlare DnsType = "cloudflare"
|
||
)
|
||
|
||
type DNSParam struct {
|
||
ID string `form:"id" json:"id"`
|
||
Token string `form:"token" json:"token"`
|
||
AccessKey string `form:"access_key" json:"access_key"`
|
||
SecretKey string `form:"secret_key" json:"secret_key"`
|
||
APIkey string `form:"api_key" json:"api_key"`
|
||
}
|
||
|
||
type DNSProvider interface {
|
||
libdns.RecordAppender
|
||
libdns.RecordDeleter
|
||
}
|
||
|
||
type manualDNSSolver struct {
|
||
check bool
|
||
controlChan chan struct{}
|
||
dataChan chan any
|
||
records *[]DNSRecord
|
||
}
|
||
|
||
func (s manualDNSSolver) Present(ctx context.Context, challenge acme.Challenge) error {
|
||
dnsName := challenge.DNS01TXTRecordName()
|
||
keyAuth := challenge.DNS01KeyAuthorization()
|
||
|
||
*s.records = append(*s.records, DNSRecord{
|
||
Key: dnsName,
|
||
Value: keyAuth,
|
||
})
|
||
s.dataChan <- *s.records
|
||
|
||
_, cancel := context.WithTimeout(ctx, 2*time.Minute)
|
||
defer cancel()
|
||
|
||
<-s.controlChan
|
||
return nil
|
||
}
|
||
|
||
func (s manualDNSSolver) CleanUp(_ context.Context, _ acme.Challenge) error {
|
||
return nil
|
||
}
|
||
|
||
type DNSRecord struct {
|
||
Key string `json:"key"`
|
||
Value string `json:"value"`
|
||
}
|