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(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() }