Skip to content

Repository files navigation

UTSLManager

UnifierTSL / TShock 多世界服务端管理器(Windows 桌面客户端,.NET 10 + Avalonia 12)

面向 NAT 后的服务器 + 机房 DPI 屏蔽 HTTP 这类环境:控制面走 SSH,数据面走 SSH 隧道,不需要在服务器上暴露面板端口。

配套服务端插件仓库:Zykor-Club/UTSLServerPlugin(WorldCommandChannel / NetPacketChannel / RuYouMusic / GenerateMap 等) 想给管理器加功能?见 PLUGINS.md(插件 API v4,独立工程或 .csx 脚本,丢进 plugins\ 即生效)


功能

分类 能力
世界与指令 世界列表;按世界/按序号执行指令(@Dev /save、@2 /save、不写前缀=全部世界);以 > 开头 = 服务端控制台口径(输出走服务端日志,控制台页直接显示)
控制台 上方是服务端日志流(逐行跟随 logs\\*.log),下方输入指令;命中/失败都会回显;输出延迟约数秒(服务端日志是缓冲写的)
进程 启停/重启、状态三灯(隧道 / 命令通道 / 接口)、看门狗(掉线自启,默认开启且记住;连续 3 次熔断 + 启动/换图保护窗口)
远端管理 SSH 远端启停(WMI + 控制台启动器)、远端日志跟随、SSH 隧道自管(20 秒保活、断线自愈)
地图 内置 TEdit 直接解析 .wld 渲染(不依赖服务端 GenerateMap、不跨 ssh 传大图):全分辨率、拼接图(按 EdgeStitch 世界链还原成一张)、拖动平移 / 双击缩放 / 滚轮(Ctrl 竖移、Shift 横移、默认以光标为中心缩放)、框选
建筑模板 地图上框选 → 导出 .utpl 区块模板;浏览服务端已有模板。由官方扩展 extensions/MapTemplates 提供(装了才有;服务端没有模板能力时自动隐藏)
文件 浏览(表格式)、下载 / 上传 / 删除、插件 .dll 一键启用停用、传输进度与取消
配置 启动器 config.json 与 EdgeStitch.json 结构化编辑、保存自动 .bak、原子写
监控 控制台仪表盘:CPU(单核 %)/ 内存(私有)/ 网络;装了服务端 MetricsChannel 插件时经现有 REST 隧道 2 秒级实时(实测单次 98~147 ms),否则回退到每 45 秒的 SSH 合并快照。速率均为滚动 5 秒窗口
云端 agent 装在被管机器上的只读 agent(daemon/UTSLDaemon,只绑回环、走已有的 SSH 隧道):日志 SSE 推送(实测 154 ms,对比轮询 12 s 限流)、指标 2 秒级(不要求服务端装 MetricsChannel 插件)。连接设置页有状态灯 + 一键安装/更新按钮
备份 远端打包 Worlds+config 并 scp 下载到本机形成离机副本
其它 世界链可视化、自检入口、插件系统(含插件管理页)、凭据 DPAPI 加密

架构

┌─────────────────────────┐         SSH(控制面:启停/日志/文件/配置)
│  UTSLManager (本机)      │ ───────────────────────────────────────────┐
│  Avalonia 桌面客户端      │                                            ▼
│                         │         SSH 隧道 -L 17878 → 远端 127.0.0.1:7878
│  · 合并快照(一次 ssh)   │ ───────────────────────────────────►  TShock/UTSL REST
│  · Fs* 文件抽象          │                                        (世界列表 / 指令 / 广播 / 存档)
│  · 90s 缓存 + 启动预热    │
│  · 内置 TEdit 渲染        │  ← 地图/模板全在本机算,服务端不参与
└─────────────────────────┘         scp(大文件:备份 / 上传下载)

设计要点

  • 服务器 REST 只监听回环,通过 SSH 隧道访问 → 公网不暴露任何面板端口。
  • 远端命令一律 ssh ... powershell -NoProfile -EncodedCommand <base64>,规避 Windows 引号/转义问题。
  • 一次 RefreshSnapshot() 取回「进程数 / 监听端口 / 启动器配置 / 世界链配置 / 根目录 + 常用子目录」并写入缓存 → 各页面读缓存,毫秒级。
  • 地图与模板全部在管理器本机算:.wld 解析(TEdit)→ 位图 → 显示;裁块 → .utpl 编码 → 上传。服务端只提供文件,不需要装 GenerateMap。

