Demo Site VPS Deploy Manual
Complete manual for deploying the online demo site of Familiars (
demo.familiars.cn) on a CPU-only VPS. The demo site runs the full Electron-rendered SPA'sVITE_DEMO_MODEweb build + a CPU-only bridge (no local models loaded), with nginx same-origin/apireverse proxy to the bridge (proxy_buffering offto preserve SSE chat streams).The demo is functionally complete — file generation, CreativeWorkshop, scheduling, todos, memory RAG, browser automation all work for real in the isolated demo workspace. Only OS-control tools (shell execution, input control, phone, social) and GPU-intensive features are blocked.
Architecture
Browser → Cloudflare → Caddy/nginx :443 (TLS)
└── / → Static SPA (electron/dist, built with `VITE_DEMO_MODE=1`)
└── /api/* → Reverse proxy to 127.0.0.1:18711 (bridge, FAMILIARS_HTTP_INSECURE=1)
proxy_buffering off (SSE chat/status streams)- The demo bridge is an independent process, with dedicated
WORKSPACE_ROUTE+FAMILIARS_DB_PATH(completely isolated from production / your local machine). FAMILIARS_APPROVAL_DISABLED=1is required for the demo — the block-list insrc/bridge/routes/demo.pyhandles security by preventing dangerous tools (shell execution, OS control, phone, social) while allowing everything else to run for real.- Never expose
POST /api/demo/tokento non-demo environments — that endpoint returns the current workspace's bridge token.
Prerequisites
- A VPS (2C4G is sufficient; no GPU needed), Ubuntu 22.04/24.04.
- Domain:
demo.familiars.cn(add an A record pointing to VPS IP; if using Cloudflare then orange cloud + Caddy). - Local repo has
electron/dist(built withVITE_DEMO_MODE=1) andsrc/.
Step 1: Build Demo SPA
On your local machine (repo root):
cd electron
VITE_DEMO_MODE=1 npm run build # Windows: set VITE_DEMO_MODE=1 && npm run buildOutput: electron/dist/ (pure static Vue SPA, base: './', auto-uses window.location.origin when no electronAPI, calls POST /api/demo/token at startup to get the public demo token).
Step 2: Prepare Server
# System dependencies (sqlite is built into Python, mainly need python3.11 + venv)
apt update && apt install -y python3.11 python3.11-venv nginxCreate directories and copy files:
mkdir -p /opt/familiars-demo/{app,spa,data}
# Copy src/ and requirements.txt from local machine
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/Step 3: Install Dependencies + Write Environment
cd /opt/familiars-demo
python3.11 -m venv .venv
.venv/bin/pip install -r requirements.txt.env (demo-specific; configure LLM endpoint as you prefer):
# LLM (also needed for demo — agent dialogue/generation goes through this OpenAI-compatible endpoint)
BASE_URL=https://your-llm-gateway/v1
API_KEY=sk-xxxx
DEFAULT_MODEL=your-model-name
MANAGER_MODEL=your-model-name
# Demo isolation (critical)
FAMILIARS_DB_PATH=/opt/familiars-demo/data/demo.db
WORKSPACE_ROUTE=/opt/familiars-demo/data/workspace
# Bridge falls back to plain HTTP (nginx on same machine does /api reverse proxy, no need for HTTPS)
FAMILIARS_HTTP_INSECURE=1
# Per-tenant: official bridge separates WORKSPACE_ROUTE/FAMILIARS_DB_PATH by user $HOME
# (see "Per-tenant Isolation" at the end of this manual)
WORKSPACE_ROUTEmust be compatible withsrc.base.config's default workspace layout — it will generateagents/,familiars.db(ifFAMILIARS_DB_PATHnot specified), etc. under this directory. Demo separates users by running separate processes.
Step 4: Start Bridge (CPU-only)
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=1 \
.venv/bin/python -m src.bridge.run
FAMILIARS_BROKER_BIND=0.0.0.0must be set. Bridge defaults to listening on127.0.0.1only (src/bridge/run.py:_bind_host), but nginx/Caddy is another container and can only reach back via the host bridge (172.18.0.1/host.docker.internal) — if bridge binds to loopback, the reverse proxy will definitely 502. After binding0.0.0.0, make absolutely sure your firewall / security group does not expose 18711 to the public internet (POST /api/demo/tokenreturns that workspace's bridge token, publicly reachable means complete takeover of demo data permissions).
FAMILIARS_APPROVAL_DISABLED=1: the demo security model is block-list + approval disabled. The block-list (_DEMO_BLOCKED_TOOLSinsrc/bridge/routes/demo.py) prevents shell execution, OS input control, phone/social sending, SSH/SCP, and GPU-intensive generation. Everything else — file generation (Word/PPT/Excel), write/edit, CreativeWorkshop, scheduling, browser automation, HTTP requests — runs for real inside the isolated demo workspace.
System service (systemd, /etc/systemd/system/familiars-demo.service):
[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
Environment=FAMILIARS_APPROVAL_DISABLED=1
ExecStart=/opt/familiars-demo/.venv/bin/python -m src.bridge.run
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload && systemctl enable --now familiars-demo
curl http://127.0.0.1:18711/api/demo/status # Expected: {"ok":true,...}
curl http://172.18.0.1:18711/api/demo/status # Container-side verification (same path as Caddy reverse proxy)Step 5: nginx Same-origin Reverse Proxy + Static Site
/etc/nginx/sites-available/familiars-demo:
server {
listen 80;
server_name demo.familiars.cn;
root /opt/familiars-demo/spa;
# —— SPA ——
location / {
try_files $uri $uri/ /index.html;
}
# —— /api reverse proxy to bridge (SSE requires buffering off) ——
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;
}ln -s /etc/nginx/sites-available/familiars-demo /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginxIf using Cloudflare and want HTTPS, just add certs to
listen 80; or add a Caddy layer. The core remains: same-origin/apireverse proxy +proxy_buffering off.
Verification (Expected Live State)
Opening https://demo.familiars.cn/ in a browser should show the SPA:
- Top banner says "共享云端演示实例" and explains that chat / memory / schedule / file generation / CreativeWorkshop work for real; DEMO模式不支持本地操作 — file read/write, code execution, browser, phone, social all return the fixed rejection message;
- First screen
POST /api/demo/token→ getsdemo_session_*token →X-Bridge-Tokenset up;
Demo token only allows
/api/demo/*and/api/chat/*, all other/api/*return 401 — the public token cannot access any other data in the workspace (will not be used as the bridge's global workspace token).
- Send a message to agent 0 → SSE
/api/chat/streamreplies character by character (observe nginx logs for no buffering stalls); - Ask the agent to generate a Word/PPT/Excel file → the generate_* tool runs for real inside the demo workspace, and the generated file is available for download;
- Open CreativeWorkshop → recipe list, editor, compile/dryrun all functional;
- Ask the agent to read a file or run a shell command → gets the fixed "DEMO模式不支持本地操作" message;
- Browser tools, phone tools, SSH/SCP — all return the same fixed rejection.
Session Isolation ("One Shared Workspace + Independent Tokens")
The demo uses shared-workspace + per-session token isolation, not one-process-per-user:
- All visitors connect to the same demo bridge process, same isolated workspace (
WORKSPACE_ROUTE). - Each person calls
/tokento get an independentdemo_session_*token (6-hour expiry); this token only allows/api/demo/*(status/feedback) and/api/chat/*(chat SSE). - Chat, todos, memory RAG, file generation, CreativeWorkshop, scheduling all run on the real backend in the shared workspace — so read-only data (like agent lists, memory index) is the same for all visitors; while blocked tools (shell execution, OS control, phone, social) return a synthetic rejection via the demo gate's block-list.
Trade-off: Solo-agent chat with DemoWorklogScope is purely sample data; if in the future you want "each user truly has their own memory space", you'd need to go back to per-tenant processes (independent WORKSPACE_ROUTE + FAMILIARS_DB_PATH), wrapping Step 4 in run-user.sh separated by user — see next section.
Per-tenant Isolation (Multi-user Version)
The current demo already uses shared-workspace + demo token (see previous section) — only switch to the per-process model below when you want "each user has their own independent memory/task space".
To give each demo user an independent database (to avoid seeing each other's chats/tasks), wrap the Step 4 startup command in a /opt/familiars-demo/run-user.sh, separated by $1 (username):
#!/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.runThen start a systemd instance for each user or wrap with another process manager (supervisord program group, @ service template both work). Each process gets an independent port, nginx uses different location/subpath (or subdomain) to route.
Demo Data Bootstrap (demo_bootstrap)
The guild rooms actually needed by the dialogue need to be pre-seeded in demo data. After first startup, run:
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
# Example: create one room each for feedback/demo (fixed room_id, 'FAMILIAR' room as public feedback receiver)
wr.create_room(conn, broker_id='<broker_id>', name='Familiar', room_id='FAMILIAR-DEMO',
created_by_cluster_id='demo')
print('rooms ok')
"Two fixed rooms (Familiar=public feedback/demo; feedback-official=only visible to official bot owner) should be pre-seeded in deploy docs with bot wallet ≥20 VTP (auto_reward_feedback rewards 20 per task, insufficient balance marks reward_pending, does not block publishing).
Operations Quick Reference
| Operation | Command |
|---|---|
| Restart bridge | systemctl restart familiars-demo |
| View logs | journalctl -u familiars-demo -f |
| Update SPA | Local VITE_DEMO_MODE=1 npm run build → scp -r electron/dist/* to /opt/familiars-demo/spa/ |
| Update bridge code | scp -r src/ to /opt/familiars-demo/app/ → restart service |
| Backup | Snapshot /opt/familiars-demo/data/ |
Related
- Demo page: /demo/
- Feedback storage (Ventaris tasks + 20 VTP): /feedback/
