Files
MoShiro/packaging/install/install.sh
T
justme 016dbc2bbf 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.
2026-10-21 11:55:41 +08:00

155 lines
9.0 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# 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,监听并拨号)、MOSHIRO_CODE(一次性的加入码)。
# 做的事:用 Docker 或 Podman 起一个容器,身份和配置存在命名卷里(重启、换新镜像重建都不用再给参数),节点名字默认取主机名。
# 可选环境变量:MOSHIRO_NAME=节点名字(名字被占用时用)、MOSHIRO_ENGINE=docker|podman、MOSHIRO_DRY_RUN=1(只打印命令)、
# MOSHIRO_GENESIS / MOSHIRO_CONTROLLERS(命令里带着:你的信任根指纹和这个节点的属主 passkey 凭证 id,节点在注册时验证面板的操作员集合并把它们固定在本地;都是公开值)、
# MOSHIRO_ALLOW_UNVERIFIED=1 加 MOSHIRO_IMAGE=镜像(面板还没有已签名的发布清单时,不验签、按这个镜像安装;默认拒绝)。
# 镜像:向面板取 CI 签名的发布清单,用下面内置的发布公钥验签,再按清单里的 digest 拉取(镜像按内容寻址,换不了)。
set -eu
SERVER="${MOSHIRO_SERVER:-}"
ROLE="${MOSHIRO_ROLE:-agent}"
CODE="${MOSHIRO_CODE:-}"
IMAGE="${MOSHIRO_IMAGE:-}"
RELEASE_KEY='-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEfw7EkNcl0FZqOETK+Vt55RMZ9K0q
YOrtO3Vso/QNdSMmepQlx1Q3v+KkN7ro3AY3I6DT2Q2txJu5pS7qzutSQA==
-----END PUBLIC KEY-----'
say() { printf '%s\n' "$*"; }
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
# 这些值来自命令行环境:只允许正常的字符,因为它们会进到 URL 和容器参数里
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)"
name="${MOSHIRO_NAME:-$(hostname -s 2>/dev/null || hostname 2>/dev/null || echo node)}"
name="$(printf '%s' "$name" | tr -c 'A-Za-z0-9._-' '-' | cut -c1-64)"
[ -n "$name" ] || name=node
engine="${MOSHIRO_ENGINE:-}"
if [ -z "$engine" ]; then
if command -v docker >/dev/null 2>&1; then engine=docker
elif command -v podman >/dev/null 2>&1; then engine=podman
else die "Docker or Podman is required (https://docs.docker.com/engine/install/)"; fi
fi
command -v "$engine" >/dev/null 2>&1 || die "$engine is not installed"
# 当前用户连不上引擎(不在 docker 组、rootful podman)就用 sudo
use_sudo=0
if ! "$engine" info >/dev/null 2>&1 </dev/null; then
[ "$(id -u)" -ne 0 ] || die "$engine is installed but not running"
command -v sudo >/dev/null 2>&1 || die "$engine needs root: run this as root or install sudo"
use_sudo=1
fi
eng() { if [ "$use_sudo" -eq 1 ]; then sudo "$engine" "$@"; else "$engine" "$@"; fi; }
container="moshiro-$ROLE"
volume="moshiro-$ROLE-config"
linux=0
[ "$(uname -s)" = Linux ] && linux=1
if [ "${MOSHIRO_DRY_RUN:-}" != 1 ] && eng container inspect "$container" >/dev/null 2>&1 </dev/null; then
eng start "$container" >/dev/null </dev/null
say "$container is already installed and running."
say "reinstall: $engine rm -f $container && $engine volume rm $volume (then run the install command again)"
exit 0
fi
# 验发布清单:签名对不上、组件不对、digest 格式不对都直接拒绝,不装
resolve_image() {
if [ "${MOSHIRO_DRY_RUN:-}" = 1 ]; then IMAGE="${IMAGE:-<the image of the signed release>}"; return 0; fi
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
have_manifest=1
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 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
command -v openssl >/dev/null 2>&1 || die "openssl is required to verify the release signature"
printf '%s\n' "$RELEASE_KEY" > "$tmp/key.pem"
openssl base64 -d -A -in "$tmp/sig.b64" -out "$tmp/sig.bin" 2>/dev/null || die "the release signature is not valid base64"
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)" = 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
case "$repo" in ''|*[!A-Za-z0-9._/:-]*) die "the signed release has a malformed image name" ;; esac
IMAGE="$repo@$digest"
say "verified release $(field version) ($(field tag)): $IMAGE"
}
resolve_image
restart=unless-stopped
[ "$engine" = podman ] && restart=always
set -- run -d --name "$container" --restart "$restart" \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=64m --cap-drop all --security-opt no-new-privileges \
-v "$volume:/etc/moshiro"
[ "$engine" = podman ] && set -- "$@" --security-opt label=disable
if [ "$linux" -eq 1 ]; then
# 宿主网络:监听的节点对外暴露端口,两种模式都靠它识别本机的公网 IPv4 和 IPv6 地址
set -- "$@" --network host
# 主机资源占用(只读),管理端的节点详情页显示 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 没有宿主网络:把监听节点的端口发布出来(面板里的地址需要指向这台机器的公网地址)
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" -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)"
fi
if [ -n "${MOSHIRO_GENESIS:-}" ]; then
case "$MOSHIRO_GENESIS${MOSHIRO_CONTROLLERS:-}" in *[!A-Za-z0-9_,-]*) die "MOSHIRO_GENESIS or MOSHIRO_CONTROLLERS has unexpected characters" ;; esac
set -- "$@" -e "MOSHIRO_GENESIS=$MOSHIRO_GENESIS" -e "MOSHIRO_CONTROLLERS=${MOSHIRO_CONTROLLERS:-}"
fi
set -- "$@" "$IMAGE"
if [ "${MOSHIRO_DRY_RUN:-}" = 1 ]; then
say "$engine $*"
exit 0
fi
say "pulling $IMAGE"
if ! eng pull "$IMAGE" >/dev/null </dev/null; then
die "could not pull $IMAGE (private registry? run: $engine login ${IMAGE%%/*})"
fi
say "starting $container as '$name'"
eng "$@" >/dev/null </dev/null
# Podman 的容器开机自启要靠 podman-restart.service
if [ "$engine" = podman ] && command -v systemctl >/dev/null 2>&1; then
if [ "$(id -u)" -eq 0 ]; then systemctl enable --now podman-restart >/dev/null 2>&1 || true
elif [ "$use_sudo" -eq 1 ]; then sudo systemctl enable --now podman-restart >/dev/null 2>&1 || true; fi
fi
# 等注册完成:第一次启动会用上面的令牌注册,成功之后才算装好
i=0
while [ "$i" -lt 30 ]; do
state="$(eng container inspect -f '{{.State.Running}}' "$container" 2>/dev/null </dev/null || echo false)"
logs="$(eng logs "$container" 2>&1 </dev/null || true)"
case "$logs" in
*"link up"*) say "done: $ROLE '$name' is online. It shows up in the panel now."; say "logs: $engine logs -f $container"; exit 0 ;;
esac
if [ "$state" != true ]; then
printf '%s\n' "$logs" | tail -n 8 >&2
die "$container stopped. If the name '$name' is taken, run again with MOSHIRO_NAME=<other-name>. To start over: $engine rm -f $container && $engine volume rm $volume"
fi
i=$((i + 1))
sleep 1
done
say "$container is running, but has not reported online yet. Check: $engine logs $container"