Files
MoShiro/packaging/install/install.sh
T

136 lines
6.8 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 节点一键安装(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; 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 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 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 ] || die "the panel has no signed release for $ROLE yet, so the image cannot be verified. Run again with MOSHIRO_ALLOW_UNVERIFIED=1 to install $IMAGE without verification"
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)" = "$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; 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 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"