改了什么: -config 默认值改为 node.ini; 未显式指定且文件不存在时按无配置启动(纯 flag/老部署兼容); 缺密钥报错文案补 node.ini 途径 为什么: 部署目录裸跑二进制(不经 start.sh)时不会主动带 -config, 直接报缺少 jwt-secret; 自动加载同目录配置后包内开箱即跑
136 lines
3.9 KiB
Go
136 lines
3.9 KiB
Go
// 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", "配置文件路径; 默认自动加载本目录 node.ini, 文件不存在且未显式 -config 时按无配置启动")
|
||
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 })
|
||
|
||
// 自动加载: -config 未显式指定且默认路径不存在 → 按无配置启动(兼容纯 flag/老部署)
|
||
if !explicit["config"] {
|
||
if _, err := os.Stat(*config); err != nil {
|
||
*config = ""
|
||
}
|
||
}
|
||
|
||
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、-config 指定文件或本目录 node.ini 的 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()
|
||
}
|