add cloud-mcp/install.sh
This commit is contained in:
@@ -0,0 +1,143 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# cloud-mcp 一键安装/升级脚本(用户侧,Linux)。
|
||||||
|
#
|
||||||
|
# curl -fsSL https://git.lingyu.org.cn/lingyu-note/releases/raw/branch/main/miiot/install.sh | sudo bash
|
||||||
|
#
|
||||||
|
# 行为:
|
||||||
|
# - 自动检测架构(amd64/arm64),从公开发布仓下载最新版,SHA256 校验
|
||||||
|
# - 首次:装到 /opt/miiot,生成 .env(随机 token),注册 systemd 并启动
|
||||||
|
# - 再次运行:原地升级(保留 .env 与数据;旧二进制自动备份,健康检查失败自动回滚)
|
||||||
|
#
|
||||||
|
# 可选:
|
||||||
|
# VERSION=1.2.0 bash install.sh # 装指定版本(默认最新)
|
||||||
|
# bash install.sh --uninstall # 卸载(保留 /opt/miiot/data 与 .env)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
APP="cloud-mcp"
|
||||||
|
GITEA="https://git.lingyu.org.cn"
|
||||||
|
REL_REPO="lingyu-note/releases"
|
||||||
|
APP_DIR="/opt/${APP}"
|
||||||
|
SERVICE="${APP}"
|
||||||
|
PORT=8550
|
||||||
|
HEALTH_URL="http://127.0.0.1:${PORT}/healthz"
|
||||||
|
KEEP_BACKUPS=5
|
||||||
|
|
||||||
|
[[ "$(id -u)" == "0" ]] || { echo "❌ 请用 root / sudo 运行" >&2; exit 1; }
|
||||||
|
[[ "$(uname -s)" == "Linux" ]] || { echo "❌ 仅支持 Linux" >&2; exit 1; }
|
||||||
|
case "$(uname -m)" in
|
||||||
|
x86_64) ARCH=amd64 ;;
|
||||||
|
aarch64|arm64) ARCH=arm64 ;;
|
||||||
|
*) echo "❌ 不支持的架构: $(uname -m)" >&2; exit 1 ;;
|
||||||
|
esac
|
||||||
|
command -v curl >/dev/null || { echo "❌ 需要 curl" >&2; exit 1; }
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||||
|
systemctl disable --now "${SERVICE}" 2>/dev/null || true
|
||||||
|
rm -f "/etc/systemd/system/${SERVICE}.service"; systemctl daemon-reload
|
||||||
|
rm -rf "${APP_DIR}/bin"
|
||||||
|
echo "✓ 已卸载(${APP_DIR}/data 与 .env 保留,需要请手动删除)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 解析版本:VERSION 环境变量,或发布仓里 tag 前缀 miiot-v 的最新 release ----
|
||||||
|
if [[ -n "${VERSION:-}" ]]; then
|
||||||
|
VER="${VERSION#v}"
|
||||||
|
else
|
||||||
|
VER=$(curl -fsSL "${GITEA}/api/v1/repos/${REL_REPO}/releases?limit=50" \
|
||||||
|
| python3 -c "
|
||||||
|
import json,sys
|
||||||
|
rels=[r for r in json.load(sys.stdin) if r['tag_name'].startswith('${APP}-v')]
|
||||||
|
print(rels[0]['tag_name'].removeprefix('${APP}-v') if rels else '')" 2>/dev/null) || VER=""
|
||||||
|
[[ -n "$VER" ]] || { echo "❌ 查询最新版本失败(发布仓暂无 ${APP} release?)" >&2; exit 1; }
|
||||||
|
fi
|
||||||
|
TAG="${APP}-v${VER}"
|
||||||
|
PKG="${APP}_${VER}_linux_${ARCH}.tar.gz"
|
||||||
|
BASE="${GITEA}/${REL_REPO}/releases/download/${TAG}"
|
||||||
|
|
||||||
|
# 已是该版本且服务健康 → 幂等退出
|
||||||
|
if [[ -x "${APP_DIR}/bin/${APP}" && -f "${APP_DIR}/.version" ]] \
|
||||||
|
&& [[ "$(cat "${APP_DIR}/.version")" == "${VER}" ]] \
|
||||||
|
&& curl -sf -o /dev/null "${HEALTH_URL}" 2>/dev/null; then
|
||||||
|
echo "✓ 已是最新版本 v${VER},无需升级"; exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "▶ 下载 ${PKG} ..."
|
||||||
|
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
|
||||||
|
curl -fsSL -o "${TMP}/${PKG}" "${BASE}/${PKG}"
|
||||||
|
curl -fsSL -o "${TMP}/SHA256SUMS" "${BASE}/SHA256SUMS"
|
||||||
|
(cd "${TMP}" && grep " ${PKG}\$" SHA256SUMS | sha256sum -c - >/dev/null) \
|
||||||
|
|| { echo "❌ SHA256 校验失败" >&2; exit 1; }
|
||||||
|
tar -xzf "${TMP}/${PKG}" -C "${TMP}"
|
||||||
|
|
||||||
|
mkdir -p "${APP_DIR}/bin/backup" "${APP_DIR}/data"
|
||||||
|
|
||||||
|
# ---- 首次:生成 .env + systemd unit ----
|
||||||
|
if [[ ! -f "${APP_DIR}/.env" ]]; then
|
||||||
|
echo "▶ 首次安装:生成 ${APP_DIR}/.env(请按需修改后 systemctl restart ${SERVICE})"
|
||||||
|
rnd() { head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n'; }
|
||||||
|
cat > "${APP_DIR}/.env" <<ENV
|
||||||
|
# cloud-mcp 配置 — 完整项见项目 README(env 前缀 LYMCP_)
|
||||||
|
LYMCP_DATA_DIR=${APP_DIR}/data
|
||||||
|
LYMCP_SQLITE_PATH=${APP_DIR}/data/cloud-mcp.sqlite3
|
||||||
|
# MCP Bearer token(客户端接入用;已随机生成,可自行替换)
|
||||||
|
LYMCP_MCP_AUTH_TOKEN=$(rnd)
|
||||||
|
# 管理 API token
|
||||||
|
LYMCP_ADMIN_AUTH_TOKEN=$(rnd)
|
||||||
|
# Web 管理台登录(首次安装随机密码,请尽快修改)
|
||||||
|
LYMCP_LOGIN_USER=admin
|
||||||
|
LYMCP_LOGIN_PASSWORD=$(rnd | head -c 16)
|
||||||
|
LYMCP_LOG_LEVEL=info
|
||||||
|
ENV
|
||||||
|
chmod 600 "${APP_DIR}/.env"
|
||||||
|
fi
|
||||||
|
if [[ ! -f "/etc/systemd/system/${SERVICE}.service" ]]; then
|
||||||
|
cat > "/etc/systemd/system/${SERVICE}.service" <<UNIT
|
||||||
|
[Unit]
|
||||||
|
Description=Lingyu ${APP} MCP
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
EnvironmentFile=${APP_DIR}/.env
|
||||||
|
ExecStart=${APP_DIR}/bin/${APP}
|
||||||
|
WorkingDirectory=${APP_DIR}
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
NoNewPrivileges=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
UNIT
|
||||||
|
systemctl daemon-reload
|
||||||
|
systemctl enable "${SERVICE}" >/dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ---- 替换二进制(旧的备份,保留最近 N 个)----
|
||||||
|
if [[ -f "${APP_DIR}/bin/${APP}" ]]; then
|
||||||
|
cp "${APP_DIR}/bin/${APP}" "${APP_DIR}/bin/backup/${APP}.$(date +%Y%m%d%H%M%S)"
|
||||||
|
ls -t "${APP_DIR}/bin/backup/" | tail -n +$((KEEP_BACKUPS+1)) \
|
||||||
|
| xargs -r -I{} rm -f "${APP_DIR}/bin/backup/{}"
|
||||||
|
fi
|
||||||
|
install -m 755 "${TMP}/${APP}" "${APP_DIR}/bin/${APP}"
|
||||||
|
echo "${VER}" > "${APP_DIR}/.version"
|
||||||
|
|
||||||
|
echo "▶ 重启 + 健康检查 ..."
|
||||||
|
systemctl restart "${SERVICE}"
|
||||||
|
ok=""
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
sleep 1
|
||||||
|
curl -sf -o /dev/null "${HEALTH_URL}" && { ok=1; break; }
|
||||||
|
done
|
||||||
|
if [[ -z "$ok" ]]; then
|
||||||
|
echo "✗ 健康检查失败" >&2
|
||||||
|
journalctl -u "${SERVICE}" -n 20 --no-pager || true
|
||||||
|
latest=$(ls -t "${APP_DIR}/bin/backup/" 2>/dev/null | head -1 || true)
|
||||||
|
if [[ -n "$latest" ]]; then
|
||||||
|
echo " 回滚到 ${latest}" >&2
|
||||||
|
cp "${APP_DIR}/bin/backup/${latest}" "${APP_DIR}/bin/${APP}"
|
||||||
|
systemctl restart "${SERVICE}"
|
||||||
|
fi
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ ${APP} v${VER} 运行中 — 端口 ${PORT},配置 ${APP_DIR}/.env"
|
||||||
|
echo " 日志:journalctl -u ${SERVICE} -f 升级:重跑本脚本即可"
|
||||||
Reference in New Issue
Block a user