mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 04:22:33 +08:00
feat: 优化证书逻辑
This commit is contained in:
@@ -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 存储所有待验证的 challenge,key 为路径,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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user