package hostnginx import ( "errors" "fmt" "net" "strconv" "strings" ) const ( nodeSsrManagedBegin = "# yms-update managed ssr upstream begin" nodeSsrManagedEnd = "# yms-update managed ssr upstream end" nodeSsrPort18910 = 18910 nodeSsrPort28910 = 28910 ) // ActiveNodeSsrPort 从 Node SSR 受控 upstream 区块读取唯一活动槽位端口。 func ActiveNodeSsrPort(content []byte) (int, error) { block, err := parseNodeSsrBlock(content) if err != nil { return 0, err } active := 0 for _, server := range block.servers { if !server.commented { if active != 0 { return 0, errors.New("managed Nginx upstream contains more than one active Node SSR server") } active = server.port } } if active == 0 { return 0, errors.New("managed Nginx upstream does not contain an active Node SSR server") } return active, nil } // RenderNodeSsrPort 只切换 Node SSR 受控 upstream 区块中的活动服务器行。 func RenderNodeSsrPort(content []byte, activePort int) ([]byte, error) { if activePort != nodeSsrPort18910 && activePort != nodeSsrPort28910 { return nil, fmt.Errorf("host Nginx Node SSR port must be 18910 or 28910: %d", activePort) } block, err := parseNodeSsrBlock(content) if err != nil { return nil, err } for _, server := range block.servers { indentLength := len(block.lines[server.line]) - len(strings.TrimLeft(block.lines[server.line], " \t")) indent := block.lines[server.line][:indentLength] serverText := strings.TrimPrefix(strings.TrimSpace(block.lines[server.line]), "# ") if server.port == activePort { block.lines[server.line] = indent + serverText } else { block.lines[server.line] = indent + "# " + serverText } } rendered := []byte(strings.Join(block.lines, "\n")) if _, err := ActiveNodeSsrPort(rendered); err != nil { return nil, fmt.Errorf("validate rendered host Nginx Node SSR upstream: %w", err) } return rendered, nil } type nodeSsrBlock struct { lines []string servers []nodeSsrServer } type nodeSsrServer struct { line int port int commented bool } func parseNodeSsrBlock(content []byte) (nodeSsrBlock, error) { if len(content) == 0 { return nodeSsrBlock{}, errors.New("host Nginx configuration is empty") } lines := strings.Split(string(content), "\n") begin, end, err := findNodeSsrMarkers(lines) if err != nil { return nodeSsrBlock{}, err } servers, err := collectNodeSsrServers(lines, begin, end) if err != nil { return nodeSsrBlock{}, err } return nodeSsrBlock{lines: lines, servers: servers}, nil } func findNodeSsrMarkers(lines []string) (int, int, error) { begin, end := -1, -1 for index, line := range lines { switch strings.TrimSpace(line) { case nodeSsrManagedBegin: if begin != -1 { return 0, 0, errors.New("host Nginx configuration contains duplicate Node SSR upstream begin markers") } begin = index case nodeSsrManagedEnd: if end != -1 { return 0, 0, errors.New("host Nginx configuration contains duplicate Node SSR upstream end markers") } end = index } } if begin == -1 || end <= begin { return 0, 0, errors.New("host Nginx configuration requires one ordered Node SSR upstream marker pair") } return begin, end, nil } func collectNodeSsrServers(lines []string, begin, end int) ([]nodeSsrServer, error) { servers := make([]nodeSsrServer, 0, 2) seen := make(map[int]struct{}, 2) for index := begin + 1; index < end; index++ { server, found, err := parseNodeSsrServer(lines[index], index) if err != nil { return nil, err } if !found { continue } if _, exists := seen[server.port]; exists { return nil, fmt.Errorf("managed Node SSR upstream contains duplicate port %d", server.port) } seen[server.port] = struct{}{} servers = append(servers, server) } if len(servers) != 2 { return nil, fmt.Errorf("managed Node SSR upstream must contain exactly two servers, got %d", len(servers)) } return servers, nil } // parseNodeSsrServer parses one server line inside the managed Node SSR block. func parseNodeSsrServer(line string, lineIndex int) (nodeSsrServer, bool, error) { trimmed := strings.TrimSpace(line) commented := strings.HasPrefix(trimmed, "# server ") active := strings.HasPrefix(trimmed, "server ") if !commented && !active { if trimmed != "" { return nodeSsrServer{}, false, fmt.Errorf("managed Node SSR upstream contains an unexpected line: %q", trimmed) } return nodeSsrServer{}, false, nil } serverText := strings.TrimPrefix(trimmed, "# ") fields := strings.Fields(serverText) if len(fields) != 4 || fields[0] != "server" || fields[2] != "max_fails=1" || fields[3] != "fail_timeout=2s;" { return nodeSsrServer{}, false, fmt.Errorf("managed Node SSR upstream server line has an unsupported format: %q", trimmed) } _, portText, err := net.SplitHostPort(fields[1]) if err != nil { return nodeSsrServer{}, false, fmt.Errorf("parse managed Node SSR upstream address %q: %w", fields[1], err) } port, err := strconv.Atoi(portText) if err != nil || (port != nodeSsrPort18910 && port != nodeSsrPort28910) { return nodeSsrServer{}, false, fmt.Errorf("managed Node SSR upstream contains unsupported port %q", portText) } return nodeSsrServer{line: lineIndex, port: port, commented: commented}, true, nil }