2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 04:22:33 +08:00

feat: 优化证书逻辑

This commit is contained in:
2026-01-08 18:17:09 +08:00
parent d850292622
commit 855ed52a8b

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"os"
"strings"
"sync"
"time"
"github.com/libdns/alidns"
@@ -27,56 +28,65 @@ import (
"github.com/acepanel/panel/pkg/systemctl"
)
var panelSolverGlobal sync.Mutex
type panelSolver struct {
ip []string
conf string
server *http.Server
// tokens 存储所有待验证的 challengekey 为路径value 为 token
tokens map[string]string
// presentCount Present 调用计数
presentCount int
// cleanupCount CleanUp 调用计数
cleanupCount int
// useBuiltin 标记是否使用内置 HTTP 服务器
useBuiltin bool
}
func (s *panelSolver) Present(_ context.Context, challenge acme.Challenge) error {
// 如果 80 端口没有被占用,则直接起一个内置的 HTTP 服务器验证
if s.presentCount == 0 {
panelSolverGlobal.Lock()
}
path := challenge.HTTP01ResourcePath()
token := challenge.KeyAuthorization
// 初始化 tokens map
if s.tokens == nil {
s.tokens = make(map[string]string)
}
// 收集所有域名的 token
s.tokens[path] = token
s.presentCount++
if s.presentCount < len(s.ip) {
return nil
}
// 如果 80 端口没有被占用,则使用内置的 HTTP 服务器
if !pkgos.TCPPortInUse(80) {
return s.presentPanel(challenge)
s.useBuiltin = true
return s.startServer()
}
conf := fmt.Sprintf(`server {
listen 80;
server_name %s;
location = %s {
default_type text/plain;
return 200 %q;
}
}
`, s.ip, challenge.HTTP01ResourcePath(), challenge.KeyAuthorization)
f, err := os.OpenFile(s.conf, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("failed to open nginx config %q: %w", s.conf, err)
}
defer func(f *os.File) { _ = f.Close() }(f)
if _, err = f.Write([]byte(conf)); err != nil {
return fmt.Errorf("failed to write to nginx config %q: %w", s.conf, err)
}
if err = systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
return fmt.Errorf("failed to reload nginx: %w", err)
}
return nil
// 否则使用 nginx 配置
s.useBuiltin = false
return s.writeNginxConfig()
}
func (s *panelSolver) presentPanel(challenge acme.Challenge) error {
mux := http.NewServeMux()
mux.HandleFunc(challenge.HTTP01ResourcePath(), func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
_, _ = w.Write([]byte(challenge.KeyAuthorization))
})
func (s *panelSolver) startServer() error {
s.server = &http.Server{
Addr: ":80",
Handler: mux,
Addr: ":80",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token, ok := s.tokens[r.URL.Path]
if !ok {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/plain")
_, _ = w.Write([]byte(token))
}),
}
errChan := make(chan error, 1)
@@ -90,16 +100,45 @@ func (s *panelSolver) presentPanel(challenge acme.Challenge) error {
// 等待一小段时间确保服务器启动成功
select {
case err := <-errChan:
s.server = nil
return fmt.Errorf("failed to start HTTP server: %w", err)
case <-time.After(100 * time.Millisecond):
return nil
}
}
// CleanUp cleans up the HTTP server if it is the last one to finish.
func (s *panelSolver) writeNginxConfig() error {
var conf strings.Builder
conf.WriteString(fmt.Sprintf("server {\n listen 80;\n server_name %s;\n", strings.Join(s.ip, " ")))
for path, token := range s.tokens {
conf.WriteString(fmt.Sprintf(" location = %s {\n default_type text/plain;\n return 200 %q;\n }\n", path, token))
}
conf.WriteString("}\n")
if err := os.WriteFile(s.conf, []byte(conf.String()), 0644); err != nil {
return fmt.Errorf("failed to write nginx config %q: %w", s.conf, err)
}
if err := systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
return fmt.Errorf("failed to reload nginx: %w", err)
}
return nil
}
// CleanUp cleans up the HTTP server on last call.
func (s *panelSolver) CleanUp(ctx context.Context, _ acme.Challenge) error {
// 如果启动了内置 HTTP 服务器,则关闭它
if s.server != nil {
s.cleanupCount++
// 等待最后一次 CleanUp
if s.cleanupCount < len(s.ip) {
return nil
}
defer panelSolverGlobal.Unlock()
if s.useBuiltin && s.server != nil {
shutdownCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := s.server.Shutdown(shutdownCtx); err != nil {
@@ -109,13 +148,13 @@ func (s *panelSolver) CleanUp(ctx context.Context, _ acme.Challenge) error {
return nil
}
// 否则清理 nginx 配置
// 清理 nginx 配置
if err := os.WriteFile(s.conf, []byte(""), 0644); err != nil {
return fmt.Errorf("failed to write to nginx config %q: %w", s.conf, err)
}
if err := systemctl.Reload("nginx"); err != nil {
_, err = shell.Execf("nginx -t")
_, _ = shell.Execf("nginx -t")
return fmt.Errorf("failed to reload nginx: %w", err)
}