初版功能完成
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/netip"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"remlink/internal/localization"
|
||||
"remlink/internal/model"
|
||||
)
|
||||
|
||||
type siteConsole struct {
|
||||
mu sync.Mutex
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func newSiteConsole(w io.Writer) *siteConsole { return &siteConsole{w: w} }
|
||||
|
||||
func (c *siteConsole) line(format string, args ...any) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
fmt.Fprintf(c.w, "%s ", time.Now().Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(c.w, format, args...)
|
||||
fmt.Fprintln(c.w)
|
||||
}
|
||||
|
||||
func (c *siteConsole) Header(server, node, version string) {
|
||||
c.line("RemLink Site %s", version)
|
||||
c.line("服务器=%s 节点=%s", server, node)
|
||||
c.line("OverlayIP=等待分配 WireGuard=正在连接 Control=正在连接 远程网段=正在初始化 子网网关=正在初始化")
|
||||
}
|
||||
|
||||
func (c *siteConsole) OverlayReady(address netip.Addr) {
|
||||
c.line("OverlayIP=%s WireGuard=已连接", address)
|
||||
}
|
||||
|
||||
func (c *siteConsole) ControlState(online bool) {
|
||||
state := "正在重连"
|
||||
if online {
|
||||
state = "已连接"
|
||||
}
|
||||
c.line("Control=%s", state)
|
||||
}
|
||||
|
||||
func (c *siteConsole) SiteReady() {
|
||||
c.line("远程网段=就绪 子网网关=gVisor netstack/就绪")
|
||||
}
|
||||
|
||||
func (c *siteConsole) Session(status model.SessionStatus, sessionID uint64, reason string) {
|
||||
if reason == "" {
|
||||
reason = "无"
|
||||
} else {
|
||||
reason = localization.Reason(reason)
|
||||
}
|
||||
c.line("[会话/SESSION] ID=%d 状态=%s 原因=%s", sessionID, localization.SessionStatus(string(status)), reason)
|
||||
}
|
||||
|
||||
func (c *siteConsole) Route(sessionID uint64, prefix netip.Prefix, result string) {
|
||||
c.line("[路由/ROUTE] 会话=%d 网段=%s 结果=%s", sessionID, prefix, localization.RouteResult(result))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/netip"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"remlink/internal/model"
|
||||
)
|
||||
|
||||
func TestSiteConsoleShowsRequiredStatusAndEvents(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
console := newSiteConsole(&output)
|
||||
console.Header("https://server.example", "Site-A", "1.0.0")
|
||||
console.OverlayReady(netip.MustParseAddr("10.88.0.20"))
|
||||
console.ControlState(true)
|
||||
console.SiteReady()
|
||||
console.Session(model.SessionActive, 42, "")
|
||||
console.Route(42, netip.MustParsePrefix("192.168.13.0/24"), "DIRECT")
|
||||
text := output.String()
|
||||
for _, wanted := range []string{"服务器=", "节点=", "OverlayIP=", "WireGuard=", "Control=", "远程网段=", "子网网关=", "[会话/SESSION]", "活动中(ACTIVE)", "[路由/ROUTE]", "直连路由(DIRECT)"} {
|
||||
if !strings.Contains(text, wanted) {
|
||||
t.Fatalf("console output missing %q: %s", wanted, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"remlink/internal/appdir"
|
||||
"remlink/internal/config"
|
||||
"remlink/internal/identity"
|
||||
"remlink/internal/logging"
|
||||
"remlink/internal/model"
|
||||
"remlink/internal/nodeagent"
|
||||
"remlink/internal/version"
|
||||
)
|
||||
|
||||
const joinTokenEnvironment = "REMLINK_JOIN_TOKEN"
|
||||
|
||||
func main() {
|
||||
if err := run(os.Args[1:]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "remlink-site: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(arguments []string) error {
|
||||
defaultConfigPath, err := appdir.Join("site.yaml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
flags := flag.NewFlagSet("remlink-site", flag.ContinueOnError)
|
||||
configPath := flags.String("config", defaultConfigPath, "Site YAML configuration path")
|
||||
joinToken := flags.String("join-token", os.Getenv(joinTokenEnvironment), "first-registration Join Token (CLI/environment override YAML)")
|
||||
identityPath := flags.String("identity", "", "override DPAPI identity path")
|
||||
if err := flags.Parse(arguments); err != nil {
|
||||
return err
|
||||
}
|
||||
clientConfig, err := config.LoadSite(*configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resolvedJoinToken := config.ResolveJoinToken(*joinToken, clientConfig.JoinToken)
|
||||
if *identityPath == "" {
|
||||
*identityPath, err = identity.DefaultPath(model.NodeTypeSite)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
applicationLogger, err := logging.New(logging.DefaultConfig(filepath.Join(filepath.Dir(*identityPath), "logs", "site.jsonl")))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer applicationLogger.Close()
|
||||
logger, _ := applicationLogger.For(logging.ModuleCore)
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
logger.Info("RemLink Site 正在启动", "version", version.String())
|
||||
console := newSiteConsole(os.Stdout)
|
||||
console.Header(clientConfig.Server, clientConfig.NodeName, version.String())
|
||||
return nodeagent.Run(ctx, nodeagent.Options{
|
||||
NodeType: model.NodeTypeSite, NodeName: clientConfig.NodeName,
|
||||
ServerURL: clientConfig.Server, JoinToken: resolvedJoinToken, IdentityPath: *identityPath,
|
||||
Version: version.String(), Logger: logger, ApplicationLogger: applicationLogger,
|
||||
TCPFlowLimit: clientConfig.Netstack.TCPFlowLimit,
|
||||
UDPFlowLimit: clientConfig.Netstack.UDPFlowLimit,
|
||||
UDPIdleTimeout: time.Duration(clientConfig.Netstack.UDPIdleSeconds) * time.Second,
|
||||
OnOverlayReady: console.OverlayReady,
|
||||
OnControlState: console.ControlState,
|
||||
OnSiteReady: console.SiteReady,
|
||||
OnSession: console.Session,
|
||||
OnRoute: console.Route,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user