diff --git a/AGENTS.md b/AGENTS.md index 5379688..56d3aee 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ Monitor is a self-hosted network path quality monitor. Backend coordinates Agent - Treat `main` as the stable release branch and `dev` as the integration branch. Keep ordinary implementation work on `dev` or a feature branch; integrate into `main` through a pull request. - 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. +- 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`. ## Project layout diff --git a/backend/agent/README.md b/backend/agent/README.md index 42266c5..48fab1a 100644 --- a/backend/agent/README.md +++ b/backend/agent/README.md @@ -47,7 +47,7 @@ fleet-enroll --server https:// --join --name --ide monitor-agent --config /etc/monitor/agent.json ``` -也可使用 OCI 镜像 `registry.oeo.one/monitor/monitor-agent:`,自动选择 Linux amd64 或 arm64 镜像。镜像同时包含 `fleet-enroll`;可通过 `--entrypoint /usr/local/bin/fleet-enroll` 执行首次注册。容器默认读取 `/etc/monitor/agent.json`,需要挂载配置与身份目录;建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。镜像以 UID/GID 1654 运行,挂载目录必须允许该用户读取配置和身份文件。 +容器节点的运行方式(podman + systemd)和宿主机更新器见 [`../../docs/deployment.md`](../../docs/deployment.md)。也可使用 OCI 镜像 `registry.oeo.one/monitor/monitor-agent:`,自动选择 Linux amd64 或 arm64 镜像。镜像同时包含 `fleet-enroll`;可通过 `--entrypoint /usr/local/bin/fleet-enroll` 执行首次注册。容器默认读取 `/etc/monitor/agent.json`,需要挂载配置与身份目录;建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。镜像以 UID/GID 1654 运行,挂载目录必须允许该用户读取配置和身份文件。 不传 `--config` 时默认读取 `/etc/monitor/agent.json`。Agent 默认监听控制端口 `7443/tcp`,数据端口 `7443/udp` 和 `7444/tcp`;防火墙和面板中的节点端口配置需与本地配置一致。`reporting.host_stats` 可关闭主机状态上报;启用时只上报资源使用等汇总数据,不包含主机名、进程或 IP。 diff --git a/backend/probe/README.md b/backend/probe/README.md index 07805f0..7adaed7 100644 --- a/backend/probe/README.md +++ b/backend/probe/README.md @@ -13,6 +13,8 @@ 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:`,容器默认读取 `/etc/monitor/probe.json`,数据目录要允许 UID/GID 1654 读写。 + ## 构建 需要 .NET SDK 10。为应用 `backend/global.json` 的 SDK 设置,请从 `backend/` 目录构建: diff --git a/docs/deployment.md b/docs/deployment.md index fc50d6b..a69f54e 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -195,6 +195,36 @@ OpenWrt 默认不带 OpenSSL:`opkg install libopenssl3`(较新的快照和 2 这次试跑发现并修掉的问题:`hy2_ladder` 的阶梯按「块」而不是按「阶段」计算,真实的 Probe 把一个阶段切成几块,4 个阶段出了 7 步(模拟数据不切块,所以之前的测试没有发现);现在同一阶段的块先合并。 +## 容器节点的运行(podman + systemd) + +Agent 和 Probe 推荐在宿主机上用 root 的 podman 加 systemd 运行,这样宿主机更新器(下一节)才能重启它们。以 Agent 为例(Probe 把名字和目录换成 `monitor-probe`,配置文件换成 `probe.json`): + +```ini +# /etc/systemd/system/monitor-agent-container.service +[Unit] +Description=Monitor Agent (podman) +After=network-online.target +Wants=network-online.target + +[Service] +EnvironmentFile=/etc/monitor-update/agent.image # IMAGE=<仓库>@sha256:,更新器更新时改写它 +Restart=always +RestartSec=5 +TimeoutStopSec=30 +ExecStartPre=-/usr/bin/podman rm -f monitor-agent +ExecStart=/usr/bin/podman run --rm --name monitor-agent --network host --read-only --security-opt no-new-privileges --cap-drop all -v /opt/monitor/etc:/etc/monitor:Z ${IMAGE} +ExecStop=/usr/bin/podman stop -t 20 monitor-agent + +[Install] +WantedBy=multi-user.target +``` + +- **数据目录**(上面挂到容器 `/etc/monitor` 的 `/opt/monitor/etc`)归容器用户(UID/GID 1654),里面放 `agent.json`、身份目录(`fleet-enroll` 注册之后的 `agent-id/`),Probe 放 `probe.json` 和 `agents.json`。`agent.json` 里的 `fleet.identity_dir` 写容器里的路径(例如 `/etc/monitor/agent-id`)。 +- **首次注册 Agent**:`podman run --rm --network host -v <数据目录>:/etc/monitor --entrypoint /usr/local/bin/fleet-enroll <镜像> --server https://<面板> --join <加入令牌> --name <名字> --identity-dir /etc/monitor/agent-id`,再写 `agent.json`。Probe:在管理端创建(或轮换密钥)之后下载 `probe.json`,放进数据目录。 +- **拉取私有镜像**需要 Registry 的登录信息:用 `podman login --authfile /etc/monitor-update/registry-auth.json …` 生成一份只有 root 能读(600)的认证文件,更新器用它拉取,容器本身用不到。 +- **ICMP 目标任务**要在 `podman run` 里加 `--cap-add NET_RAW`。 +- `packaging/compose/` 里的 compose 文件是不带宿主机更新器的简单方式:更新靠手动改版本再 `docker compose pull && up -d`,面板的更新按钮对它不起作用。 + ## 容器节点的更新(Agent、Probe) 面板通知、节点验签、宿主机更新,**面板即使被攻破也装不上假镜像**: @@ -215,6 +245,7 @@ CI 构建并推镜像(Zot)──► ci/release-manifest.sh:对镜像 diges - **镜像仓库、服务名、公钥都在本机配置里**,清单里写什么都不会改变拉取哪个仓库;清单被篡改、签名对不上、组件或仓库不符、`seq` 不比已安装的大(重放旧清单),都会被拒绝,状态是 `invalid`。 - **业务进程不能改二进制,也不执行命令**:它只能往 `update/` 里放清单和请求;状态在 root 拥有的 `update-status/` 里(只读)。 - **CI 的配置**:Woodpecker 密钥 `release_signing_key`(发布私钥,PEM)、`release_api_token`(面板的 API 令牌,作用域 `release`,只能登记清单);环境变量 `RELEASE_API_URL`(面板地址)。没有 `release_signing_key` 就只构建镜像、不发清单。 +- **管理端**:「系统 → 版本」页显示最新发布的 tag、每个节点的更新状态(已是最新、有新版本、更新中、已回滚、清单无效),有新版本的节点有「更新」按钮,也可以「全部更新」。更新策略在节点本机配置:`/etc/monitor-update/<组件>.conf` 里 `POLICY=manual`(默认,等管理员请求)或 `auto`(验签通过就更新)。 - **发布公钥**在 `packaging/update/release-key.pub.pem`;私钥不入库,请离线备份。更换密钥:节点上换新公钥之后再用新私钥发布。 - 纯二进制的安装方式(`fleet-updater`、`fleet-release`,见 [fleet/update.md](fleet/update.md))还没有实现;容器方式优先。 diff --git a/docs/roadmap.md b/docs/roadmap.md index 2ade908..5575f63 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -62,6 +62,8 @@ ## F2 Fleet 更新(在 M6 之前) +> **状态**:容器节点(Agent、Probe)的更新已经实现并在线上验证过(签名发布清单、面板转发、宿主机更新器验签、按 digest 拉取、健康确认和回滚,见 [deployment.md](deployment.md) 的「容器节点的更新」)。**还没实现**:纯二进制安装方式的 `fleet-updater` 和 `fleet-release`、Windows 服务和 OpenWrt 的适配器、通道与灰度(现在由管理员在管理端手动选节点)、`POLICY=auto` 的线上验证。下面保留原来的完整设计。 + - `Fleet.Update`、`fleet-updater`(systemd 与 Windows 服务适配器、健康信标、回滚、防回滚)、`fleet-release`(CI 签名)、`Fleet.Server.Releases`(通道、灰度、版本汇总)。 **验收**: @@ -81,7 +83,11 @@ ## 实现状态(后端与 Agent) -已实现并有自动化测试(287 个,另有 2 个跳过的测试,其中 1 个需要运行中 NativeAOT 产物): +已实现并有自动化测试(331 个,另有 2 个跳过的测试,其中 1 个需要运行中 NativeAOT 产物): + +- **Probe 直连面板**:Probe 用自己的身份连 Backend(`probe.json` 里有 `server`),Agent 列表由面板作为期望状态 `probe_agents` 下发;Agent 不再中转列表和 Probe 的系统信息。 +- **节点信息与构建信息**:系统、架构、国家和运营商(Backend 用来源地址查询,节点不访问外部服务)、版本、tag、提交和构建时间;展示端按 `site.show_build` 开关显示,管理端「系统 → 版本」看全部。 +- **容器节点的更新**:CI 对镜像 digest 签名,面板转发,宿主机 `monitor-update` 验签、拉取、重启并在不健康时回滚;管理端有更新状态和按钮,CI 用只能登记清单的 `release` 令牌。 - **多 Probe**:Probe 身份由 Backend 生成(创建、轮换密钥、吊销、删除),一条线路是一对(Probe,Agent),Agent 为每个 Probe 会话各起一组流,数据按 `line:` 隔离;Agent 上报连着的 Probe(id、版本、远端地址、时间),管理端可以断开某个 Probe;吊销立即生效。 - **`tcp_bulk` 原语**:批量传输(fwd / rev、本地码率上限、两端的 tcp_info),评分用持续 1 秒的吞吐峰值。 diff --git a/packaging/compose/agent/compose.yaml b/packaging/compose/agent/compose.yaml index 48cad7c..ca379c8 100644 --- a/packaging/compose/agent/compose.yaml +++ b/packaging/compose/agent/compose.yaml @@ -9,7 +9,8 @@ # {"fleet":{"server":"https://","identity_dir":"/etc/monitor/agent-id"},"listen":{"bind":"0.0.0.0"},"limits":{"max_total_mbps":100,"max_total_pps":10000}} # docker compose up -d # -# 更新:改 MONITOR_VERSION(或 MONITOR_IMAGE),再 docker compose pull && docker compose up -d。.data 不动,身份和历史都保留。 +# 更新:改 MONITOR_VERSION(或 MONITOR_IMAGE),再 docker compose pull && docker compose up -d。 +# 这种方式没有宿主机更新器:管理端的「更新」按钮对它不起作用;要自动更新请用 podman + systemd(docs/deployment.md「容器节点的运行」)。.data 不动,身份和历史都保留。 services: agent: image: ${MONITOR_IMAGE:-registry.oeo.one/monitor/monitor-agent}:${MONITOR_VERSION:-0.1.1} diff --git a/packaging/compose/probe/compose.yaml b/packaging/compose/probe/compose.yaml index 7c1a2a8..1d6fa41 100644 --- a/packaging/compose/probe/compose.yaml +++ b/packaging/compose/probe/compose.yaml @@ -5,7 +5,8 @@ # 在管理端创建 Probe,下载一次性身份配置,保存为 .data/probe.json(含私钥,权限 600,不要提交到版本控制) # docker compose up -d # -# 更新:改 MONITOR_VERSION(或 MONITOR_IMAGE),再 docker compose pull && docker compose up -d。.data 不动。 +# 更新:改 MONITOR_VERSION(或 MONITOR_IMAGE),再 docker compose pull && docker compose up -d。 +# 这种方式没有宿主机更新器:管理端的「更新」按钮对它不起作用;要自动更新请用 podman + systemd(docs/deployment.md「容器节点的运行」)。.data 不动。 services: probe: image: ${MONITOR_IMAGE:-registry.oeo.one/monitor/monitor-probe}:${MONITOR_VERSION:-0.1.1} diff --git a/web/README.md b/web/README.md index 298fcfa..815f7c8 100644 --- a/web/README.md +++ b/web/README.md @@ -5,7 +5,7 @@ | 目录 | 内容 | 访问 | | --- | --- | --- | | `display/` | 展示端:Agent 卡片总览(线路质量 + 主机资源占用,实时刷新)、线路详情(延迟、丢包、抖动、TCP、UDP、评分、历史)、线路对比 | `/`,匿名可看;站点没有公开时提示登录 | -| `admin/` | 管理端:Agent(添加、审批、启停、接管、删除、加入令牌)、Probe(创建、轮换密钥、吊销、删除、连接)、任务、按需测试、系统(设置、API 令牌、用户、事件、原始数据、分析) | `/admin/`,要登录 | +| `admin/` | 管理端:Agent(添加、审批、启停、接管、删除、加入令牌)、Probe(创建、轮换密钥、吊销、删除、连接)、任务、按需测试、系统(设置、版本与更新、API 令牌、用户、事件、原始数据、分析) | `/admin/`,要登录 | | `shared/` | `theme/`(「纸墨」主题令牌和基础样式)、`api/`(客户端、从 OpenAPI 生成的类型)、`charts/`(uPlot 封装)、`ui/`(通用组件) | | ```sh