#!/bin/sh # MoShiro 节点一键安装(Agent 和 Probe 共用同一份)。面板在 /install?code=… 生成这份脚本,把下面四个值填好: # curl -fsSL 'https://panel/install?code=jt_…' | bash (macOS 用 zsh) # 做的事:用 Docker 或 Podman 起一个容器,身份和配置存在命名卷里(重启、换新镜像重建都不用再给参数),节点名字默认取主机名。 # 可选环境变量:MOSHIRO_NAME=节点名字(名字被占用时用)、MOSHIRO_ENGINE=docker|podman、MOSHIRO_DRY_RUN=1(只打印命令)、 # MOSHIRO_ALLOW_UNVERIFIED=1(面板还没有已签名的发布清单时,不验签、按标签安装;默认拒绝)。 # 镜像:向面板取 CI 签名的发布清单,用下面内置的发布公钥验签,再按清单里的 digest 拉取(镜像按内容寻址,换不了)。 set -eu ROLE='@@ROLE@@' SERVER='@@SERVER@@' CODE='@@CODE@@' IMAGE='@@IMAGE@@' RELEASE_KEY='@@RELEASE_KEY@@' say() { printf '%s\n' "$*"; } die() { printf 'error: %s\n' "$*" >&2; exit 1; } 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 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 inspect "$container" >/dev/null 2>&1 /dev/null /dev/null /dev/null /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)" = "$ROLE" ] || die "the signed release is for '$(field component)', not '$ROLE'" 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 # 宿主网络:Agent 对外暴露端口,也靠它识别本机的公网 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 elif [ "$ROLE" = agent ]; then # macOS 上的 Docker 没有宿主网络:把 Agent 的端口发布出来(面板里的地址需要指向这台机器的公网地址) 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" "$IMAGE" if [ "${MOSHIRO_DRY_RUN:-}" = 1 ]; then say "$engine $*" exit 0 fi say "pulling $IMAGE" if ! eng pull "$IMAGE" >/dev/null /dev/null /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 inspect -f '{{.State.Running}}' "$container" 2>/dev/null &1 &2 die "$container stopped. If the name '$name' is taken, run again with MOSHIRO_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"