mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 06:47:20 +08:00
236 lines
5.9 KiB
Go
236 lines
5.9 KiB
Go
package nginx
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/acepanel/panel/pkg/webserver/types"
|
|
)
|
|
|
|
// upstreamFilePattern 匹配 upstream 配置文件名 (100-XXX-name.conf)
|
|
var upstreamFilePattern = regexp.MustCompile(`^(\d{3})-(.+)\.conf$`)
|
|
|
|
// parseUpstreamFiles 从 shared 目录解析所有 upstream 配置
|
|
func parseUpstreamFiles(sharedDir string) ([]types.Upstream, error) {
|
|
entries, err := os.ReadDir(sharedDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
var upstreams []types.Upstream
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
matches := upstreamFilePattern.FindStringSubmatch(entry.Name())
|
|
if matches == nil {
|
|
continue
|
|
}
|
|
|
|
num, _ := strconv.Atoi(matches[1])
|
|
if num < UpstreamStartNum {
|
|
continue
|
|
}
|
|
|
|
filePath := filepath.Join(sharedDir, entry.Name())
|
|
upstream, err := parseUpstreamFile(filePath, matches[2])
|
|
if err != nil {
|
|
continue // 跳过解析失败的文件
|
|
}
|
|
if upstream != nil {
|
|
upstreams = append(upstreams, *upstream)
|
|
}
|
|
}
|
|
|
|
return upstreams, nil
|
|
}
|
|
|
|
// parseUpstreamFile 解析单个 upstream 配置文件
|
|
func parseUpstreamFile(filePath string, expectedName string) (*types.Upstream, error) {
|
|
content, err := os.ReadFile(filePath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
contentStr := string(content)
|
|
|
|
// 解析 upstream 块
|
|
// upstream backend {
|
|
// least_conn;
|
|
// server 127.0.0.1:8080 weight=5;
|
|
// keepalive 32;
|
|
// }
|
|
upstreamPattern := regexp.MustCompile(`upstream\s+(\S+)\s*\{([^}]+)}`)
|
|
matches := upstreamPattern.FindStringSubmatch(contentStr)
|
|
if matches == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
name := matches[1]
|
|
if expectedName != "" && name != expectedName {
|
|
return nil, nil
|
|
}
|
|
|
|
blockContent := matches[2]
|
|
upstream := &types.Upstream{
|
|
Name: name,
|
|
Servers: make(map[string]string),
|
|
Resolver: []string{},
|
|
}
|
|
|
|
// 解析负载均衡算法
|
|
algoPatterns := []string{"least_conn", "ip_hash", "hash", "random"}
|
|
for _, algo := range algoPatterns {
|
|
if regexp.MustCompile(`\b` + algo + `\b`).MatchString(blockContent) {
|
|
upstream.Algo = algo
|
|
break
|
|
}
|
|
}
|
|
|
|
// 解析 server 指令
|
|
// 匹配: server 127.0.0.1:8080; 或 server 127.0.0.1:8080 weight=5;
|
|
serverPattern := regexp.MustCompile(`server\s+([^\s;]+)(?:\s+([^;]+))?;`)
|
|
serverMatches := serverPattern.FindAllStringSubmatch(blockContent, -1)
|
|
for _, sm := range serverMatches {
|
|
addr := sm[1]
|
|
options := ""
|
|
if len(sm) > 2 {
|
|
options = strings.TrimSpace(sm[2])
|
|
}
|
|
upstream.Servers[addr] = options
|
|
}
|
|
|
|
// 解析 keepalive 指令
|
|
keepalivePattern := regexp.MustCompile(`keepalive\s+(\d+);`)
|
|
if km := keepalivePattern.FindStringSubmatch(blockContent); km != nil {
|
|
upstream.Keepalive, _ = strconv.Atoi(km[1])
|
|
}
|
|
|
|
// 解析 resolver
|
|
resolverPattern := regexp.MustCompile(`resolver\s+([^;]+);`)
|
|
if rm := resolverPattern.FindStringSubmatch(blockContent); rm != nil {
|
|
parts := strings.Fields(rm[1])
|
|
upstream.Resolver = parts
|
|
}
|
|
|
|
// 解析 resolver_timeout
|
|
resolverTimeoutPattern := regexp.MustCompile(`resolver_timeout\s+(\d+)([smh]?);`)
|
|
if rtm := resolverTimeoutPattern.FindStringSubmatch(blockContent); rtm != nil {
|
|
value, _ := strconv.Atoi(rtm[1])
|
|
unit := rtm[2]
|
|
switch unit {
|
|
case "m":
|
|
upstream.ResolverTimeout = time.Duration(value) * time.Minute
|
|
case "h":
|
|
upstream.ResolverTimeout = time.Duration(value) * time.Hour
|
|
default:
|
|
upstream.ResolverTimeout = time.Duration(value) * time.Second
|
|
}
|
|
}
|
|
|
|
return upstream, nil
|
|
}
|
|
|
|
// writeUpstreamFiles 将 upstream 配置写入文件
|
|
func writeUpstreamFiles(sharedDir string, upstreams []types.Upstream) error {
|
|
// 删除现有的 upstream 配置文件
|
|
if err := clearUpstreamFiles(sharedDir); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 写入新的配置文件,保持顺序
|
|
for i, upstream := range upstreams {
|
|
num := UpstreamStartNum + i
|
|
fileName := fmt.Sprintf("%03d-%s.conf", num, upstream.Name)
|
|
filePath := filepath.Join(sharedDir, fileName)
|
|
|
|
content := generateUpstreamConfig(upstream)
|
|
if err := os.WriteFile(filePath, []byte(content), 0600); err != nil {
|
|
return fmt.Errorf("failed to write upstream config: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// clearUpstreamFiles 清除所有 upstream 配置文件
|
|
func clearUpstreamFiles(sharedDir string) error {
|
|
entries, err := os.ReadDir(sharedDir)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
|
|
matches := upstreamFilePattern.FindStringSubmatch(entry.Name())
|
|
if matches == nil {
|
|
continue
|
|
}
|
|
|
|
num, _ := strconv.Atoi(matches[1])
|
|
if num >= UpstreamStartNum {
|
|
filePath := filepath.Join(sharedDir, entry.Name())
|
|
if err = os.Remove(filePath); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("failed to delete upstream config: %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// generateUpstreamConfig 生成 upstream 配置内容
|
|
func generateUpstreamConfig(upstream types.Upstream) string {
|
|
var sb strings.Builder
|
|
|
|
sb.WriteString("# Auto-generated by AcePanel. DO NOT EDIT MANUALLY!\n")
|
|
sb.WriteString(fmt.Sprintf("# Upstream: %s\n", upstream.Name))
|
|
sb.WriteString(fmt.Sprintf("upstream %s {\n", upstream.Name))
|
|
|
|
// 负载均衡算法
|
|
if upstream.Algo != "" {
|
|
sb.WriteString(fmt.Sprintf(" %s;\n", upstream.Algo))
|
|
}
|
|
|
|
// resolver 配置
|
|
if len(upstream.Resolver) > 0 {
|
|
sb.WriteString(fmt.Sprintf(" resolver %s;\n", strings.Join(upstream.Resolver, " ")))
|
|
if upstream.ResolverTimeout > 0 {
|
|
sb.WriteString(fmt.Sprintf(" resolver_timeout %ds;\n", int(upstream.ResolverTimeout.Seconds())))
|
|
}
|
|
}
|
|
|
|
// 服务器列表
|
|
for addr, options := range upstream.Servers {
|
|
if options != "" {
|
|
sb.WriteString(fmt.Sprintf(" server %s %s;\n", addr, options))
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf(" server %s;\n", addr))
|
|
}
|
|
}
|
|
|
|
// keepalive 连接数
|
|
if upstream.Keepalive > 0 {
|
|
sb.WriteString(fmt.Sprintf(" keepalive %d;\n", upstream.Keepalive))
|
|
}
|
|
|
|
sb.WriteString("}\n")
|
|
|
|
return sb.String()
|
|
}
|