# 部署文档（DEPLOY.md）

> 三种部署形态 + 完整运维手册  
> 适用版本：v1.6.37（2026-06-07）

---

## 形态 A：远端 NAS（推荐 · 公司 13458）

### 适用场景
- 多人 / 跨机 / 跨网段访问
- 24×7 在线（NAS 不关机）
- 唯一权威 Hub，所有 IDE 共享

### 前置条件
| 项 | 要求 |
|:---|:---|
| NAS IP | 192.168.110.101（公司）/ 192.168.1.201（公司 LAN） |
| Node | v22（路径 `/usr/local/opt/node@22/bin/node`） |
| 端口 | 13458（HTTP） |
| 用户 | `fnvtk`（SSH） |
| 数据目录 | `/volume1/homes/fnvtk/.persistent-chat-local/` |

### 部署步骤

```bash
# 1. SSH 登录 NAS
ssh fnvtk@192.168.110.101

# 2. 创建部署目录
mkdir -p /volume1/homes/fnvtk/.persistent-chat-local
cd /volume1/homes/fnvtk/.persistent-chat-local

# 3. 上传 6 个核心文件
# （用 scp / git pull / rsync 任一方式）
# 必需文件：server.js / hub.js / panel.html / ensure-hub.sh / mongo_sync.py / cursor-title.js

# 4. 设置权限
chmod +x ensure-hub.sh

# 5. 启动 Hub（前台调试用）
/usr/local/opt/node@22/bin/node hub.js &

# 6. 验证
curl http://192.168.110.101:13458/api/health
# 期望：{"ok":true,"pid":<X>,"panelVersion":"1.6.37"}
```

### 后台守护（用 nohup）

```bash
cd /volume1/homes/fnvtk/.persistent-chat-local
nohup /usr/local/opt/node@22/bin/node hub.js > logs/hub.log 2>&1 &
disown
echo $! > /tmp/pchat-hub.pid
```

### 系统级守护（Synology Task Scheduler）

1. DSM 控制面板 → 任务计划
2. 新增 → 触发的任务 → 用户自定义脚本
3. **事件**：开机 / 启动
4. **用户**：root
5. **脚本**：
   ```bash
   #!/bin/bash
   cd /volume1/homes/fnvtk/.persistent-chat-local
   /usr/local/opt/node@22/bin/node hub.js >> /volume1/homes/fnvtk/.persistent-chat-local/logs/hub.log 2>&1 &
   ```

---

## 形态 B：本地备份（127.0.0.1:13458）

### 适用场景
- 单机 fallback（远端挂时自动接上）
- 开发调试（启停方便）
- 离线工作

### macOS launchd 守护（推荐）

```bash
# 1. 写 plist
cat > ~/Library/LaunchAgents/com.karuo.persistent-chat-hub.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.karuo.persistent-chat-hub</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/opt/node@22/bin/node</string>
    <string>/Users/karuo/.persistent-chat-local/hub.js</string>
  </array>
  <key>WorkingDirectory</key><string>/Users/karuo/.persistent-chat-local</string>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/Users/karuo/.persistent-chat-local/logs/launchd.log</string>
  <key>StandardErrorPath</key><string>/Users/karuo/.persistent-chat-local/logs/launchd.err</string>
</dict>
</plist>
EOF

# 2. 加载
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.karuo.persistent-chat-hub.plist

# 3. 验证
launchctl print gui/$UID/com.karuo.persistent-chat-hub | head -10
curl http://127.0.0.1:13458/api/health
```

### 启停命令

```bash
# 启动
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/com.karuo.persistent-chat-hub.plist

# 停止
launchctl bootout gui/$UID ~/Library/LaunchAgents/com.karuo.persistent-chat-hub.plist

# 状态
launchctl print gui/$UID/com.karuo.persistent-chat-hub

# 日志
tail -f /Users/karuo/.persistent-chat-local/logs/launchd.log
```

---

## 形态 C：Trae / Cursor 插件（stdio MCP）

### 适用场景
- 直接在 IDE 里用
- 与 Hub 通过 stdio 通信（ensure-hub.sh 桥接）

### 部署步骤

```bash
# 1. 创建本地目录
mkdir -p ~/.persistent-chat-local

# 2. 拷贝核心文件
cp src/server.js src/hub.js src/cursor-title.js ~/.persistent-chat-local/
cp web/panel.html ~/.persistent-chat-local/panel.html
cp scripts/ensure-hub.sh ~/.persistent-chat-local/
chmod +x ~/.persistent-chat-local/ensure-hub.sh

# 3. 编辑 MCP 配置（见 mcp-config/trae.mcp.json）
```

