147 lines
4.8 KiB
Go
147 lines
4.8 KiB
Go
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 := -1, -1
|
|
for index, line := range lines {
|
|
switch strings.TrimSpace(line) {
|
|
case nodeSsrManagedBegin:
|
|
if begin != -1 {
|
|
return nodeSsrBlock{}, errors.New("host Nginx configuration contains duplicate Node SSR upstream begin markers")
|
|
}
|
|
begin = index
|
|
case nodeSsrManagedEnd:
|
|
if end != -1 {
|
|
return nodeSsrBlock{}, errors.New("host Nginx configuration contains duplicate Node SSR upstream end markers")
|
|
}
|
|
end = index
|
|
}
|
|
}
|
|
if begin == -1 || end <= begin {
|
|
return nodeSsrBlock{}, errors.New("host Nginx configuration requires one ordered Node SSR upstream marker pair")
|
|
}
|
|
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 nodeSsrBlock{}, err
|
|
}
|
|
if !found {
|
|
continue
|
|
}
|
|
if _, exists := seen[server.port]; exists {
|
|
return nodeSsrBlock{}, 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 nodeSsrBlock{}, fmt.Errorf("managed Node SSR upstream must contain exactly two servers, got %d", len(servers))
|
|
}
|
|
return nodeSsrBlock{lines: lines, servers: 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
|
|
}
|