Appearance
Demo 站 VPS 部署手册
把 Familiars 的在线演示站(
demo.familiars.cn)部署到一台 CPU-only VPS 的完整手册。 演示站跑完整的 Electron 渲染 SPA 的VITE_DEMO_MODEweb 构建 + 只读的 CPU-only bridge(不加载本地模型),通过 nginx 同源/api反代到 bridge(proxy_buffering off保 SSE 聊天流)。
架构
浏览器 → Cloudflare → Caddy/nginx :443 (TLS)
└── / → 静态 SPA(electron/dist,`VITE_DEMO_MODE=1` 构建)
└── /api/* → 反代到 127.0.0.1:18711(bridge,FAMILIARS_HTTP_INSECURE=1)
proxy_buffering off(SSE 聊天/状态流)- 演示 bridge 是独立进程,专用
WORKSPACE_ROUTE+FAMILIARS_DB_PATH(与生产/你本机完全隔离)。 - 只开
FAMILIARS_APPROVAL_DISABLED=1仅当需要跳过工具审批(演示默认开着审批,展示真实流程)。 - 绝不暴露
POST /api/demo/token给非演示环境 —— 该端点会返回当前 workspace 的 bridge token。
前置
- 一台 VPS(2C4G 即可;无需 GPU),Ubuntu 22.04/24.04。
- 域名:
demo.familiars.cn(加一条 A 记录到 VPS IP;若用 Cloudflare 则橙色云 + Caddy)。 - 本机仓库里有
electron/dist(VITE_DEMO_MODE=1构建)与src/。
第 1 步:构建演示 SPA
在本机(仓库根):
bash
cd electron
VITE_DEMO_MODE=1 npm run build # Windows: set VITE_DEMO_MODE=1 && npm run build产物:electron/dist/(纯静态 Vue SPA,base: './',无 electronAPI 时自动走 window.location.origin, 启动时 POST /api/demo/token 拿公共演示 token)。
第 2 步:准备服务器
bash
# 系统依赖(sqlite 由 Python 内置,主要是 python3.11 + venv)
apt update && apt install -y python3.11 python3.11-venv nginx创建目录并拷贝:
bash
mkdir -p /opt/familiars-demo/{app,spa,data}
# 从本机把 src/ 与 requirements.txt 复制过去
scp -r src/ root@<VPS>:/opt/familiars-demo/app/
scp requirements.txt root@<VPS>:/opt/familiars-demo/
scp -r electron/dist root@<VPS>:/opt/familiars-demo/spa/第 3 步:装依赖 + 写环境
bash
cd /opt/familiars-demo
python3.11 -m venv .venv
.venv/bin/pip install -r requirements.txt.env(演示专用;LLM 端点按你熟悉的配):
bash
# LLM(演示也要用到 —— agent 的对话/生成都走这个 OpenAI 兼容端点)
BASE_URL=https://你的-llm-网关/v1
API_KEY=sk-xxxx
DEFAULT_MODEL=你的模型名
MANAGER_MODEL=你的模型名
# 演示隔离(关键)
FAMILIARS_DB_PATH=/opt/familiars-demo/data/demo.db
WORKSPACE_ROUTE=/opt/familiars-demo/data/workspace
# 桥接回退到明文 HTTP(nginx 在同一台机器上做 /api 反代,不需要 HTTPS 动机)
FAMILIARS_HTTP_INSECURE=1
# 每租户:官方桥把 WORKSPACE_ROUTE/FAMILIARS_DB_PATH 按用户 $HOME 分开即可
# (见本手册末尾“每租户隔离”)
WORKSPACE_ROUTE必须与src.base.config的默认 workspace 布局兼容 —— 它会在此目录下生成agents/、familiars.db(若未指定FAMILIARS_DB_PATH)等。Demo 各用户分开则另起进程。
第 4 步:起 bridge(CPU-only)
bash
cd /opt/familiars-demo
WORKSPACE_ROUTE=/opt/familiars-demo/data/workspace \
FAMILIARS_DB_PATH=/opt/familiars-demo/data/demo.db \
FAMILIARS_HTTP_INSECURE=1 \
FAMILIARS_BROKER_BIND=0.0.0.0 \
FAMILIARS_APPROVAL_DISABLED=0 \
.venv/bin/python -m src.bridge.run
FAMILIARS_BROKER_BIND=0.0.0.0必须设。bridge 默认只监听127.0.0.1(src/bridge/run.py:_bind_host),而 nginx/Caddy 是另一个容器,它只能 通过宿主机网桥(172.18.0.1/host.docker.internal)回连 —— 若 bridge 绑在 loopback,反代必然 502。绑0.0.0.0后请务必确保防火墙 / 安全组不把 18711 暴露到公网 (POST /api/demo/token会返回该 workspace 的 bridge token,公网可达等于 完全接管演示数据的权限)。
系统服务(systemd,/etc/systemd/system/familiars-demo.service):
ini
[Unit]
Description=Familiars Demo Bridge
After=network.target
[Service]
User=root
WorkingDirectory=/opt/familiars-demo
EnvironmentFile=/opt/familiars-demo/.env
Environment=FAMILIARS_BROKER_BIND=0.0.0.0
ExecStart=/opt/familiars-demo/.venv/bin/python -m src.bridge.run
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.targetbash
systemctl daemon-reload && systemctl enable --now familiars-demo
curl http://127.0.0.1:18711/api/demo/status # 期望: {"ok":true,...}
curl http://172.18.0.1:18711/api/demo/status # 容器侧验证(与 Caddy 反代同路径)第 5 步:nginx 同源反代 + 静态站点
/etc/nginx/sites-available/familiars-demo:
nginx
server {
listen 80;
server_name demo.familiars.cn;
root /opt/familiars-demo/spa;
# —— SPA ——
location / {
try_files $uri $uri/ /index.html;
}
# —— /api 反代到 bridge(SSE 必须关闭 buffering)——
location /api/ {
proxy_pass http://127.0.0.1:18711;
proxy_http_version 1.1;
proxy_buffering off;
proxy_cache off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
client_max_body_size 50m;
}bash
ln -s /etc/nginx/sites-available/familiars-demo /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx若套 Cloudflare 且想上 HTTPS,把
listen 80加证书即可;或加一层 Caddy。核心不变: 同源/api反代 +proxy_buffering off。
验证(期望看到的直播状态)
浏览器打开 https://demo.familiars.cn/ 应看到 SPA:
- 顶部横幅提示“共享云端演示实例 · 只读沙箱”,第二行说明这里只有一个共享的云上 agent、 不提供 apps / 汇报 / 数字人 / 本机文件;
- 首屏
POST /api/demo/token→ 拿到demo_session_*token →X-Bridge-Token装好;演示 token 只在
/api/demo/*与/api/chat/*放行,其余/api/*一律 401 —— 公共 token 拿不到 workspace 里任何其他数据(不会被当成 bridge 全局 token 用)。 - 给 agent 0 发一条消息 → SSE
/api/chat/stream逐字回复(观察 nginx 日志无 buffering 卡顿); - 点一个写型工具(如
write/exec)→ 拿到的是一条明确的「云端演示沙箱:只读演示,不会真实写入」 的模拟返回,而不是卡死或报错。
会话隔离("一个共享工作区 + 独立 token")
演示采用 shared-workspace + per-session token 隔离,而不是每用户一进程:
- 所有访问者连到同一个演示 bridge 进程、同一个只读工作区(
WORKSPACE_ROUTE)。 - 每人调用
/token领到独立的demo_session_*token(6 小时过期); 这个 token 只放行/api/demo/*(状态/反馈)与/api/chat/*(聊天 SSE)。 - 聊天、待办、记忆 RAG 都是真实后端在共享工作区里跑 —— 所以只读数据(比如 agents 列表、 记忆索引)在所有访问者眼里是同一份;而写型工具被 gate 模拟掉,不会落进共享工作区。
权衡:solo-agent 聊天用 DemoWorklogScope 是纯样本的;如果未来要「每个用户真有自己的记忆空间」, 就得回到每租户进程(独立 WORKSPACE_ROUTE + FAMILIARS_DB_PATH),把第 4 步包进 run-user.sh 按用户分开 —— 见下一节。
每租户隔离(多用户版)
当前演示已经是 shared-workspace + demo token(见上节)—— 两个都想要"每个用户独立的 记忆/任务空间"时,才需要切到这下面的每进程模型。
想要每个演示用户一个独立数据库(避免互相看到对话/任务),把第 4 步的启动命令包进一个 /opt/familiars-demo/run-user.sh,按 $1(用户名)分开:
bash
#!/usr/bin/env bash
USER="$1"
DB="/opt/familiars-demo/data/users/${USER}/demo.db"
WS="/opt/familiars-demo/data/users/${USER}/workspace"
mkdir -p "$(dirname "$DB")" "$WS"
WORKSPACE_ROUTE="$WS" FAMILIARS_DB_PATH="$DB" FAMILIARS_HTTP_INSECURE=1 FAMILIARS_BROKER_BIND=0.0.0.0 \
/opt/familiars-demo/.venv/bin/python -m src.bridge.run然后为每个用户起一个 systemd instance 或再套一层进程管理(supervisord 的 program 组、@ service 模板均可)。每个进程独立端口,nginx 用不同 location/子路径(或子域名)分发。
演示数据引导(demo_bootstrap)
对话框真正要用的公会屋子需要在演示数据里预置。首次启动后可执行:
bash
WORKSPACE_ROUTE=/opt/familiars-demo/data/workspace FAMILIARS_HTTP_INSECURE=1 \
FAMILIARS_DB_PATH=/opt/familiars-demo/data/demo.db \
.venv/bin/python -c "
from src.bridge.deps import get_broker_hub
from src.broker.workshop import repos as wr
conn = get_broker_hub().conn
# 示例:为反馈/演示各建一屋(room_id 固定,`FAMILIAR` 屋作为公开反馈接收处)
wr.create_room(conn, broker_id='<broker_id>', name='Familiar', room_id='FAMILIAR-DEMO',
created_by_cluster_id='demo')
print('rooms ok')
"两个固定 room(Familiar=公开反馈/演示;feedback-official=仅官方 bot owner 看到)在部署文档里用 bot 钱包预置 ≥20 VTP(auto_reward_feedback 每任务奖励 20,余额不足会标 reward_pending,不阻断发布)。
运维速查
| 操作 | 命令 |
|---|---|
| 重启 bridge | systemctl restart familiars-demo |
| 看日志 | journalctl -u familiars-demo -f |
| 更新 SPA | 本机 VITE_DEMO_MODE=1 npm run build → scp -r electron/dist/* 到 /opt/familiars-demo/spa/ |
| 更新 bridge 代码 | scp -r src/ 到 /opt/familiars-demo/app/ → 重启服务 |
| 备份 | 快照 /opt/familiars-demo/data/ |
相关
- Demo 页:/demo/
- 反馈入库(Ventaris 任务 + 20 VTP):/feedback/