### ensure-hub.sh 原理

```
MCP stdio ←→ ensure-hub.sh ←→ Hub HTTP (13458)
```

- 收到 MCP 调用 → 转发到 Hub HTTP
- Hub 持久挂着，session 不丢
- IDE 重启不影响 Hub 状态

---

## 三形态组合

**推荐**：远端 NAS 为主（24×7），本地为备份（断网续命），Trae 插件为入口。

```
Trae ←stdio→ ensure-hub.sh ←HTTP→ [本地 13458] (fallback)
                                    ↑
                                [远端 192.168.110.101:13458] (主)
```

通过 `PCHAT_HUB_HOST` 环境变量切换。

---

## 验证清单

部署完后挨个跑：

```bash
# 1. 健康
curl http://192.168.110.101:13458/api/health
curl http://127.0.0.1:13458/api/health

# 2. session list
curl http://192.168.110.101:13458/api/state | python3 -m json.tool

# 3. 面板可访问
open http://192.168.110.101:13458/
open http://127.0.0.1:13458/

# 4. Trae 插件已加载
ps aux | grep "server.js" | grep -v grep
# 期望：看到 /Users/karuo/.persistent-chat-local/server.js

# 5. 跑 init + select + wait 三步
```

---

## 升级流程

```bash
# 1. 备份
cp -r ~/.persistent-chat-local ~/.persistent-chat-local.bak.$(date +%Y%m%d)
ssh fnvtk@192.168.110.101 "cp -r /volume1/homes/fnvtk/.persistent-chat-local /volume1/homes/fnvtk/.persistent-chat-local.bak.\$(date +%Y%m%d)"

# 2. 拉新代码
cd /Users/karuo/Documents/个人/persistent-chat-plugin
git pull

# 3. 覆盖核心文件（不动 data/）
cp src/* ~/.persistent-chat-local/
cp web/panel.html ~/.persistent-chat-local/

# 4. 远端同步
scp src/* fnvtk@192.168.110.101:/volume1/homes/fnvtk/.persistent-chat-local/
scp web/panel.html fnvtk@192.168.110.101:/volume1/homes/fnvtk/.persistent-chat-local/

# 5. 重启 Hub（不影响 session 文件）
launchctl bootout gui/$UID/com.karuo.persistent-chat-hub
launchctl bootstrap gui/$UID/com.karuo.persistent-chat-hub.plist

ssh fnvtk@192.168.110.101 "pkill -f 'node hub.js' ; cd /volume1/homes/fnvtk/.persistent-chat-local && nohup /usr/local/opt/node@22/bin/node hub.js > logs/hub.log 2>&1 &"

# 6. Trae Cmd+Q 重开
```

---

## 数据备份

- `sessions.json`（远端/本地）：所有 session 状态
- `bindings.json`：工作区→token 绑定
- `plan-bindings.json`：计划→token 绑定
- `thread-bindings/`：线程→token 绑定
- `logs/`：运行日志

**建议**：每天 cron 备份整个 `.persistent-chat-local` 目录到 NAS / 云盘。

```bash
# cron 任务：每天 02:00 备份
0 2 * * * tar czf ~/backups/pchat-$(date +\%Y\%m\%d).tar.gz ~/.persistent-chat-local
```

---

## 故障排查

| 症状 | 原因 | 处理 |
|:---|:---|:---|
| 面板打不开 | Hub 未启动 | `launchctl print gui/$UID/com.karuo.persistent-chat-hub` |
| `Token未获取` | Trae 未重启 | `Cmd+Q` 完全退出重开 |
| session 串台 | 多用户同 IP | 加 `hostIp` 区分 / 改 cursorTitle |
| Hub 占 CPU | 短轮询频繁 | 改 `POLL_TIMEOUT_MS` 到 30000 |
| 数据丢失 | 没备份 | 立刻 cron 跑起来 |

---

## 安全注意

- 13458 端口默认仅监听 LAN IP，**不暴露公网**
- 跨网段访问靠 SSH 隧道或 VPN
- 永远不要把 `127.0.0.1:13458` 暴露到 0.0.0.0

---

## 监控

```bash
# Hub 状态
watch -n 5 'curl -s http://192.168.110.101:13458/api/health | python3 -c "import json,sys;d=json.load(sys.stdin);print(d.get(\"panelVersion\"),d.get(\"pid\"),\"sessions=\"+str(len(json.load(open(\"/dev/null\")))))" 2>/dev/null'

# session 数量
curl -s http://192.168.110.101:13458/api/state | python3 -c "import json,sys;print(len(json.load(sys.stdin)['sessions']))"
```

---

> 最后更新：2026-06-26 · 卡若AI
