Skip to content

Demo 站 VPS 部署手册

把 Familiars 的在线演示站demo.familiars.cn)部署到一台 CPU-only VPS 的完整手册。 演示站跑完整的 Electron 渲染 SPA 的 VITE_DEMO_MODE web 构建 + 只读的 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/distVITE_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.1src/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.target
bash
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:

  1. 顶部横幅提示“共享云端演示实例 · 只读沙箱”,第二行说明这里只有一个共享的云上 agent不提供 apps / 汇报 / 数字人 / 本机文件
  2. 首屏 POST /api/demo/token → 拿到 demo_session_* token → X-Bridge-Token 装好;

    演示 token 只在 /api/demo/*/api/chat/* 放行,其余 /api/* 一律 401 —— 公共 token 拿不到 workspace 里任何其他数据(不会被当成 bridge 全局 token 用)。

  3. 给 agent 0 发一条消息 → SSE /api/chat/stream 逐字回复(观察 nginx 日志无 buffering 卡顿);
  4. 点一个写型工具(如 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,不阻断发布)。

运维速查

操作命令
重启 bridgesystemctl restart familiars-demo
看日志journalctl -u familiars-demo -f
更新 SPA本机 VITE_DEMO_MODE=1 npm run buildscp -r electron/dist/*/opt/familiars-demo/spa/
更新 bridge 代码scp -r src//opt/familiars-demo/app/ → 重启服务
备份快照 /opt/familiars-demo/data/

相关

✨ Familiars · 多 Agent AI 桌面应用 —— 对话树记忆 · 长期记忆 · 工具系统 · 数字人 · Broker 生态