scripts: consolidate Agent deployment and remove Probe packages

Use Agent for native, container, systemd and OpenWrt installations and signed updates. Remove retired Probe deployment templates and smoke scripts.
This commit is contained in:
justme committed 2026-10-21 11:55:41 +08:00
1 parent ad5efc62ff
commit 016dbc2bbf
30 files changed
+96 -1206

No files matched your search

+1 -1
View File
@@ -14,7 +14,7 @@ services:
container_name: moshiro-agent
restart: unless-stopped
init: true
# Probe 要直连 Agent 的 7443(TCP+UDP)和 7444(TCP),用宿主网络,不做端口映射
# 其他 Agent 要直连此节点 的 7443(TCP+UDP)和 7444(TCP),用宿主网络,不做端口映射
network_mode: host
volumes:
- agent-config:/etc/moshiro
-50
View File
@@ -1,50 +0,0 @@
# MoShiro Probe。Docker 和 rootless/rootful Podman 共用。
#
# 用法(面板「添加 Probe」会给出带好参数的 .env 内容):
# mkdir moshiro-probe && cd moshiro-probe
# 把面板给的 .env 内容存成 .env,再下载本文件为 compose.yaml
# podman compose up -d # Docker 用 docker compose
# 更新:podman compose pull && podman compose up -d (身份和配置在命名卷里,不会动)
# 固定版本:在 .env 里写 MOSHIRO_VERSION=<版本号>;想要更严,写成 MOSHIRO_VERSION=<版本号>@sha256:<digest>。
# macOS、Windows 的 Docker Desktop 没有宿主网络:面板给的 .env 里已经写了 MOSHIRO_NETWORK_MODE=bridge。
# .env 里的 MOSHIRO_SERVER、MOSHIRO_JOIN(只在第一次启动用来注册,之后可以删掉)、MOSHIRO_GENESIS、MOSHIRO_CONTROLLERS、MOSHIRO_NAME 原样传进容器。
# 说明见 docs/ops/deployment.md。
services:
probe:
image: ${MOSHIRO_IMAGE:-registry.oeo.one/moshiro/moshiro-probe}:${MOSHIRO_VERSION:-latest}
container_name: moshiro-probe
restart: unless-stopped
init: true
network_mode: ${MOSHIRO_NETWORK_MODE:-host}
# .env 里的变量原样传进容器(不用带默认值的变量替换:有的 compose 提供者,比如 podman-compose,不替换环境变量里的默认值)
env_file:
- .env
environment:
HOST_PROC: /host/proc
HOST_SYS: /host/sys
HOST_ROOT: /host/root
volumes:
- probe-config:/etc/moshiro
- probe-status:/etc/moshiro/update-status:ro
- /etc/os-release:/etc/os-release:ro
# 主机资源占用(CPU、内存、磁盘、网卡流量、运行时长,只有汇总数字,没有型号和 IP):只读挂载宿主机的 /proc、/sys 和根目录;
# 不想上报:在 probe.json 里写 reporting.host_stats=false,再删掉下面三行挂载。Docker Desktop 上看到的是它的虚拟机,不是 Windows 或 macOS 本身。
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/host/root:ro
read_only: true
tmpfs:
- /tmp:rw,noexec,nosuid,size=64m,mode=1777
security_opt:
- label=disable
- no-new-privileges:true
cap_drop:
- ALL
# Linux ICMP 使用 ping sockets;ping_group_range 需允许容器映射后的 GID。
stop_grace_period: 20s
volumes:
probe-config:
name: moshiro-probe-config
probe-status:
name: moshiro-probe-status
+1 -3
View File
@@ -2,7 +2,7 @@ FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
ARG APP_VERSION=unknown
LABEL org.opencontainers.image.title="MoShiro Agent" \
org.opencontainers.image.description="MoShiro node agent for Linux" \
org.opencontainers.image.description="MoShiro node for Linux: listens and dials (MOSHIRO_MODE=agent) or only dials (MOSHIRO_MODE=dial)" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.version="$APP_VERSION"
@@ -12,10 +12,8 @@ COPY --chown=1654:1654 app/fleet-enroll /usr/local/bin/fleet-enroll
COPY --chown=1654:1654 config/ /etc/moshiro/
COPY --chown=1654:1654 --chmod=0755 entrypoint.sh /usr/local/bin/moshiro-entrypoint
ENV MOSHIRO_ROLE=agent
ENV LD_LIBRARY_PATH=/usr/local/bin
USER 1654:1654
WORKDIR /etc/moshiro
VOLUME ["/etc/moshiro"]
ENTRYPOINT ["/usr/local/bin/moshiro-entrypoint"]
CMD ["--config", "/etc/moshiro/agent.json"]
@@ -52,7 +52,6 @@ COPY --chown=1654:1654 --from=build /out/enroll/fleet-enroll /usr/local/bin/flee
COPY --chown=1654:1654 config/ /etc/moshiro/
COPY --chown=1654:1654 --chmod=0755 entrypoint.sh /usr/local/bin/moshiro-entrypoint
ENV MOSHIRO_ROLE=agent
USER 1654:1654
WORKDIR /etc/moshiro
VOLUME ["/etc/moshiro"]
-21
View File
@@ -1,21 +0,0 @@
FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
ARG APP_VERSION=unknown
LABEL org.opencontainers.image.title="MoShiro Probe" \
org.opencontainers.image.description="MoShiro network measurement probe for Linux" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.version="$APP_VERSION"
COPY --chown=1654:1654 app/moshiro-probe /usr/local/bin/moshiro-probe
COPY --chown=1654:1654 app/lib*.so.* /usr/local/bin/
COPY --chown=1654:1654 app/fleet-enroll /usr/local/bin/fleet-enroll
COPY --chown=1654:1654 config/ /etc/moshiro/
COPY --chown=1654:1654 --chmod=0755 entrypoint.sh /usr/local/bin/moshiro-entrypoint
ENV MOSHIRO_ROLE=probe
ENV LD_LIBRARY_PATH=/usr/local/bin
USER 1654:1654
WORKDIR /etc/moshiro
VOLUME ["/etc/moshiro"]
ENTRYPOINT ["/usr/local/bin/moshiro-entrypoint"]
CMD ["--config", "/etc/moshiro/probe.json"]
@@ -1,60 +0,0 @@
# AotAnywhere cross-links both Alpine architectures on the native amd64 runner.
ARG NATIVEAOT_BUILDER_IMAGE=localhost/moshiro-dotnet10-nativeaot-aotanywhere:ci
FROM --platform=linux/amd64 ${NATIVEAOT_BUILDER_IMAGE} AS build
WORKDIR /src/backend
COPY restore/backend/ ./
ARG NODE_RID=linux-musl-x64
ENV NUGET_PACKAGES=/root/.nuget/packages
RUN dotnet restore probe/src/MoShiro.Probe -r "$NODE_RID" -p:PublishAot=true -p:UseAotAnywhere=true \
-p:StaticMsQuic=true -p:StaticExecutable=true -p:RuntimeFrameworkVersion=10.0.12 \
-p:InvariantGlobalization=true --artifacts-path /artifacts \
&& dotnet restore fleet/tools/fleet-enroll -r "$NODE_RID" -p:PublishAot=true -p:UseAotAnywhere=true \
-p:StaticMsQuic=true -p:StaticExecutable=true -p:RuntimeFrameworkVersion=10.0.12 -p:InvariantGlobalization=true \
--artifacts-path /artifacts
COPY source/backend/ ./
COPY source/packaging/native/msquic/ /src/packaging/native/msquic/
# 构建信息:CI 通过 --build-arg 传入;不传就是空(放在 restore 之后,每次构建都变的时间不会让依赖还原的缓存失效)
ARG MOSHIRO_TAG=
ARG MOSHIRO_COMMIT=
ARG MOSHIRO_BUILT_AT=
ENV MOSHIRO_TAG=$MOSHIRO_TAG MOSHIRO_COMMIT=$MOSHIRO_COMMIT MOSHIRO_BUILT_AT=$MOSHIRO_BUILT_AT
RUN dotnet publish probe/src/MoShiro.Probe -m:3 -c Release -r "$NODE_RID" \
--self-contained true --no-restore -p:PublishAot=true -p:UseAotAnywhere=true \
-p:StaticMsQuic=true -p:StaticExecutable=true -p:RuntimeFrameworkVersion=10.0.12 \
-p:InvariantGlobalization=true -p:UseAppHost=true -p:StripSymbols=true \
-p:DebugType=none -p:DebugSymbols=false --artifacts-path /artifacts -o /out/probe \
&& dotnet publish fleet/tools/fleet-enroll -m:3 -c Release -r "$NODE_RID" \
--self-contained true --no-restore -p:PublishAot=true -p:UseAotAnywhere=true \
-p:StaticMsQuic=true -p:StaticExecutable=true -p:RuntimeFrameworkVersion=10.0.12 -p:InvariantGlobalization=true \
-p:UseAppHost=true -p:StripSymbols=true \
-p:DebugType=none -p:DebugSymbols=false --artifacts-path /artifacts -o /out/enroll \
&& rm -rf /artifacts
RUN test -x /out/probe/moshiro-probe && test -x /out/enroll/fleet-enroll \
&& test "$(stat -c%s /out/probe/moshiro-probe)" -gt 2000000 \
&& test "$(stat -c%s /out/enroll/fleet-enroll)" -gt 2000000 \
&& ! readelf -l /out/probe/moshiro-probe | grep -q INTERP \
&& ! readelf -d /out/probe/moshiro-probe 2>&1 | grep -q NEEDED \
&& ! readelf -l /out/enroll/fleet-enroll | grep -q INTERP \
&& ! readelf -d /out/enroll/fleet-enroll 2>&1 | grep -q NEEDED
FROM mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine
ARG APP_VERSION=main
LABEL org.opencontainers.image.title="MoShiro Probe" \
org.opencontainers.image.description="MoShiro network measurement probe for Linux" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.version="$APP_VERSION"
COPY --chown=1654:1654 --from=build /out/probe/moshiro-probe /usr/local/bin/moshiro-probe
COPY --chown=1654:1654 --from=build /out/enroll/fleet-enroll /usr/local/bin/fleet-enroll
COPY --chown=1654:1654 config/ /etc/moshiro/
COPY --chown=1654:1654 --chmod=0755 entrypoint.sh /usr/local/bin/moshiro-entrypoint
ENV MOSHIRO_ROLE=probe
USER 1654:1654
WORKDIR /etc/moshiro
VOLUME ["/etc/moshiro"]
ENTRYPOINT ["/usr/local/bin/moshiro-entrypoint"]
CMD ["--config", "/etc/moshiro/probe.json"]
+5 -18
View File
@@ -1,24 +1,22 @@
#!/bin/sh
# 部署或更新容器服务(Agent、Probe;Backend 用 Quadlet,见 packaging/quadlet/moshiro-backend.container)。可以重复运行:每次都会拉取镜像、把配置迁移到当前版本需要的样子、
# 部署或更新容器服务(Agent;Backend 用 Quadlet,见 packaging/quadlet/moshiro-backend.container)。可以重复运行:每次都会拉取镜像、把配置迁移到当前版本需要的样子、
# 重新安装单元和更新器,然后重启节点;已有的身份和配置不会被覆盖。
#
# 首次部署 Agent:sudo sh deploy-node.sh agent --image-repo REPO --server https://panel --join jt_xxx --name hk-1
# 首次部署 Probe:sudo sh deploy-node.sh probe --image-repo REPO --server https://panel --join jt_xxx --name home-1
# 更新(配置迁移、单元和更新器、镜像):sudo sh deploy-node.sh agent (其余参数沿用上次的更新器配置)
# 旧版 Probe(probe.json 里没有 fleet 段):加上 --server --join --name 重新运行即可,旧文件备份为 probe.json.old
#
# 需要 root、systemd,以及 Podman 或 Docker;脚本要和 packaging/ 里的 systemd/、update/ 放在一起。
# rootless Podman 见 docs/ops/deployment.md。
set -eu
usage() {
echo "usage: deploy-node.sh <agent|probe> [--engine podman|docker] [--image-repo R] [--channel dev|release] [--tag T] [--image REF]" >&2
echo "usage: deploy-node.sh <agent> [--engine podman|docker] [--image-repo R] [--channel dev|release] [--tag T] [--image REF]" >&2
echo " [--server URL --join TOKEN --name NAME] [--policy auto|manual] [--authfile F]" >&2
exit 2
}
ROLE="${1:-}"; shift || true
case "$ROLE" in agent|probe) ;; *) usage ;; esac
case "$ROLE" in agent) ;; *) usage ;; esac
ENGINE=""; REPO=""; TAG=""; IMAGE=""; SERVER=""; JOIN=""; NAME=""; POLICY=""; AUTHFILE=""; CHANNEL=""
while [ $# -gt 0 ]; do
case "$1" in
@@ -121,7 +119,7 @@ in_volume() {
}
in_volume 'mkdir -p /etc/moshiro/update' </dev/null
# 3. 配置:身份由节点自己生成(Agent 和 Probe 一样),注册之后面板只认这把公钥
# 3. 配置:身份由节点自己生成(所有 Agent 一样),注册之后面板只认这把公钥
ID_DIR="/etc/moshiro/$ROLE-id"
if ! in_volume "test -f $ID_DIR/node.json" </dev/null; then
[ -n "$SERVER" ] && [ -n "$JOIN" ] && [ -n "$NAME" ] || { echo "first deployment needs --server, --join and --name" >&2; exit 1; }
@@ -139,18 +137,7 @@ if [ "$ROLE" = agent ]; then
in_volume 'sed -i "s/\"bind\" *: *\"0\.0\.0\.0\"/\"bind\": \"::\"/" /etc/moshiro/agent.json' </dev/null
echo "config: listen.bind 0.0.0.0 -> ::"
fi
elif [ "$ROLE" = probe ]; then
# 旧格式(面板生成的 probe.json:没有 fleet 段)备份之后按新格式重写
if in_volume 'test -f /etc/moshiro/probe.json' </dev/null && ! in_volume 'grep -q "\"fleet\"" /etc/moshiro/probe.json' </dev/null; then
in_volume 'mv /etc/moshiro/probe.json /etc/moshiro/probe.json.old' </dev/null
echo "config: old probe.json moved to probe.json.old"
fi
if ! in_volume 'test -f /etc/moshiro/probe.json' </dev/null; then
[ -n "$SERVER" ] || { echo "probe.json is missing; pass --server to create it" >&2; exit 1; }
printf '{"fleet":{"server":"%s","identity_dir":"/etc/moshiro/probe-id"}}\n' "$SERVER" \
| in_volume 'umask 077; cat > /etc/moshiro/probe.json'
echo "config: created probe.json"
fi
fi
# 4. 单元和更新器(保持原来的更新策略)
+5 -8
View File
@@ -1,9 +1,10 @@
#!/bin/sh
# 容器入口(Agent 和 Probe 共用):第一次启动用 MOSHIRO_SERVER / MOSHIRO_JOIN(MOSHIRO_NAME 不写就用主机名)自己注册并写配置,
# 容器入口(监听和拨号两种模式共用,镜像里只有 moshiro-agent):第一次启动用 MOSHIRO_SERVER / MOSHIRO_JOIN(MOSHIRO_NAME 不写就用主机名)自己注册并写配置,
# 身份和配置都在命名卷 /etc/moshiro 里,之后重启(或换新镜像重建容器)直接启动,不需要再给参数。
set -eu
role="${MOSHIRO_ROLE:?MOSHIRO_ROLE is set by the image}"
# 所有节点默认同时监听和拨号,身份与配置统一保存。
role=agent
# Quadlet 单元和 compose 默认用宿主网络;Docker Desktop(没有宿主网络)的 .env 里写 MOSHIRO_NETWORK_SCOPE=container
export MOSHIRO_NETWORK_SCOPE="${MOSHIRO_NETWORK_SCOPE:-host}"
dir=/etc/moshiro
@@ -39,11 +40,7 @@ if [ ! -f "$config" ]; then
echo "$config is missing: set MOSHIRO_SERVER to create it" >&2
exit 1
fi
if [ "$role" = agent ]; then
printf '{"fleet":{"server":"%s","identity_dir":"%s"},"listen":{"bind":"::"}}\n' "$MOSHIRO_SERVER" "$identity" > "$config"
else
printf '{"fleet":{"server":"%s","identity_dir":"%s"}}\n' "$MOSHIRO_SERVER" "$identity" > "$config"
fi
printf '{"fleet":{"server":"%s","identity_dir":"%s"},"listen":{"bind":"::"}}\n' "$MOSHIRO_SERVER" "$identity" > "$config"
fi
exec "/usr/local/bin/moshiro-$role" "$@"
exec /usr/local/bin/moshiro-agent --config "$config" "$@"
+12 -17
View File
@@ -1,10 +1,9 @@
#!/bin/sh
# Installs moshiro-agent or moshiro-probe from an extracted release directory (the directory this script lives in).
# Installs moshiro-agent or moshiro-agent from an extracted release directory (the directory this script lives in).
# POSIX sh: runs on Debian/Ubuntu/Alpine/OpenWrt (busybox).
#
# ./install.sh agent --server https://panel.example.com --join <join-token> --name hk-1
# ./install.sh probe --server https://panel --join jt_xxx --name home-1
# ./install.sh uninstall agent|probe [--purge]
# ./install.sh uninstall agent [--purge]
#
# Binaries go to /usr/lib/moshiro (root-owned: the collector cannot rewrite its own executable);
# config and identity go to /etc/moshiro (owned by the unprivileged `moshiro` user).
@@ -80,7 +79,7 @@ write_config() {
}
JSON
else
cat >"$CONFIG/probe.json" <<JSON
cat >"$CONFIG/agent.json" <<JSON
{
"fleet": { "server": "$2", "identity_dir": "$CONFIG" }
}
@@ -122,8 +121,7 @@ install_role() {
if [ "$have_user" -eq 0 ]; then sed -i '/^User=/d;/^Group=/d' "/etc/systemd/system/moshiro-$role.service"; fi
systemctl daemon-reload ;;
procd)
[ "$role" = probe ] || die "the agent is meant for servers; on OpenWrt only the probe is supported"
put 0755 "$HERE/openwrt/moshiro-probe.init" /etc/init.d/moshiro-probe
put 0755 "$HERE/openwrt/moshiro-agent.init" /etc/init.d/moshiro-agent
mkdir -p /lib/upgrade/keep.d
put 0644 "$HERE/openwrt/moshiro.keep" /lib/upgrade/keep.d/moshiro ;;
*) echo "warning: no systemd or procd found: start with LD_LIBRARY_PATH=$PREFIX $PREFIX/moshiro-$role --config $CONFIG/$role.json under your own supervisor" >&2 ;;
@@ -145,14 +143,14 @@ install_role() {
if [ "$ready" -eq 1 ] && [ "$no_start" -eq 0 ]; then
case "$init" in
systemd) systemctl enable --now "moshiro-$role" ;;
procd) /etc/init.d/moshiro-probe enable; /etc/init.d/moshiro-probe restart ;;
procd) /etc/init.d/moshiro-agent enable; /etc/init.d/moshiro-agent restart ;;
esac
say "moshiro-$role is installed"
else
say "installed, not started yet. Next:"
echo " $PREFIX/fleet-enroll --server <panel-url> --join <token> --name <name> --identity-dir $CONFIG (as the '$USER_NAME' user)"
echo " write $CONFIG/$role.json (see README.md), then: systemctl enable --now moshiro-$role"
[ "$role" = agent ] || echo " (OpenWrt: /etc/init.d/moshiro-probe enable && /etc/init.d/moshiro-probe start)"
echo " (OpenWrt: /etc/init.d/moshiro-agent enable && /etc/init.d/moshiro-agent start)"
fi
}
@@ -165,15 +163,13 @@ uninstall() {
rm -f "/etc/systemd/system/moshiro-$role.service"
systemctl daemon-reload ;;
procd)
/etc/init.d/moshiro-probe stop 2>/dev/null || true
/etc/init.d/moshiro-probe disable 2>/dev/null || true
rm -f /etc/init.d/moshiro-probe /lib/upgrade/keep.d/moshiro ;;
/etc/init.d/moshiro-agent stop 2>/dev/null || true
/etc/init.d/moshiro-agent disable 2>/dev/null || true
rm -f /etc/init.d/moshiro-agent /lib/upgrade/keep.d/moshiro ;;
esac
rm -f "$PREFIX/moshiro-$role"
rm -f "$PREFIX/fleet-enroll"
other_role=agent
[ "$role" = agent ] && other_role=probe
[ -x "$PREFIX/moshiro-$other_role" ] || rm -f "$PREFIX"/lib*.so.*
rm -f "$PREFIX"/lib*.so.*
rmdir "$PREFIX" 2>/dev/null || true
if [ "$purge" -eq 1 ]; then
rm -rf "$CONFIG"
@@ -185,12 +181,11 @@ uninstall() {
cmd="${1:-}"
case "$cmd" in
agent|probe) shift; install_role "$cmd" "$@" ;;
agent) shift; install_role "$cmd" "$@" ;;
uninstall)
shift
role="${1:-}"
[ "$role" = agent ] || [ "$role" = probe ] || die "usage: install.sh uninstall agent|probe [--purge]"
shift
uninstall "$role" "$@" ;;
*) die "usage: install.sh agent|probe [options] | uninstall agent|probe [--purge]" ;;
*) die "usage: install.sh agent [options] | uninstall agent [--purge]" ;;
esac
+8 -8
View File
@@ -1,6 +1,6 @@
# MoShiro 节点一键安装(Windows,Agent 和 Probe 共用同一份,**静态**文件:不替换任何值,所以它的 SHA-256 在一个版本内固定不变)。
# MoShiro 节点一键安装(Windows,监听和拨号两种模式共用同一份,**静态**文件:不替换任何值,所以它的 SHA-256 在一个版本内固定不变)。
# 面板给的命令先下载这份文件、核对 SHA-256(CI 构建时算出,写进签名的发布清单,也公布在发布页),一致才执行。
# 必需的环境变量:MOSHIRO_SERVER(面板地址)、MOSHIRO_ROLE(agent 或 probe)、MOSHIRO_CODE(一次性的加入码)。
# 必需的环境变量:MOSHIRO_SERVER(面板地址)、MOSHIRO_ROLE(默认 agent,监听并拨号)、MOSHIRO_CODE(一次性的加入码)。
# 做的事:用 Docker Desktop 起一个容器,身份和配置存在命名卷里(重启、换新镜像重建都不用再给参数),节点名字默认取计算机名。
# 可选环境变量:MOSHIRO_NAME=节点名字(名字被占用时用)、MOSHIRO_GENESIS / MOSHIRO_CONTROLLERS(命令里带着:信任根指纹和这个节点的属主 passkey 凭证 id,都是公开值)、
# MOSHIRO_ALLOW_UNVERIFIED=1 加 MOSHIRO_IMAGE=镜像(面板还没有已签名的发布清单时,不验签、按这个镜像安装;默认拒绝)。
@@ -31,7 +31,7 @@ function Test-MoShiroSignature([byte[]]$data, [byte[]]$derSignature, [string]$pe
function Install-MoShiro {
$ErrorActionPreference = 'Stop'
$role = $env:MOSHIRO_ROLE
$role = if ($env:MOSHIRO_ROLE) { $env:MOSHIRO_ROLE } else { 'agent' }
$server = $env:MOSHIRO_SERVER
$code = $env:MOSHIRO_CODE
$image = $env:MOSHIRO_IMAGE
@@ -43,7 +43,7 @@ YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
'@
# 这些值来自命令行环境:只允许正常的字符,因为它们会进到 URL 和容器参数里
if ($role -notin @('agent', 'probe')) { Write-Error 'MOSHIRO_ROLE must be agent or probe'; return }
if ($role -notin @('agent')) { Write-Error 'MOSHIRO_ROLE must be agent'; return }
if ($server -notmatch '^https?://[A-Za-z0-9._:/@~%+\[\]-]+$') { Write-Error 'MOSHIRO_SERVER must be the panel address (http:// or https://)'; return }
if ($code -notmatch '^[A-Za-z0-9._~%+-]+$') { Write-Error 'MOSHIRO_CODE is required (copy the whole command from the panel)'; return }
@@ -83,7 +83,7 @@ YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
}
if ($null -eq $manifest) {
if ($env:MOSHIRO_ALLOW_UNVERIFIED -ne '1' -or -not $image) {
Write-Error "the panel has no signed release for $role yet, so the image cannot be verified. To install anyway set `$env:MOSHIRO_ALLOW_UNVERIFIED='1' and `$env:MOSHIRO_IMAGE"
Write-Error "the panel has no signed release yet, so the image cannot be verified. To install anyway set `$env:MOSHIRO_ALLOW_UNVERIFIED='1' and `$env:MOSHIRO_IMAGE"
return
}
Write-Host "warning: installing $image without signature verification"
@@ -93,7 +93,7 @@ YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
return
}
$release = [Text.Encoding]::UTF8.GetString($manifest) | ConvertFrom-Json
if ($release.component -ne $role) { Write-Error "the signed release is for '$($release.component)', not '$role'"; return }
if ($release.component -ne 'agent') { Write-Error "the signed release is for '$($release.component)', not 'agent'"; return }
if ($release.digest -notmatch '^sha256:[0-9a-f]{64}$') { Write-Error 'the signed release has a malformed digest'; return }
if ($release.image -notmatch '^[A-Za-z0-9._/:-]+$') { Write-Error 'the signed release has a malformed image name'; return }
$image = "$($release.image)@$($release.digest)"
@@ -110,9 +110,9 @@ YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
$arguments = @('run', '-d', '--name', $container, '--restart', 'unless-stopped',
'--read-only', '--tmpfs', '/tmp:rw,noexec,nosuid,size=64m', '--cap-drop', 'all', '--security-opt', 'no-new-privileges',
'-v', "${volume}:/etc/moshiro")
# Docker Desktop 没有宿主网络:Agent 把端口发布出来(面板里的地址需要指向这台机器的公网地址)
# Docker Desktop 没有宿主网络:监听的节点把端口发布出来(面板里的地址需要指向这台机器的公网地址)
if ($role -eq 'agent') { $arguments += @('-p', '7443:7443/tcp', '-p', '7443:7443/udp', '-p', '7444:7444/tcp') }
$arguments += @('-e', "MOSHIRO_SERVER=$server", '-e', "MOSHIRO_JOIN=$code", '-e', "MOSHIRO_NAME=$name")
$arguments += @('-e', "MOSHIRO_SERVER=$server", '-e', "MOSHIRO_JOIN=$code", '-e', "MOSHIRO_NAME=$name", '-e', "MOSHIRO_MODE=$role")
# 每个节点都必须有属主(passkey):命令里的信任根指纹和属主缺一个就不装(面板的命令总是带着;只有没有信任根的老面板才需要 MOSHIRO_ALLOW_NO_OWNER=1)
if ((-not $env:MOSHIRO_GENESIS -or -not $env:MOSHIRO_CONTROLLERS) -and $env:MOSHIRO_ALLOW_NO_OWNER -ne '1') {
Write-Error 'this node has no owner: MOSHIRO_GENESIS and MOSHIRO_CONTROLLERS are required (copy the complete command from the panel; set up the trust root first)'
+12 -14
View File
@@ -1,8 +1,8 @@
#!/bin/sh
# MoShiro 节点一键安装(Agent 和 Probe 共用同一份,**静态**文件:不替换任何值,所以它的 SHA-256 在一个版本内固定不变)。
# MoShiro 节点一键安装(监听和拨号两种模式共用同一份,**静态**文件:不替换任何值,所以它的 SHA-256 在一个版本内固定不变)。
# 面板给的命令先下载这份文件、核对 SHA-256(CI 构建时算出,写进签名的发布清单,也公布在发布页),一致才执行:
# f=$(mktemp) && curl -fsSL 'https://panel/install' -o "$f" && echo "<sha256> $f" | sha256sum -c - && MOSHIRO_SERVER=… MOSHIRO_ROLE=… MOSHIRO_CODE=… sh "$f"
# 必需的环境变量:MOSHIRO_SERVER(面板地址)、MOSHIRO_ROLE(agent 或 probe)、MOSHIRO_CODE(一次性的加入码)。
# 必需的环境变量:MOSHIRO_SERVER(面板地址)、MOSHIRO_ROLE(默认 agent,监听并拨号)、MOSHIRO_CODE(一次性的加入码)。
# 做的事:用 Docker 或 Podman 起一个容器,身份和配置存在命名卷里(重启、换新镜像重建都不用再给参数),节点名字默认取主机名。
# 可选环境变量:MOSHIRO_NAME=节点名字(名字被占用时用)、MOSHIRO_ENGINE=docker|podman、MOSHIRO_DRY_RUN=1(只打印命令)、
# MOSHIRO_GENESIS / MOSHIRO_CONTROLLERS(命令里带着:你的信任根指纹和这个节点的属主 passkey 凭证 id,节点在注册时验证面板的操作员集合并把它们固定在本地;都是公开值)、
@@ -11,7 +11,7 @@
set -eu
SERVER="${MOSHIRO_SERVER:-}"
ROLE="${MOSHIRO_ROLE:-}"
ROLE="${MOSHIRO_ROLE:-agent}"
CODE="${MOSHIRO_CODE:-}"
IMAGE="${MOSHIRO_IMAGE:-}"
RELEASE_KEY='-----BEGIN PUBLIC KEY-----
@@ -23,7 +23,7 @@ say() { printf '%s\n' "$*"; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
# 这些值来自命令行环境:只允许正常的字符,因为它们会进到 URL 和容器参数里
case "$ROLE" in agent|probe) ;; *) die "MOSHIRO_ROLE must be agent or probe" ;; esac
case "$ROLE" in agent) ;; *) die "MOSHIRO_ROLE must be agent" ;; esac
case "$SERVER" in http://*|https://*) ;; *) die "MOSHIRO_SERVER must be the panel address (http:// or https://)" ;; esac
case "$SERVER$CODE" in *[!A-Za-z0-9._:/@~%+\[\]-]*|'') die "MOSHIRO_SERVER or MOSHIRO_CODE has unexpected characters" ;; esac
[ -n "$CODE" ] || die "MOSHIRO_CODE is required (copy the whole command from the panel)"
@@ -70,7 +70,7 @@ resolve_image() {
curl -fsS "$SERVER/install/manifest?code=$CODE" -o "$tmp/manifest" 2>/dev/null </dev/null || have_manifest=0
curl -fsS "$SERVER/install/manifest.sig?code=$CODE" -o "$tmp/sig.b64" 2>/dev/null </dev/null || have_manifest=0
if [ "$have_manifest" -eq 0 ]; then
[ "${MOSHIRO_ALLOW_UNVERIFIED:-}" = 1 ] && [ -n "$IMAGE" ] || die "the panel has no signed release for $ROLE yet, so the image cannot be verified. To install anyway set MOSHIRO_ALLOW_UNVERIFIED=1 and MOSHIRO_IMAGE=<image>"
[ "${MOSHIRO_ALLOW_UNVERIFIED:-}" = 1 ] && [ -n "$IMAGE" ] || die "the panel has no signed release yet, so the image cannot be verified. To install anyway set MOSHIRO_ALLOW_UNVERIFIED=1 and MOSHIRO_IMAGE=<image>"
say "warning: installing $IMAGE without signature verification"
return 0
fi
@@ -80,7 +80,7 @@ resolve_image() {
openssl dgst -sha256 -verify "$tmp/key.pem" -signature "$tmp/sig.bin" "$tmp/manifest" >/dev/null 2>&1 \
|| die "the release signature does not verify against the built-in release key; refusing to install"
field() { sed -n "s/.*\"$1\":\"\([^\"]*\)\".*/\1/p" "$tmp/manifest" | head -n 1; }
[ "$(field component)" = "$ROLE" ] || die "the signed release is for '$(field component)', not '$ROLE'"
[ "$(field component)" = agent ] || die "the signed release is for '$(field component)', not 'agent'"
repo="$(field image)"
digest="$(field digest)"
case "$digest" in sha256:????????????????????????????????????????????????????????????????) ;; *) die "the signed release has a malformed digest" ;; esac
@@ -97,18 +97,16 @@ set -- run -d --name "$container" --restart "$restart" \
-v "$volume:/etc/moshiro"
[ "$engine" = podman ] && set -- "$@" --security-opt label=disable
if [ "$linux" -eq 1 ]; then
# 宿主网络:Agent 对外暴露端口,也靠它识别本机的公网 IPv4 和 IPv6 地址
# 宿主网络:监听的节点对外暴露端口,两种模式都靠它识别本机的公网 IPv4 和 IPv6 地址
set -- "$@" --network host
if [ "$ROLE" = agent ]; then
# 主机资源占用(只读),管理端的 Agent 详情页显示 CPU、内存、磁盘、网络
set -- "$@" -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro \
-e MOSHIRO_NETWORK_SCOPE=host -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root
fi
# 主机资源占用(只读),管理端的节点详情页显示 CPU、内存、磁盘、网络
set -- "$@" -v /etc/os-release:/etc/os-release:ro -v /proc:/host/proc:ro -v /sys:/host/sys:ro -v /:/host/root:ro \
-e MOSHIRO_NETWORK_SCOPE=host -e HOST_PROC=/host/proc -e HOST_SYS=/host/sys -e HOST_ROOT=/host/root
elif [ "$ROLE" = agent ]; then
# macOS 上的 Docker 没有宿主网络:把 Agent 的端口发布出来(面板里的地址需要指向这台机器的公网地址)
# macOS 上的 Docker 没有宿主网络:把监听节点的端口发布出来(面板里的地址需要指向这台机器的公网地址)
set -- "$@" -p 7443:7443/tcp -p 7443:7443/udp -p 7444:7444/tcp
fi
set -- "$@" -e "MOSHIRO_SERVER=$SERVER" -e "MOSHIRO_JOIN=$CODE" -e "MOSHIRO_NAME=$name"
set -- "$@" -e "MOSHIRO_SERVER=$SERVER" -e "MOSHIRO_JOIN=$CODE" -e "MOSHIRO_NAME=$name" -e "MOSHIRO_MODE=$ROLE"
# 每个节点都必须有属主(passkey):命令里的信任根指纹和属主缺一个就不装(面板的命令总是带着;只有没有信任根的老面板才需要 MOSHIRO_ALLOW_NO_OWNER=1)
if [ -z "${MOSHIRO_GENESIS:-}" ] || [ -z "${MOSHIRO_CONTROLLERS:-}" ]; then
[ "${MOSHIRO_ALLOW_NO_OWNER:-}" = 1 ] || die "this node has no owner: MOSHIRO_GENESIS and MOSHIRO_CONTROLLERS are required (copy the complete command from the panel; set up the trust root first)"
+17 -17
View File
@@ -1,5 +1,5 @@
#!/bin/sh
# Native OpenWrt Probe installer/updater. No container engine or .NET runtime.
# Native OpenWrt installer/updater for an Agent. No container engine or .NET runtime.
# Bootstrap: verify this script's published hash before running `sh install.sh install`.
# Trust/configuration live outside the collector's writable identity directory.
set -eu
@@ -9,7 +9,7 @@ ROOT=${MOSHIRO_NATIVE_ROOT:-/usr/lib/moshiro-native}
CONF=${MOSHIRO_NATIVE_CONF:-/etc/moshiro-native.conf}
STATE=${MOSHIRO_NATIVE_STATE:-/etc/moshiro-native-state}
STATUS=${MOSHIRO_NATIVE_STATUS:-/var/run/moshiro-update}
RUNTIME=${MOSHIRO_NATIVE_RUNTIME:-/var/run/moshiro-probe}
RUNTIME=${MOSHIRO_NATIVE_RUNTIME:-/var/run/moshiro-agent}
ACTION=${1:-install}
FEED=https://git.oeo.one/api/packages/justme/generic/moshiro-openwrt
CHANNEL=${MOSHIRO_CHANNEL:-release}
@@ -26,7 +26,7 @@ die() { echo "moshiro-openwrt: $*" >&2; exit 1; }
log() { echo "moshiro-openwrt: $*"; }
ctl() {
if [ "${MOSHIRO_NATIVE_TEST:-}" = 1 ]; then "$MOSHIRO_NATIVE_SERVICE" "$@"
else /etc/init.d/moshiro-probe "$@"; fi
else /etc/init.d/moshiro-agent "$@"; fi
}
field() { jsonfilter -i "$WORK/manifest.json" -e "@.$1"; }
download() {
@@ -121,7 +121,7 @@ verify() {
download "$FEED/$CHANNEL/manifest-$RID.sig" 1024 "$WORK/manifest.sig" || return 1
base64 -d "$WORK/manifest.sig" > "$WORK/signature" 2>/dev/null || return 1
openssl dgst -sha256 -verify "$STATE/release-key.pem" -signature "$WORK/signature" "$WORK/manifest.json" >/dev/null 2>&1 || return 1
[ "$(field schema)" = 1 ] && [ "$(field component)" = probe ] \
[ "$(field schema)" = 1 ] && [ "$(field component)" = agent ] \
&& [ "$(field channel)" = "$CHANNEL" ] && [ "$(field rid)" = "$RID" ] || return 1
HASH=$(field sha256); SIZE=$(field size); SEQ=$(field seq); VERSION=$(field version); PACKAGE=$(field package)
case "$HASH" in ''|*[!0-9a-f]*) return 1 ;; esac
@@ -130,7 +130,7 @@ verify() {
[ -n "$SEQ" ] && [ "${#SEQ}" -le 15 ] && [ -n "$SIZE" ] && [ "${#SIZE}" -le 9 ] && [ "$SIZE" -gt 0 ] && [ "$SIZE" -le "$MAX_BYTES" ] || return 1
case "$VERSION" in ''|*[!A-Za-z0-9.+_-]*) return 1 ;; esac
[ "${#VERSION}" -le 100 ] && printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[A-Za-z0-9.-]+)?(\+[A-Za-z0-9.-]+)?$' || return 1
[ "$PACKAGE" = "probe-$RID-$HASH.tar.gz" ] || return 1
[ "$PACKAGE" = "agent-$RID-$HASH.tar.gz" ] || return 1
}
switch_to() {
@@ -168,7 +168,7 @@ stage() {
[ "$(sort -u "$WORK/members" | wc -l)" -eq "$(wc -l < "$WORK/members")" ] || return 1
while IFS= read -r member; do
case "$member" in
moshiro-probe|fleet-enroll|openwrt.sh|probe.init|updater.init) ;;
moshiro-agent|fleet-enroll|openwrt.sh|agent.init|updater.init) ;;
lib*.so.*) case "$member" in *[!A-Za-z0-9._+-]*) return 1 ;; esac ;;
*) return 1 ;;
esac
@@ -185,7 +185,7 @@ stage() {
if ! tar -xzf "$WORK/package.tar.gz" -C "$STAGED"; then
return 1
fi
for required in moshiro-probe fleet-enroll openwrt.sh probe.init updater.init; do
for required in moshiro-agent fleet-enroll openwrt.sh agent.init updater.init; do
[ -s "$STAGED/$required" ] || return 1
chmod 755 "$STAGED/$required" || return 1
done
@@ -206,7 +206,7 @@ healthy() {
version=$(jsonfilter -i "$health" -e '@.version' 2>/dev/null || true)
pid=$(jsonfilter -i "$health" -e '@.pid' 2>/dev/null || true)
at=$(jsonfilter -i "$health" -e '@.at' 2>/dev/null || echo 0)
active=$(ubus call service list '{"name":"moshiro-probe"}' 2>/dev/null | jsonfilter -e '@["moshiro-probe"].instances.*.pid' 2>/dev/null || true)
active=$(ubus call service list '{"name":"moshiro-agent"}' 2>/dev/null | jsonfilter -e '@["moshiro-agent"].instances.*.pid' 2>/dev/null || true)
case "$pid:$at" in ''|*[!0-9:]*) ;; *)
now=$(date +%s)
if [ "$version" = "$VERSION" ] && [ -n "$pid" ] && [ "$pid" = "$active" ] && [ "$at" -ge "$started" ] && [ $((now - at)) -le 30 ]; then
@@ -280,9 +280,9 @@ install() {
: "${MOSHIRO_SERVER:?panel URL required}" "${MOSHIRO_CODE:?join token required}" "${MOSHIRO_GENESIS:?trust root required}" "${MOSHIRO_CONTROLLERS:?owner required}"
case "$MOSHIRO_SERVER" in https://*) ;; *) die "panel must use HTTPS" ;; esac
case "$MOSHIRO_SERVER" in *[!A-Za-z0-9:/._-]*) die "unsupported panel URL characters" ;; esac
[ ! -e /etc/moshiro/node.json ] && [ ! -e /etc/moshiro/probe.json ] || die "existing native identity/config found; migrate explicitly instead of overwriting it"
[ ! -e /etc/moshiro/node.json ] && [ ! -e /etc/moshiro/agent.json ] || die "existing native identity/config found; migrate explicitly instead of overwriting it"
else
[ -f /etc/moshiro/node.json ] && [ -f /etc/moshiro/trust.json ] && [ -f /etc/moshiro/probe.json ] || die "saved identity/config incomplete; restore a backup before reinstalling"
[ -f /etc/moshiro/node.json ] && [ -f /etc/moshiro/trust.json ] && [ -f /etc/moshiro/agent.json ] || die "saved identity/config incomplete; restore a backup before reinstalling"
fi
acquire || die "another installation is running"
if [ ! -f "$STATE/release-key.pem" ]; then
@@ -301,7 +301,7 @@ KEY
uid=900
while awk -F: -v uid="$uid" '$3 == uid {found=1} END {exit !found}' /etc/passwd /etc/group; do uid=$((uid + 1)); done
echo "moshiro:x:$uid:" >> /etc/group
echo "moshiro:x:$uid:$uid:MoShiro:/var/run/moshiro-probe:/bin/false" >> /etc/passwd
echo "moshiro:x:$uid:$uid:MoShiro:/var/run/moshiro-agent:/bin/false" >> /etc/passwd
}
mkdir -p /etc/moshiro "$RUNTIME"
chmod 750 /etc/moshiro "$RUNTIME"
@@ -310,7 +310,7 @@ KEY
LD_LIBRARY_PATH="$ROOT/versions/$HASH" "$ROOT/versions/$HASH/fleet-enroll" \
--server "$MOSHIRO_SERVER" --join "$MOSHIRO_CODE" --name "${MOSHIRO_NAME:-$(uname -n)}" \
--identity-dir /etc/moshiro --genesis "$MOSHIRO_GENESIS" --controllers "$MOSHIRO_CONTROLLERS"
printf '{"fleet":{"server":"%s","identity_dir":"/etc/moshiro"},"limits":{"max_test_mbps":20,"max_total_pps":2000}}\n' "$MOSHIRO_SERVER" > /etc/moshiro/probe.json
printf '{"fleet":{"server":"%s","identity_dir":"/etc/moshiro"},"limits":{"max_test_mbps":20,"max_total_pps":2000}}\n' "$MOSHIRO_SERVER" > /etc/moshiro/agent.json
fi
chown -R moshiro:moshiro /etc/moshiro "$RUNTIME"
chmod 600 /etc/moshiro/node.key /etc/moshiro/tls.pfx
@@ -321,9 +321,9 @@ KEY
switch_to "versions/$HASH"
mkdir -p /usr/libexec
ln -sf "$ROOT/current/openwrt.sh" /usr/libexec/moshiro-openwrt
cp "$ROOT/current/probe.init" /etc/init.d/moshiro-probe
cp "$ROOT/current/updater.init" /etc/init.d/moshiro-probe-update
chmod 755 /etc/init.d/moshiro-probe /etc/init.d/moshiro-probe-update
cp "$ROOT/current/agent.init" /etc/init.d/moshiro-agent
cp "$ROOT/current/updater.init" /etc/init.d/moshiro-agent-update
chmod 755 /etc/init.d/moshiro-agent /etc/init.d/moshiro-agent-update
mkdir -p /lib/upgrade/keep.d
printf '/etc/moshiro/\n%s\n%s/\n' "$CONF" "$STATE" > /lib/upgrade/keep.d/moshiro
printf '%s\n' "$SEQ" > "$STATE/$CHANNEL.seq"
@@ -336,8 +336,8 @@ KEY
die "installed but not healthy; inspect logread -e moshiro, then rerun install (identity is preserved)"
fi
status up_to_date
/etc/init.d/moshiro-probe-update enable
/etc/init.d/moshiro-probe-update restart
/etc/init.d/moshiro-agent-update enable
/etc/init.d/moshiro-agent-update restart
log "installed $VERSION ($CHANNEL/$RID); automatic signed updates enabled"
}
@@ -1,16 +1,16 @@
#!/bin/sh /etc/rc.common
# procd service for moshiro-probe (OpenWrt). Installed as /etc/init.d/moshiro-probe.
# procd service for moshiro-agent (OpenWrt). Installed as /etc/init.d/moshiro-agent.
START=95
STOP=10
USE_PROCD=1
PROG=/usr/lib/moshiro/moshiro-probe
CONF=/etc/moshiro/probe.json
PROG=/usr/lib/moshiro/moshiro-agent
CONF=/etc/moshiro/agent.json
start_service() {
[ -f "$CONF" ] || {
logger -t moshiro-probe "$CONF not found: enroll first (install.sh probe --server URL --join TOKEN --name NAME)"
logger -t moshiro-agent "$CONF not found: enroll first (install.sh agent --server URL --join TOKEN --name NAME)"
return 1
}
procd_open_instance
@@ -4,12 +4,12 @@ STOP=10
USE_PROCD=1
start_service() {
[ -f /etc/moshiro/probe.json ] || return 1
mkdir -p /var/run/moshiro-probe
chown moshiro:moshiro /var/run/moshiro-probe
chmod 750 /var/run/moshiro-probe
[ -f /etc/moshiro/agent.json ] || return 1
mkdir -p /var/run/moshiro-agent
chown moshiro:moshiro /var/run/moshiro-agent
chmod 750 /var/run/moshiro-agent
procd_open_instance
procd_set_param command /usr/lib/moshiro-native/current/moshiro-probe --config /etc/moshiro/probe.json
procd_set_param command /usr/lib/moshiro-native/current/moshiro-agent --config /etc/moshiro/agent.json
procd_set_param env LD_LIBRARY_PATH=/usr/lib/moshiro-native/current MOSHIRO_UPDATE=openwrt
procd_set_param user moshiro
procd_set_param group moshiro
+1 -1
View File
@@ -1,4 +1,4 @@
# MoShiro Backend(面板)的 Podman Quadlet 单元(Podman 4.6 及以上),更新方式和 Agent、Probe 完全一样:
# MoShiro Backend(面板)的 Podman Quadlet 单元(Podman 4.6 及以上),更新方式和 Agent 完全一样:
# systemd 每次启动这个单元时 Pull=always 先从镜像仓库拉最新的镜像,Backend 在管理端确认更新之后有序退出(先把数据库备份到数据卷的 backups/),
# Restart=always 把它拉起来,退出再被拉起就是更新(Backend 自己不下载、不替换任何东西)。
# 不会自动重启:Backend 指挥所有节点,更新要管理员在管理端点「更新并重启」。
-55
View File
@@ -1,55 +0,0 @@
# MoShiro Probe 的 Podman Quadlet 单元(Podman 4.6 及以上)。节点的更新也靠它:systemd 每次启动这个单元时 Pull=always 先从镜像仓库拉最新的镜像,
# 所以节点退出再被拉起就是更新(节点自己不下载、不替换任何东西)。默认自动更新:面板发现新版本后,节点运行满 10 分钟就自己有序退出;
# 想改成在管理端点「更新」才更新,在 env 文件里写 MOSHIRO_UPDATE_POLICY=manual。
#
# 安装(rootless,普通用户,不用 sudo):
# mkdir -p ~/.config/containers/systemd ~/.config/moshiro
# 把面板「添加 Probe」给的 .env 内容存成 ~/.config/moshiro/probe.env,chmod 600
# 把本文件放进 ~/.config/containers/systemd/
# systemctl --user daemon-reload && systemctl --user start moshiro-probe # Quadlet 会自动生成服务,不用 enable;[Install] 让它开机自启
# loginctl enable-linger $USER # 退出登录后继续运行(需要管理员执行一次)
# rootful:本文件放进 /etc/containers/systemd/,.env 内容存成 /etc/moshiro/probe.env,把下面的 EnvironmentFile 改成这个路径、
# WantedBy 改成 multi-user.target,用 systemctl(不加 --user)。
# 开发通道:把 Image 的 moshiro 换成 moshiro-dev。固定版本:把 :latest 换成 :<版本号>(固定之后更新不会换版本)。
# 想要完全自动、不用在管理端点:另外启用 podman-auto-update.timer,并在 [Container] 里加 AutoUpdate=registry(Pull=always 也保留)。
# 说明见 docs/ops/deployment.md。
[Unit]
Description=MoShiro Probe
After=network-online.target
Wants=network-online.target
[Container]
Image=registry.oeo.one/moshiro/moshiro-probe:latest
ContainerName=moshiro-probe
Pull=always
Network=host
RunInit=true
ReadOnly=true
NoNewPrivileges=true
DropCapability=ALL
SecurityLabelDisable=true
Tmpfs=/tmp:rw,noexec,nosuid,size=64m,mode=1777
StopTimeout=20
Volume=moshiro-probe-config:/etc/moshiro
Volume=/etc/os-release:/etc/os-release:ro
# 主机资源占用(只有汇总数字):只读挂载宿主机的 /proc、/sys 和根目录;不想上报就删掉这三行和下面三个 HOST_ 变量
Volume=/proc:/host/proc:ro
Volume=/sys:/host/sys:ro
Volume=/:/host/root:ro
Environment=HOST_PROC=/host/proc
Environment=HOST_SYS=/host/sys
Environment=HOST_ROOT=/host/root
Environment=MOSHIRO_NETWORK_SCOPE=host
Environment=LD_LIBRARY_PATH=/usr/local/bin
# 告诉节点:退出之后外面会重新拉取,所以「更新」就是退出(没有这一行,节点不报告更新状态)
Environment=MOSHIRO_UPDATE=restart
EnvironmentFile=%h/.config/moshiro/probe.env
[Service]
Restart=always
RestartSec=5
# 第一次拉取镜像可能要一会儿
TimeoutStartSec=600
[Install]
WantedBy=default.target
@@ -1,16 +0,0 @@
[Unit]
Description=MoShiro Probe (Podman container)
After=network-online.target
Wants=network-online.target
[Service]
EnvironmentFile=/etc/moshiro-update/probe.image
Restart=always
RestartSec=5
TimeoutStopSec=30
ExecStartPre=-/usr/bin/podman rm -f moshiro-probe
ExecStart=/usr/bin/podman run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -e MOSHIRO_NETWORK_SCOPE=host -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
ExecStop=/usr/bin/podman stop -t 20 moshiro-probe
[Install]
WantedBy=multi-user.target
@@ -1,16 +0,0 @@
[Unit]
Description=MoShiro Probe (Docker container)
After=network-online.target
Wants=network-online.target
[Service]
EnvironmentFile=/etc/moshiro-update/probe.image
Restart=always
RestartSec=5
TimeoutStopSec=30
ExecStartPre=-/usr/bin/docker rm -f moshiro-probe
ExecStart=/usr/bin/docker run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -e MOSHIRO_NETWORK_SCOPE=host -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
ExecStop=/usr/bin/docker stop -t 20 moshiro-probe
[Install]
WantedBy=multi-user.target
@@ -1,16 +0,0 @@
[Unit]
Description=MoShiro Probe (rootless Podman container)
After=network-online.target
Wants=network-online.target
[Service]
EnvironmentFile=%h/.config/moshiro/probe.image
Restart=always
RestartSec=5
TimeoutStopSec=30
ExecStartPre=-/usr/bin/podman rm -f moshiro-probe
ExecStart=/usr/bin/podman run --rm --name moshiro-probe --network host --read-only --security-opt no-new-privileges --security-opt label=disable --cap-drop all --tmpfs /tmp:rw,noexec,nosuid,size=64m,mode=1777 -e MOSHIRO_NETWORK_SCOPE=host -v /etc/os-release:/etc/os-release:ro -v moshiro-probe-config:/etc/moshiro -v moshiro-probe-status:/etc/moshiro/update-status:ro ${IMAGE}
ExecStop=/usr/bin/podman stop -t 20 moshiro-probe
[Install]
WantedBy=default.target
-44
View File
@@ -1,44 +0,0 @@
[Unit]
Description=MoShiro probe (network line quality measurement origin)
After=network-online.target
Wants=network-online.target
[Service]
Type=exec
User=moshiro
Group=moshiro
Environment=LD_LIBRARY_PATH=/usr/lib/moshiro
ExecStart=/usr/lib/moshiro/moshiro-probe --config /etc/moshiro/probe.json
Restart=always
RestartSec=5
# The probe is a pure collector: it never starts processes and writes no measurement data.
# The only file the probe writes is agents.json (the latest agent list) next to probe.json.
ReadWritePaths=/etc/moshiro
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
LockPersonality=yes
# NativeAOT does not JIT, so writable+executable memory is never needed.
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
# No capabilities needed: the probe only dials out.
CapabilityBoundingSet=
MemoryMax=64M
TasksMax=64
LimitNOFILE=4096
[Install]
WantedBy=multi-user.target
+1 -1
View File
@@ -5,7 +5,7 @@
set -eu
COMPONENT="${1:-}"; shift || true
case "$COMPONENT" in agent|probe) ;; *) echo "usage: install-updater.sh <agent|probe> [--user] [--engine podman|docker] --data-dir D --image-repo R --unit U [--policy auto|manual] [--channel dev|release] [--health-url URL] [--authfile F] [--no-enable]" >&2; exit 2 ;; esac
case "$COMPONENT" in agent) ;; *) echo "usage: install-updater.sh <agent> [--user] [--engine podman|docker] --data-dir D --image-repo R --unit U [--policy auto|manual] [--channel dev|release] [--health-url URL] [--authfile F] [--no-enable]" >&2; exit 2 ;; esac
DATA_DIR=""; IMAGE_REPO=""; UNIT=""; POLICY=manual; AUTHFILE=""; ENGINE=podman; SERVICE_SCOPE=system; ENABLE=true; DATA_VOLUME=""; STATUS_VOLUME=""; HELPER_IMAGE=""
CHANNEL=""; HEALTH_URL=""
while [ $# -gt 0 ]; do
+6 -6
View File
@@ -1,12 +1,12 @@
#!/bin/sh
# MoShiro 容器节点(Agent、Probe)的宿主机更新器。用法:moshiro-update <check|apply> <组件> 组件 = agent 或 probe
# MoShiro 容器节点(Agent)的宿主机更新器。用法:moshiro-update <check|apply> <组件> 组件 = agent
#
# 信任模型(防篡改、防中间人):
# - 只接受用发布私钥签名的清单(ECDSA P-256 / SHA-256,对清单文件的原始字节签名);公钥放在本机,面板和 Registry 都改不了它。
# - 清单里写的是镜像的 digest:拉取时用 repo@sha256:…,内容寻址,Registry 或链路上的人换不出另一个镜像。
# - 镜像仓库、组件、服务名都来自本机的配置(/etc/moshiro-update/<组件>.conf),清单里写什么都不会改变拉哪个仓库。
# - 清单的 seq 必须比已安装的大(防止旧清单被重放回滚)。
# - 业务进程(Agent、Probe)只能往数据目录里写「清单」和「请求」文件,更新由这个宿主机脚本完成,业务进程不执行任何命令。
# - 业务进程(Agent)只能往数据目录里写「清单」和「请求」文件,更新由这个宿主机脚本完成,业务进程不执行任何命令。
# Shell snippets below execute inside helper containers.
# shellcheck disable=SC2016
set -eu
@@ -14,8 +14,8 @@ umask 077
CMD="${1:-}"
COMPONENT="${2:-}"
case "$CMD" in check|apply) ;; *) echo "usage: moshiro-update <check|apply> <agent|probe>" >&2; exit 2 ;; esac
case "$COMPONENT" in agent|probe) ;; *) echo "component must be agent or probe" >&2; exit 2 ;; esac
case "$CMD" in check|apply) ;; *) echo "usage: moshiro-update <check|apply> <agent>" >&2; exit 2 ;; esac
case "$COMPONENT" in agent) ;; *) echo "component must be agent" >&2; exit 2 ;; esac
if [ "$(id -u)" -eq 0 ]; then
DEFAULT_CONF_DIR=/etc/moshiro-update
@@ -55,7 +55,7 @@ pull_image() {
"$ENGINE" pull --quiet "$1"
fi
}
# 业务进程(容器里的 Agent、Probe)可写的目录只有 update/:它在里面放 offer.manifest、offer.sig、apply.request。
# 业务进程(容器里的 Agent)可写的目录只有 update/:它在里面放 offer.manifest、offer.sig、apply.request。
# 更新器在容器外运行:只读取普通文件(不跟符号链接)、读取之后先复制到自己的临时目录再验证和使用(防止验证之后被换掉),
# 状态目录在 rootless 容器里另行只读挂载;更新器配置和已安装版本状态不挂入容器。
if [ -n "$DATA_VOLUME" ]; then
@@ -139,7 +139,7 @@ fetch_mailbox() {
now() { date +%s; }
# 状态文件:更新器写,Agent / Probe 从只读挂载中读取并随心跳上报。
# 状态文件:更新器写,Agent 从只读挂载中读取并随心跳上报。
write_status() { # state available_json error
tmp="$WORK/status.json"
printf '{"state":"%s","current":"%s","available":%s,"error":"%s","at":%s}\n' \
+1 -1
View File
@@ -108,7 +108,7 @@ resolve() { # 签名私钥 组件 镜像仓库
}
expect "first install: signed manifest pins the digest" "$REPO@$DIGEST" "$(resolve "$T/b.pem" agent "$REPO")"
expect "first install: wrong signing key is refused" "REFUSED" "$(resolve "$T/c.pem" agent "$REPO")"
expect "first install: manifest for another component is refused" "REFUSED" "$(resolve "$T/b.pem" probe "$REPO")"
expect "first install: manifest for another component is refused" "REFUSED" "$(resolve "$T/b.pem" backend "$REPO")"
expect "first install: manifest naming another repository is refused" "REFUSED" "$(resolve "$T/b.pem" agent "registry.evil/moshiro-dev/moshiro-agent")"
# --- 第一次部署:正在跑的镜像就是清单里的这个 digest → 记成已安装(不当作有更新)