初版功能完成
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled

This commit is contained in:
qsc
2026-08-29 13:12:17 +08:00
commit 142e5dc7d6
217 changed files with 21313 additions and 0 deletions
+62
View File
@@ -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))
}
+27
View File
@@ -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)
}
}
}
+78
View File
@@ -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,
})
}