refactor!: rename the project from Monitor to MoShiro [CI]
ci/woodpecker/push/main-amd64-build Pipeline was successful
ci/woodpecker/push/main-amd64-build Pipeline was successful
Namespaces, assemblies, solution, binaries, images, systemd units, config paths, environment variables, protocol methods and the Linux user are all renamed. Breaking: existing databases, node identities and installed units must be recreated.
This commit is contained in:
1 parent
27ac7fb787
commit
2e146d4da7
309 files changed
+1565
-1572
No files matched your search
@@ -23,4 +23,4 @@ steps:
|
||||
environment:
|
||||
NUGET_PACKAGES: /home/justme/.nuget/packages
|
||||
commands:
|
||||
- cd backend && dotnet test --solution Monitor.slnx -c Release
|
||||
- cd backend && dotnet test --solution MoShiro.slnx -c Release
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Project
|
||||
|
||||
Monitor is a self-hosted network path quality monitor. Backend coordinates Agents and Probes, stores measurements, and serves the REST/SSE API. `web/display` is the read-only overview; `web/admin` manages nodes, tests, and settings.
|
||||
MoShiro is a self-hosted network path quality monitor. Backend coordinates Agents and Probes, stores measurements, and serves the REST/SSE API. `web/display` is the read-only overview; `web/admin` manages nodes, tests, and settings.
|
||||
|
||||
## Branch and workspace safety
|
||||
|
||||
@@ -10,12 +10,12 @@ Monitor is a self-hosted network path quality monitor. Backend coordinates Agent
|
||||
- A push to `main` builds images only if its commit message contains `[CI]` (for a merged PR, put it in the PR or merge commit title). Without `[CI]` nothing is built; use `[CI SKIP]` to skip the pipeline entirely. When squashing or pushing to `main`, add `[CI]` unless a build is explicitly not wanted.
|
||||
- Before editing, inspect `git status`. Preserve pre-existing user changes: do not reset, clean, stash, or include unrelated files in a commit.
|
||||
- Do not commit credentials, API keys, registry passwords, or generated runtime data. CI credentials belong in Woodpecker secrets (`zot_secret`, `gitea_token`, `release_signing_key` for signing release manifests, `release_api_token` with the `release` scope). The release signing private key never goes into the repository or the panel; only the public key in `packaging/update/` does.
|
||||
- Keep development OCI images in the `monitor-dev/` repository path and release images in `monitor/`. Do not let a development build overwrite release tags such as `latest`.
|
||||
- Keep development OCI images in the `moshiro-dev/` repository path and release images in `moshiro/`. Do not let a development build overwrite release tags such as `latest`.
|
||||
|
||||
## Project layout
|
||||
|
||||
- `backend/src/Monitor.Backend/`: service entry point and composition.
|
||||
- `backend/src/Monitor.Backend.Api/`: REST endpoints, contracts, and OpenAPI.
|
||||
- `backend/src/MoShiro.Backend/`: service entry point and composition.
|
||||
- `backend/src/MoShiro.Backend.Api/`: REST endpoints, contracts, and OpenAPI.
|
||||
- `backend/agent/`, `backend/probe/`: node applications and their READMEs.
|
||||
- `backend/fleet/`, `backend/shared/`: reusable node framework, measurement engine, protocols, and data formats.
|
||||
- `web/display/`, `web/admin/`: Vite applications; `web/shared/`: API client, generated types, charts, and shared UI.
|
||||
@@ -25,12 +25,12 @@ Monitor is a self-hosted network path quality monitor. Backend coordinates Agent
|
||||
|
||||
Requirements: .NET SDK 10 and Node.js/npm. The SDK selection is defined by the root `global.json`; NuGet package versions are centralized in the root `Directory.Packages.props`.
|
||||
|
||||
- All .NET projects: `dotnet build Monitor.slnx -c Release` from the repository root.
|
||||
- Backend build: `cd backend && dotnet build Monitor.slnx -c Release`
|
||||
- Backend tests: `cd backend && dotnet test --solution Monitor.slnx -c Release`
|
||||
- All .NET projects: `dotnet build MoShiro.slnx -c Release` from the repository root.
|
||||
- Backend build: `cd backend && dotnet build MoShiro.slnx -c Release`
|
||||
- Backend tests: `cd backend && dotnet test --solution MoShiro.slnx -c Release`
|
||||
- Frontend dependencies/build: `npm ci --prefix web && npm run build --prefix web`
|
||||
- Frontend style check: `npm run lint:css --prefix web`
|
||||
- Local backend: from `backend/`, run `dotnet run --project src/Monitor.Backend -- --urls http://localhost:5000`.
|
||||
- Local backend: from `backend/`, run `dotnet run --project src/MoShiro.Backend -- --urls http://localhost:5000`.
|
||||
- Local web apps: from the repository root, run `npm ci --prefix web` then `npm run dev --prefix web`. The current dev config uses display port 5173 and admin port 5175; `/admin/` is proxied through the display server. Check the Vite configs when working on another branch because ports can differ.
|
||||
- After changing API contracts, regenerate/check `backend/openapi/v1.json` and run `npm run gen:api --prefix web` so frontend API types stay aligned.
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<PropertyGroup>
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
<!-- Native SQLite source is pinned separately from the JIT/test binary package. -->
|
||||
<MonitorSqliteSourceVersion>3530400</MonitorSqliteSourceVersion>
|
||||
<MonitorSqliteSourceYear>2026</MonitorSqliteSourceYear>
|
||||
<MonitorSqliteSourceSha256>1e71ddf93849c6a6ecf58b827c0692073d2dd7ee40196158068f7b29f422e87d</MonitorSqliteSourceSha256>
|
||||
<MoShiroSqliteSourceVersion>3530400</MoShiroSqliteSourceVersion>
|
||||
<MoShiroSqliteSourceYear>2026</MoShiroSqliteSourceYear>
|
||||
<MoShiroSqliteSourceSha256>1e71ddf93849c6a6ecf58b827c0692073d2dd7ee40196158068f7b29f422e87d</MoShiroSqliteSourceSha256>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="5.6.0" />
|
||||
|
||||
+17
-17
@@ -17,36 +17,36 @@
|
||||
<Project Path="backend/fleet/tools/fleet-enroll/fleet-enroll.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/shared/">
|
||||
<Project Path="backend/shared/Monitor.Measurement/Monitor.Measurement.csproj" />
|
||||
<Project Path="backend/shared/Monitor.Protocol/Monitor.Protocol.csproj" />
|
||||
<Project Path="backend/shared/MoShiro.Measurement/MoShiro.Measurement.csproj" />
|
||||
<Project Path="backend/shared/MoShiro.Protocol/MoShiro.Protocol.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/shared/tests/">
|
||||
<Project Path="backend/shared/tests/Monitor.Measurement.Tests/Monitor.Measurement.Tests.csproj" />
|
||||
<Project Path="backend/shared/tests/Monitor.Protocol.Tests/Monitor.Protocol.Tests.csproj" />
|
||||
<Project Path="backend/shared/tests/MoShiro.Measurement.Tests/MoShiro.Measurement.Tests.csproj" />
|
||||
<Project Path="backend/shared/tests/MoShiro.Protocol.Tests/MoShiro.Protocol.Tests.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/agent/src/">
|
||||
<Project Path="backend/agent/src/Monitor.Agent/Monitor.Agent.csproj" />
|
||||
<Project Path="backend/agent/src/MoShiro.Agent/MoShiro.Agent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/probe/src/">
|
||||
<Project Path="backend/probe/src/Monitor.Probe/Monitor.Probe.csproj" />
|
||||
<Project Path="backend/probe/src/MoShiro.Probe/MoShiro.Probe.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/probe/tests/">
|
||||
<Project Path="backend/probe/tests/Monitor.Probe.Tests/Monitor.Probe.Tests.csproj" />
|
||||
<Project Path="backend/probe/tests/MoShiro.Probe.Tests/MoShiro.Probe.Tests.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/src/">
|
||||
<Project Path="backend/src/Monitor.Backend.Analysis/Monitor.Backend.Analysis.csproj" />
|
||||
<Project Path="backend/src/Monitor.Backend.Api/Monitor.Backend.Api.csproj" />
|
||||
<Project Path="backend/src/Monitor.Backend.Application/Monitor.Backend.Application.csproj" />
|
||||
<Project Path="backend/src/Monitor.Backend.Gateway/Monitor.Backend.Gateway.csproj" />
|
||||
<Project Path="backend/src/Monitor.Backend.Storage.Sqlite/Monitor.Backend.Storage.Sqlite.csproj" />
|
||||
<Project Path="backend/src/Monitor.Backend/Monitor.Backend.csproj" />
|
||||
<Project Path="backend/src/MoShiro.Backend.Analysis/MoShiro.Backend.Analysis.csproj" />
|
||||
<Project Path="backend/src/MoShiro.Backend.Api/MoShiro.Backend.Api.csproj" />
|
||||
<Project Path="backend/src/MoShiro.Backend.Application/MoShiro.Backend.Application.csproj" />
|
||||
<Project Path="backend/src/MoShiro.Backend.Gateway/MoShiro.Backend.Gateway.csproj" />
|
||||
<Project Path="backend/src/MoShiro.Backend.Storage.Sqlite/MoShiro.Backend.Storage.Sqlite.csproj" />
|
||||
<Project Path="backend/src/MoShiro.Backend/MoShiro.Backend.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/tests/">
|
||||
<Project Path="backend/tests/Monitor.Backend.Analysis.Tests/Monitor.Backend.Analysis.Tests.csproj" />
|
||||
<Project Path="backend/tests/Monitor.Backend.Tests/Monitor.Backend.Tests.csproj" />
|
||||
<Project Path="backend/tests/Monitor.TestSupport/Monitor.TestSupport.csproj" />
|
||||
<Project Path="backend/tests/MoShiro.Backend.Analysis.Tests/MoShiro.Backend.Analysis.Tests.csproj" />
|
||||
<Project Path="backend/tests/MoShiro.Backend.Tests/MoShiro.Backend.Tests.csproj" />
|
||||
<Project Path="backend/tests/MoShiro.TestSupport/MoShiro.TestSupport.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/ci/">
|
||||
<Project Path="ci/Monitor.Tar/Monitor.Tar.csproj" />
|
||||
<Project Path="ci/MoShiro.Tar/MoShiro.Tar.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
@@ -1,18 +1,36 @@
|
||||
# Monitor
|
||||
# MoShiro
|
||||
|
||||
Monitor 是一个自托管的网络线路质量观测工具。它在 Probe 与多个 Agent 之间执行主动网络测量,记录逐包和逐事件结果;Backend 保存原始数据并生成可查询的线路指标,Web 界面用于查看、比较和管理节点。
|
||||
MoShiro 是一个自托管的**网络线路质量观测工具**。它在若干 Probe(例如家宽出口)与多台 Agent(例如海外 VPS)之间做主动网络测量,只记录逐包、逐事件的原始数据;Backend 保存这些原始数据,推算延迟、丢包、抖动、吞吐等指标,并回答「如果在这条线路上部署某类服务,体验会怎样」;Web 界面用来查看、比较和管理。
|
||||
|
||||
它面向需要比较不同网络出口、评估线路稳定性,或观察特定网络服务体验的场景。Agent 与 Probe 专注于测量和上报;它们不提供远程终端、命令执行或文件管理能力。
|
||||
## 项目边界
|
||||
|
||||
## 能力概览
|
||||
**它做什么**
|
||||
|
||||
- **主动测量**:组合 TCP、UDP 等测量模块,采集延迟、丢包、抖动、吞吐和连接事件。
|
||||
- **线路分析**:从原始测量数据生成时间序列指标,并比较线路在不同服务模型下的体验。
|
||||
- **节点管理**:通过 Fleet 节点框架处理身份、注册、连接、任务同步和数据上报。
|
||||
- **实时面板**:展示端查看线路状态和历史趋势;管理端负责节点、任务和系统配置。
|
||||
- **线路管理**:Backend 决定哪些 Probe 连哪些 Agent(自动连接开关加手动选择),在管理端的线路矩阵里一键开关。
|
||||
- **节点信息与更新**:节点自报系统、架构和构建信息(版本、tag、提交、构建时间),国家和运营商由 Backend 查询;容器节点的更新由 CI 签名、Backend 转发、节点宿主机上的更新器验签并执行,失败自动回滚,管理端可以看状态和点「更新」。
|
||||
- **自托管**:Backend 使用段文件和 SQLite 保存数据;提供 Linux、systemd 和 OpenWrt 相关部署文件。
|
||||
- 主动测量:组合 TCP、UDP、TLS、ICMP 等采集积木,只记录时间、序号、大小这类原始事实,**不转发也不记录业务内容**。
|
||||
- 线路分析:Backend 从原始数据生成时间序列指标,按服务模型(REALITY、Hysteria2、Minecraft 等)评分并对多条线路排序、对比;模型和指标可以事后重算。
|
||||
- 管理:节点注册与审批、Probe 和 Agent 之间连哪些线路、任务与按需测试、节点版本与更新,都在管理端完成。
|
||||
|
||||
**它不做什么**
|
||||
|
||||
- **节点是纯采集器**:Agent 和 Probe 没有终端、命令执行、文件读写和自更新能力,也不加载外部代码。更新由节点宿主机上的更新器完成,节点进程只放面板转来的发布清单。
|
||||
- **不是主机监控**:Agent 只上报 CPU、内存、磁盘、网卡流量和运行时长这类只读的汇总数字,不采集型号、主机名、进程和任何 IP;Probe 不上报主机资源。
|
||||
- **不暴露地址**:公开接口里没有 IP、城市、ASN;国家和运营商只由 Backend 查询(节点自己不访问任何外部服务)。
|
||||
- 暂不包含告警(见文末待办)。
|
||||
|
||||
## 信任关系
|
||||
|
||||
```text
|
||||
管理员 ── 账号密码(Cookie 会话 + CSRF)或 API 令牌(read · admin · release)──▶ Backend
|
||||
访客 ── 匿名只读,站点设为公开时才可用(没有 IP、城市、ASN)──────────────▶ Backend 公开接口
|
||||
|
||||
节点 ── 加入令牌一次性注册,之后用自己的 ECDSA P-256 密钥签名认证 ───────▶ Backend(只存公钥)
|
||||
Probe ── 用同一把密钥签名建立测量会话 ───────────────────────────────────▶ Agent(只放行面板名单里的 Probe 公钥)
|
||||
CI ── 发布私钥对「镜像 digest 清单」签名 ──▶ Backend 原样转发 ──▶ 节点宿主机的更新器用本机公钥验签
|
||||
```
|
||||
|
||||
- **Backend 不信任节点上报的内容**:数据按线路隔离存放;节点的身份靠签名,数据库泄露也无法冒充节点。
|
||||
- **节点不信任 Backend 的下发**:任务超出节点本机的硬上限(目标网段、码率、包速率、监听端口)就被拒绝;节点不执行任何「执行类」指令。
|
||||
- **更新不依赖面板的诚实**:清单必须由发布私钥签名;节点宿主机的更新器用本机公钥验签、只认本机配置的镜像仓库、按 digest 拉取、拒绝旧的 `seq`。面板被攻破也推不出恶意镜像。
|
||||
|
||||
## 架构
|
||||
|
||||
@@ -35,99 +53,68 @@ Monitor 是一个自托管的网络线路质量观测工具。它在 Probe 与
|
||||
测量会话:TLS 控制通道 + UDP/TCP 数据通道,由 Probe 主动拨出
|
||||
```
|
||||
|
||||
两种连接:**Probe 和 Agent 都用长连接连 Backend**(身份、任务、状态、更新通知),**测量只走 Probe → Agent**(Probe 主动拨出,不需要入站端口)。哪些 Probe 连哪些 Agent 由 Backend 决定:默认自动全连,也可以在管理端手动选择。数据存储:段文件保存原始数据,SQLite 保存索引、指标与配置。
|
||||
两种连接:**Probe 和 Agent 都用长连接连 Backend**(身份、任务、状态、更新通知),**测量只走 Probe → Agent**(Probe 主动拨出,Agent 不需要连 Probe,家宽也不需要入站端口)。哪些 Probe 连哪些 Agent 由 Backend 决定:默认自动全连,也可以在管理端手动选择。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 环境要求
|
||||
- .NET SDK 10;项目使用 `global.json` 指定 SDK 版本和滚动策略。
|
||||
- Node.js 与 npm,用于构建 Web 前端。
|
||||
|
||||
### 构建
|
||||
|
||||
从仓库根目录构建全部 .NET 项目(Backend、Agent、Probe、Fleet、测试和 CI 工具):
|
||||
|
||||
```sh
|
||||
dotnet build Monitor.slnx -c Release
|
||||
```text
|
||||
采集(只记录) 传输(有界、至少一次) 分析(注册制、可重算) 展示
|
||||
Probe / Agent 积木 ──▶ 原始列块(10 秒一块)──▶ Backend 段文件 ──▶ 指标与服务模型 ──▶ REST / SSE ──▶ Web
|
||||
```
|
||||
|
||||
NuGet 包版本统一在根目录 `Directory.Packages.props` 管理,项目中的 `PackageReference` 不写版本号。AotAnywhere 的 MSBuild SDK 版本在根目录 `global.json` 的 `msbuild-sdks` 中管理。`backend/Monitor.slnx` 保留用于只构建后端目录内的项目。
|
||||
Backend 用段文件保存原始数据,用 SQLite 保存索引、指标与配置。**完整的架构、数据链路、安全边界和设计决策见 [`docs/architecture.md`](docs/architecture.md)。**
|
||||
|
||||
安装前端依赖并构建展示端和管理端(在仓库根目录运行):
|
||||
## 协议
|
||||
|
||||
- **控制链路(Fleet)**:节点到 Backend 的 `wss` 长连接(`/api/agent/v1/rpc`),JSON-RPC 2.0 信封加二进制通道;挑战加签名认证,`hello` 协商版本和能力,心跳;**期望状态同步**(Backend 下发完整快照,节点调和并回报已应用的版本和被拒绝的条目);**可靠发件箱**(原始块至少一次投递,按 id 去重)。Fleet 与业务无关,放在 `backend/fleet/`。
|
||||
- **业务方法**:`moshiro.*`(按需测试、Probe 断开、事件、主机汇总、更新通知等),状态文档有 `task_set`(Agent 的任务集)和 `probe_agents`(Probe 的 Agent 列表)。
|
||||
- **测量数据面**:Probe 到 Agent 的 TLS 控制通道、UDP 和 TCP 数据通道(默认 `7443/tcp`、`7443/udp`、`7444/tcp`);会话用 `session_id` 和 MAC 认证、不绑定五元组,NAT 映射变化后自动跟随。
|
||||
- **REST + SSE**:`/api/public`(展示,可匿名)、`/api/admin`(管理);OpenAPI 在 `backend/openapi/v1.json`,前端据此生成类型。
|
||||
|
||||
细节见 [`docs/protocol.md`](docs/protocol.md)、[`docs/fleet/link.md`](docs/fleet/link.md)、[`docs/api.md`](docs/api.md)、[`docs/enrollment.md`](docs/enrollment.md)。
|
||||
|
||||
## 如何构建
|
||||
|
||||
环境:.NET SDK 10(`global.json` 指定版本)、Node.js 与 npm。在仓库根目录:
|
||||
|
||||
```sh
|
||||
npm ci --prefix web
|
||||
npm run build --prefix web
|
||||
npm run gen:api --prefix web # API schema changed after Backend edits
|
||||
npm run lint:css --prefix web
|
||||
dotnet build MoShiro.slnx -c Release # Backend、Agent、Probe、Fleet、测试和 CI 工具
|
||||
npm ci --prefix web && npm run build --prefix web # 展示端和管理端,产物在 web/display/dist、web/admin/dist
|
||||
```
|
||||
|
||||
前端产物位于 `web/display/dist/` 和 `web/admin/dist/`。NativeAOT 目标平台和 Windows 上使用 AotAnywhere 交叉构建的说明见 [`docs/deployment.md`](docs/deployment.md)。
|
||||
本地运行:先在 `backend/` 里 `dotnet run --project src/MoShiro.Backend -- --urls http://localhost:5000`,再 `npm run dev --prefix web`,展示端是 <http://localhost:5173/>,管理端是 <http://localhost:5173/admin/>。
|
||||
|
||||
Backend、Agent 和 Probe 均以 OCI 镜像发布,支持 Linux/amd64 与 Linux/arm64;Agent 和 Probe 另外提供 Linux glibc/musl、macOS 与 Windows 二进制包。容器部署配置见 [`compose.yaml`](compose.yaml),发布说明见 [`docs/deployment.md`](docs/deployment.md)。
|
||||
|
||||
### 本地运行
|
||||
|
||||
1. **启动 Backend。** 在仓库根目录运行:
|
||||
测试和检查:
|
||||
|
||||
```sh
|
||||
cd backend
|
||||
dotnet run --project src/Monitor.Backend -- --urls http://localhost:5000
|
||||
dotnet test --solution MoShiro.slnx # 后端和节点的测试
|
||||
npm run lint:css --prefix web # 前端样式(颜色只能写在主题令牌里)
|
||||
npm run gen:api --prefix web # 后端改了接口之后重新生成前端类型
|
||||
```
|
||||
|
||||
2. **启动前端。** 保持 Backend 运行,在另一个终端的仓库根目录运行:
|
||||
Backend、Agent、Probe 以 OCI 镜像发布(Linux amd64、arm64),Agent 和 Probe 另有 Linux glibc/musl、macOS、Windows 的二进制包。NativeAOT 的构建细节、CI 触发规则(`[CI]`、`[SKIP TESTS]`、版本 tag)、镜像路径、容器节点的运行和更新见 [`docs/deployment.md`](docs/deployment.md);前端见 [`web/README.md`](web/README.md),后端见 [`backend/README.md`](backend/README.md)。
|
||||
|
||||
```sh
|
||||
npm ci --prefix web
|
||||
npm run dev --prefix web
|
||||
```
|
||||
## 文档
|
||||
|
||||
打开 <http://localhost:5173/> 使用展示端,或访问 <http://localhost:5173/admin/> 进入管理端。依赖安装和前端配置见 [`web/README.md`](web/README.md);Backend 的首次启动与配置见 [`backend/README.md`](backend/README.md)。
|
||||
|
||||
### CI 构建规则
|
||||
|
||||
- Pull request 和 `dev` 分支 push:先跑 Backend tests,通过后再构建前端。
|
||||
- 在需要跳过 Backend tests 的提交信息后加 `[SKIP TESTS]`;Gitea pull request 需要把标记写进 PR 标题。前端构建仍会运行。
|
||||
- `main` 上的提交信息包含 `[CI]` 时,并行构建 Backend、Agent、Probe 的 Linux/amd64 镜像,推送到 Zot 的 `monitor-dev/` 仓库;普通 main push 不构建。
|
||||
- 推送 `vMAJOR.MINOR.PATCH` tag 会触发全平台正式构建和 Release 发布。
|
||||
|
||||
完整触发条件、开发镜像标签和发布路径见 [`docs/deployment.md`](docs/deployment.md)。
|
||||
|
||||
## 项目布局
|
||||
|
||||
| 路径 | 说明 |
|
||||
| 文档 | 内容 |
|
||||
| --- | --- |
|
||||
| [`backend/`](backend/README.md) | Backend 服务、Fleet 框架、共享测量代码与 .NET 测试 |
|
||||
| [`backend/agent/`](backend/agent/README.md) | Agent 节点:注册到 Backend,接受 Probe 测量并上报数据 |
|
||||
| [`backend/probe/`](backend/probe/README.md) | Probe 节点:发起线路测量并将采集数据交给 Agent |
|
||||
| `backend/fleet/` | 可复用的节点身份、注册和连接框架 |
|
||||
| `backend/shared/` | 测量引擎、协议和原始数据格式 |
|
||||
| `web/display/`、`web/admin/` | 展示端和管理端 |
|
||||
| `web/shared/` | 前端 API、图表、组件与主题 |
|
||||
| `packaging/`、`scripts/` | 部署、构建与维护脚本 |
|
||||
| `docs/` | [架构](docs/architecture.md)、[测量模型](docs/measurement.md)、[API](docs/api.md)、[部署](docs/deployment.md)等文档 |
|
||||
| [`docs/architecture.md`](docs/architecture.md) | 架构、数据链路、安全边界、设计决策 |
|
||||
| [`docs/protocol.md`](docs/protocol.md)、[`docs/fleet/`](docs/fleet/overview.md) | 线协议、Fleet 节点框架(身份、连接、更新) |
|
||||
| [`docs/enrollment.md`](docs/enrollment.md) | 注册、身份、Probe 与 Agent 的接入 |
|
||||
| [`docs/measurement.md`](docs/measurement.md)、[`docs/analysis.md`](docs/analysis.md) | 采集引擎与任务模型、分析层与指标 |
|
||||
| [`docs/storage.md`](docs/storage.md)、[`docs/query.md`](docs/query.md) | 存储、展示数据查询 |
|
||||
| [`docs/api.md`](docs/api.md)、[`docs/api-guidelines.md`](docs/api-guidelines.md) | API 规范与约束 |
|
||||
| [`docs/deployment.md`](docs/deployment.md) | 构建、发布、CI、部署与更新 |
|
||||
| [`docs/project-layout.md`](docs/project-layout.md)、[`docs/coding-style.md`](docs/coding-style.md) | 仓库结构与依赖规则、代码风格 |
|
||||
|
||||
代码目录:`backend/`(Backend、`fleet/` 节点框架、`shared/` 测量引擎和协议、`agent/`、`probe/`、`tests/`)、`web/`(`display` 展示端、`admin` 管理端、`shared`)、`packaging/` 和 `scripts/`(部署与维护)、`ci/` 和 `.woodpecker/`(流水线)。
|
||||
|
||||
## 待办
|
||||
|
||||
已实现的能力见上面的概览和 `docs/`;还没做的:
|
||||
|
||||
- **纯二进制节点的更新**:`fleet-updater`、`fleet-release`、Windows 服务和 OpenWrt(procd)适配器;设计见 [`docs/fleet/update.md`](docs/fleet/update.md)。容器节点(Agent、Probe)的签名发布和宿主机更新已经实现,见 [`docs/deployment.md`](docs/deployment.md)。
|
||||
- **纯二进制节点的更新**:`fleet-updater`、`fleet-release`、Windows 服务和 OpenWrt(procd)适配器;设计见 [`docs/fleet/update.md`](docs/fleet/update.md)。容器节点的签名发布和宿主机更新已经实现。
|
||||
- **更新的灰度和自动策略**:按标签分批发布;`POLICY=auto` 的线上验证;后端自身的更新。
|
||||
- **OpenWrt 打包**:procd 脚本、ipk/apk 包、sysupgrade 保留列表,在 aarch64 路由器上长时间运行验证。
|
||||
- **安全加固**:对 UDP 包、控制帧、RawChunk、JSON-RPC 做模糊测试;伪造签名和重放的专项测试。
|
||||
- **以后**:告警(Webhook)、真实 QUIC 握手积木、pcap/eBPF 采集。
|
||||
|
||||
## 测试
|
||||
|
||||
```sh
|
||||
dotnet test --solution Monitor.slnx
|
||||
```
|
||||
|
||||
```sh
|
||||
# 前端样式检查(仓库根目录)
|
||||
npm run lint:css --prefix web
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
本项目采用 [Apache License 2.0](LICENSE)。
|
||||
@@ -17,33 +17,33 @@
|
||||
<Project Path="fleet/tools/fleet-enroll/fleet-enroll.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/shared/">
|
||||
<Project Path="shared/Monitor.Measurement/Monitor.Measurement.csproj" />
|
||||
<Project Path="shared/Monitor.Protocol/Monitor.Protocol.csproj" />
|
||||
<Project Path="shared/MoShiro.Measurement/MoShiro.Measurement.csproj" />
|
||||
<Project Path="shared/MoShiro.Protocol/MoShiro.Protocol.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/shared/tests/">
|
||||
<Project Path="shared/tests/Monitor.Measurement.Tests/Monitor.Measurement.Tests.csproj" />
|
||||
<Project Path="shared/tests/Monitor.Protocol.Tests/Monitor.Protocol.Tests.csproj" />
|
||||
<Project Path="shared/tests/MoShiro.Measurement.Tests/MoShiro.Measurement.Tests.csproj" />
|
||||
<Project Path="shared/tests/MoShiro.Protocol.Tests/MoShiro.Protocol.Tests.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/agent/src/">
|
||||
<Project Path="agent/src/Monitor.Agent/Monitor.Agent.csproj" />
|
||||
<Project Path="agent/src/MoShiro.Agent/MoShiro.Agent.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/probe/src/">
|
||||
<Project Path="probe/src/Monitor.Probe/Monitor.Probe.csproj" />
|
||||
<Project Path="probe/src/MoShiro.Probe/MoShiro.Probe.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/probe/tests/">
|
||||
<Project Path="probe/tests/Monitor.Probe.Tests/Monitor.Probe.Tests.csproj" />
|
||||
<Project Path="probe/tests/MoShiro.Probe.Tests/MoShiro.Probe.Tests.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/src/">
|
||||
<Project Path="src/Monitor.Backend.Analysis/Monitor.Backend.Analysis.csproj" />
|
||||
<Project Path="src/Monitor.Backend.Api/Monitor.Backend.Api.csproj" />
|
||||
<Project Path="src/Monitor.Backend.Application/Monitor.Backend.Application.csproj" />
|
||||
<Project Path="src/Monitor.Backend.Gateway/Monitor.Backend.Gateway.csproj" />
|
||||
<Project Path="src/Monitor.Backend.Storage.Sqlite/Monitor.Backend.Storage.Sqlite.csproj" />
|
||||
<Project Path="src/Monitor.Backend/Monitor.Backend.csproj" />
|
||||
<Project Path="src/MoShiro.Backend.Analysis/MoShiro.Backend.Analysis.csproj" />
|
||||
<Project Path="src/MoShiro.Backend.Api/MoShiro.Backend.Api.csproj" />
|
||||
<Project Path="src/MoShiro.Backend.Application/MoShiro.Backend.Application.csproj" />
|
||||
<Project Path="src/MoShiro.Backend.Gateway/MoShiro.Backend.Gateway.csproj" />
|
||||
<Project Path="src/MoShiro.Backend.Storage.Sqlite/MoShiro.Backend.Storage.Sqlite.csproj" />
|
||||
<Project Path="src/MoShiro.Backend/MoShiro.Backend.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/backend/tests/">
|
||||
<Project Path="tests/Monitor.Backend.Analysis.Tests/Monitor.Backend.Analysis.Tests.csproj" />
|
||||
<Project Path="tests/Monitor.Backend.Tests/Monitor.Backend.Tests.csproj" />
|
||||
<Project Path="tests/Monitor.TestSupport/Monitor.TestSupport.csproj" />
|
||||
<Project Path="tests/MoShiro.Backend.Analysis.Tests/MoShiro.Backend.Analysis.Tests.csproj" />
|
||||
<Project Path="tests/MoShiro.Backend.Tests/MoShiro.Backend.Tests.csproj" />
|
||||
<Project Path="tests/MoShiro.TestSupport/MoShiro.TestSupport.csproj" />
|
||||
</Folder>
|
||||
</Solution>
|
||||
+12
-12
@@ -1,17 +1,17 @@
|
||||
# Backend
|
||||
|
||||
Backend 是 Monitor 的控制面和数据服务,负责 Agent 注册与连接、任务编排、原始测量数据持久化、指标分析,以及供前端使用的 REST API 和 SSE 实时数据流。
|
||||
Backend 是 MoShiro 的控制面和数据服务,负责 Agent 注册与连接、任务编排、原始测量数据持久化、指标分析,以及供前端使用的 REST API 和 SSE 实时数据流。
|
||||
|
||||
## 组件
|
||||
|
||||
| 路径 | 职责 |
|
||||
| --- | --- |
|
||||
| `src/Monitor.Backend/` | 应用入口与依赖组合 |
|
||||
| `src/Monitor.Backend.Api/` | Public、Admin API 和 OpenAPI |
|
||||
| `src/Monitor.Backend.Gateway/` | Fleet RPC、节点状态与测量数据接收 |
|
||||
| `src/Monitor.Backend.Application/` | 业务用例、查询、任务和后台服务 |
|
||||
| `src/Monitor.Backend.Analysis/` | 指标计算、累加器和服务模型 |
|
||||
| `src/Monitor.Backend.Storage.Sqlite/` | 段文件与 SQLite 存储 |
|
||||
| `src/MoShiro.Backend/` | 应用入口与依赖组合 |
|
||||
| `src/MoShiro.Backend.Api/` | Public、Admin API 和 OpenAPI |
|
||||
| `src/MoShiro.Backend.Gateway/` | Fleet RPC、节点状态与测量数据接收 |
|
||||
| `src/MoShiro.Backend.Application/` | 业务用例、查询、任务和后台服务 |
|
||||
| `src/MoShiro.Backend.Analysis/` | 指标计算、累加器和服务模型 |
|
||||
| `src/MoShiro.Backend.Storage.Sqlite/` | 段文件与 SQLite 存储 |
|
||||
| `fleet/` | 通用节点身份、注册和连接框架 |
|
||||
| `shared/` | 测量引擎、协议和数据格式 |
|
||||
| `tests/` | 集成测试和测试支持代码 |
|
||||
@@ -26,14 +26,14 @@ Backend 是 Monitor 的控制面和数据服务,负责 Agent 注册与连接
|
||||
|
||||
```sh
|
||||
cd backend
|
||||
dotnet build Monitor.slnx -c Release
|
||||
dotnet test --solution Monitor.slnx
|
||||
dotnet build MoShiro.slnx -c Release
|
||||
dotnet test --solution MoShiro.slnx
|
||||
```
|
||||
|
||||
发布当前平台的 NativeAOT Backend:
|
||||
|
||||
```sh
|
||||
dotnet publish src/Monitor.Backend -c Release -r <RID> -p:PublishAot=true
|
||||
dotnet publish src/MoShiro.Backend -c Release -r <RID> -p:PublishAot=true
|
||||
```
|
||||
|
||||
例如 `<RID>` 可为 `win-x64`、`linux-x64` 或 `linux-arm64`。NativeAOT 发布需要对应平台的 C/C++ 工具链。仓库根目录的 `scripts/publish.sh backend <RID>` 可构建单个平台的 Backend 可执行文件;容器发布由 Woodpecker tag 流水线完成,详见 [`../docs/deployment.md`](../docs/deployment.md)。
|
||||
@@ -46,9 +46,9 @@ dotnet publish src/Monitor.Backend -c Release -r <RID> -p:PublishAot=true
|
||||
|
||||
```sh
|
||||
cd backend
|
||||
dotnet run --project src/Monitor.Backend -- --urls http://localhost:5000
|
||||
dotnet run --project src/MoShiro.Backend -- --urls http://localhost:5000
|
||||
```
|
||||
|
||||
首次启动时可用 `Monitor__InitialAdminUser` 和 `Monitor__InitialAdminPassword` 设置初始管理员。数据目录、公开地址、Cookie 和 CORS 等设置通过 `Monitor` 配置节传入;完整配置说明见 [`../docs/deployment.md`](../docs/deployment.md)。站点默认需要登录,可在管理端修改公开设置。
|
||||
首次启动时可用 `MoShiro__InitialAdminUser` 和 `MoShiro__InitialAdminPassword` 设置初始管理员。数据目录、公开地址、Cookie 和 CORS 等设置通过 `MoShiro` 配置节传入;完整配置说明见 [`../docs/deployment.md`](../docs/deployment.md)。站点默认需要登录,可在管理端修改公开设置。
|
||||
|
||||
OpenAPI 文档由构建生成到 `openapi/v1.json`。API 设计和端点说明见 [`../docs/api.md`](../docs/api.md);项目总览见仓库根目录的 [`README.md`](../README.md)。
|
||||
@@ -1,4 +1,4 @@
|
||||
# Monitor Agent
|
||||
# MoShiro Agent
|
||||
|
||||
Agent 是部署在 VPS 或其他可达网络节点上的采集进程。它向 Backend 注册并保持连接,接受 Probe 的测量会话,执行线路任务中的 Agent 侧工作,再将两端数据合并后上报。
|
||||
|
||||
@@ -18,14 +18,14 @@ Probe ──TLS 控制 / UDP、TCP 测量──> Agent ──长连接与数据
|
||||
|
||||
```sh
|
||||
cd backend
|
||||
dotnet build agent/src/Monitor.Agent -c Release
|
||||
dotnet build agent/src/MoShiro.Agent -c Release
|
||||
dotnet build fleet/tools/fleet-enroll -c Release
|
||||
```
|
||||
|
||||
发布 NativeAOT 程序需要对应平台的 C/C++ 工具链;`<RID>` 应与构建环境和目标平台匹配:
|
||||
|
||||
```sh
|
||||
dotnet publish agent/src/Monitor.Agent -c Release -r <RID> -p:PublishAot=true
|
||||
dotnet publish agent/src/MoShiro.Agent -c Release -r <RID> -p:PublishAot=true
|
||||
dotnet publish fleet/tools/fleet-enroll -c Release -r <RID> -p:PublishAot=true
|
||||
```
|
||||
|
||||
@@ -36,7 +36,7 @@ dotnet publish fleet/tools/fleet-enroll -c Release -r <RID> -p:PublishAot=true
|
||||
先在管理端创建加入令牌,然后在 Agent 主机上运行一次性注册工具:
|
||||
|
||||
```sh
|
||||
fleet-enroll --server https://<backend> --join <token> --name <agent-name> --identity-dir /etc/monitor
|
||||
fleet-enroll --server https://<backend> --join <token> --name <agent-name> --identity-dir /etc/moshiro
|
||||
```
|
||||
|
||||
注册工具会在身份目录写入节点身份和证书。准备本地 `agent.json`,其中 `fleet.server` 指向 Backend,`fleet.identity_dir` 指向上述目录;`listen`、`limits` 和 `reporting` 可按节点环境调整。配置文件不会由 Backend 修改。限制目标网段和测量预算时,应按实际网络与机器能力配置;`allowed_target_cidrs` 默认为空,不允许 Agent 执行目标探测任务。
|
||||
@@ -44,12 +44,12 @@ fleet-enroll --server https://<backend> --join <token> --name <agent-name> --ide
|
||||
启动 Agent:
|
||||
|
||||
```sh
|
||||
monitor-agent --config /etc/monitor/agent.json
|
||||
moshiro-agent --config /etc/moshiro/agent.json
|
||||
```
|
||||
|
||||
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md)。也可使用 OCI 镜像 `registry.oeo.one/monitor/monitor-agent:<version>`,自动选择 Linux amd64 或 arm64 镜像。镜像同时包含 `fleet-enroll`;可通过 `--entrypoint /usr/local/bin/fleet-enroll` 执行首次注册。容器默认读取命名 volume 中的 `/etc/monitor/agent.json` 与身份文件;建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。镜像以 UID/GID 1654 运行,新命名 volume 由引擎按镜像目录初始化权限。
|
||||
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md)。也可使用 OCI 镜像 `registry.oeo.one/moshiro/moshiro-agent:<version>`,自动选择 Linux amd64 或 arm64 镜像。镜像同时包含 `fleet-enroll`;可通过 `--entrypoint /usr/local/bin/fleet-enroll` 执行首次注册。容器默认读取命名 volume 中的 `/etc/moshiro/agent.json` 与身份文件;建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。镜像以 UID/GID 1654 运行,新命名 volume 由引擎按镜像目录初始化权限。
|
||||
|
||||
不传 `--config` 时默认读取 `/etc/monitor/agent.json`。Agent 默认监听控制端口 `7443/tcp`,数据端口 `7443/udp` 和 `7444/tcp`;防火墙和面板中的节点端口配置需与本地配置一致。`reporting.host_stats` 可关闭主机状态上报;启用时只上报资源使用等汇总数据,不包含主机名、进程或 IP。
|
||||
不传 `--config` 时默认读取 `/etc/moshiro/agent.json`。Agent 默认监听控制端口 `7443/tcp`,数据端口 `7443/udp` 和 `7444/tcp`;防火墙和面板中的节点端口配置需与本地配置一致。`reporting.host_stats` 可关闭主机状态上报;启用时只上报资源使用等汇总数据,不包含主机名、进程或 IP。
|
||||
|
||||
## 设计说明
|
||||
|
||||
|
||||
+26
-26
@@ -4,15 +4,15 @@ using Fleet.Core;
|
||||
using Fleet.Hosting;
|
||||
using Fleet.Link;
|
||||
using Fleet.Rpc;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>Agent 的组装点:数据面、点对点监听、与 Backend 的链路都在这里接起来。没有全局状态,测试可以在同一进程里启动多个。</summary>
|
||||
public sealed class AgentApp : IDisposable
|
||||
{
|
||||
public const string Version = Monitor.Protocol.BuildInfo.Version;
|
||||
public const string Version = MoShiro.Protocol.BuildInfo.Version;
|
||||
|
||||
private readonly AgentConfig _config;
|
||||
private readonly LoadedNodeIdentity _identity;
|
||||
@@ -35,7 +35,7 @@ public sealed class AgentApp : IDisposable
|
||||
private readonly TestRunner _tests;
|
||||
private readonly FleetLink _link;
|
||||
|
||||
/// <param name="dataDirectory">配置目录(容器里的 <c>/etc/monitor</c>):宿主机更新器和节点在这里交换清单、请求和状态。</param>
|
||||
/// <param name="dataDirectory">配置目录(容器里的 <c>/etc/moshiro</c>):宿主机更新器和节点在这里交换清单、请求和状态。</param>
|
||||
public AgentApp(AgentConfig config, LoadedNodeIdentity identity, string dataDirectory, TimeProvider time, INodeLog log)
|
||||
{
|
||||
_update = new UpdateBridge(dataDirectory, new ConfigStore(dataDirectory).Write, log);
|
||||
@@ -185,48 +185,48 @@ public sealed class AgentApp : IDisposable
|
||||
HashSet<string>? allowed = _config.Policy.AllowMethods is { } list ? [.. list] : null;
|
||||
bool Allow(string method) => allowed is null || allowed.Contains(method);
|
||||
|
||||
if (Allow(MonitorMethods.TestRun))
|
||||
if (Allow(MoShiroMethods.TestRun))
|
||||
{
|
||||
router.On(
|
||||
MonitorMethods.TestRun,
|
||||
MonitorJsonContext.Default.TestRunParams,
|
||||
MonitorJsonContext.Default.TestRunResult,
|
||||
MoShiroMethods.TestRun,
|
||||
MoShiroJsonContext.Default.TestRunParams,
|
||||
MoShiroJsonContext.Default.TestRunResult,
|
||||
(_, run, _) => ValueTask.FromResult(_tests.Start(run)));
|
||||
}
|
||||
|
||||
if (Allow(MonitorMethods.TestCancel))
|
||||
if (Allow(MoShiroMethods.TestCancel))
|
||||
{
|
||||
router.On(
|
||||
MonitorMethods.TestCancel,
|
||||
MonitorJsonContext.Default.TestCancelParams,
|
||||
MonitorJsonContext.Default.TestRunResult,
|
||||
MoShiroMethods.TestCancel,
|
||||
MoShiroJsonContext.Default.TestCancelParams,
|
||||
MoShiroJsonContext.Default.TestRunResult,
|
||||
(_, cancel, _) => ValueTask.FromResult(Result<TestRunResult>.Ok(new TestRunResult { Accepted = _tests.Cancel(cancel.TestId) })));
|
||||
}
|
||||
|
||||
if (Allow(MonitorMethods.UpdateOffer))
|
||||
if (Allow(MoShiroMethods.UpdateOffer))
|
||||
{
|
||||
router.OnNotify(MonitorMethods.UpdateOffer, MonitorJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
|
||||
router.OnNotify(MoShiroMethods.UpdateOffer, MoShiroJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
|
||||
{
|
||||
_update.WriteOffer(offer);
|
||||
return ValueTask.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
if (Allow(MonitorMethods.UpdateApply))
|
||||
if (Allow(MoShiroMethods.UpdateApply))
|
||||
{
|
||||
router.OnNotify(MonitorMethods.UpdateApply, MonitorJsonContext.Default.UpdateOfferParams, (_, _, _) =>
|
||||
router.OnNotify(MoShiroMethods.UpdateApply, MoShiroJsonContext.Default.UpdateOfferParams, (_, _, _) =>
|
||||
{
|
||||
_update.RequestApply();
|
||||
return ValueTask.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
if (Allow(MonitorMethods.ProbeDisconnect))
|
||||
if (Allow(MoShiroMethods.ProbeDisconnect))
|
||||
{
|
||||
router.On(
|
||||
MonitorMethods.ProbeDisconnect,
|
||||
MonitorJsonContext.Default.ProbeDisconnectParams,
|
||||
MonitorJsonContext.Default.TestRunResult,
|
||||
MoShiroMethods.ProbeDisconnect,
|
||||
MoShiroJsonContext.Default.ProbeDisconnectParams,
|
||||
MoShiroJsonContext.Default.TestRunResult,
|
||||
async (_, request, _) => Result<TestRunResult>.Ok(new TestRunResult { Accepted = await _hub.DisconnectAsync(request.ProbeId.Value) }));
|
||||
}
|
||||
|
||||
@@ -236,11 +236,11 @@ public sealed class AgentApp : IDisposable
|
||||
Identity = _identity.Identity,
|
||||
NodeId = _identity.NodeId,
|
||||
NodeVersion = Version,
|
||||
Capabilities = [MonitorCapabilities.Base, MonitorCapabilities.KindsPrefix + string.Join(',', BlockCatalog.AgentKinds)],
|
||||
Capabilities = [MoShiroCapabilities.Base, MoShiroCapabilities.KindsPrefix + string.Join(',', BlockCatalog.AgentKinds)],
|
||||
HelloMeta = HelloMeta,
|
||||
HeartbeatExtra = HeartbeatExtra,
|
||||
};
|
||||
var link = new FleetLink(options, router, [new StateHandler<TaskSet>(TaskSet.StateType, MonitorJsonContext.Default.TaskSet, _tasks.ApplyAsync)], _outbox, _time, _log);
|
||||
var link = new FleetLink(options, router, [new StateHandler<TaskSet>(TaskSet.StateType, MoShiroJsonContext.Default.TaskSet, _tasks.ApplyAsync)], _outbox, _time, _log);
|
||||
|
||||
// 链路重新连上之后补报:在线的 Probe 都告诉 Backend(Backend 重启或链路抖动期间错过了 probe_connected)
|
||||
link.OnConnected = async (_, _) =>
|
||||
@@ -257,7 +257,7 @@ public sealed class AgentApp : IDisposable
|
||||
{
|
||||
var meta = new AgentHelloMeta
|
||||
{
|
||||
Platform = Monitor.Measurement.PlatformDetector.Detect(),
|
||||
Platform = MoShiro.Measurement.PlatformDetector.Detect(),
|
||||
TlsSpki = _identity.File.TlsSpki,
|
||||
ProbeTcpPort = ControlPort,
|
||||
ProbeUdpPorts = [.. UdpPorts],
|
||||
@@ -269,7 +269,7 @@ public sealed class AgentApp : IDisposable
|
||||
MaxConcurrentTests = _config.Limits.MaxConcurrentTests,
|
||||
},
|
||||
};
|
||||
return JsonSerializer.SerializeToElement(meta, MonitorJsonContext.Default.AgentHelloMeta);
|
||||
return JsonSerializer.SerializeToElement(meta, MoShiroJsonContext.Default.AgentHelloMeta);
|
||||
}
|
||||
|
||||
private long _lastIn;
|
||||
@@ -296,6 +296,6 @@ public sealed class AgentApp : IDisposable
|
||||
_lastIn = packetsIn;
|
||||
_lastOut = packetsOut;
|
||||
_lastAtMs = nowMs;
|
||||
return JsonSerializer.SerializeToElement(extra, MonitorJsonContext.Default.AgentHeartbeatExtra);
|
||||
return JsonSerializer.SerializeToElement(extra, MoShiroJsonContext.Default.AgentHeartbeatExtra);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// <c>agent.json</c>:节点本地配置,面板无法修改。其中 <c>limits</c> 是本地硬上限:期望任务集超出这些范围时,
|
||||
@@ -40,7 +40,7 @@ public sealed class FleetSection
|
||||
public string Server { get; set; } = "";
|
||||
|
||||
[JsonPropertyName("identity_dir")]
|
||||
public string IdentityDir { get; set; } = "/etc/monitor";
|
||||
public string IdentityDir { get; set; } = "/etc/moshiro";
|
||||
}
|
||||
|
||||
public sealed class ListenSection
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
using Fleet.Core;
|
||||
using Fleet.Link;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// Probe 的列块到达之后:找到对应的流 → 与 Agent 在数据面记录的列合并 → 盖章(块头、时钟锚点)→ 编码 → 放进发件箱(通道 16)。
|
||||
@@ -70,6 +70,6 @@ public sealed class ChunkAssembler
|
||||
/// <summary>Agent 自己采集的块(目标任务)直接发出。</summary>
|
||||
public void Emit(RawChunk chunk)
|
||||
{
|
||||
_outbox.Enqueue(MonitorChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk));
|
||||
_outbox.Enqueue(MoShiroChannels.RawChunk, chunk.Header.ChunkId.Value, RawChunkCodec.Encode(chunk));
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -4,10 +4,10 @@ using System.Net.Sockets;
|
||||
using Fleet.Core;
|
||||
using Fleet.Link;
|
||||
using Fleet.Rpc;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>进程内计数(只有计数,没有主机信息):用于心跳和面板上的节点状态。数据面的包计数由共享的 <see cref="DataPlaneCounters"/> 提供。</summary>
|
||||
public sealed class AgentCounters
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Globalization;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// 主机资源占用的只读快照:CPU 占用、内存、根分区、网卡累计流量与瞬时网速、系统运行时长。
|
||||
+4
-4
@@ -4,8 +4,8 @@
|
||||
Condition="'$(UseAotAnywhere)' == 'true'" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AssemblyName>monitor-probe</AssemblyName>
|
||||
<RootNamespace>Monitor.Probe</RootNamespace>
|
||||
<AssemblyName>moshiro-agent</AssemblyName>
|
||||
<RootNamespace>MoShiro.Agent</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
@@ -15,10 +15,10 @@
|
||||
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Monitor.Backend.Tests" />
|
||||
<InternalsVisibleTo Include="MoShiro.Backend.Tests" />
|
||||
<ProjectReference Include="..\..\..\fleet\src\Fleet.Link\Fleet.Link.csproj" />
|
||||
<ProjectReference Include="..\..\..\fleet\src\Fleet.Hosting\Fleet.Hosting.csproj" />
|
||||
<ProjectReference Include="..\..\..\shared\Monitor.Measurement\Monitor.Measurement.csproj" />
|
||||
<ProjectReference Include="..\..\..\shared\MoShiro.Measurement\MoShiro.Measurement.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
|
||||
<Import Project="Sdk.targets" Sdk="StuDev.AotAnywhere"
|
||||
+11
-11
@@ -2,10 +2,10 @@ using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Link;
|
||||
using Fleet.Rpc;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>Agent → Backend 的上行:事件和测试状态是尽力而为的通知(链路断开时丢弃;列块走发件箱,不会丢)。</summary>
|
||||
public sealed class BackendUplink
|
||||
@@ -31,7 +31,7 @@ public sealed class BackendUplink
|
||||
}
|
||||
|
||||
var parameters = new AgentEventParams { Kind = kind, AtUtcMs = _time.GetUtcNow().ToUnixTimeMilliseconds(), Detail = detail, Probe = probe };
|
||||
await SafeAsync(() => session.NotifyAsync(MonitorMethods.Event, parameters, MonitorJsonContext.Default.AgentEventParams, CancellationToken.None));
|
||||
await SafeAsync(() => session.NotifyAsync(MoShiroMethods.Event, parameters, MoShiroJsonContext.Default.AgentEventParams, CancellationToken.None));
|
||||
}
|
||||
|
||||
public async ValueTask SendHostAsync(HostSnapshot snapshot)
|
||||
@@ -42,7 +42,7 @@ public sealed class BackendUplink
|
||||
return;
|
||||
}
|
||||
|
||||
await SafeAsync(() => session.NotifyAsync(MonitorMethods.Host, snapshot, MonitorJsonContext.Default.HostSnapshot, CancellationToken.None));
|
||||
await SafeAsync(() => session.NotifyAsync(MoShiroMethods.Host, snapshot, MoShiroJsonContext.Default.HostSnapshot, CancellationToken.None));
|
||||
}
|
||||
|
||||
public async ValueTask SendTestStatusAsync(TestId test, string status, int? phase = null, string? errorCode = null)
|
||||
@@ -54,7 +54,7 @@ public sealed class BackendUplink
|
||||
}
|
||||
|
||||
var parameters = new TestStatusParams { TestId = test, Status = status, Phase = phase, ErrorCode = errorCode };
|
||||
await SafeAsync(() => session.NotifyAsync(MonitorMethods.TestStatus, parameters, MonitorJsonContext.Default.TestStatusParams, CancellationToken.None));
|
||||
await SafeAsync(() => session.NotifyAsync(MoShiroMethods.TestStatus, parameters, MoShiroJsonContext.Default.TestStatusParams, CancellationToken.None));
|
||||
}
|
||||
|
||||
private static async ValueTask SafeAsync(Func<ValueTask> send)
|
||||
@@ -115,7 +115,7 @@ public sealed class PeerHub
|
||||
var order = new Queue<Guid>();
|
||||
rpc.OnBinary = async (frame, token) =>
|
||||
{
|
||||
if (!OutboxFrame.TryParse(frame, out byte channel, out Guid id, out ReadOnlyMemory<byte> payload) || channel != MonitorChannels.ColumnBlock)
|
||||
if (!OutboxFrame.TryParse(frame, out byte channel, out Guid id, out ReadOnlyMemory<byte> payload) || channel != MoShiroChannels.ColumnBlock)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -251,7 +251,7 @@ public sealed class PeerHub
|
||||
{
|
||||
Type = LineTaskSet.StateType,
|
||||
Version = set.Version,
|
||||
Doc = JsonSerializer.SerializeToElement(set, MonitorJsonContext.Default.LineTaskSet),
|
||||
Doc = JsonSerializer.SerializeToElement(set, MoShiroJsonContext.Default.LineTaskSet),
|
||||
};
|
||||
try
|
||||
{
|
||||
@@ -270,8 +270,8 @@ public sealed class PeerHub
|
||||
private static RpcRouter<PeerContext> BuildRouter() =>
|
||||
new RpcRouter<PeerContext>()
|
||||
.On(
|
||||
MonitorMethods.Clock,
|
||||
MonitorJsonContext.Default.ClockParams,
|
||||
MonitorJsonContext.Default.ClockResult,
|
||||
MoShiroMethods.Clock,
|
||||
MoShiroJsonContext.Default.ClockParams,
|
||||
MoShiroJsonContext.Default.ClockResult,
|
||||
(context, _, _) => ValueTask.FromResult(Result<ClockResult>.Ok(new ClockResult { AgentMonoUs = context.Clock.NowUs() })));
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Fleet.Core;
|
||||
using Fleet.Hosting;
|
||||
using Monitor.Agent;
|
||||
using MoShiro.Agent;
|
||||
|
||||
TimeProvider time = TimeProvider.System;
|
||||
INodeLog log = new ConsoleNodeLog(time);
|
||||
@@ -11,7 +11,7 @@ if (PlatformCheck.VerifyCrypto() is { } problem)
|
||||
return 1;
|
||||
}
|
||||
|
||||
string configPath = "/etc/monitor/agent.json";
|
||||
string configPath = "/etc/moshiro/agent.json";
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (args[i] == "--config")
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// 目标任务(Agent 自己探测一个地址,没有对端):用和线路任务同一套积木(<see cref="BlockCatalog"/> 里的拨号方,例如 icmp_echo、connect),
|
||||
+3
-3
@@ -1,10 +1,10 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Rpc;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// 期望任务集(<c>task_set</c>)的调和器:每个任务先经过 <see cref="BudgetGuard"/>(参数校验 + 本地硬上限)。
|
||||
+17
-17
@@ -1,15 +1,15 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Fleet.Core;
|
||||
using Fleet.Rpc;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Agent;
|
||||
namespace MoShiro.Agent;
|
||||
|
||||
/// <summary>
|
||||
/// 按需测试的编排(<c>monitor.test.run</c>):Agent 是时间的主人。每个阶段:
|
||||
/// 登记数据面的流 → <c>monitor.test.phase</c>(Probe 准备好自己一侧的角色)→ 运行(单向数据流由 Agent 发包,回显类等待 Probe 发包)
|
||||
/// → <c>monitor.test.phase_end</c>(Probe 收尾,回复它交出的列块个数)→ 等所有列块合并上传 → <c>phase_done</c>。
|
||||
/// 按需测试的编排(<c>moshiro.test.run</c>):Agent 是时间的主人。每个阶段:
|
||||
/// 登记数据面的流 → <c>moshiro.test.phase</c>(Probe 准备好自己一侧的角色)→ 运行(单向数据流由 Agent 发包,回显类等待 Probe 发包)
|
||||
/// → <c>moshiro.test.phase_end</c>(Probe 收尾,回复它交出的列块个数)→ 等所有列块合并上传 → <c>phase_done</c>。
|
||||
/// 同一时间只能运行 <c>max_concurrent_tests</c> 个测试;Probe 掉线或被取消时测试中止,已经上传的阶段数据保留。
|
||||
/// </summary>
|
||||
public sealed class TestRunner
|
||||
@@ -43,17 +43,17 @@ public sealed class TestRunner
|
||||
|
||||
public int Running => _running.Count;
|
||||
|
||||
/// <summary>校验并接受一个测试(立即返回),实际运行在后台进行,状态通过 <c>monitor.test.status</c> 回报。</summary>
|
||||
/// <summary>校验并接受一个测试(立即返回),实际运行在后台进行,状态通过 <c>moshiro.test.status</c> 回报。</summary>
|
||||
public Result<TestRunResult> Start(TestRunParams run)
|
||||
{
|
||||
if (_sessions.Get(run.ProbeId.Value) is not { } probe)
|
||||
{
|
||||
return Result<TestRunResult>.Fail(MonitorErrors.ProbeOffline, $"probe {run.ProbeId.Value} is not connected to this agent");
|
||||
return Result<TestRunResult>.Fail(MoShiroErrors.ProbeOffline, $"probe {run.ProbeId.Value} is not connected to this agent");
|
||||
}
|
||||
|
||||
if (run.Phases.Count == 0)
|
||||
{
|
||||
return Result<TestRunResult>.Fail(MonitorErrors.ValidationFailed, "a test needs at least one phase");
|
||||
return Result<TestRunResult>.Fail(MoShiroErrors.ValidationFailed, "a test needs at least one phase");
|
||||
}
|
||||
|
||||
var resolved = new List<(TestPhase Phase, TaskDecision Decision)>();
|
||||
@@ -64,7 +64,7 @@ public sealed class TestRunner
|
||||
if (!decision.Accepted)
|
||||
{
|
||||
return Result<TestRunResult>.Fail(
|
||||
decision.Reason == RejectReasons.Unsupported ? FleetErrors.Unsupported : MonitorErrors.BudgetExceeded,
|
||||
decision.Reason == RejectReasons.Unsupported ? FleetErrors.Unsupported : MoShiroErrors.BudgetExceeded,
|
||||
$"phase {phase.Kind} refused: {decision.Reason}");
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ public sealed class TestRunner
|
||||
if (_running.Count >= _maxConcurrent || !_running.TryAdd(run.TestId.Value, running))
|
||||
{
|
||||
running.Dispose();
|
||||
return Result<TestRunResult>.Fail(MonitorErrors.TestBusy, "this agent is already running a test");
|
||||
return Result<TestRunResult>.Fail(MoShiroErrors.TestBusy, "this agent is already running a test");
|
||||
}
|
||||
|
||||
_ = Task.Run(() => RunAsync(run.TestId, resolved, running), CancellationToken.None);
|
||||
@@ -154,7 +154,7 @@ public sealed class TestRunner
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
finalStatus = running.Lost ? TestStatuses.Failed : TestStatuses.Aborted;
|
||||
errorCode = running.Lost ? MonitorErrors.ProbeOffline.Value : null;
|
||||
errorCode = running.Lost ? MoShiroErrors.ProbeOffline.Value : null;
|
||||
await TryAbortProbeAsync(running.Probe, test);
|
||||
}
|
||||
catch (TestFailure failure)
|
||||
@@ -183,7 +183,7 @@ public sealed class TestRunner
|
||||
_streams.Add(stream);
|
||||
try
|
||||
{
|
||||
RpcSession<PeerContext> rpc = probe.Rpc ?? throw new TestFailure(MonitorErrors.ProbeOffline.Value);
|
||||
RpcSession<PeerContext> rpc = probe.Rpc ?? throw new TestFailure(MoShiroErrors.ProbeOffline.Value);
|
||||
var start = new TestPhaseStartParams
|
||||
{
|
||||
TestId = test,
|
||||
@@ -194,7 +194,7 @@ public sealed class TestRunner
|
||||
DurationSeconds = durationSeconds,
|
||||
Port = stream.Port,
|
||||
};
|
||||
Result<TestPhaseStartResult> accepted = await rpc.RequestAsync(MonitorMethods.TestPhase, start, MonitorJsonContext.Default.TestPhaseStartParams, MonitorJsonContext.Default.TestPhaseStartResult, ct);
|
||||
Result<TestPhaseStartResult> accepted = await rpc.RequestAsync(MoShiroMethods.TestPhase, start, MoShiroJsonContext.Default.TestPhaseStartParams, MoShiroJsonContext.Default.TestPhaseStartResult, ct);
|
||||
if (!accepted.TryGet(out TestPhaseStartResult? started, out FleetError? error) || !started.Accepted)
|
||||
{
|
||||
throw new TestFailure(error?.Code.Value ?? FleetErrors.Unsupported.Value);
|
||||
@@ -209,7 +209,7 @@ public sealed class TestRunner
|
||||
}
|
||||
catch (BlockFaultException)
|
||||
{
|
||||
throw new TestFailure(MonitorErrors.ProbeOffline.Value);
|
||||
throw new TestFailure(MoShiroErrors.ProbeOffline.Value);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -219,7 +219,7 @@ public sealed class TestRunner
|
||||
|
||||
long sent = stream.Running.Block.Produced;
|
||||
var end = new TestPhaseEndParams { TestId = test, Phase = index, StreamId = stream.Id, SentCount = sent };
|
||||
Result<TestPhaseEndResult> ended = await rpc.RequestAsync(MonitorMethods.TestPhaseEnd, end, MonitorJsonContext.Default.TestPhaseEndParams, MonitorJsonContext.Default.TestPhaseEndResult, ct, s_phaseSlack);
|
||||
Result<TestPhaseEndResult> ended = await rpc.RequestAsync(MoShiroMethods.TestPhaseEnd, end, MoShiroJsonContext.Default.TestPhaseEndParams, MoShiroJsonContext.Default.TestPhaseEndResult, ct, s_phaseSlack);
|
||||
if (!ended.TryGet(out TestPhaseEndResult? finished, out FleetError? endError))
|
||||
{
|
||||
throw new TestFailure(endError.Code.Value);
|
||||
@@ -254,7 +254,7 @@ public sealed class TestRunner
|
||||
|
||||
try
|
||||
{
|
||||
await rpc.NotifyAsync(MonitorMethods.TestAbort, new TestAbortParams { TestId = test }, MonitorJsonContext.Default.TestAbortParams, CancellationToken.None);
|
||||
await rpc.NotifyAsync(MoShiroMethods.TestAbort, new TestAbortParams { TestId = test }, MoShiroJsonContext.Default.TestAbortParams, CancellationToken.None);
|
||||
}
|
||||
catch (Exception ex) when (ex is ObjectDisposedException or InvalidOperationException or IOException)
|
||||
{
|
||||
@@ -3,7 +3,7 @@ using System.Text.Json.Serialization;
|
||||
|
||||
namespace Fleet.Core;
|
||||
|
||||
/// <summary>节点 ID(带类型前缀的 ULID)。Monitor 中的 AgentId 就是 NodeId。</summary>
|
||||
/// <summary>节点 ID(带类型前缀的 ULID)。MoShiro 中的 AgentId 就是 NodeId。</summary>
|
||||
[JsonConverter(typeof(NodeIdJsonConverter))]
|
||||
public readonly record struct NodeId(string Value)
|
||||
{
|
||||
|
||||
@@ -49,7 +49,7 @@ public sealed class FleetLinkOptions
|
||||
|
||||
public string NodeVersion { get; init; } = "0.0.0";
|
||||
|
||||
/// <summary>框架之外的能力,例如 <c>monitor.v1</c> 与 <c>monitor.kinds:udp_echo,tcp_echo</c>。</summary>
|
||||
/// <summary>框架之外的能力,例如 <c>moshiro.v1</c> 与 <c>moshiro.kinds:udp_echo,tcp_echo</c>。</summary>
|
||||
public IReadOnlyList<string> Capabilities { get; init; } = [];
|
||||
|
||||
/// <summary>附加到 <c>fleet.hello</c> 的产品元数据。</summary>
|
||||
|
||||
@@ -58,7 +58,7 @@ public sealed class HelloParams
|
||||
[JsonPropertyName("now_utc_ms")]
|
||||
public long NowUtcMs { get; set; }
|
||||
|
||||
/// <summary>产品自定义的元数据(例如 Monitor 的 tls_spki、probe_ports、local_limits 摘要)。</summary>
|
||||
/// <summary>产品自定义的元数据(例如 MoShiro 的 tls_spki、probe_ports、local_limits 摘要)。</summary>
|
||||
[JsonPropertyName("meta")]
|
||||
public JsonElement? Meta { get; set; }
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ namespace Fleet.Server;
|
||||
|
||||
public sealed class FleetServerOptions
|
||||
{
|
||||
/// <summary>节点 ID 的类型前缀,Monitor 使用 <c>agt</c>。</summary>
|
||||
/// <summary>节点 ID 的类型前缀,MoShiro 使用 <c>agt</c>。</summary>
|
||||
public string NodeIdPrefix { get; set; } = "node";
|
||||
|
||||
public int MaxFrameBytes { get; set; } = 16 << 20;
|
||||
|
||||
@@ -2,7 +2,7 @@ using Fleet.Core;
|
||||
|
||||
namespace Fleet.Server;
|
||||
|
||||
/// <summary>Fleet 服务端需要的存储。产品可以自己实现(Monitor 把它放进 monitor.db)。</summary>
|
||||
/// <summary>Fleet 服务端需要的存储。产品可以自己实现(MoShiro 把它放进 moshiro.db)。</summary>
|
||||
public interface IFleetStore
|
||||
{
|
||||
ValueTask<JoinTokenRecord?> FindJoinTokenAsync(ReadOnlyMemory<byte> tokenSha256, CancellationToken ct);
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class FleetServerBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>注册产品的 RPC 方法(<c>monitor.*</c> 等)。处理函数通过 <see cref="IServiceProvider"/> 在调用时解析服务,避免构造期的循环依赖。</summary>
|
||||
/// <summary>注册产品的 RPC 方法(<c>moshiro.*</c> 等)。处理函数通过 <see cref="IServiceProvider"/> 在调用时解析服务,避免构造期的循环依赖。</summary>
|
||||
public FleetServerBuilder MapMethods(Action<RpcRouter<NodeCallContext>, IServiceProvider> configure)
|
||||
{
|
||||
_routeConfigurators.Add(configure);
|
||||
|
||||
@@ -2,7 +2,7 @@ using System.Text.Json.Nodes;
|
||||
using Fleet.Core;
|
||||
using Fleet.Enrollment.Client;
|
||||
|
||||
// fleet-enroll --server https://panel --join jt_xxx --name jp-1 --identity-dir /etc/monitor [--meta '{"probe_tcp_port":7443,...}'] [--version 1.0]
|
||||
// fleet-enroll --server https://panel --join jt_xxx --name jp-1 --identity-dir /etc/moshiro [--meta '{"probe_tcp_port":7443,...}'] [--version 1.0]
|
||||
// 一次性运行:生成身份、注册、写入身份目录,然后退出。
|
||||
string? Arg(string name) => args.SkipWhile(a => a != name).Skip(1).FirstOrDefault();
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"openapi": "3.1.1",
|
||||
"info": {
|
||||
"title": "monitor-backend | v1",
|
||||
"title": "moshiro-backend | v1",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"paths": {
|
||||
@@ -3168,7 +3168,7 @@
|
||||
"tags": [
|
||||
"admin-data"
|
||||
],
|
||||
"summary": "Download a raw chunk (application/vnd.monitor.rawchunk)",
|
||||
"summary": "Download a raw chunk (application/vnd.moshiro.rawchunk)",
|
||||
"operationId": "raw_chunks_get",
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Monitor Probe
|
||||
# MoShiro Probe
|
||||
|
||||
Probe 是网络测量的发起端,通常运行在家宽或其他需要观测的网络出口。它连接 Backend(身份、Agent 列表、状态)和 Agent(测量),执行线路任务,并把本端采集的原始数据交给 Agent 汇总上报。Probe 不提供远程命令或文件管理功能。
|
||||
|
||||
@@ -13,7 +13,7 @@ Backend ──任务──> Agent <──测量会话──> Probe
|
||||
|
||||
Probe 使用面板生成的 `probe.json` 读取自身身份、面板地址(`server`)和本地测试限制。连上面板之后,面板下发要连接的 Agent 列表;Probe 将列表缓存到同目录的 `agents.json`,面板暂时连不上时靠它继续测量。`probe.json` 含私钥,请限制文件访问权限,不要提交到版本控制。
|
||||
|
||||
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md);Probe 也可以用 OCI 镜像 `registry.oeo.one/monitor/monitor-probe:<version>`,容器默认读取 `/etc/monitor/probe.json`,命名 volume 保存身份配置和缓存,镜像以 UID/GID 1654 运行。
|
||||
容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md);Probe 也可以用 OCI 镜像 `registry.oeo.one/moshiro/moshiro-probe:<version>`,容器默认读取 `/etc/moshiro/probe.json`,命名 volume 保存身份配置和缓存,镜像以 UID/GID 1654 运行。
|
||||
|
||||
## 构建
|
||||
|
||||
@@ -21,33 +21,33 @@ Probe 使用面板生成的 `probe.json` 读取自身身份、面板地址(`se
|
||||
|
||||
```sh
|
||||
cd backend
|
||||
dotnet build probe/src/Monitor.Probe -c Release
|
||||
dotnet build probe/src/MoShiro.Probe -c Release
|
||||
```
|
||||
|
||||
发布 NativeAOT 程序需要对应平台的 C/C++ 工具链;`<RID>` 应与构建环境和目标平台匹配:
|
||||
|
||||
```sh
|
||||
dotnet publish probe/src/Monitor.Probe -c Release -r <RID> -p:PublishAot=true
|
||||
dotnet publish probe/src/MoShiro.Probe -c Release -r <RID> -p:PublishAot=true
|
||||
```
|
||||
|
||||
也可在仓库根目录运行 `bash scripts/publish.sh probe <RID>`,再使用 `scripts/package.sh probe <RID>` 生成部署包。支持平台和安装流程见 [`../../docs/deployment.md`](../../docs/deployment.md)。
|
||||
|
||||
## 配置与运行
|
||||
|
||||
在管理端创建 Probe 并下载一次性身份配置,将文件保存为目标机上的 `/etc/monitor/probe.json`。随后运行:
|
||||
在管理端创建 Probe 并下载一次性身份配置,将文件保存为目标机上的 `/etc/moshiro/probe.json`。随后运行:
|
||||
|
||||
```sh
|
||||
monitor-probe --config /etc/monitor/probe.json
|
||||
moshiro-probe --config /etc/moshiro/probe.json
|
||||
```
|
||||
|
||||
不传 `--config` 时默认读取 `/etc/monitor/probe.json`。配置中的 `limits` 是本地硬上限,Backend 无法远程放宽;路由器等低性能设备可调低测试码率和包速率,避免 Probe 自身成为测量瓶颈。部署时可使用仓库的 `packaging/` 文件和安装脚本。
|
||||
不传 `--config` 时默认读取 `/etc/moshiro/probe.json`。配置中的 `limits` 是本地硬上限,Backend 无法远程放宽;路由器等低性能设备可调低测试码率和包速率,避免 Probe 自身成为测量瓶颈。部署时可使用仓库的 `packaging/` 文件和安装脚本。
|
||||
|
||||
也可使用 OCI 镜像 `registry.oeo.one/monitor/monitor-probe:<version>`,自动选择 Linux amd64 或 arm64 镜像。容器默认读取 `/etc/monitor/probe.json`,并在同一目录维护 `agents.json` 缓存,因此挂载目录必须允许 UID/GID 1654 读写。建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。
|
||||
也可使用 OCI 镜像 `registry.oeo.one/moshiro/moshiro-probe:<version>`,自动选择 Linux amd64 或 arm64 镜像。容器默认读取 `/etc/moshiro/probe.json`,并在同一目录维护 `agents.json` 缓存,因此挂载目录必须允许 UID/GID 1654 读写。建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。
|
||||
|
||||
## 设计说明
|
||||
|
||||
- Probe 只执行编译进程序的测量模块。它不落盘测量数据,也不能执行命令或自行更新(更新由宿主机上的更新器完成,Probe 只写清单和请求文件)。
|
||||
- 测量模块由 `backend/shared/Monitor.Measurement/` 与 Agent 共用;Probe 负责发起连接、调度任务和记录本端数据。
|
||||
- 测量模块由 `backend/shared/MoShiro.Measurement/` 与 Agent 共用;Probe 负责发起连接、调度任务和记录本端数据。
|
||||
- 测量模型和协议细节见 [`../../docs/measurement.md`](../../docs/measurement.md) 与 [`../../docs/protocol.md`](../../docs/protocol.md);整体架构见 [`../../docs/architecture.md`](../../docs/architecture.md)。
|
||||
|
||||
Docker 与 rootless/rootful Podman 共用 `packaging/compose/probe/compose.yaml`,配置、身份和缓存放在命名 volume 中,无需宿主配置目录、keep-id 或手动 chown。首次导入和自动更新见部署文档。
|
||||
+13
-13
@@ -6,10 +6,10 @@ using Fleet.Core;
|
||||
using Fleet.Identity;
|
||||
using Fleet.Link;
|
||||
using Fleet.Rpc;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Probe;
|
||||
namespace MoShiro.Probe;
|
||||
|
||||
/// <summary>所有连接共用的东西。</summary>
|
||||
public sealed record ConnectionEnv(NodeIdentity Identity, string ProbeId, ProbeLimits Limits, AgentDirectory Directory, MonoClock Clock, TimeProvider Time, INodeLog Log);
|
||||
@@ -154,7 +154,7 @@ public sealed class AgentConnection
|
||||
for (int i = 0; i < rounds; i++)
|
||||
{
|
||||
long sent = _env.Clock.NowUs();
|
||||
Result<ClockResult> reply = await rpc.RequestAsync(MonitorMethods.Clock, new ClockParams { ProbeMonoUs = sent }, MonitorJsonContext.Default.ClockParams, MonitorJsonContext.Default.ClockResult, ct);
|
||||
Result<ClockResult> reply = await rpc.RequestAsync(MoShiroMethods.Clock, new ClockParams { ProbeMonoUs = sent }, MoShiroJsonContext.Default.ClockParams, MoShiroJsonContext.Default.ClockResult, ct);
|
||||
if (reply.TryGet(out ClockResult? value, out _))
|
||||
{
|
||||
clock.Add(sent, value.AgentMonoUs, _env.Clock.NowUs());
|
||||
@@ -189,7 +189,7 @@ public sealed class AgentConnection
|
||||
var result = new StateSyncResult { AppliedVersion = sync.Version };
|
||||
if (sync.Type == LineTaskSet.StateType)
|
||||
{
|
||||
LineTaskSet? set = sync.Doc.Deserialize(MonitorJsonContext.Default.LineTaskSet);
|
||||
LineTaskSet? set = sync.Doc.Deserialize(MoShiroJsonContext.Default.LineTaskSet);
|
||||
if (set is null)
|
||||
{
|
||||
return Result<StateSyncResult>.Fail(FleetErrors.InvalidParams, "line_task_set could not be parsed");
|
||||
@@ -201,18 +201,18 @@ public sealed class AgentConnection
|
||||
return Result<StateSyncResult>.Ok(result);
|
||||
})
|
||||
.On(
|
||||
MonitorMethods.TestPhase,
|
||||
MonitorJsonContext.Default.TestPhaseStartParams,
|
||||
MonitorJsonContext.Default.TestPhaseStartResult,
|
||||
MoShiroMethods.TestPhase,
|
||||
MoShiroJsonContext.Default.TestPhaseStartParams,
|
||||
MoShiroJsonContext.Default.TestPhaseStartResult,
|
||||
(session, start, _) => ValueTask.FromResult(Result<TestPhaseStartResult>.Ok(new TestPhaseStartResult { Accepted = session.StartPhase(start) })))
|
||||
.On(
|
||||
MonitorMethods.TestPhaseEnd,
|
||||
MonitorJsonContext.Default.TestPhaseEndParams,
|
||||
MonitorJsonContext.Default.TestPhaseEndResult,
|
||||
MoShiroMethods.TestPhaseEnd,
|
||||
MoShiroJsonContext.Default.TestPhaseEndParams,
|
||||
MoShiroJsonContext.Default.TestPhaseEndResult,
|
||||
async (session, end, ct) => Result<TestPhaseEndResult>.Ok(new TestPhaseEndResult { Fragments = await session.EndPhaseAsync(end, ct) }))
|
||||
.OnNotify(
|
||||
MonitorMethods.TestAbort,
|
||||
MonitorJsonContext.Default.TestAbortParams,
|
||||
MoShiroMethods.TestAbort,
|
||||
MoShiroJsonContext.Default.TestAbortParams,
|
||||
async (session, abort, _) => await session.AbortAsync(abort.TestId))
|
||||
.OnNotify(
|
||||
FleetMethods.OutboxAck,
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Hosting;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Probe;
|
||||
namespace MoShiro.Probe;
|
||||
|
||||
/// <summary>
|
||||
/// Probe 要连接的 Agent 列表:由面板作为期望状态(<c>probe_agents</c>)直接下发;面板连不上时用上一次保存的 <c>agents.json</c>。
|
||||
+5
-5
@@ -1,13 +1,13 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Fleet.Link;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Probe;
|
||||
namespace MoShiro.Probe;
|
||||
|
||||
/// <summary>
|
||||
/// 估计 Agent 单调时钟 − Probe 单调时钟:每次 <c>monitor.clock</c> 往返取中点,在最近 8 次里取往返最短的一次
|
||||
/// 估计 Agent 单调时钟 − Probe 单调时钟:每次 <c>moshiro.clock</c> 往返取中点,在最近 8 次里取往返最短的一次
|
||||
/// (往返越短,对称性假设的误差越小)。Probe 的列块头里带着这个偏移,Backend 据此对齐两端的时间戳。
|
||||
/// </summary>
|
||||
public sealed class ClockEstimator
|
||||
@@ -82,6 +82,6 @@ public sealed class FragmentShipper : IFragmentSink
|
||||
}
|
||||
}
|
||||
|
||||
_outbox.Enqueue(MonitorChannels.ColumnBlock, header.ChunkId.Value, RawChunkCodec.Encode(new RawChunk(header, streams)));
|
||||
_outbox.Enqueue(MoShiroChannels.ColumnBlock, header.ChunkId.Value, RawChunkCodec.Encode(new RawChunk(header, streams)));
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -4,8 +4,8 @@
|
||||
Condition="'$(UseAotAnywhere)' == 'true'" />
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AssemblyName>monitor-agent</AssemblyName>
|
||||
<RootNamespace>Monitor.Agent</RootNamespace>
|
||||
<AssemblyName>moshiro-probe</AssemblyName>
|
||||
<RootNamespace>MoShiro.Probe</RootNamespace>
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
<PublishAot>true</PublishAot>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
@@ -15,10 +15,10 @@
|
||||
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Monitor.Backend.Tests" />
|
||||
<InternalsVisibleTo Include="MoShiro.Backend.Tests" />
|
||||
<ProjectReference Include="..\..\..\fleet\src\Fleet.Link\Fleet.Link.csproj" />
|
||||
<ProjectReference Include="..\..\..\fleet\src\Fleet.Hosting\Fleet.Hosting.csproj" />
|
||||
<ProjectReference Include="..\..\..\shared\Monitor.Measurement\Monitor.Measurement.csproj" />
|
||||
<ProjectReference Include="..\..\..\shared\MoShiro.Measurement\MoShiro.Measurement.csproj" />
|
||||
</ItemGroup>
|
||||
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
|
||||
<Import Project="Sdk.targets" Sdk="StuDev.AotAnywhere"
|
||||
+10
-10
@@ -4,10 +4,10 @@ using System.Text.Json;
|
||||
using Fleet.Identity;
|
||||
using Fleet.Link;
|
||||
using Fleet.Rpc;
|
||||
using Monitor.Measurement;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Measurement;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Probe;
|
||||
namespace MoShiro.Probe;
|
||||
|
||||
/// <summary>
|
||||
/// Probe 的组装点:到面板的长连接(身份、Agent 列表、状态)+ 每个 Agent 一个测量连接(认证、重连、角色)。没有全局状态,测试可以在同一进程里启动多个。
|
||||
@@ -15,7 +15,7 @@ namespace Monitor.Probe;
|
||||
/// </summary>
|
||||
public sealed class ProbeApp : IDisposable
|
||||
{
|
||||
public const string Version = Monitor.Protocol.BuildInfo.Version;
|
||||
public const string Version = MoShiro.Protocol.BuildInfo.Version;
|
||||
|
||||
private readonly ConnectionEnv _env;
|
||||
private readonly Dictionary<string, Running> _connections = [];
|
||||
@@ -38,25 +38,25 @@ public sealed class ProbeApp : IDisposable
|
||||
Identity = identity,
|
||||
NodeId = new NodeId(file.ProbeId),
|
||||
NodeVersion = Version,
|
||||
Capabilities = [MonitorCapabilities.Base],
|
||||
HeartbeatExtra = () => JsonSerializer.SerializeToElement(new ProbeHeartbeatExtra { Update = update.ReadStatus() }, MonitorJsonContext.Default.ProbeHeartbeatExtra),
|
||||
HelloMeta = () => JsonSerializer.SerializeToElement(new ProbeHelloMeta { Platform = PlatformDetector.Detect() }, MonitorJsonContext.Default.ProbeHelloMeta),
|
||||
Capabilities = [MoShiroCapabilities.Base],
|
||||
HeartbeatExtra = () => JsonSerializer.SerializeToElement(new ProbeHeartbeatExtra { Update = update.ReadStatus() }, MoShiroJsonContext.Default.ProbeHeartbeatExtra),
|
||||
HelloMeta = () => JsonSerializer.SerializeToElement(new ProbeHelloMeta { Platform = PlatformDetector.Detect() }, MoShiroJsonContext.Default.ProbeHelloMeta),
|
||||
};
|
||||
var list = new StateHandler<ProbeAgentList>(
|
||||
ProbeAgentList.StateType,
|
||||
MonitorJsonContext.Default.ProbeAgentList,
|
||||
MoShiroJsonContext.Default.ProbeAgentList,
|
||||
(version, doc, _) =>
|
||||
{
|
||||
directory.Apply(doc);
|
||||
return ValueTask.FromResult(new StateSyncResult { AppliedVersion = version });
|
||||
});
|
||||
var router = new RpcRouter<LinkContext>()
|
||||
.OnNotify(MonitorMethods.UpdateOffer, MonitorJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
|
||||
.OnNotify(MoShiroMethods.UpdateOffer, MoShiroJsonContext.Default.UpdateOfferParams, (_, offer, _) =>
|
||||
{
|
||||
update.WriteOffer(offer);
|
||||
return ValueTask.CompletedTask;
|
||||
})
|
||||
.OnNotify(MonitorMethods.UpdateApply, MonitorJsonContext.Default.UpdateOfferParams, (_, _, _) =>
|
||||
.OnNotify(MoShiroMethods.UpdateApply, MoShiroJsonContext.Default.UpdateOfferParams, (_, _, _) =>
|
||||
{
|
||||
update.RequestApply();
|
||||
return ValueTask.CompletedTask;
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Monitor.Probe;
|
||||
namespace MoShiro.Probe;
|
||||
|
||||
/// <summary>
|
||||
/// <c>probe.json</c>:面板 <c>POST /api/admin/probe/identity</c> 生成的文件(只能下载一次)。
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Fleet.Core;
|
||||
using Fleet.Hosting;
|
||||
using Monitor.Probe;
|
||||
using MoShiro.Probe;
|
||||
|
||||
TimeProvider time = TimeProvider.System;
|
||||
INodeLog log = new ConsoleNodeLog(time);
|
||||
@@ -11,7 +11,7 @@ if (PlatformCheck.VerifyCrypto() is { } problem)
|
||||
return 1;
|
||||
}
|
||||
|
||||
string configPath = "/etc/monitor/probe.json";
|
||||
string configPath = "/etc/moshiro/probe.json";
|
||||
for (int i = 0; i < args.Length - 1; i++)
|
||||
{
|
||||
if (args[i] == "--config")
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<ProjectReference Include="..\..\src\Monitor.Probe\Monitor.Probe.csproj" />
|
||||
<ProjectReference Include="..\..\src\MoShiro.Probe\MoShiro.Probe.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using Fleet.Core;
|
||||
using Fleet.Hosting;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Probe.Tests;
|
||||
namespace MoShiro.Probe.Tests;
|
||||
|
||||
public sealed class ProbeTests
|
||||
{
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Net;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>积木的公共部分:环境、参数、把行切成列块交出去。</summary>
|
||||
public abstract class BlockBase : IBlock
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 一种采集方法(Kind)的两个角色:拨号方(Probe)和监听方(Agent),以及它能不能作为目标任务(Agent 自己探测一个地址,没有对端)运行。
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Net.Sockets;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>批量传输的收发循环(两端共用)。</summary>
|
||||
internal static class BulkIo
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 回显积木的行表(拨号方,行的主导方):发出时记 size、t1,回复到达时按序号回填 t4,到点把已经稳定的行切成列块。
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>什么也不做的监听方:connect 的另一端就是 TCP 监听器本身(接受连接、认证失败就关闭),不需要积木,但需要占一个流编号。</summary>
|
||||
public sealed class IdleListener : BlockBase
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 单向数据流(UDP train),监听方(Agent,行的主导方):按声明的码率均匀发包,发出的每个包 (seq, size, t_send) 记在按序号的记录里,
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 数据面会话:<c>session_id</c> 和密钥(由点对点握手里的 ECDH 派生)。两端的数据面包都用它认证。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>节点的单调时钟(微秒):t1…t4、t_send、t_recv 都用它;Probe 和 Agent 各自一个,块头里的时钟锚点把它对应到 UTC。</summary>
|
||||
public sealed class MonoClock
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 一侧记录的行:只含这一侧拥有的列(见 <see cref="StreamSpec.ColumnsOf"/>),按连接键(通常是 <c>seq</c>)索引的环形缓冲,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>一个运行中的积木:自己的取消令牌和后台任务。Probe 和 Agent 共用。</summary>
|
||||
public sealed class RunningBlock : IDisposable
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>节点的本地硬上限(写在本机配置里,Backend 无法修改)。</summary>
|
||||
public sealed class NodeLimits
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>Agent 单调时钟(微秒)与 UTC(毫秒)的对应关系:块头里的时钟锚点。</summary>
|
||||
public readonly record struct ClockAnchor(long AgentMonoUs, long UtcMs);
|
||||
+5
-5
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>Agent 一侧为合并保留的按键记录(由正在运行的积木提供)。</summary>
|
||||
public interface IRecordSource
|
||||
@@ -29,7 +29,7 @@ public static class FragmentMerger
|
||||
StreamSpec? streamSpec = spec.FindStream(stream.Name);
|
||||
if (streamSpec is null)
|
||||
{
|
||||
return Result<IReadOnlyList<RawStream>>.Fail(MonitorErrors.ValidationFailed, $"stream {stream.Name} is not declared by {spec.Id}");
|
||||
return Result<IReadOnlyList<RawStream>>.Fail(MoShiroErrors.ValidationFailed, $"stream {stream.Name} is not declared by {spec.Id}");
|
||||
}
|
||||
|
||||
Result<RawStream> merged = Join(streamSpec, stream, fragment.Header, records.Records(streamId, stream.Name));
|
||||
@@ -71,7 +71,7 @@ public static class FragmentMerger
|
||||
RawColumn? key = probe.Find(spec.Key);
|
||||
if (key is null || probeColumns.Any(c => probe.Find(c.Name) is null))
|
||||
{
|
||||
return Result<RawStream>.Fail(MonitorErrors.ValidationFailed, $"a fragment of stream {spec.Name} needs the columns {string.Join(", ", new[] { spec.Key }.Concat(probeColumns.Select(c => c.Name)))}");
|
||||
return Result<RawStream>.Fail(MoShiroErrors.ValidationFailed, $"a fragment of stream {spec.Name} needs the columns {string.Join(", ", new[] { spec.Key }.Concat(probeColumns.Select(c => c.Name)))}");
|
||||
}
|
||||
|
||||
return spec.Driver == Side.Probe ? JoinOnProbeRows(spec, probe, key, agent) : JoinOnAgentRows(spec, probe, key, header, agent);
|
||||
@@ -118,7 +118,7 @@ public static class FragmentMerger
|
||||
{
|
||||
if (!TryGetRange(header, out uint from, out uint to))
|
||||
{
|
||||
return Result<RawStream>.Fail(MonitorErrors.ValidationFailed, $"a fragment of stream {spec.Name} must declare its sequence range");
|
||||
return Result<RawStream>.Fail(MoShiroErrors.ValidationFailed, $"a fragment of stream {spec.Name} must declare its sequence range");
|
||||
}
|
||||
|
||||
// 行取自 Agent 的记录:范围内发出过的每一个包都有一行,Probe 没有报告到达的就是丢失
|
||||
+2
-2
@@ -3,8 +3,8 @@
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Monitor.Measurement.Tests" />
|
||||
<InternalsVisibleTo Include="MoShiro.Measurement.Tests" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)../../BannedSymbols.Node.txt" />
|
||||
<ProjectReference Include="..\Monitor.Protocol\Monitor.Protocol.csproj" />
|
||||
<ProjectReference Include="..\MoShiro.Protocol\MoShiro.Protocol.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+2
-2
@@ -5,9 +5,9 @@ using System.Net.Sockets;
|
||||
using System.Security.Authentication;
|
||||
using System.Security.Cryptography;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>拨号方的一条数据面 TCP 连接(已经完成 TLS 和首帧认证)。</summary>
|
||||
public sealed class TcpLink : IDisposable
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Net.Sockets;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>内核对一条 TCP 连接的采样(Linux <c>TCP_INFO</c>)。</summary>
|
||||
public readonly record struct TcpInfoSample(long SrttUs, long RttVarUs, long Cwnd, long TotalRetrans, long SegsOut, long BytesAcked);
|
||||
+2
-2
@@ -2,9 +2,9 @@ using System.Collections.Concurrent;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>一个通过校验的 UDP 包,交给按 <c>stream_id</c> 登记的处理函数。处理函数在接收循环里同步执行,不能阻塞。</summary>
|
||||
public readonly ref struct UdpDelivery
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 读出本机的系统名和架构,只用于在面板上展示。故意只取粗粒度的信息:
|
||||
+4
-4
@@ -1,12 +1,12 @@
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement;
|
||||
namespace MoShiro.Measurement;
|
||||
|
||||
/// <summary>
|
||||
/// 节点进程和宿主机更新器之间的文件交换(容器里 <c>/etc/monitor</c>,也就是数据目录):
|
||||
/// 节点进程和宿主机更新器之间的文件交换(容器里 <c>/etc/moshiro</c>,也就是数据目录):
|
||||
/// 节点把面板发来的清单和签名写进 <c>update/</c>,把「更新」请求写成 <c>update/apply.request</c>;更新器验签并更新,状态由宿主更新器写在只读挂载的 <c>update-status/status.json</c>。
|
||||
/// 节点进程不执行任何命令,也不信任清单的内容(验签在宿主机上)。没有安装更新器时目录写不了或没人读,调用只是返回 false。
|
||||
/// </summary>
|
||||
@@ -88,7 +88,7 @@ public sealed class UpdateBridge
|
||||
return null;
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize(File.ReadAllBytes(_statusFile), MonitorJsonContext.Default.UpdateStatus);
|
||||
return JsonSerializer.Deserialize(File.ReadAllBytes(_statusFile), MoShiroJsonContext.Default.UpdateStatus);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException)
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
[Flags]
|
||||
public enum Scope
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// Kind 目录。新增一种采集方法 = 在这里加一个静态字段并放进 <see cref="All"/>;
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
public enum ParamType
|
||||
{
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
[JsonConverter(typeof(ProbeIdJsonConverter))]
|
||||
public readonly record struct ProbeId(string Value)
|
||||
+5
-5
@@ -7,7 +7,7 @@
|
||||
<ProjectReference Include="..\..\fleet\src\Fleet.Rpc\Fleet.Rpc.csproj" />
|
||||
</ItemGroup>
|
||||
<!--
|
||||
构建信息:版本来自 $(Version),tag、提交、构建时间来自环境变量 MONITOR_TAG / MONITOR_COMMIT / MONITOR_BUILT_AT
|
||||
构建信息:版本来自 $(Version),tag、提交、构建时间来自环境变量 MOSHIRO_TAG / MOSHIRO_COMMIT / MOSHIRO_BUILT_AT
|
||||
(scripts/publish.sh 和 CI 会设置;本地开发不设置就是空,这样本地构建是可重复的、不会每次都重新编译)。
|
||||
生成一个带常量的源文件,NativeAOT 下不用反射。
|
||||
-->
|
||||
@@ -16,14 +16,14 @@
|
||||
<BuildInfoFile>$(IntermediateOutputPath)BuildInfo.g.cs</BuildInfoFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<BuildInfoLine Include="namespace Monitor.Protocol%3B" />
|
||||
<BuildInfoLine Include="namespace MoShiro.Protocol%3B" />
|
||||
<BuildInfoLine Include="/// <summary>这个程序是什么时候、从哪个版本构建出来的(构建时生成)。</summary>" />
|
||||
<BuildInfoLine Include="public static class BuildInfo" />
|
||||
<BuildInfoLine Include="{" />
|
||||
<BuildInfoLine Include=" public const string Version = "$(Version)"%3B" />
|
||||
<BuildInfoLine Include=" public const string Tag = "$(MONITOR_TAG)"%3B" />
|
||||
<BuildInfoLine Include=" public const string Commit = "$(MONITOR_COMMIT)"%3B" />
|
||||
<BuildInfoLine Include=" public const string BuiltAt = "$(MONITOR_BUILT_AT)"%3B" />
|
||||
<BuildInfoLine Include=" public const string Tag = "$(MOSHIRO_TAG)"%3B" />
|
||||
<BuildInfoLine Include=" public const string Commit = "$(MOSHIRO_COMMIT)"%3B" />
|
||||
<BuildInfoLine Include=" public const string BuiltAt = "$(MOSHIRO_BUILT_AT)"%3B" />
|
||||
<BuildInfoLine Include="}" />
|
||||
</ItemGroup>
|
||||
<WriteLinesToFile File="$(BuildInfoFile)" Lines="@(BuildInfoLine)" Overwrite="true" WriteOnlyWhenDifferent="true" />
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>Monitor 的错误码(在 Fleet 错误码之上扩展)。REST、JSON-RPC、OpenAPI 共用同一份。</summary>
|
||||
public static class MonitorErrors
|
||||
/// <summary>MoShiro 的错误码(在 Fleet 错误码之上扩展)。REST、JSON-RPC、OpenAPI 共用同一份。</summary>
|
||||
public static class MoShiroErrors
|
||||
{
|
||||
public static readonly ErrorCode ValidationFailed = new("validation_failed");
|
||||
public static readonly ErrorCode BudgetExceeded = new("budget_exceeded");
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
public static class ProtocolVersion
|
||||
{
|
||||
@@ -25,8 +25,8 @@ public static class ProtocolLimits
|
||||
public const long MaxLineBudgetBitsPerSecond = 100_000;
|
||||
}
|
||||
|
||||
/// <summary>二进制通道号(0–15 属于 Fleet,Monitor 从 16 开始)。</summary>
|
||||
public static class MonitorChannels
|
||||
/// <summary>二进制通道号(0–15 属于 Fleet,MoShiro 从 16 开始)。</summary>
|
||||
public static class MoShiroChannels
|
||||
{
|
||||
/// <summary>Agent → Backend:RawChunk(发件箱)。</summary>
|
||||
public const byte RawChunk = 16;
|
||||
@@ -35,9 +35,9 @@ public static class MonitorChannels
|
||||
public const byte ColumnBlock = 17;
|
||||
}
|
||||
|
||||
public static class MonitorCapabilities
|
||||
public static class MoShiroCapabilities
|
||||
{
|
||||
public const string Base = "monitor.v1";
|
||||
public const string KindsPrefix = "monitor.kinds:";
|
||||
public const string BlocksPrefix = "monitor.blocks:";
|
||||
public const string Base = "moshiro.v1";
|
||||
public const string KindsPrefix = "moshiro.kinds:";
|
||||
public const string BlocksPrefix = "moshiro.blocks:";
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>CRC-32C(Castagnoli),RawChunk 的完整性校验。</summary>
|
||||
public static class Crc32c
|
||||
+1
-1
@@ -2,7 +2,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>Agent 单调时钟与 UTC 的锚点,以及 Probe 时钟相对 Agent 时钟的偏移估计。</summary>
|
||||
public sealed class ChunkClock
|
||||
+6
-6
@@ -3,18 +3,18 @@ using System.IO.Compression;
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// RawChunk 的编解码(Probe、Agent、Backend、离线工具共用同一份代码)。
|
||||
/// 布局:<c>"MNRC" | u8 container_version | u32 crc32c(压缩数据) | Brotli( u32 header_len | header JSON | 各列数据 )</c>。
|
||||
/// 布局:<c>"MSRC" | u8 container_version | u32 crc32c(压缩数据) | Brotli( u32 header_len | header JSON | 各列数据 )</c>。
|
||||
/// 列数据:可空列先写有效位图,再写有效值;plain 为无符号 varint(有符号类型用 zigzag),delta 为与上一个有效值之差的 zigzag varint。
|
||||
/// </summary>
|
||||
public static class RawChunkCodec
|
||||
{
|
||||
private const int PrefixBytes = 4 + 1 + 4;
|
||||
|
||||
private static ReadOnlySpan<byte> Magic => "MNRC"u8;
|
||||
private static ReadOnlySpan<byte> Magic => "MSRC"u8;
|
||||
|
||||
/// <summary>压缩之前的正文:<c>u32 header_len | header JSON | 各列数据</c>。布局变化时黄金字节测试会失败,提醒升级版本。</summary>
|
||||
public static byte[] EncodeBody(RawChunk chunk)
|
||||
@@ -40,7 +40,7 @@ public static class RawChunkCodec
|
||||
header.Streams.Add(streamHeader);
|
||||
}
|
||||
|
||||
byte[] headerJson = JsonSerializer.SerializeToUtf8Bytes(header, MonitorJsonContext.Default.RawChunkHeader);
|
||||
byte[] headerJson = JsonSerializer.SerializeToUtf8Bytes(header, MoShiroJsonContext.Default.RawChunkHeader);
|
||||
|
||||
using var body = new MemoryStream();
|
||||
Span<byte> length = stackalloc byte[4];
|
||||
@@ -114,7 +114,7 @@ public static class RawChunkCodec
|
||||
RawChunkHeader? header;
|
||||
try
|
||||
{
|
||||
header = JsonSerializer.Deserialize(plain.AsSpan(4, (int)headerLength), MonitorJsonContext.Default.RawChunkHeader);
|
||||
header = JsonSerializer.Deserialize(plain.AsSpan(4, (int)headerLength), MoShiroJsonContext.Default.RawChunkHeader);
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
@@ -159,7 +159,7 @@ public static class RawChunkCodec
|
||||
}
|
||||
|
||||
private static Result<RawChunk> Corrupt(string message) =>
|
||||
Result<RawChunk>.Fail(MonitorErrors.RawCorrupt, message);
|
||||
Result<RawChunk>.Fail(MoShiroErrors.RawCorrupt, message);
|
||||
|
||||
private static byte[]? Inflate(ReadOnlySpan<byte> payload)
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>按 <see cref="StreamSpec"/> 逐行构造一个 <see cref="RawStream"/>(用于测试、模拟器和离线工具,不用于节点热路径)。</summary>
|
||||
public sealed class StreamBuilder
|
||||
+19
-19
@@ -2,25 +2,25 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>monitor.* 方法名。新增方法 = 在这里定义名称和 params/result,再在组装处加一行 <c>.On(...)</c>。</summary>
|
||||
public static class MonitorMethods
|
||||
/// <summary>moshiro.* 方法名。新增方法 = 在这里定义名称和 params/result,再在组装处加一行 <c>.On(...)</c>。</summary>
|
||||
public static class MoShiroMethods
|
||||
{
|
||||
public const string TestRun = "monitor.test.run";
|
||||
public const string TestCancel = "monitor.test.cancel";
|
||||
public const string ProbeDisconnect = "monitor.probe.disconnect";
|
||||
public const string TestStatus = "monitor.test.status";
|
||||
public const string Event = "monitor.event";
|
||||
public const string Host = "monitor.host";
|
||||
public const string UpdateOffer = "monitor.update.offer";
|
||||
public const string UpdateApply = "monitor.update.apply";
|
||||
public const string TestRun = "moshiro.test.run";
|
||||
public const string TestCancel = "moshiro.test.cancel";
|
||||
public const string ProbeDisconnect = "moshiro.probe.disconnect";
|
||||
public const string TestStatus = "moshiro.test.status";
|
||||
public const string Event = "moshiro.event";
|
||||
public const string Host = "moshiro.host";
|
||||
public const string UpdateOffer = "moshiro.update.offer";
|
||||
public const string UpdateApply = "moshiro.update.apply";
|
||||
|
||||
// Probe ↔ Agent(点对点会话)
|
||||
public const string Clock = "monitor.clock";
|
||||
public const string TestPhase = "monitor.test.phase";
|
||||
public const string TestPhaseEnd = "monitor.test.phase_end";
|
||||
public const string TestAbort = "monitor.test.abort";
|
||||
public const string Clock = "moshiro.clock";
|
||||
public const string TestPhase = "moshiro.test.phase";
|
||||
public const string TestPhaseEnd = "moshiro.test.phase_end";
|
||||
public const string TestAbort = "moshiro.test.abort";
|
||||
}
|
||||
|
||||
public static class AgentEventKinds
|
||||
@@ -159,7 +159,7 @@ public sealed class ProbeAgentList
|
||||
public List<AgentEndpoint> Agents { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary>注册元数据(Monitor 定义,经 Fleet 的 <c>meta</c> 字段带上来)。</summary>
|
||||
/// <summary>注册元数据(MoShiro 定义,经 Fleet 的 <c>meta</c> 字段带上来)。</summary>
|
||||
public sealed class AgentEnrollMeta
|
||||
{
|
||||
[JsonPropertyName("probe_tcp_port")]
|
||||
@@ -220,7 +220,7 @@ public sealed class PlatformInfo
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 面板给节点的更新清单(<c>monitor.update.offer</c>):<c>manifest</c> 是发布清单的原始文本,<c>signature</c> 是 base64 的签名。
|
||||
/// 面板给节点的更新清单(<c>moshiro.update.offer</c>):<c>manifest</c> 是发布清单的原始文本,<c>signature</c> 是 base64 的签名。
|
||||
/// 节点只是把它们原样放进数据目录;验签、拉取、重启都由宿主机上的更新器完成(节点进程不执行任何命令,也不信任清单的内容)。
|
||||
/// </summary>
|
||||
public sealed class UpdateOfferParams
|
||||
@@ -436,7 +436,7 @@ public sealed class LineTaskSet
|
||||
public List<int> TcpPorts { get; set; } = [];
|
||||
}
|
||||
|
||||
/// <summary><c>monitor.clock</c>:Probe 用它估计与 Agent 单调时钟的偏移(对称往返假设)。</summary>
|
||||
/// <summary><c>moshiro.clock</c>:Probe 用它估计与 Agent 单调时钟的偏移(对称往返假设)。</summary>
|
||||
public sealed class ClockParams
|
||||
{
|
||||
[JsonPropertyName("probe_mono_us")]
|
||||
@@ -543,4 +543,4 @@ public static class FragmentKeys
|
||||
[JsonSerializable(typeof(AgentHelloMeta))]
|
||||
[JsonSerializable(typeof(AgentHeartbeatExtra))]
|
||||
[JsonSerializable(typeof(RawChunkHeader))]
|
||||
public sealed partial class MonitorJsonContext : JsonSerializerContext;
|
||||
public sealed partial class MoShiroJsonContext : JsonSerializerContext;
|
||||
+3
-3
@@ -3,7 +3,7 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>Task = { id, scope, kind, target, params, schedule, enabled }。参数已经按 ParamSpec 校验并补全默认值。</summary>
|
||||
public sealed class TaskSpec
|
||||
@@ -79,9 +79,9 @@ public static class TaskSchedule
|
||||
{
|
||||
return minutes >= 60
|
||||
? Result<int?>.Ok(minutes)
|
||||
: Result<int?>.Fail(MonitorErrors.ValidationFailed, "every:N requires N >= 60 minutes");
|
||||
: Result<int?>.Fail(MoShiroErrors.ValidationFailed, "every:N requires N >= 60 minutes");
|
||||
}
|
||||
|
||||
return Result<int?>.Fail(MonitorErrors.ValidationFailed, "schedule must be continuous, once or every:<minutes>");
|
||||
return Result<int?>.Fail(MoShiroErrors.ValidationFailed, "schedule must be continuous, once or every:<minutes>");
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ using System.Buffers.Binary;
|
||||
using System.Security.Cryptography;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
/// <summary>
|
||||
/// 数据面的 TCP 连接(Fleet 之外的线格式)。连接都由 Probe 发起;TLS 是可选的(ClientHello 以 0x16 开头,
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace Monitor.Protocol;
|
||||
namespace MoShiro.Protocol;
|
||||
|
||||
public enum UdpPacketType : byte
|
||||
{
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Measurement.Tests;
|
||||
namespace MoShiro.Measurement.Tests;
|
||||
|
||||
public sealed class MeasurementTests
|
||||
{
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<ProjectReference Include="..\..\Monitor.Measurement\Monitor.Measurement.csproj" />
|
||||
<ProjectReference Include="..\..\MoShiro.Measurement\MoShiro.Measurement.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Measurement.Tests;
|
||||
namespace MoShiro.Measurement.Tests;
|
||||
|
||||
public sealed class PlatformDetectorTests
|
||||
{
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
using System.Text.Json;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Protocol.Tests;
|
||||
namespace MoShiro.Protocol.Tests;
|
||||
|
||||
public sealed class CatalogTests
|
||||
{
|
||||
@@ -101,8 +101,8 @@ public sealed class CatalogTests
|
||||
Tasks = [new TaskSpec { Id = new TaskId("tsk_1"), Kind = "udp_echo", Params = new Dictionary<string, JsonElement> { ["rate_hz"] = Json("10") } }],
|
||||
Peers = [new PeerSpec { ProbeId = new ProbeId("prb_1"), PublicKey = "AAAA" }],
|
||||
};
|
||||
string text = JsonSerializer.Serialize(set, MonitorJsonContext.Default.TaskSet);
|
||||
TaskSet back = JsonSerializer.Deserialize(text, MonitorJsonContext.Default.TaskSet)!;
|
||||
string text = JsonSerializer.Serialize(set, MoShiroJsonContext.Default.TaskSet);
|
||||
TaskSet back = JsonSerializer.Deserialize(text, MoShiroJsonContext.Default.TaskSet)!;
|
||||
|
||||
Assert.Contains("\"id\":\"tsk_1\"", text);
|
||||
Assert.Equal(7, back.Version);
|
||||
@@ -114,7 +114,7 @@ public sealed class CatalogTests
|
||||
[Fact]
|
||||
public void AgentEnrollMeta_ToleratesUnknownFields_AndKeepsDefaults()
|
||||
{
|
||||
AgentEnrollMeta meta = JsonSerializer.Deserialize("{\"kinds\":[\"udp_echo\"],\"future\":1}", MonitorJsonContext.Default.AgentEnrollMeta)!;
|
||||
AgentEnrollMeta meta = JsonSerializer.Deserialize("{\"kinds\":[\"udp_echo\"],\"future\":1}", MoShiroJsonContext.Default.AgentEnrollMeta)!;
|
||||
Assert.Equal(7443, meta.ProbeTcpPort);
|
||||
Assert.Equal(["udp_echo"], meta.Kinds);
|
||||
}
|
||||
@@ -122,7 +122,7 @@ public sealed class CatalogTests
|
||||
[Fact]
|
||||
public void ErrorCodes_AreSnakeCase()
|
||||
{
|
||||
foreach (System.Reflection.FieldInfo field in typeof(MonitorErrors).GetFields())
|
||||
foreach (System.Reflection.FieldInfo field in typeof(MoShiroErrors).GetFields())
|
||||
{
|
||||
string value = ((ErrorCode)field.GetValue(null)!).Value;
|
||||
Assert.Matches("^[a-z]+(_[a-z]+)*$", value);
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="xunit.v3" />
|
||||
<ProjectReference Include="..\..\Monitor.Protocol\Monitor.Protocol.csproj" />
|
||||
<ProjectReference Include="..\..\..\tests\Monitor.TestSupport\Monitor.TestSupport.csproj" />
|
||||
<ProjectReference Include="..\..\MoShiro.Protocol\MoShiro.Protocol.csproj" />
|
||||
<ProjectReference Include="..\..\..\tests\MoShiro.TestSupport\MoShiro.TestSupport.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
+5
-5
@@ -1,10 +1,10 @@
|
||||
using System.Buffers.Binary;
|
||||
using System.IO.Compression;
|
||||
using Fleet.Core;
|
||||
using Monitor.Protocol;
|
||||
using Monitor.TestSupport;
|
||||
using MoShiro.Protocol;
|
||||
using MoShiro.TestSupport;
|
||||
|
||||
namespace Monitor.Protocol.Tests;
|
||||
namespace MoShiro.Protocol.Tests;
|
||||
|
||||
public sealed class RawChunkCodecTests
|
||||
{
|
||||
@@ -103,7 +103,7 @@ public sealed class RawChunkCodecTests
|
||||
byte[] badCrc = (byte[])good.Clone();
|
||||
badCrc[^1] ^= 0xFF;
|
||||
|
||||
Assert.Equal(MonitorErrors.RawCorrupt, RawChunkCodec.Decode(badMagic).Error.Code);
|
||||
Assert.Equal(MoShiroErrors.RawCorrupt, RawChunkCodec.Decode(badMagic).Error.Code);
|
||||
Assert.Contains("version", RawChunkCodec.Decode(badVersion).Error.Message);
|
||||
Assert.Contains("crc", RawChunkCodec.Decode(badCrc).Error.Message);
|
||||
Assert.False(RawChunkCodec.Decode(good.AsSpan(0, good.Length / 2)).IsSuccess);
|
||||
@@ -190,7 +190,7 @@ public sealed class RawChunkCodecTests
|
||||
private static byte[] Wrap(byte[] payload)
|
||||
{
|
||||
byte[] result = new byte[9 + payload.Length];
|
||||
"MNRC"u8.CopyTo(result);
|
||||
"MSRC"u8.CopyTo(result);
|
||||
result[4] = ProtocolVersion.Container;
|
||||
BinaryPrimitives.WriteUInt32LittleEndian(result.AsSpan(5), Crc32c.Compute(payload));
|
||||
payload.CopyTo(result, 9);
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public enum AccKind : byte
|
||||
{
|
||||
+1
-1
@@ -2,7 +2,7 @@ using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
using Fleet.Core;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>一个时间桶里所有指标的累加器:<c>指标 id → 累加器</c>。</summary>
|
||||
public sealed class MetricSet
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>
|
||||
/// 固定对数桶直方图:100 µs ~ 30 s,每 2 倍 8 个桶(相对误差 ≤ 4.5%)。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>
|
||||
/// 估计两端的时钟偏移和线性漂移:单向时延 x(t) = 偏移 + 漂移·t + 真实的时延变化。
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>变点检测(均值漂移):找到使前后两段均值差最显著的位置,用来判断 QoS 从什么时候开始限速。</summary>
|
||||
public static class ChangePoint
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>算子把观测值交给这里;窗口评估器负责按时间分桶。</summary>
|
||||
public interface IObservationSink
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public enum Unit
|
||||
{
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
using System.Text.Json;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public sealed class TestStepResult
|
||||
{
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>
|
||||
/// 把原始块按时间分桶算成累加器。流式路径(1s 桶)和持久路径(分钟窗口)调用同一份代码,
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>线路基础指标(udp_echo / icmp_echo 的回显流)。新增指标 = 加一行声明。</summary>
|
||||
public static class LinkMetrics
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public sealed record PresetDef(string Id, string Label, IReadOnlyList<string> MetricIds);
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>连接建立、长连接、UDP 压测(udp_train)的指标。</summary>
|
||||
public static class SessionMetrics
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>TCP 指标:回显的 RTT 与卡顿、内核采样的重传、批量传输的吞吐。</summary>
|
||||
public static class TcpMetrics
|
||||
+1
-1
@@ -3,6 +3,6 @@
|
||||
<IsAotCompatible>true</IsAotCompatible>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\shared\Monitor.Protocol\Monitor.Protocol.csproj" />
|
||||
<ProjectReference Include="..\..\shared\MoShiro.Protocol\MoShiro.Protocol.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>线路基础质量:RTT、丢包、抖动、连续丢包。</summary>
|
||||
public sealed class BaseModel : ServiceModelBase
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>模型的输入:一个时间段内的导出值(例如 <c>rtt.p95</c>)。没有数据的指标不存在。</summary>
|
||||
public sealed class MetricView
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
public enum Dir
|
||||
{
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>算子工厂:<c>Ops.Rtt</c>、<c>Ops.Loss(Dir.Up)</c> 等,供 <c>MetricDef</c> 声明使用。</summary>
|
||||
public static class Ops
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Analysis;
|
||||
namespace MoShiro.Backend.Analysis;
|
||||
|
||||
/// <summary>tcp_info 流的辅助:<c>side</c> 为 0 表示 Probe 一侧记录(Probe 时钟),否则为 Agent 一侧。</summary>
|
||||
internal static class TcpInfoTime
|
||||
+6
-6
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>Agent 管理:列表、修改、审批、启停、接管、删除;加入令牌;Probe 身份。</summary>
|
||||
public static class AgentEndpoints
|
||||
@@ -77,7 +77,7 @@ public static class AgentEndpoints
|
||||
AgentView? view = await agents.GetAsync(new NodeId(id), ct);
|
||||
if (view is null)
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.AgentNotFound, "agent not found");
|
||||
return Problems.Create(http, MoShiroErrors.AgentNotFound, "agent not found");
|
||||
}
|
||||
|
||||
Concurrency.SetETag(http, view.Agent.RowVersion);
|
||||
@@ -166,7 +166,7 @@ public static class AgentEndpoints
|
||||
private static async Task<IResult> GetProbeAsync(HttpContext http, ProbeService probes, string id, CancellationToken ct) =>
|
||||
await probes.GetAsync(id, ct) is { } view
|
||||
? TypedResults.Json(Mappers.ToDto(view), ApiJsonContext.Default.ProbeDto)
|
||||
: Problems.Create(http, MonitorErrors.ProbeNotFound, "probe not found");
|
||||
: Problems.Create(http, MoShiroErrors.ProbeNotFound, "probe not found");
|
||||
|
||||
private static async Task<IResult> PatchProbeAsync(HttpContext http, ProbeService probes, PatchProbeRequest request, string id, CancellationToken ct)
|
||||
{
|
||||
@@ -211,7 +211,7 @@ public static class AgentEndpoints
|
||||
return Problems.From(http, error);
|
||||
}
|
||||
|
||||
return dropped ? TypedResults.NoContent() : Problems.Create(http, MonitorErrors.ProbeOffline, "this probe has no session with this agent");
|
||||
return dropped ? TypedResults.NoContent() : Problems.Create(http, MoShiroErrors.ProbeOffline, "this probe has no session with this agent");
|
||||
}
|
||||
|
||||
/// <summary>Probe 要连的面板地址:配置的公开地址,没有就用这次请求的地址。</summary>
|
||||
+4
-4
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>登录、退出、当前身份。登录接口限流,失败一律返回 401,不区分“用户不存在”和“密码错误”。</summary>
|
||||
public static class AuthEndpoints
|
||||
@@ -35,7 +35,7 @@ public static class AuthEndpoints
|
||||
string source = http.Connection.RemoteIpAddress?.ToString() ?? "unknown";
|
||||
if (!limiter.TryAcquire(source, out int retryAfter))
|
||||
{
|
||||
return Problems.RetryAfter(http, new FleetError(MonitorErrors.TooManyRequests, "too many sign-in attempts"), retryAfter);
|
||||
return Problems.RetryAfter(http, new FleetError(MoShiroErrors.TooManyRequests, "too many sign-in attempts"), retryAfter);
|
||||
}
|
||||
|
||||
Result<LoginResult> result = await auth.LoginAsync(request.Username ?? "", request.Password ?? "", http.RequestAborted);
|
||||
+4
-4
@@ -2,10 +2,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>构建信息与更新:后端和每个节点的版本、tag、构建时间,CI 签名的发布清单,以及「更新这个节点」的请求。更新本身由节点宿主机上的更新器验签并执行。</summary>
|
||||
public static class BuildEndpoints
|
||||
@@ -54,7 +54,7 @@ public static class BuildEndpoints
|
||||
}
|
||||
|
||||
private static async Task<IResult> RequestUpdateAsync(HttpContext http, ReleaseService releases, string id, CancellationToken ct) =>
|
||||
await releases.RequestUpdateAsync(new NodeId(id), ct) ? TypedResults.NoContent() : Problems.Create(http, MonitorErrors.AgentOffline, "the node is not connected");
|
||||
await releases.RequestUpdateAsync(new NodeId(id), ct) ? TypedResults.NoContent() : Problems.Create(http, MoShiroErrors.AgentOffline, "the node is not connected");
|
||||
|
||||
private static ReleaseDto ToDto(ReleaseOffer offer) => new(offer.Component, offer.Seq, offer.Tag, offer.Version, offer.Digest);
|
||||
|
||||
+7
-7
@@ -3,10 +3,10 @@ using Fleet.Core;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Monitor.Backend.Application;
|
||||
using Monitor.Protocol;
|
||||
using MoShiro.Backend.Application;
|
||||
using MoShiro.Protocol;
|
||||
|
||||
namespace Monitor.Backend.Api;
|
||||
namespace MoShiro.Backend.Api;
|
||||
|
||||
/// <summary>原始数据检索与下载、事件、指标重算进度。</summary>
|
||||
public static class DataEndpoints
|
||||
@@ -18,8 +18,8 @@ public static class DataEndpoints
|
||||
group.MapGet("/raw/chunks", ListChunksAsync)
|
||||
.Api("raw_chunks_list", "Raw chunk metadata", Tag).Returns<PageDto<RawChunkMetaDto>>().ProducesProblems(400, 401, 403);
|
||||
group.MapGet("/raw/chunks/{chunk_id}", DownloadChunkAsync)
|
||||
.Api("raw_chunks_get", "Download a raw chunk (application/vnd.monitor.rawchunk)", Tag)
|
||||
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.monitor.rawchunk").ProducesProblems(401, 403, 404, 410);
|
||||
.Api("raw_chunks_get", "Download a raw chunk (application/vnd.moshiro.rawchunk)", Tag)
|
||||
.Produces(StatusCodes.Status200OK, contentType: "application/vnd.moshiro.rawchunk").ProducesProblems(401, 403, 404, 410);
|
||||
group.MapGet("/events", ListEventsAsync)
|
||||
.Api("events_list", "Agent events", Tag).Returns<PageDto<EventDto>>().ProducesProblems(400, 401, 403);
|
||||
group.MapGet("/analysis/recompute", RecomputeAsync)
|
||||
@@ -57,7 +57,7 @@ public static class DataEndpoints
|
||||
{
|
||||
if (!Guid.TryParse(chunk_id, out Guid id))
|
||||
{
|
||||
return Problems.Create(http, MonitorErrors.ChunkNotFound, "chunk not found");
|
||||
return Problems.Create(http, MoShiroErrors.ChunkNotFound, "chunk not found");
|
||||
}
|
||||
|
||||
Result<(RawChunkMeta Meta, byte[] Bytes)> result = await raw.GetAsync(new ChunkId(id), http.RequestAborted);
|
||||
@@ -67,7 +67,7 @@ public static class DataEndpoints
|
||||
}
|
||||
|
||||
http.Response.Headers.CacheControl = "private, max-age=3600";
|
||||
return TypedResults.File(found.Bytes, "application/vnd.monitor.rawchunk", chunk_id + ".mnrc");
|
||||
return TypedResults.File(found.Bytes, "application/vnd.moshiro.rawchunk", chunk_id + ".msrc");
|
||||
}
|
||||
|
||||
private static async Task<IResult> ListEventsAsync(
|
||||
Loaded 100 of 309 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user