Compare commits
13 Commits
cloud-mcp-v0.1.0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 1ff0ff63ef | |||
| 2d9aae71f7 | |||
| 35bd340d02 | |||
| 7a8bd6a4ce | |||
| e8216d37f6 | |||
| 622d7e6d0b | |||
| b9d47e0f05 | |||
| ac0d107dba | |||
| 3b4a70c399 | |||
| 7010c9d34a | |||
| 3f80164190 | |||
| 246328e9ed | |||
| 77c8e863a6 |
@@ -1,3 +1,44 @@
|
||||
# releases
|
||||
# 翎羽笔记 · 自托管组件发布仓
|
||||
|
||||
构建产物发布仓(install.sh 下载源)— 只放二进制包,无源码
|
||||
本仓库只存放**构建产物**(tar.gz + SHA256SUMS),供一键安装脚本下载,不含源码。
|
||||
|
||||
## 一键安装 / 升级
|
||||
|
||||
在目标 Linux 机器上执行(重复执行即为升级,配置与数据保留,失败自动回滚):
|
||||
|
||||
### miiot — 小米 IoT MCP(设备/场景)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.lingyu.org.cn/lingyu-note/releases/raw/branch/main/miiot/install.sh | sudo bash
|
||||
```
|
||||
- 架构:amd64 / arm64,Go 静态二进制,零依赖
|
||||
- 端口 8677;配置 `/opt/miiot/.env`(安装时自动生成随机 token)
|
||||
|
||||
### cloud-mcp — 聚合 MCP 网关(browser 代理 / weather / train / maps)
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.lingyu.org.cn/lingyu-note/releases/raw/branch/main/cloud-mcp/install.sh | sudo bash
|
||||
```
|
||||
- 架构:amd64 / arm64,Go 静态二进制,零依赖
|
||||
- 端口 8550;配置 `/opt/cloud-mcp/.env`
|
||||
|
||||
### sandbox-worker — 沙箱执行器
|
||||
|
||||
```bash
|
||||
curl -fsSL https://git.lingyu.org.cn/lingyu-note/releases/raw/branch/main/sandbox-worker/install.sh | sudo bash
|
||||
```
|
||||
- 前置:Node ≥ 22、Docker(worker 通过 docker API 启动沙箱容器)
|
||||
- 端口 8443;配置 `/opt/sandbox-worker/.env`
|
||||
|
||||
## 常用操作
|
||||
|
||||
```bash
|
||||
VERSION=0.1.0 bash install.sh # 安装指定版本(默认最新)
|
||||
bash install.sh --uninstall # 卸载(保留数据与 .env)
|
||||
journalctl -u <服务名> -f # 看日志(服务名 = miiot / cloud-mcp / sandbox-worker)
|
||||
```
|
||||
|
||||
## 版本与校验
|
||||
|
||||
- Release tag 格式:`<组件>-v<semver>`(如 `miiot-v0.1.0`),各组件独立演进
|
||||
- 每个 release 附 `SHA256SUMS`,安装脚本自动校验;手动下载请自行 `sha256sum -c`
|
||||
|
||||
@@ -23,9 +23,21 @@ KEEP_BACKUPS=3
|
||||
[[ "$(id -u)" == "0" ]] || { echo "❌ 请用 root / sudo 运行" >&2; exit 1; }
|
||||
[[ "$(uname -s)" == "Linux" ]] || { echo "❌ 仅支持 Linux" >&2; exit 1; }
|
||||
command -v curl >/dev/null || { echo "❌ 需要 curl" >&2; exit 1; }
|
||||
command -v node >/dev/null || { echo "❌ 需要 Node.js ≥ 22" >&2; exit 1; }
|
||||
NODE_MAJOR=$(node -e 'console.log(process.versions.node.split(".")[0])')
|
||||
[[ "${NODE_MAJOR}" -ge 22 ]] || { echo "❌ Node ≥ 22(当前 $(node -v))" >&2; exit 1; }
|
||||
# node 可能装在普通用户的 nvm 下(root PATH 看不见)。查找顺序:
|
||||
# 1. 显式 NODE_BIN 环境变量(自升级路径:worker 传自己的 process.execPath)
|
||||
# 2. PATH;3. 已装 unit 的 ExecStart(升级场景,上次装机时已解析对);4. SUDO_USER 登录环境
|
||||
NODE_BIN="${NODE_BIN:-}"
|
||||
[[ -n "${NODE_BIN}" && -x "${NODE_BIN}" ]] || NODE_BIN="$(command -v node || true)"
|
||||
if [[ -z "${NODE_BIN}" && -f "/etc/systemd/system/${SERVICE}.service" ]]; then
|
||||
NODE_BIN="$(awk -F= '/^ExecStart=/{print $2}' "/etc/systemd/system/${SERVICE}.service" | awk '{print $1}')"
|
||||
[[ -x "${NODE_BIN}" ]] || NODE_BIN=""
|
||||
fi
|
||||
if [[ -z "${NODE_BIN}" && -n "${SUDO_USER:-}" ]]; then
|
||||
NODE_BIN="$(su - "${SUDO_USER}" -c 'command -v node' 2>/dev/null || true)"
|
||||
fi
|
||||
[[ -n "${NODE_BIN}" ]] || { echo "❌ 需要 Node.js ≥ 22(NODE_BIN/PATH/unit/SUDO_USER 均未找到 node)" >&2; exit 1; }
|
||||
NODE_MAJOR=$("${NODE_BIN}" -e 'console.log(process.versions.node.split(".")[0])')
|
||||
[[ "${NODE_MAJOR}" -ge 22 ]] || { echo "❌ Node ≥ 22(当前 $("${NODE_BIN}" -v))" >&2; exit 1; }
|
||||
command -v docker >/dev/null || { echo "❌ 需要 Docker(worker 用它起沙箱容器)" >&2; exit 1; }
|
||||
|
||||
if [[ "${1:-}" == "--uninstall" ]]; then
|
||||
@@ -76,6 +88,9 @@ if [[ ! -f "${APP_DIR}/.env" ]]; then
|
||||
PORT=${PORT}
|
||||
# 调用方 x-api-key(已随机生成;server 侧需配同一值)
|
||||
WORKER_API_KEY=$(rnd)
|
||||
# Web UI 登录(/ui,查看连接信息 + 一键升级;首次安装随机密码,请尽快修改)
|
||||
SANDBOX_UI_USER=admin
|
||||
SANDBOX_UI_PASSWORD=$(rnd | head -c 16)
|
||||
ENV
|
||||
chmod 600 "${APP_DIR}/.env"
|
||||
fi
|
||||
@@ -88,7 +103,7 @@ Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=${APP_DIR}/.env
|
||||
ExecStart=$(command -v node) ${APP_DIR}/current/dist/server.js
|
||||
ExecStart=${NODE_BIN} ${APP_DIR}/current/dist/server.js
|
||||
WorkingDirectory=${APP_DIR}/current
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env bash
|
||||
# tunnel-agent 一键安装/升级(用户侧,Linux)。
|
||||
#
|
||||
# curl -fsSL https://git.lingyu.org.cn/lingyu-note/releases/raw/branch/main/tunnel-agent/install.sh \
|
||||
# | sudo TUNNEL_TOKEN=<控制台生成的令牌> bash
|
||||
#
|
||||
# 行为:下载二进制(amd64/arm64,SHA256 校验)→ /opt/tunnel-agent;首次生成
|
||||
# config.yaml —— 自动探测本机已装的 miiot(:8677)/ cloud-mcp(:8550)并读取其
|
||||
# .env 里的 MCP token 一并声明;systemd 常驻,断线自动重连。
|
||||
# 再次运行 = 升级(config 保留;旧二进制备份,健康失败回滚)。
|
||||
#
|
||||
# 可选:TUNNEL_SERVER 覆盖服务器地址(默认 wss://penna.pro/tunnel);VERSION 钉版本。
|
||||
set -euo pipefail
|
||||
|
||||
APP="tunnel-agent"
|
||||
GITEA="https://git.lingyu.org.cn"
|
||||
REL_REPO="lingyu-note/releases"
|
||||
APP_DIR="/opt/${APP}"
|
||||
SERVICE="${APP}"
|
||||
TUNNEL_SERVER="${TUNNEL_SERVER:-wss://penna.pro/tunnel}"
|
||||
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}/config.yaml 保留)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ---- 版本解析 ----
|
||||
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 "❌ 查询最新版本失败" >&2; exit 1; }
|
||||
fi
|
||||
TAG="${APP}-v${VER}"
|
||||
PKG="${APP}_${VER}_linux_${ARCH}.tar.gz"
|
||||
BASE="${GITEA}/${REL_REPO}/releases/download/${TAG}"
|
||||
|
||||
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"
|
||||
|
||||
# ---- 首次:生成 config.yaml(自动探测本机服务)----
|
||||
if [[ ! -f "${APP_DIR}/config.yaml" ]]; then
|
||||
[[ -n "${TUNNEL_TOKEN:-}" ]] || { echo "❌ 首次安装需要 TUNNEL_TOKEN(控制台「自托管节点」页生成)" >&2; exit 1; }
|
||||
echo "▶ 生成 ${APP_DIR}/config.yaml(自动探测本机服务)..."
|
||||
{
|
||||
echo "server: ${TUNNEL_SERVER}"
|
||||
echo "token: ${TUNNEL_TOKEN}"
|
||||
echo "services:"
|
||||
found=0
|
||||
# miiot(:8677)
|
||||
if curl -sf -o /dev/null --max-time 2 http://127.0.0.1:8677/healthz 2>/dev/null; then
|
||||
tok=$(grep -E '^MIOT_MCP_AUTH_TOKEN=' /opt/miiot/.env 2>/dev/null | cut -d= -f2- || true)
|
||||
echo " - svc: miiot"
|
||||
echo " port: 8677"
|
||||
[[ -n "$tok" ]] && echo " mcp_token: ${tok}"
|
||||
echo " mcp_endpoints:"
|
||||
echo " - { name: 小米设备, path: /mcp/miot-devices }"
|
||||
echo " - { name: 小米场景, path: /mcp/miot-scenes }"
|
||||
echo " - { name: 小米摄像头, path: /mcp/miot-cameras }"
|
||||
echo " - { name: 小米自动化, path: /mcp/miot-flows }"
|
||||
found=1
|
||||
echo " ✓ 探测到 miiot(:8677)" >&2
|
||||
fi
|
||||
# cloud-mcp(:8550)
|
||||
if curl -sf -o /dev/null --max-time 2 http://127.0.0.1:8550/healthz 2>/dev/null; then
|
||||
tok=$(grep -E '^LYMCP_MCP_AUTH_TOKEN=' /opt/cloud-mcp/.env 2>/dev/null | cut -d= -f2- || true)
|
||||
echo " - svc: cloud-mcp"
|
||||
echo " port: 8550"
|
||||
[[ -n "$tok" ]] && echo " mcp_token: ${tok}"
|
||||
echo " mcp_endpoints:"
|
||||
echo " - { name: 浏览器, path: /mcp/browser }"
|
||||
echo " - { name: 天气, path: /mcp/weather }"
|
||||
echo " - { name: 火车票, path: /mcp/train }"
|
||||
echo " - { name: 地图, path: /mcp/maps }"
|
||||
found=1
|
||||
echo " ✓ 探测到 cloud-mcp(:8550)" >&2
|
||||
fi
|
||||
if [[ "$found" == "0" ]]; then
|
||||
echo " # 未探测到本机服务 —— 按需手工声明后 systemctl restart ${SERVICE}"
|
||||
echo " # - svc: miiot"
|
||||
echo " # port: 8677"
|
||||
echo " # mcp_token: <MIOT_MCP_AUTH_TOKEN>"
|
||||
echo " # mcp_endpoints:"
|
||||
echo " # - { name: 小米设备, path: /mcp/miot-devices }"
|
||||
echo " ⚠️ 未探测到 miiot/cloud-mcp,请编辑 ${APP_DIR}/config.yaml 手工声明" >&2
|
||||
fi
|
||||
} > "${APP_DIR}/config.yaml"
|
||||
chmod 600 "${APP_DIR}/config.yaml"
|
||||
fi
|
||||
if [[ ! -f "/etc/systemd/system/${SERVICE}.service" ]]; then
|
||||
cat > "/etc/systemd/system/${SERVICE}.service" <<UNIT
|
||||
[Unit]
|
||||
Description=Lingyu Tunnel Agent (reverse tunnel)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Environment=TUNNEL_CONFIG=${APP_DIR}/config.yaml
|
||||
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
|
||||
|
||||
# ---- 替换二进制 + 重启(agent 无本地 HTTP,健康 = systemd active + 日志无 crash)----
|
||||
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"
|
||||
|
||||
systemctl restart "${SERVICE}"
|
||||
sleep 3
|
||||
if ! systemctl is-active --quiet "${SERVICE}"; then
|
||||
echo "✗ 服务未能启动,回滚" >&2
|
||||
journalctl -u "${SERVICE}" -n 20 --no-pager || true
|
||||
latest=$(ls -t "${APP_DIR}/bin/backup/" 2>/dev/null | head -1 || true)
|
||||
[[ -n "$latest" ]] && { cp "${APP_DIR}/bin/backup/${latest}" "${APP_DIR}/bin/${APP}"; systemctl restart "${SERVICE}"; }
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ ${APP} v${VER} 运行中 — 稍后到控制台「自托管节点」查看在线状态与已同步工具"
|
||||
echo " 日志:journalctl -u ${SERVICE} -f 配置:${APP_DIR}/config.yaml"
|
||||
Reference in New Issue
Block a user