Files
zonat/cmd/node/main.go
T
4566704 4dad7fbbbe feat(node): 配置文件启动与隧道端口范围——云主机防火墙可按范围放行
改了什么:
- cmd/node/config.go: 零依赖 ini 配置加载([server] Addr/TunnelBind/JwtSecret/SweepSeconds + [tunnel] PortRange); -config node.ini 启用
- main.go: -config 与 -port-range flag; 配置来源优先级 显式 flag > 配置文件(非零项) > 内置默认(flag.Visit 判显式)
- internal/node/portrange.go: PortRange 解析(20000-21000/单端口/容错) + listen(随机起点顺延探测, 占用即生效避免 TOCTOU)
- node.go: TunnelPortRange 字段; listenTunnel 统一隧道建听——端口 0 且配置范围时范围内取空闲端口, 否则原路径(指定端口直监听/系统随机)
- Start 日志打出端口范围; portrange_test.go 覆盖解析与占满/释放取端口

为什么: agent 注册隧道从不指定端口(ListenPort=0), 节点 net.Listen 落系统随机高位段, 云主机防火墙无法预开; 配置范围后临时/永久隧道都落在固定段内, 防火墙放行 5212+范围即可
2026-09-21 18:28:26 +08:00

129 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// zonat 节点入口:TCP 桥接监听 + 隧道注册表 + 纯 TCP 隧道监听。
// 无管理 API、无数据库;隧道由 agent 经桥接控制流注册。
// agent 登录用 JWT 校验(HS256);token 正式部署由 cloud 签发,
// 手工/测试场景可用 -print-token 现场签发。
//
// 配置来源优先级:显式命令行 flag > -config 指定的配置文件 > 内置默认。
package main
import (
"flag"
"fmt"
"log/slog"
"os"
"os/signal"
"syscall"
"time"
"git.zeroonesoft.cn/golib/zonat/internal/node"
)
func main() {
var (
config = flag.String("config", "", "配置文件路径(node.ini); 显式 flag 可覆盖配置项")
addr = flag.String("addr", ":5212", "桥接监听地址")
tunnelBind = flag.String("tunnel-bind", "0.0.0.0", "隧道监听绑定地址")
jwtSecret = flag.String("jwt-secret", "", "agent 登录 JWT 校验密钥(HS256)")
sweep = flag.Duration("sweep", 30*time.Second, "TTL 清扫间隔")
portRange = flag.String("port-range", "", "隧道监听端口范围(如 20000-21000); 未指定端口的隧道在范围内取空闲端口, 空=系统随机")
// 便捷签发:node -jwt-secret s3cret -print-token -agent a1 -ttl 24h
printToken = flag.Bool("print-token", false, "用 -jwt-secret 签发一张 agent 登录 token 后退出")
tokenAgent = flag.String("agent", "", "print-token 时写入 agentId claim")
tokenTTL = flag.Duration("ttl", 24*time.Hour, "print-token 签发的有效期")
)
flag.Parse()
if *printToken {
if *jwtSecret == "" || *tokenTTL <= 0 {
slog.Error("print-token 需要 -jwt-secret 与正的 -ttl")
os.Exit(1)
}
tok, err := node.SignToken(*jwtSecret, *tokenAgent, *tokenTTL)
if err != nil {
slog.Error("签发失败", "err", err.Error())
os.Exit(1)
}
fmt.Println(tok)
return
}
// 终值 = 内置默认 ← 配置文件(非零项) ← 显式 flag
explicit := map[string]bool{}
flag.Visit(func(f *flag.Flag) { explicit[f.Name] = true })
var (
finalAddr = *addr
finalTunnelBind = *tunnelBind
finalSecret = *jwtSecret
finalSweep = *sweep
finalRange node.PortRange
)
if *config != "" {
cfg, err := loadConfigFile(*config)
if err != nil {
slog.Error("读取配置文件失败", "path", *config, "err", err.Error())
os.Exit(1)
}
if cfg.Addr != "" {
finalAddr = cfg.Addr
}
if cfg.TunnelBind != "" {
finalTunnelBind = cfg.TunnelBind
}
if cfg.JwtSecret != "" {
finalSecret = cfg.JwtSecret
}
if d := cfg.sweepDuration(); d > 0 {
finalSweep = d
}
if cfg.PortRange != "" {
r, err := node.ParsePortRange(cfg.PortRange)
if err != nil {
slog.Error("配置文件端口范围无效", "err", err.Error())
os.Exit(1)
}
finalRange = r
}
}
if explicit["addr"] {
finalAddr = *addr
}
if explicit["tunnel-bind"] {
finalTunnelBind = *tunnelBind
}
if explicit["jwt-secret"] {
finalSecret = *jwtSecret
}
if explicit["sweep"] {
finalSweep = *sweep
}
if explicit["port-range"] {
r, err := node.ParsePortRange(*portRange)
if err != nil {
slog.Error("端口范围无效", "err", err.Error())
os.Exit(1)
}
finalRange = r
}
if finalSecret == "" {
slog.Error("缺少 jwt-secret(agent 登录 JWT 校验密钥; -jwt-secret 或配置文件 server.jwt-secret)")
os.Exit(1)
}
n := node.New()
n.JwtSecret = finalSecret
n.BindTunnel = finalTunnelBind
n.SweepInterval = finalSweep
n.TunnelPortRange = finalRange
if err := n.Start(finalAddr); err != nil {
slog.Error("节点启动失败", "err", err.Error())
os.Exit(1)
}
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
<-quit
n.Stop()
}