Skip to content

Commit 9d5a66b

Browse files
nieaoclaude
andcommitted
feat(deploy): VPS LLM 代理 + 新机一键部署 (66.245.216.250)
新增 server/vps-llm-proxy.js — 把 /canvas/api/llm/chat 转发到 DeepSeek (或其它 OpenAI 兼容 endpoint), 凭据保管在 systemd EnvironmentFile, 不暴露给浏览器. 部署组件: - deploy/know-canvas-llm-proxy.service — systemd unit, 读 /etc/know-canvas/llm.env - deploy/Caddyfile.newvps — IP 模式 :80, /canvas/ 静态 + /yws/ WS + /canvas/api/llm/ 反代 - deploy/deploy-newvps.sh — SSH 一键部署 (探测 → build → rsync → systemd → caddy → 健康检查) 用法: DEEPSEEK_API_KEY=sk-xxx bash deploy/deploy-newvps.sh Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 67c1f0f commit 9d5a66b

6 files changed

Lines changed: 461 additions & 0 deletions

File tree

deploy/Caddyfile.newvps

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Know Canvas — IP-based deploy on new VPS (66.245.216.250)
2+
#
3+
# 没有域名, 走 :80 HTTP (后续如配域名再换 HTTPS).
4+
#
5+
# 路径布局:
6+
# / → 提示页 (默认重定向到 /canvas/)
7+
# /canvas/ → 前端 build 静态产物
8+
# /yws/ → Yjs 协作 WebSocket (反代 127.0.0.1:1234)
9+
# /canvas/api/llm/ → DeepSeek 代理 (反代 127.0.0.1:17080)
10+
#
11+
# 安装:
12+
# sudo cp deploy/Caddyfile.newvps /etc/caddy/Caddyfile
13+
# sudo caddy validate --config /etc/caddy/Caddyfile
14+
# sudo systemctl reload caddy
15+
#
16+
# 验证:
17+
# curl -I http://66.245.216.250/canvas/
18+
# curl http://66.245.216.250/canvas/api/llm/health
19+
20+
{
21+
# 全局选项
22+
admin off
23+
# 日志走 journal
24+
log {
25+
output stderr
26+
level INFO
27+
}
28+
}
29+
30+
:80 {
31+
# 默认根路径直接 302 到 /canvas/
32+
@root path /
33+
redir @root /canvas/ 302
34+
35+
# === Know Canvas 静态前端 ===
36+
handle_path /canvas/api/llm/* {
37+
reverse_proxy 127.0.0.1:17080 {
38+
header_up Host {host}
39+
header_up X-Real-IP {remote_host}
40+
}
41+
}
42+
43+
handle_path /canvas/api/log {
44+
reverse_proxy 127.0.0.1:18091/log {
45+
header_up Host {host}
46+
header_up X-Real-IP {remote_host}
47+
}
48+
}
49+
handle_path /canvas/api/log/* {
50+
reverse_proxy 127.0.0.1:18091 {
51+
header_up Host {host}
52+
header_up X-Real-IP {remote_host}
53+
}
54+
}
55+
56+
# === Yjs WebSocket sync ===
57+
handle_path /yws/* {
58+
reverse_proxy 127.0.0.1:1234 {
59+
header_up Host {host}
60+
header_up X-Real-IP {remote_host}
61+
header_up Connection {>Connection}
62+
header_up Upgrade {>Upgrade}
63+
}
64+
}
65+
66+
# === Know Canvas 静态资源 (放在 api 反代之后, 顺序很重要) ===
67+
handle_path /canvas/* {
68+
root * /var/www/know-canvas
69+
try_files {path} /index.html
70+
file_server
71+
encode gzip
72+
}
73+
74+
# 兜底: 其它路径返回简单提示
75+
handle {
76+
respond "Know Canvas — go to /canvas/" 200
77+
}
78+
}

deploy/deploy-newvps.sh

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/env bash
2+
# Know Canvas — 一键部署到新 VPS (66.245.216.250:8765)
3+
#
4+
# 用法 (本地开发机):
5+
# DEEPSEEK_API_KEY=sk-xxx bash deploy/deploy-newvps.sh
6+
#
7+
# 可选环境变量:
8+
# REMOTE_USER 默认 root
9+
# REMOTE_HOST 默认 66.245.216.250
10+
# REMOTE_PORT 默认 8765
11+
# DEEPSEEK_API_KEY 必填; 写入远端 /etc/know-canvas/llm.env (不进 git)
12+
# LLM_BASE_URL 默认 https://api.deepseek.com/v1
13+
# LLM_MODEL 默认 deepseek-chat
14+
#
15+
# 干什么:
16+
# 1. 本地 npm run build:canvas
17+
# 2. SSH 远端: 检查 / 安装 caddy + node 22
18+
# 3. rsync dist/ → /var/www/know-canvas/
19+
# 4. rsync server/ → /opt/know-canvas/server/ + npm install --production
20+
# 5. 写 /etc/know-canvas/llm.env (LLM_API_KEY + base url + model)
21+
# 6. 装 systemd unit (yws + llm-proxy), enable + start
22+
# 7. 装 Caddyfile, reload caddy
23+
# 8. 健康检查 /canvas/ + /canvas/api/llm/health
24+
25+
set -euo pipefail
26+
27+
REMOTE_USER="${REMOTE_USER:-root}"
28+
REMOTE_HOST="${REMOTE_HOST:-66.245.216.250}"
29+
REMOTE_PORT="${REMOTE_PORT:-8765}"
30+
LLM_BASE_URL="${LLM_BASE_URL:-https://api.deepseek.com/v1}"
31+
LLM_MODEL="${LLM_MODEL:-deepseek-chat}"
32+
33+
if [ -z "${DEEPSEEK_API_KEY:-}" ]; then
34+
echo "ERROR: DEEPSEEK_API_KEY 必填"
35+
echo "用法: DEEPSEEK_API_KEY=sk-xxx bash deploy/deploy-newvps.sh"
36+
exit 1
37+
fi
38+
39+
REMOTE="${REMOTE_USER}@${REMOTE_HOST}"
40+
SSH_OPTS="-p ${REMOTE_PORT} -o StrictHostKeyChecking=accept-new"
41+
RSYNC_SSH="ssh -p ${REMOTE_PORT} -o StrictHostKeyChecking=accept-new"
42+
PROJECT_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
43+
44+
echo "[deploy-newvps] 项目根: $PROJECT_ROOT"
45+
echo "[deploy-newvps] 目标: $REMOTE (port $REMOTE_PORT)"
46+
echo "[deploy-newvps] 上游: $LLM_BASE_URL model=$LLM_MODEL"
47+
echo ""
48+
49+
# 1. 本地 build
50+
echo "==> 1/8 本地 build (base=/canvas/)"
51+
cd "$PROJECT_ROOT"
52+
npm run build:canvas
53+
if [ ! -d "dist" ]; then
54+
echo "ERROR: dist/ 不存在 — build 失败"
55+
exit 1
56+
fi
57+
58+
# 2. 远端环境探测 + 安装依赖
59+
echo ""
60+
echo "==> 2/8 探测远端环境 (caddy / node)"
61+
ssh $SSH_OPTS "$REMOTE" 'bash -s' <<'REMOTE_PROBE'
62+
set -e
63+
echo "[probe] OS: $(. /etc/os-release && echo $PRETTY_NAME)"
64+
echo "[probe] uname: $(uname -a)"
65+
66+
# Node
67+
if ! command -v node >/dev/null 2>&1; then
68+
echo "[probe] 装 Node 22..."
69+
apt update
70+
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
71+
apt install -y nodejs
72+
fi
73+
echo "[probe] node: $(node -v)"
74+
75+
# Caddy
76+
if ! command -v caddy >/dev/null 2>&1; then
77+
echo "[probe] 装 Caddy..."
78+
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
79+
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
80+
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
81+
apt update
82+
apt install -y caddy
83+
fi
84+
echo "[probe] caddy: $(caddy version | head -1)"
85+
86+
# rsync
87+
if ! command -v rsync >/dev/null 2>&1; then
88+
apt install -y rsync
89+
fi
90+
91+
# 准备目录
92+
mkdir -p /opt/know-canvas/server /var/www/know-canvas /etc/know-canvas
93+
chown -R www-data:www-data /opt/know-canvas /var/www/know-canvas
94+
echo "[probe] 目录就绪"
95+
REMOTE_PROBE
96+
97+
# 3. 上传前端
98+
echo ""
99+
echo "==> 3/8 rsync dist/ → /var/www/know-canvas/"
100+
rsync -az --delete -e "$RSYNC_SSH" "$PROJECT_ROOT/dist/" "$REMOTE:/var/www/know-canvas/"
101+
ssh $SSH_OPTS "$REMOTE" "chown -R www-data:www-data /var/www/know-canvas"
102+
103+
# 4. 上传 server + 装依赖
104+
echo ""
105+
echo "==> 4/8 rsync server/ → /opt/know-canvas/server/"
106+
rsync -az --delete --exclude='node_modules' --exclude='yjs-data' \
107+
-e "$RSYNC_SSH" \
108+
"$PROJECT_ROOT/server/" "$REMOTE:/opt/know-canvas/server/"
109+
110+
ssh $SSH_OPTS "$REMOTE" 'set -e
111+
cd /opt/know-canvas/server
112+
npm install --production
113+
mkdir -p yjs-data
114+
chown -R www-data:www-data /opt/know-canvas
115+
'
116+
117+
# 5. 写 /etc/know-canvas/llm.env
118+
echo ""
119+
echo "==> 5/8 写 /etc/know-canvas/llm.env (含 DeepSeek key)"
120+
ssh $SSH_OPTS "$REMOTE" "set -e
121+
cat > /etc/know-canvas/llm.env <<EOF
122+
LLM_API_KEY=${DEEPSEEK_API_KEY}
123+
LLM_BASE_URL=${LLM_BASE_URL}
124+
LLM_MODEL=${LLM_MODEL}
125+
EOF
126+
chmod 600 /etc/know-canvas/llm.env
127+
chown root:root /etc/know-canvas/llm.env
128+
echo '[llm.env] 已写入'
129+
"
130+
131+
# 6. 装 systemd units
132+
echo ""
133+
echo "==> 6/8 装 systemd unit (yws + llm-proxy)"
134+
scp -P "$REMOTE_PORT" \
135+
"$PROJECT_ROOT/deploy/know-canvas-yws.service" \
136+
"$PROJECT_ROOT/deploy/know-canvas-llm-proxy.service" \
137+
"$REMOTE:/tmp/"
138+
139+
ssh $SSH_OPTS "$REMOTE" 'set -e
140+
mv /tmp/know-canvas-yws.service /etc/systemd/system/
141+
mv /tmp/know-canvas-llm-proxy.service /etc/systemd/system/
142+
systemctl daemon-reload
143+
systemctl enable know-canvas-yws know-canvas-llm-proxy
144+
systemctl restart know-canvas-yws know-canvas-llm-proxy
145+
sleep 2
146+
echo "--- yws ---"
147+
systemctl status know-canvas-yws --no-pager -l | head -15
148+
echo "--- llm-proxy ---"
149+
systemctl status know-canvas-llm-proxy --no-pager -l | head -15
150+
'
151+
152+
# 7. 装 Caddyfile
153+
echo ""
154+
echo "==> 7/8 装 Caddyfile + reload"
155+
scp -P "$REMOTE_PORT" "$PROJECT_ROOT/deploy/Caddyfile.newvps" "$REMOTE:/tmp/Caddyfile"
156+
ssh $SSH_OPTS "$REMOTE" 'set -e
157+
mv /tmp/Caddyfile /etc/caddy/Caddyfile
158+
caddy validate --config /etc/caddy/Caddyfile
159+
systemctl reload caddy || systemctl restart caddy
160+
sleep 1
161+
systemctl status caddy --no-pager -l | head -10
162+
'
163+
164+
# 8. 健康检查
165+
echo ""
166+
echo "==> 8/8 健康检查"
167+
ssh $SSH_OPTS "$REMOTE" 'set +e
168+
echo "--- 本机 yws health ---"
169+
curl -sf http://127.0.0.1:1234/health && echo
170+
echo "--- 本机 llm-proxy health ---"
171+
curl -sf http://127.0.0.1:17080/health && echo
172+
echo "--- 通过 caddy /canvas/ ---"
173+
curl -sI http://127.0.0.1/canvas/ | head -3
174+
echo "--- 通过 caddy /canvas/api/llm/health ---"
175+
curl -sf http://127.0.0.1/canvas/api/llm/health && echo
176+
'
177+
178+
echo ""
179+
echo "============================================================"
180+
echo "部署完成。访问:"
181+
echo ""
182+
echo " http://${REMOTE_HOST}/canvas/"
183+
echo ""
184+
echo "排查:"
185+
echo " ssh -p ${REMOTE_PORT} ${REMOTE} 'journalctl -u know-canvas-yws -n 50'"
186+
echo " ssh -p ${REMOTE_PORT} ${REMOTE} 'journalctl -u know-canvas-llm-proxy -n 50'"
187+
echo " ssh -p ${REMOTE_PORT} ${REMOTE} 'journalctl -u caddy -n 50'"
188+
echo ""
189+
echo "更换 API key:"
190+
echo " ssh -p ${REMOTE_PORT} ${REMOTE} 'nano /etc/know-canvas/llm.env'"
191+
echo " ssh -p ${REMOTE_PORT} ${REMOTE} 'systemctl restart know-canvas-llm-proxy'"
192+
echo "============================================================"
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Know Canvas LLM Proxy systemd unit
2+
#
3+
# 把前端 /canvas/api/llm/* 请求转发到 DeepSeek (或其它 OpenAI 兼容 endpoint)
4+
# 凭据保管在 /etc/know-canvas/llm.env, 不暴露给浏览器
5+
#
6+
# 安装:
7+
# sudo mkdir -p /etc/know-canvas
8+
# sudo tee /etc/know-canvas/llm.env >/dev/null <<'EOF'
9+
# LLM_API_KEY=sk-xxxxxxxxxxxx
10+
# LLM_BASE_URL=https://api.deepseek.com/v1
11+
# LLM_MODEL=deepseek-chat
12+
# EOF
13+
# sudo chmod 600 /etc/know-canvas/llm.env
14+
#
15+
# sudo cp deploy/know-canvas-llm-proxy.service /etc/systemd/system/
16+
# sudo systemctl daemon-reload
17+
# sudo systemctl enable know-canvas-llm-proxy
18+
# sudo systemctl start know-canvas-llm-proxy
19+
# sudo systemctl status know-canvas-llm-proxy
20+
#
21+
# 查看日志:
22+
# sudo journalctl -u know-canvas-llm-proxy -f
23+
#
24+
# 更换 API key:
25+
# sudo nano /etc/know-canvas/llm.env
26+
# sudo systemctl restart know-canvas-llm-proxy
27+
28+
[Unit]
29+
Description=Know Canvas LLM Proxy (DeepSeek / OpenAI compatible)
30+
After=network.target
31+
32+
[Service]
33+
Type=simple
34+
User=www-data
35+
Group=www-data
36+
WorkingDirectory=/opt/know-canvas/server
37+
Environment=NODE_ENV=production
38+
Environment=HOST=127.0.0.1
39+
Environment=PORT=17080
40+
EnvironmentFile=/etc/know-canvas/llm.env
41+
42+
ExecStart=/usr/bin/node /opt/know-canvas/server/vps-llm-proxy.js
43+
Restart=on-failure
44+
RestartSec=5
45+
StandardOutput=journal
46+
StandardError=journal
47+
48+
# 资源限制 (LLM 转发不需要太多内存)
49+
MemoryMax=256M
50+
CPUQuota=30%
51+
52+
# 安全加固
53+
NoNewPrivileges=true
54+
ProtectSystem=strict
55+
ProtectHome=true
56+
PrivateTmp=true
57+
58+
[Install]
59+
WantedBy=multi-user.target

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"yws": "node server/y-ws-server.js",
1414
"bridge": "node server/claude-bridge.js",
1515
"hermes": "node server/hermes-proxy.js",
16+
"llm-proxy": "node server/vps-llm-proxy.js",
1617
"test:e2e": "playwright test",
1718
"test:e2e:ui": "playwright test --ui",
1819
"deploy": "bash deploy/deploy.sh"

server/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"yws": "node y-ws-server.js",
1010
"bridge": "node claude-bridge.js",
1111
"hermes": "node hermes-proxy.js",
12+
"llm-proxy": "node vps-llm-proxy.js",
1213
"dispatcher": "node orchestra-dispatcher.js",
1314
"worker:hermes": "node orchestra-hermes-worker.js",
1415
"conductor": "node orchestra-conductor.js",

0 commit comments

Comments
 (0)