init: 自 zomaintain/backend/zonat 平移独立成库; module path 改为 git.zeroonesoft.cn/golib/zonat; 内部 import 同步重写

This commit is contained in:
w11
2026-09-19 18:55:43 +08:00
commit 984041f1a0
20 changed files with 2427 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
// zonat 被控端入口:跑在内网机器上,按隧道定义登录各自节点并注册。
// 每条隧道自带节点地址与登录令牌(JWT),可分布在多个不同节点。
// 隧道通过 -tunnel 参数下发(后续由 capricorn cloud 经 WS 下发,见分析文档 P1):
//
// -tunnel id=web1,target=127.0.0.1:3000,node=10.0.0.2:5212,token=<jwt>,port=0,ttl=0
// -tunnel id=vnc1,target=127.0.0.1:5900,ttl=300 // node/token 取 -node/-token 缺省值
package main
import (
"flag"
"fmt"
"log/slog"
"net"
"os"
"strconv"
"strings"
"time"
"git.zeroonesoft.cn/golib/zonat/agent"
)
// slogLogger 把 slog 适配为 agent.Logger 接口(独立二进制用;
// 宿主项目若是 logrus 可直接注入 logrus.StandardLogger())
type slogLogger struct{ l *slog.Logger }
func (s slogLogger) Debugf(format string, args ...any) { s.l.Debug(fmt.Sprintf(format, args...)) }
func (s slogLogger) Infof(format string, args ...any) { s.l.Info(fmt.Sprintf(format, args...)) }
func (s slogLogger) Warnf(format string, args ...any) { s.l.Warn(fmt.Sprintf(format, args...)) }
func (s slogLogger) Errorf(format string, args ...any) { s.l.Error(fmt.Sprintf(format, args...)) }
func main() {
nodeAddr := flag.String("node", "127.0.0.1:5212", "缺省节点桥接地址(隧道未带 node= 时使用)")
token := flag.String("token", "", "缺省登录令牌/JWT(隧道未带 token= 时使用)")
id := flag.String("id", "", "agent 唯一标识(默认取主机名)")
backoff := flag.Duration("backoff", 5*time.Second, "永久隧道重连退避")
ping := flag.Duration("ping", 30*time.Second, "控制流心跳间隔")
var tunnels sliceFlag
flag.Var(&tunnels, "tunnel", "隧道定义 id=..,target=ip:port[,node=addr][,token=jwt][,port=0][,ttl=0],可重复")
flag.Parse()
if *id == "" {
host, err := os.Hostname()
if err != nil {
slog.Error("获取主机名失败", "err", err.Error())
os.Exit(1)
}
*id = host
}
a := agent.New(*id)
a.Logger = slogLogger{slog.Default()}
a.Backoff = *backoff
a.PingEvery = *ping
a.OnTunnelPort = func(tunnelId string, port int) {
if port > 0 {
slog.Info("隧道端口", "id", tunnelId, "listen", port)
}
}
for _, spec := range tunnels {
t, err := parseTunnel(spec, *nodeAddr, *token)
if err != nil {
slog.Error("隧道参数错误", "spec", spec, "err", err.Error())
os.Exit(1)
}
if _, err := a.RegisterTunnel(t); err != nil {
slog.Warn("注册隧道失败(永久隧道会在后台持续重试)", "id", t.Id, "err", err.Error())
}
}
if err := a.Run(); err != nil {
slog.Error("agent 退出", "err", err.Error())
os.Exit(1)
}
}
// parseTunnel 解析一条隧道定义,node/token 未带时回落到全局缺省值
func parseTunnel(spec, defNode, defToken string) (agent.Tunnel, error) {
t := agent.Tunnel{NodeAddr: defNode, Token: defToken}
for _, kv := range strings.Split(spec, ",") {
parts := strings.SplitN(kv, "=", 2)
if len(parts) != 2 {
return t, errBad("键值对缺少=:" + kv)
}
switch parts[0] {
case "id":
t.Id = parts[1]
case "node":
t.NodeAddr = parts[1]
case "token":
t.Token = parts[1]
case "target":
host, portStr, err := net.SplitHostPort(parts[1])
if err != nil {
return t, err
}
port, err := strconv.Atoi(portStr)
if err != nil {
return t, err
}
t.TargetIp, t.TargetPort = host, port
case "port":
v, err := strconv.Atoi(parts[1])
if err != nil {
return t, err
}
t.ListenPort = v
case "ttl":
v, err := strconv.Atoi(parts[1])
if err != nil {
return t, err
}
t.TTLSec = v
default:
return t, errBad("未知键:" + parts[0])
}
}
if t.Id == "" || t.TargetIp == "" || t.TargetPort == 0 {
return t, errBad("缺少 id 或 target")
}
if t.NodeAddr == "" || t.Token == "" {
return t, errBad("缺少 node 或 token(登录令牌/JWT)")
}
return t, nil
}
func errBad(msg string) error { return &badSpec{msg} }
type badSpec struct{ msg string }
func (e *badSpec) Error() string { return e.msg }
type sliceFlag []string
func (f *sliceFlag) String() string { return strings.Join(*f, " ") }
func (f *sliceFlag) Set(v string) error {
*f = append(*f, v)
return nil
}