云管演进:UTSLDaemon(P0 已落地)

现在的模型是「控制面走 SSH、每次动作新开一条 ssh」(实测握手 1.6 s;40+ 进程时劣化到 14~18 s), 日志靠轮询文件(远端限流 12 s)、指标靠 45 s 合并快照。MCSManager 那套能推送,是因为它远端常驻一个 daemon, 但它的代价是要把 daemon 端口暴露出去。

本项目把两者合体:agent 装进已有的那条常驻 SSH 隧道(多一条 -L 17879:127.0.0.1:7879), 能力与 MCSManager 相当,公网暴露面仍然是零。

daemon/UTSLDaemon(只读,只绑回环):

接口 用途
GET /ping 探活 + 版本 + 部署根(管理器用它判断"这台装了 agent 没有",没装就回落现在的 SSH 路径)
GET /metrics 进程/整机指标:CPU 单核%、私有内存、工作集、线程、句柄、网卡累计字节
GET /stream/log SSE 推送 logs\*.log 的新增行

实现:TcpListener 手写极简 HTTP/1.1(不用 HttpListener,避开需要管理员权限的 URL ACL); 日志只把完整行计入已消费位置;自带 --selftest(进程内起监听并自己连自己量延迟,用系统分配的空闲端口)。

部署:管理器「连接设置」页有 安装 / 更新云端 agent 按钮(内部流程:先停旧进程 → scp 推送 → 重建计划任务 → 重探)。也可以手工执行:

部署(远端没有 .NET 10 时用自包含单文件):

