// 节点配置文件:`-config node.ini` 启用;显式命令行 flag 优先于配置项,配置项优先于内置默认。 // 零依赖 ini:[节] 下 key = value,`#`/`;` 注释行忽略,等号两侧空白忽略。 package main import ( "bufio" "errors" "os" "strconv" "strings" "time" ) // nodeConfig 配置文件映射(零值表示未配置,由调用方回退 flag/默认值) type nodeConfig struct { Addr string // [server] Addr 桥接监听地址 TunnelBind string // [server] TunnelBind 隧道监听绑定地址 JwtSecret string // [server] JwtSecret agent 登录 JWT 校验密钥 SweepSeconds int // [server] SweepSeconds TTL 清扫间隔(秒) PortRange string // [tunnel] PortRange 隧道监听端口范围(如 20000-21000) } // loadConfigFile 解析 ini 文件;文件不存在返回错误(-config 显式指定了就应当存在)。 func loadConfigFile(path string) (*nodeConfig, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() cfg := &nodeConfig{} section := "" sc := bufio.NewScanner(f) lineNo := 0 for sc.Scan() { lineNo++ line := strings.TrimSpace(sc.Text()) if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, ";") { continue } if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") { section = strings.ToLower(strings.TrimSpace(line[1 : len(line)-1])) continue } key, value, ok := strings.Cut(line, "=") if !ok { continue // 容错:无等号的行忽略 } key = strings.ToLower(strings.TrimSpace(key)) value = strings.TrimSpace(value) switch section + "." + key { case "server.addr": cfg.Addr = value case "server.tunnelbind": cfg.TunnelBind = value case "server.jwtsecret": cfg.JwtSecret = value case "server.sweepseconds": cfg.SweepSeconds, err = strconv.Atoi(value) if err != nil || cfg.SweepSeconds <= 0 { return nil, errors.New("server.sweepseconds 需为正整数, 行 " + strconv.Itoa(lineNo)) } case "tunnel.portrange": cfg.PortRange = value } } return cfg, sc.Err() } // sweepDuration 秒数转 Duration;0 表示未配置 func (c *nodeConfig) sweepDuration() time.Duration { if c.SweepSeconds <= 0 { return 0 } return time.Duration(c.SweepSeconds) * time.Second }