diff --git a/.woodpecker/gitea-release.yml b/.woodpecker/gitea-release.yml
index 981bde8..322c7ce 100644
--- a/.woodpecker/gitea-release.yml
+++ b/.woodpecker/gitea-release.yml
@@ -1,27 +1,43 @@
-labels:
- ci: domain-linux-native
- platform: linux/amd64
-
-clone:
- - name: git
- image: plugin-git
- settings:
- lfs: false
-
-when:
- - event: tag
- ref: refs/tags/v*
-
-depends_on:
- - linux-release
- - windows-release
-
-steps:
- - name: publish-gitea-release
- image: bash
- environment:
+labels:
+ ci: domain-linux-native
+ platform: linux/amd64
+
+clone:
+ - name: git
+ image: plugin-git
+ settings:
+ lfs: false
+
+when:
+ - event: tag
+ ref: refs/tags/v*
+
+depends_on:
+ - linux-release
+ - windows-release
+
+steps:
+ - name: publish-agent-probe-oci
+ image: bash
+ environment:
GITEA_BASE: http://127.0.0.1:3100
- GITEA_TOKEN:
- from_secret: gitea_token
- commands:
- - bash ci/publish-gitea-release.sh
+ GITEA_TOKEN:
+ from_secret: gitea_token
+ ZOT_REGISTRY: registry.oeo.one
+ ZOT_USERNAME: justme
+ ZOT_SECRET:
+ from_secret: zot_secret
+ commands:
+ - bash ci/release-node-images.sh
+ depends_on: []
+
+ - name: publish-gitea-release
+ image: bash
+ environment:
+ GITEA_BASE: http://127.0.0.1:3100
+ GITEA_TOKEN:
+ from_secret: gitea_token
+ commands:
+ - bash ci/publish-gitea-release.sh
+ depends_on:
+ - publish-agent-probe-oci
diff --git a/README.md b/README.md
index dfc4fb4..dda569a 100644
--- a/README.md
+++ b/README.md
@@ -44,7 +44,7 @@ npm run build --prefix web
前端产物位于 `web/display/dist/` 和 `web/admin/dist/`。NativeAOT 目标平台和 Windows 上使用 AotAnywhere 交叉构建的说明见 [`docs/deployment.md`](docs/deployment.md)。
-Backend 以 OCI 镜像发布,支持 Linux/amd64 与 Linux/arm64;Agent 和 Probe 发布 Linux glibc/musl x64、arm64,macOS x64/arm64,以及 Windows x64/arm64 二进制包。容器部署配置见 [`compose.yaml`](compose.yaml),发布说明见 [`docs/deployment.md`](docs/deployment.md)。
+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)。
### 本地运行
diff --git a/backend/Directory.Build.props b/backend/Directory.Build.props
index bbd8e84..65e3c69 100644
--- a/backend/Directory.Build.props
+++ b/backend/Directory.Build.props
@@ -13,7 +13,7 @@
true
$(MSBuildThisFileDirectory)artifacts
true
- 0.1.0
+ 0.1.1
false
diff --git a/backend/agent/README.md b/backend/agent/README.md
index 53ae9b4..42266c5 100644
--- a/backend/agent/README.md
+++ b/backend/agent/README.md
@@ -47,6 +47,8 @@ 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 运行,挂载目录必须允许该用户读取配置和身份文件。
+
不传 `--config` 时默认读取 `/etc/monitor/agent.json`。Agent 默认监听控制端口 `7443/tcp`,数据端口 `7443/udp` 和 `7444/tcp`;防火墙和面板中的节点端口配置需与本地配置一致。`reporting.host_stats` 可关闭主机状态上报;启用时只上报资源使用等汇总数据,不包含主机名、进程或 IP。
## 设计说明
diff --git a/backend/agent/src/Monitor.Agent/AgentApp.cs b/backend/agent/src/Monitor.Agent/AgentApp.cs
index a7136a7..c79fb0a 100644
--- a/backend/agent/src/Monitor.Agent/AgentApp.cs
+++ b/backend/agent/src/Monitor.Agent/AgentApp.cs
@@ -12,7 +12,7 @@ namespace Monitor.Agent;
/// Agent 的组装点:数据面、点对点监听、与 Backend 的链路都在这里接起来。没有全局状态,测试可以在同一进程里启动多个。
public sealed class AgentApp : IDisposable
{
- public const string Version = "0.1.0";
+ public const string Version = "0.1.1";
private readonly AgentConfig _config;
private readonly LoadedNodeIdentity _identity;
diff --git a/backend/probe/README.md b/backend/probe/README.md
index 4b50574..cfeb861 100644
--- a/backend/probe/README.md
+++ b/backend/probe/README.md
@@ -39,6 +39,8 @@ monitor-probe --config /etc/monitor/probe.json
不传 `--config` 时默认读取 `/etc/monitor/probe.json`。配置中的 `limits` 是本地硬上限,Backend 无法远程放宽;路由器等低性能设备可调低测试码率和包速率,避免 Probe 自身成为测量瓶颈。部署时可使用仓库的 `packaging/` 文件和安装脚本。
+也可使用 OCI 镜像 `registry.oeo.one/monitor/monitor-probe:`,自动选择 Linux amd64 或 arm64 镜像。容器默认读取 `/etc/monitor/probe.json`,并在同一目录维护 `agents.json` 缓存,因此挂载目录必须允许 UID/GID 1654 读写。建议使用宿主网络,并按 ICMP 测量需求添加 `NET_RAW` 能力。
+
## 设计说明
- Probe 只执行编译进程序的测量模块。它不认识 Backend,不落盘测量数据,也不能执行命令或自行更新。
diff --git a/backend/probe/src/Monitor.Probe/ProbeApp.cs b/backend/probe/src/Monitor.Probe/ProbeApp.cs
index f6a71a9..aeb880c 100644
--- a/backend/probe/src/Monitor.Probe/ProbeApp.cs
+++ b/backend/probe/src/Monitor.Probe/ProbeApp.cs
@@ -11,7 +11,7 @@ namespace Monitor.Probe;
///
public sealed class ProbeApp : IDisposable
{
- public const string Version = "0.1.0";
+ public const string Version = "0.1.1";
private readonly ConnectionEnv _env;
private readonly Dictionary _connections = [];
diff --git a/ci/publish-gitea-release.sh b/ci/publish-gitea-release.sh
index 4f0b182..80dabea 100644
--- a/ci/publish-gitea-release.sh
+++ b/ci/publish-gitea-release.sh
@@ -26,7 +26,7 @@ printf 'machine %s login justme password %s\n' "$gitea_host" "$GITEA_TOKEN" > "$
chmod 600 "$netrc"
trap 'rm -rf "$tmp"' EXIT
-release_body="Agent and Probe builds: Linux glibc and musl (x64, arm64), macOS (x64, arm64), and Windows (x64, arm64). macOS binaries are not Developer ID signed or notarized. Backend OCI images: registry.oeo.one/monitor/monitor-backend:$version (linux/amd64 and linux/arm64)."
+release_body="Agent and Probe builds: Linux glibc and musl (x64, arm64), macOS (x64, arm64), and Windows (x64, arm64). Linux glibc Agent and Probe OCI images: registry.oeo.one/monitor/monitor-agent:$version and registry.oeo.one/monitor/monitor-probe:$version (linux/amd64 and linux/arm64). Backend OCI image: registry.oeo.one/monitor/monitor-backend:$version (linux/amd64 and linux/arm64). macOS binaries are not Developer ID signed or notarized."
payload="$(printf '{"tag_name":"%s","target_commitish":"%s","name":"MyMonitor %s","body":"%s","draft":false,"prerelease":false}' \
"$tag" "${CI_COMMIT_SHA:-}" "$tag" "$release_body")"
release_status="$(curl --silent --show-error --retry 3 --netrc-file "$netrc" \
diff --git a/ci/release-node-images.sh b/ci/release-node-images.sh
new file mode 100644
index 0000000..8adb51f
--- /dev/null
+++ b/ci/release-node-images.sh
@@ -0,0 +1,119 @@
+#!/usr/bin/env bash
+# Publish glibc OCI images for the Linux Agent and Probe on amd64 and arm64.
+set -euo pipefail
+
+tag="${CI_COMMIT_TAG:-}"
+[[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]] || {
+ echo "release must run from a vMAJOR.MINOR.PATCH tag" >&2
+ exit 2
+}
+[[ -n "${GITEA_TOKEN:-}" ]] || { echo "GITEA_TOKEN is required" >&2; exit 2; }
+[[ -n "${ZOT_USERNAME:-}" && -n "${ZOT_SECRET:-}" ]] || {
+ echo "Zot credentials are required" >&2
+ exit 2
+}
+
+version="${tag#v}"
+root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+project_version="$(sed -n 's#.*\(.*\).*#\1#p' "$root/backend/Directory.Build.props" | head -1)"
+if [[ "$version" != "$project_version" ]]; then
+ echo "tag version $version does not match project version $project_version" >&2
+ exit 1
+fi
+
+gitea_base="${GITEA_BASE:-http://127.0.0.1:3100}"
+gitea_base="${gitea_base%/}"
+gitea_host="${gitea_base#https://}"
+gitea_host="${gitea_host#http://}"
+gitea_host="${gitea_host%%/*}"
+gitea_host="${gitea_host%%:*}"
+registry="${ZOT_REGISTRY:-registry.oeo.one}"
+image_root="$registry/monitor"
+work="$root/.release/node-images"
+netrc="$work/netrc"
+buildah_home="/home/justme/woodpecker/buildah-home/node-images"
+mkdir -p "$buildah_home"
+export HOME="$buildah_home"
+mkdir -p "$work"
+chmod 700 "$work"
+printf 'machine %s login justme password %s\n' "$gitea_host" "$GITEA_TOKEN" > "$netrc"
+chmod 600 "$netrc"
+export REGISTRY_AUTH_FILE="$work/zot-auth.json"
+trap 'rm -rf "$work"' EXIT
+
+logged_in=false
+for attempt in 1 2 3 4; do
+ if buildah login --tls-verify=true --username "$ZOT_USERNAME" --password-stdin "$registry" <<< "$ZOT_SECRET" >/dev/null; then
+ logged_in=true
+ break
+ fi
+ if [[ "$attempt" -lt 4 ]]; then
+ echo "Zot login attempt $attempt failed; retrying in 3 seconds" >&2
+ sleep 3
+ fi
+done
+[[ "$logged_in" == true ]] || { echo "could not authenticate to Zot after 4 attempts" >&2; exit 1; }
+
+build_arch_image() {
+ local component="$1" arch="$2" suffix="$3"
+ local filename="monitor-$component-v$version-linux-$suffix.tar.gz"
+ local context="$work/$component-$arch"
+ local archive="$work/$filename"
+ local containerfile="$root/packaging/container/${component^}.Containerfile"
+ local image="$image_root/monitor-$component"
+ local local_image="localhost/monitor-$component:$version-$arch"
+
+ curl --fail-with-body --silent --show-error --retry 3 --netrc-file "$netrc" \
+ "$gitea_base/api/packages/justme/generic/monitor-node/$version/$filename" -o "$archive"
+ mkdir -p "$context/app" "$context/config"
+ if [[ "$component" == agent ]]; then
+ tar -xzf "$archive" -C "$work" monitor-agent fleet-enroll
+ install -m 0755 "$work/fleet-enroll" "$context/app/fleet-enroll"
+ else
+ tar -xzf "$archive" -C "$work" "monitor-$component"
+ fi
+ install -m 0755 "$work/monitor-$component" "$context/app/monitor-$component"
+ : > "$context/config/.keep"
+
+ buildah bud --tls-verify=true --arch "$arch" \
+ --build-arg "APP_VERSION=$version" \
+ -f "$containerfile" -t "$local_image" "$context" >/dev/null
+
+ local pushed=false
+ for attempt in 1 2 3; do
+ if buildah push --tls-verify=true "$local_image" "docker://$image:$version-$arch" >/dev/null; then
+ pushed=true
+ break
+ fi
+ if [[ "$attempt" -lt 3 ]]; then
+ echo "Zot push for $component/$arch attempt $attempt failed; retrying in 5 seconds" >&2
+ sleep 5
+ fi
+ done
+ [[ "$pushed" == true ]] || { echo "could not push $component/$arch image after 3 attempts" >&2; return 1; }
+ echo "pushed Zot image $image:$version-$arch"
+}
+
+publish_index() {
+ local component="$1" image="$image_root/monitor-$component"
+ local manifest="localhost/monitor-$component:$version-multiarch"
+ buildah manifest rm "$manifest" >/dev/null 2>&1 || true
+ buildah manifest create "$manifest" >/dev/null
+ buildah manifest add "$manifest" "docker://$image:$version-amd64" >/dev/null
+ buildah manifest add "$manifest" "docker://$image:$version-arm64" >/dev/null
+ buildah manifest push --all --format oci "$manifest" "docker://$image:$version" >/dev/null
+ if [[ "$version" != *-* ]]; then
+ buildah manifest push --all --format oci "$manifest" "docker://$image:latest" >/dev/null
+ fi
+ buildah manifest rm "$manifest" >/dev/null
+ echo "published multi-architecture index $image:$version"
+ if [[ "$version" != *-* ]]; then
+ echo "published multi-architecture index $image:latest"
+ fi
+}
+
+for component in agent probe; do
+ build_arch_image "$component" amd64 amd64
+ build_arch_image "$component" arm64 arm64
+ publish_index "$component"
+done
diff --git a/compose.yaml b/compose.yaml
index 93b92cc..1b83feb 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -1,6 +1,6 @@
services:
backend:
- image: registry.oeo.one/monitor/monitor-backend:${MONITOR_VERSION:-0.1.0}
+ image: registry.oeo.one/monitor/monitor-backend:${MONITOR_VERSION:-0.1.1}
container_name: monitor-backend
restart: unless-stopped
init: true
diff --git a/docs/deployment.md b/docs/deployment.md
index 22860d1..6800961 100644
--- a/docs/deployment.md
+++ b/docs/deployment.md
@@ -36,7 +36,21 @@ Compose 将容器端口只绑定到宿主机 `127.0.0.1:8080`,适合由同机
## 节点二进制发布
-Woodpecker 在 `vMAJOR.MINOR.PATCH` tag 上,由 Windows runner 使用 AotAnywhere 并行构建 Agent/Probe 的 Linux glibc、Linux musl、macOS 和 Windows 产物。Agent 包还包含一次性注册工具 `fleet-enroll`。Linux 归档带有安装脚本和 systemd/OpenWrt 单元;Windows 产物为 `.zip`,Linux/macOS 产物为 `.tar.gz`。所有平台产物和 Backend OCI 镜像成功后,CI 会创建对应的 Gitea Release,并附上压缩包和 SHA-256 校验文件。macOS 产物目前未签名/公证。安装说明见 [`../backend/agent/README.md`](../backend/agent/README.md) 与 [`../backend/probe/README.md`](../backend/probe/README.md)。
+Woodpecker 在 `vMAJOR.MINOR.PATCH` tag 上,由 Windows runner 使用 AotAnywhere 并行构建 Agent/Probe 的 Linux glibc、Linux musl、macOS 和 Windows 产物。Agent 包还包含一次性注册工具 `fleet-enroll`。Linux 归档带有安装脚本和 systemd/OpenWrt 单元;Windows 产物为 `.zip`,Linux/macOS 产物为 `.tar.gz`。Linux glibc 的 Agent 与 Probe 二进制还会打包为 OCI 镜像,支持 `linux/amd64` 和 `linux/arm64`;musl、macOS、Windows 仍通过对应平台归档分发。所有平台产物和 OCI 镜像发布成功后,CI 会创建对应的 Gitea Release,并附上压缩包和 SHA-256 校验文件。macOS 产物目前未签名/公证。安装说明见 [`../backend/agent/README.md`](../backend/agent/README.md) 与 [`../backend/probe/README.md`](../backend/probe/README.md)。
+
+Agent 和 Probe 镜像分别发布为 `registry.oeo.one/monitor/monitor-agent:` 与 `registry.oeo.one/monitor/monitor-probe:`,包含 amd64、arm64 多架构索引;也可指定 `:-amd64` 或 `:-arm64`。稳定版本同时更新 `latest`。镜像以非 root 用户(UID/GID 1654)运行,默认读取 `/etc/monitor/agent.json` 或 `/etc/monitor/probe.json`。运行容器时挂载对应配置目录;Probe 还会在该目录写入 `agents.json`,因此目录需可由 1654 用户写入。Agent、Probe 需使用宿主网络以保持节点端口和测量流量行为,并按需添加 `NET_RAW` 能力以执行 ICMP 测量:
+
+```sh
+docker run -d --name monitor-agent --restart unless-stopped --network host \
+ --cap-add NET_RAW -v "$PWD/agent-config:/etc/monitor" \
+ registry.oeo.one/monitor/monitor-agent:
+
+docker run -d --name monitor-probe --restart unless-stopped --network host \
+ --cap-add NET_RAW -v "$PWD/probe-config:/etc/monitor" \
+ registry.oeo.one/monitor/monitor-probe:
+```
+
+宿主机配置目录应允许 UID/GID 1654 读写;Probe 需要写权限来更新 `agents.json`。也可通过 Docker Compose 配置相同的网络、卷和能力。
实测体积(`StackTraceSupport=false`、`IlcFoldIdenticalMethodBodies=true`、`OptimizationPreference=Size`):
diff --git a/docs/project-layout.md b/docs/project-layout.md
index 6dfdf2c..b3a7c83 100644
--- a/docs/project-layout.md
+++ b/docs/project-layout.md
@@ -253,7 +253,7 @@ Backend 允许的包:ASP.NET Core 共享框架、`Microsoft.Data.Sqlite`、`Mi
true
true
true
- 0.1.0
+ 0.1.1
diff --git a/packaging/container/Agent.Containerfile b/packaging/container/Agent.Containerfile
new file mode 100644
index 0000000..7b0e07b
--- /dev/null
+++ b/packaging/container/Agent.Containerfile
@@ -0,0 +1,17 @@
+FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseled-extra
+
+ARG APP_VERSION=unknown
+LABEL org.opencontainers.image.title="Monitor Agent" \
+ org.opencontainers.image.description="MyMonitor node agent for Linux" \
+ org.opencontainers.image.licenses="Apache-2.0" \
+ org.opencontainers.image.version="$APP_VERSION"
+
+COPY --chown=1654:1654 app/monitor-agent /usr/local/bin/monitor-agent
+COPY --chown=1654:1654 app/fleet-enroll /usr/local/bin/fleet-enroll
+COPY --chown=1654:1654 config/ /etc/monitor/
+
+USER 1654:1654
+WORKDIR /etc/monitor
+VOLUME ["/etc/monitor"]
+ENTRYPOINT ["/usr/local/bin/monitor-agent"]
+CMD ["--config", "/etc/monitor/agent.json"]
diff --git a/packaging/container/Probe.Containerfile b/packaging/container/Probe.Containerfile
new file mode 100644
index 0000000..cfeb418
--- /dev/null
+++ b/packaging/container/Probe.Containerfile
@@ -0,0 +1,16 @@
+FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-noble-chiseled-extra
+
+ARG APP_VERSION=unknown
+LABEL org.opencontainers.image.title="Monitor Probe" \
+ org.opencontainers.image.description="MyMonitor network measurement probe for Linux" \
+ org.opencontainers.image.licenses="Apache-2.0" \
+ org.opencontainers.image.version="$APP_VERSION"
+
+COPY --chown=1654:1654 app/monitor-probe /usr/local/bin/monitor-probe
+COPY --chown=1654:1654 config/ /etc/monitor/
+
+USER 1654:1654
+WORKDIR /etc/monitor
+VOLUME ["/etc/monitor"]
+ENTRYPOINT ["/usr/local/bin/monitor-probe"]
+CMD ["--config", "/etc/monitor/probe.json"]