dotnet publish daemon/UTSLDaemon -c Release -r win-x64 --self-contained true `
  -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true -p:DebugType=none -o out
# 上传后常驻(开机自启):
schtasks /Create /TN UTSLAgent /TR "\"C:\utsl-agent\UTSLDaemon.exe\" --root C:\utsl-deploy" /SC ONSTART /RU SYSTEM /RL HIGHEST /F
schtasks /Run /TN UTSLAgent

实测(本机 + 真实远端)

场景 数据
本机 agent 推送延迟 5 次 159/155/155/155/156 ms → 平均 156 ms
远端 agent 推送延迟 5 次 149/154/156/156/156 ms → 平均 154 ms
经隧道 /metrics 往返 222 / 154 / 132 ms
经隧道 SSE 端到端 远端写入 → 本机收到 1 行命中 ✓
自包含 agent 体积 单文件 35.9 MB(远端只有 .NET 9,实测可跑)
部署同步 186 MB scp 2.7 MB/s / 68.9 s;远端解压 428 文件 407.6 MB 用 2 s
指标轮询周期(远端) 90~100 秒窗口实测 平均 2.4 秒(原为 45 秒快照)

对比现状:远端日志靠 12 s 限流的 ssh 轮询、指标靠 45 s 合并快照 → 推送 154 ms。

构建与发布

环境要求

  • Windows 10/11(必须 Windows:桌面 UI + user32/crypt32 P/Invoke + DPAPI)
  • .NET 10 SDK
  • 系统自带 OpenSSH 客户端(ssh / scp,用于远端模式)
  • TEdit 源码:地图渲染依赖它。用 scripts/get-tedit.ps1 取(固定提交,已就绪时秒退;CI 带缓存)

本地构建

git clone https://github.com/Zykor-Club/UTSL-Manager.git
cd UTSL-Manager

# 0) 取 TEdit 源码到 third_party/(固定提交,已存在则秒退)
./scripts/get-tedit.ps1

# 1) 发布管理器(⚠️ 只 build 不会部署,必须 publish)
dotnet publish -c Release
#   默认输出到 仓库上两级\AvaloniaUTSL\(与 csproj 里 PublishDir 一致)
#   想换位置:dotnet publish -c Release -p:PublishDir=D:\somewhere\

# 2) 构建官方扩展插件(会自动部署到 <管理器目录>\plugins\)
dotnet build extensions/PacketInspector/PacketInspector.csproj -c Release
dotnet build extensions/RuYouMusicPanel/RuYouMusicPanel.csproj -c Release
dotnet build extensions/ApiProbe/ApiProbe.csproj -c Release
dotnet build extensions/MapTemplates/MapTemplates.csproj -c Release
#   管理器不在默认目录时:加 -p:ManagerDir=D:\path\to\AvaloniaUTSL

# 2.5) 打包云端 agent(自包含单文件 → <管理器目录>\agent\UTSLDaemon.exe)
#      「安装/更新云端 agent」按钮用的就是它;被管机器通常没有 .NET 10,所以必须自包含。
#      ⚠ 本机若禁止执行脚本,需要 -ExecutionPolicy Bypass
powershell -NoProfile -ExecutionPolicy Bypass -File ./scripts/publish-agent.ps1
# 3) 运行
.\..\..\AvaloniaUTSL\UTSLManager.exe

扩展插件是独立工程,构建时读取管理器发布目录里的 UTSLManager.dll(-p:ManagerDir 指定)。 MapTemplates 只在编译期引用宿主的 UTSLManager.Maps.*(不重复带 TEdit/SkiaSharp,产物约 18 KB)。 .gitignore 忽略的 bin/、obj/、publish/、third_party/ 不会进仓库。

CI 工作流(GitHub Actions)

工作流 触发 做什么
.github/workflows/build.yml push / PR 到 main、手动 windows-latest + .NET 10 → 取 TEdit(带缓存)→ restore → build → publish → 构建四个官方扩展 → 上传产物 UTSLManager-win-x64
.github/workflows/release.yml 打 v* 标签、手动 同上发布 + 打包 UTSLManager-win-x64.zip + 创建 Release,并校验产物里扩展数 ≥4

发版流程

git tag v1.0.0
git push origin v1.0.0     # 推送标签后,release 工作流自动产出 Release 与 zip

CI 里统一用 -p:PublishDir=${{ github.workspace }}/publish/ 覆盖发布目录, 并用 -p:ManagerDir=…/publish 让扩展插件找到管理器 DLL。

首次运行(连接设置)

首次运行后在 连接设置 页填写:REST 主机/端口与令牌、SSH 主机/端口/用户/密钥、远端 UTSL 目录、远端 REST 端口,点「保存并应用」→ 配置文件 connection.json 就在 exe 同目录:

{
  "host": "127.0.0.1", "port": 17878,          // 本机隧道入口
  "language": "zh-CN",
  "tokenEnc": "<DPAPI 加密后的令牌>",            // 不写明文;换机器/换用户需重新填
  "ssh": {
    "host": "<你的服务器>", "port": 22, "user": "<用户>",
    "key": "<私钥路径>", "dir": "C:\\utsl-deploy",
    "dotnet": "C:\\dotnet\\dotnet.exe", "launcher": "C:\\utsl-launch\\utsl-launch.dll",
    "restPort": 7878
  },
  "paths": {
    "localDir": "<本机 utsl-deploy 路径,本机模式用>",
    "watchdog": true                            // 掉线自启(默认 true;界面改过会记住)
  }
}

远端准备:UTSL 运行目录就位;另外需要一个「控制台启动器」(utsl-launch.dll)—— UTSL 启动时要用真实控制台,纯 SSH 会话里 Console.WindowHeight 会抛「句柄无效」,启动器会用 AllocConsole + CREATE_NEW_CONSOLE 补一个。


自检(推荐)

.\AvaloniaUTSL\UTSLManager.exe --selfcheck
# 结果写到 exe 同目录 selfcheck.txt

会逐项打印真实耗时:连接配置 / 隧道端口 / REST 世界列表 / 合并快照 / config / 根目录 / 地图 / 远端服务端 / 扩展 / 文件传输往返 / DPAPI / 凭据存储 / 备份。


真实性能数据(本机实测,非估算)

项目 数据
ssh 单次握手 1.6 s(并发堆积到 40+ 个进程时会劣化到 14~18 s)
单次远端读取(文件/配置) 3~8 s
scp 下载 / 上传 1.31 MB/s / 2.39 MB/s
换加密算法(aes128-gcm/ctr、chacha20)/ 压缩 / 2~4 流并发 均无提升(瓶颈是链路)
base64 走 ssh(常驻通道口径) 0.30~0.37 MB/s(比 scp 慢 4 倍)
合并快照(唯一一次 ssh,每 45 s 后台) 6.3~9.1 s
各页面读取(快照命中后) config 0~1 ms、根目录 0~3 ms、服务端 0 ms
REST 世界列表(走隧道) 275~336 ms
备份 打包 7,339,267 B / 7.2 s + 下载 7 MB / 5.8 s(92 条目)
失焦节流 前台 5% → 最小化 1% CPU
传输往返校验 上传 64 KB → 远端 64 KB → 下载逐字节一致 → 删除已清除
地图渲染(本机内置,全分辨率) Dev.wld 4200×2400:载入 ~820 ms(缓存命中 0 ms)、渲染 384 ms
拼接图(EdgeStitch 三片) 12000×2400:~2390 ms(载入 1808 + 渲染 578)
拼接后内存回收 工作集 1029 MB → 65 MB(GC.Collect(2, Aggressive, compacting);不做的话驻留 ~1 GB)
.utpl 区块模板往返 裁块 → 写入 → 读回:逐字节一致(789 B 夹具);箱子物品逐槽一致
看门狗 本机杀掉服务端后 6 秒自动拉起(存活判据用 !HasExited,见"已知限制")

场景:3 个世界(4200×2400)、本机/远端两种模式。


已知限制

  • 远端读取不是瞬时的:每次 ssh 都有握手成本(实测 1.6 s 起)。应对是「合并快照 + 90 s 缓存 + 启动预热 + 先显示后刷新」,页面体感毫秒级,但首次冷启动仍会有几秒。
  • 上传没有真实百分比:scp 不回传进度,只显示大小与已用时间(不伪造进度条)。
  • 地图全分辨率渲染吃内存:单世界工作集 430 MB;拼接三片峰值 **1.21.4 GB**(渲染完做一次大对象堆压缩回收,落回 ~65 MB)。内存紧张时可考虑加"降采样渲染"开关。
  • 控制台指令输出的边界:管理器走 REST /world/command 执行,命令正文的输出(如 /help 的分页)由服务端写进它自己的日志,管理器靠"日志流"呈现;REST 的 response 拿不到这部分文本(宿主 CommandExecutor 的设计所限;配套 UTSL 侧已打补丁让世界控制台输出回流到共享日志)。
  • 不要给服务端做 stdin/stdout 重定向(例如想"接管控制台"把输入喂进进程):UTSL 启动时要读控制台缓冲,一旦没有真实控制台,ServerSurfaceConsole 构造抛 IOException → TShock 静态构造失败 → 20 个插件里 16 个初始化失败(含 VirtualWorldCore / 尘歌壶)。管理器因此不重定向,改用"日志流 + REST"。
  • 进程存活判据不能用进程名:这台机器上被杀掉的进程会留下工作集 0 / 线程 0 / HasExited=True 的僵尸条目 → 按名字判会以为服务端还活着(看门狗曾因此永不触发)。现统一用 LocalProcessBackend.AnyAlive()。
  • 常驻命令通道(ssh 上挂 PowerShell 循环)已停用:实测远端 PowerShell 在 ssh 循环里读 stdin 不可靠(第一次正常、之后收不到或空转刷屏),收益不值风险。
  • 需要远端是 Windows + OpenSSH(脚本走 PowerShell)。

目录结构

├─ Program.cs                  入口(含 --selfcheck)
├─ App.axaml(.cs)              Avalonia 应用(含全局滚动条外观与背景兜底)
├─ MainWindow.axaml(.cs)       主窗口:视图、远端访问(Fs*/SshRun/Snapshot)、REST、看门狗、指标图表
├─ Backends/                   契约与本机/远端实现(IFileBackend / IProcessBackend / IEnvironmentInfo …)
├─ Maps/                       地图与模板(全部本机算)
│   ├─ WorldMapRenderer.cs     .wld → 位图(TEdit),含拼接图渲染与进度
│   ├─ StitchConfig.cs         EdgeStitch 世界链/切片间距 → 拼接方案
│   ├─ WorldCropper.cs         按矩形裁块 → 区块
│   ├─ UtplCodec.cs            .utpl v3 读写(语义化格式,含尺寸/格子数上限防御)
│   └─ ServerCapabilities.cs   服务端"有没有模板能力"的探测
├─ Plugins/                    管理器插件系统(**API v4**:IManagerPlugin / IMapContributionPlugin / IManagerContext / PluginHost…)
├─ extensions/                 官方扩展源码(数据包查看器、点歌机面板、API 探针、**建筑模板**)
├─ daemon/UTSLDaemon/          云端 agent 源码(只读:日志 SSE 推送 + 指标;只绑回环,走 SSH 隧道)
├─ scripts/get-tedit.ps1       取 TEdit 源码(固定提交 + CI 缓存)
├─ scripts/publish-agent.ps1   把 agent 发布成自包含单文件放进 <管理器目录>\agent\(安装按钮的源)
└─ PLUGINS.md                  插件开发指南

许可证

本项目以 GNU General Public License v3.0(GPL-3.0)发布,全文见 LICENSE。

Copyright (C) 2026 Zykor-Club

Releases

Packages

Used by

Contributors

Languages