运行无界面服务器
在一台常开的机器上运行 cc_server,然后从桌面端、浏览器或手机连接它。该服务器是纯 Dart 原生二进制文件——不含 Flutter 引擎——因此可在 macOS、Linux 或 Windows 上无界面运行。它掌控数据库、运行智能体,并发出所有外部 API 调用;客户端只负责渲染。
用 Docker 运行(预构建镜像)
标题为“用 Docker 运行(预构建镜像)”的部分每个版本都会发布一套自托管技术栈,共四个 GHCR 镜像——后端、两个静态瘦客户端以及可选的配对中继——外加一个用于公共演示主机的独立 cc-server-demo 镜像。自托管安装无需检出源代码。请固定版本标签(ghcr.io/samuelalev/<image>:v0.0.1-rc.1),或跟随 latest。
| 镜像 | 它是什么 | 发布端口 |
|---|---|---|
ghcr.io/samuelalev/cc-server |
后端:数据库、智能体、MCP、webhook | -p 9030:9030 |
ghcr.io/samuelalev/cc-webapp |
Web 客户端(静态 nginx) | -p 8080:8080 |
ghcr.io/samuelalev/cc-remote |
手机伴侣 PWA(静态 nginx) | -p 8081:8080 |
ghcr.io/samuelalev/cc-signaling-server |
WebRTC 配对中继(可选) | -p 8788:8788 |
整个技术栈的 compose 文件如下:
services: server: image: ghcr.io/samuelalev/cc-server:latest ports: ["9030:9030"] volumes: ["cc_data:/data"] restart: unless-stopped web: image: ghcr.io/samuelalev/cc-webapp:latest ports: ["8080:8080"] restart: unless-stopped remote: image: ghcr.io/samuelalev/cc-remote:latest ports: ["8081:8080"] restart: unless-stopped signaling: image: ghcr.io/samuelalev/cc-signaling-server:latest ports: ["8788:8788"] restart: unless-stopped
volumes: cc_data:在把其中任何一项暴露到回环之外以前,有几件事值得了解:
-
cc-server镜像自带CC_SERVER_INSECURE=1(它必须绑定0.0.0.0才能接收发布端口的流量),这只有在可信网络上、位于终止 TLS 的反向代理——Caddy、Traefik、nginx 或 Cloudflare Tunnel——之后才安全。若要在进程内终止 TLS,请挂载证书并设置CC_SERVER_TLS_CERT/CC_SERVER_TLS_KEY(然后取消设置CC_SERVER_INSECURE)。这一切都可以用docker run -e …或 compose 的environment:块覆盖。 -
/data是唯一的状态。 SQLite 数据库、已配对设备的密钥、下载的模型和会议音频全都存放在该卷中——只要备份它,容器就可以随时丢弃。 -
在首次连接之前,从镜像内部配对客户端,这相当于容器版的第 3 步:
终端窗口 docker run --rm -it -v cc_data:/data \--entrypoint /app/bin/cc_server ghcr.io/samuelalev/cc-server:latest pair -
两个客户端镜像都是静态 nginx 主机——运行时无需任何配置;各客户端的连接表单接受你的服务器 URL、设备 ID 和配对密钥。信令中继同样只需要它的端口 (
SIGNALING_HOST/SIGNALING_PORT可覆盖);它无状态、不保存任何密钥,并且只是一条兜底路径——只要有可达的路径,手机就会使用回环、LAN、tailnet 或直连wss://,只有在全部不可达时才回退到中继。如果你的客户端总能直连服务器,可以跳过它。
本指南余下的部分从源代码构建服务器;其中涉及的标志和行为,与镜像通过环境变量注入的是同一套。
前置条件
标题为“前置条件”的部分- Dart SDK。本仓库用 fvm 固定其 SDK 版本,因此请为每条命令加上
fvm前缀。 - 已检出本仓库。
- 暂存脚本所需的原生工具链——Rust/
cargo,以及meson、ninja、pkg-config、git和 C++ 编译器。每个构建脚本都会说明它找不到什么,以及如何安装。 - 一台可访问的主机:本地使用用回环地址,远程客户端则用带 TLS 的 LAN/公网地址。
第 1 步:暂存原生库
标题为“第 1 步:暂存原生库”的部分原生库是必需的——不存在降级模式。在做其他任何事情之前,请先从仓库根目录构建它们:
scripts/natives/build_natives.sh这会把 rift、fff、tree-sitter 及其语法、aec、lame、
ccpty、cc_watcher、cc_inference 以及 SAML 原生库构建到
build/natives/ 中。cc_server 的构建
钩子会把暂存在那里的内容重新写入 bundle,因此跳过这一步要么会直接导致构建失败,要么——借助下面的应急开关——产出一个二进制文件,其启动预检会拒绝启动并指出缺失的库。
第 2 步:构建二进制文件
标题为“第 2 步:构建二进制文件”的部分cd apps/cc_serverfvm dart build cli该 bundle 会把 libsqlite3 和已暂存的原生库与二进制文件一起打包——无需系统 SQLite,也无需 Flutter 引擎。可执行文件位于
./build/cli/<os_arch>/bundle/bin/cc_server。
第 3 步:在首次启动前配对客户端
标题为“第 3 步:在首次启动前配对客户端”的部分全新的数据目录既没有已配对设备,也没有预共享密钥,因此客户端弹出的配对密钥输入框还无法满足。请用 pair 子命令生成一个。
它会直接写入 global.db 和 secrets.json;已经在服务该数据目录的服务器无需重启即可识别到新设备:
./build/cli/<os_arch>/bundle/bin/cc_server pair --data-dir ./data --port 9030它会打印三个值,供你粘贴到客户端的连接表单中:
Connect a thin client (paste into its connect form): Server ws://localhost:9030/rpc Device id web-client Pairing key <a freshly generated key>有用的标志:--device(默认 web-client;该 id 同时决定设备层级——desktop、ios、android,其他任何值都是 Web 客户端)、--label
(默认为平台名称加上本机主机名)、--host(要打印在 URL 中的主机,因为回环地址无法从另一台机器访问)以及
--client-url。重新运行 pair 会轮换该设备的密钥,并断开它的活动会话。配对永远不会创建工作区:连接方客户端的入门引导会命名并创建第一个工作区。
使用 --client-url 时,它还会打印一个可用终端扫描的二维码,打开时 Web 客户端已经填好:
./build/cli/<os_arch>/bundle/bin/cc_server pair --data-dir ./data --port 9030 \ --bind any --host 192.168.1.42 --client-url https://app.usectrl.dev由 cc_server pair 生成的凭据没有有效期;而在应用中生成的凭据总是在 30 天后过期。用完之后,请从
设置 → 你 → 你的设备中吊销 CLI 生成的设备。
第 4 步:启动服务器
标题为“第 4 步:启动服务器”的部分在回环地址上——这是默认值,也是同一台机器上的客户端唯一需要的:
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030若要从另一台机器访问它,请绑定所有网络接口。没有 TLS 的非回环绑定会在启动时被拒绝(Refusing to bind non-loopback address … without TLS),因此请提供证书:
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030 \ --bind any --tls-cert /path/fullchain.pem --tls-key /path/privkey.pem \ --public-url wss://server.example:9030/rpc或者在可信的专用网络上由反向代理终止 TLS,并在其后面选择明文绑定:
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030 \ --bind any --insecure --public-url wss://server.example/rpc当 --tls-cert 和 --tls-key 都已设置时,--insecure 会被忽略——TLS
始终优先。服务器会在 GET /healthz 中以 insecure: true 公布明文状态,客户端会把这类连接标记为不安全。
服务器会一直运行,直到收到 SIGINT/SIGTERM,然后干净地关闭。
第 5 步:连接客户端
标题为“第 5 步:连接客户端”的部分让客户端指向第 3 步中的服务器 URL 和配对密钥:
- 桌面应用:在首次运行界面(Control Center 应如何运行?)中填写连接字段,而不是选择在此应用中运行,也可以稍后在 设置 → 服务器 → 连接与状态中添加该服务器。参见连接到远程服务器。
- Web 客户端:打开 app.usectrl.dev(或你自己的 Web 构建副本),然后输入服务器 URL、设备 ID 和配对密钥。
- 手机:从正在运行的客户端中配对 Remote 应用——参见配对设备。
第 6 步:连接 Google Calendar(可选)
标题为“第 6 步:连接 Google Calendar(可选)”的部分要在没有图形界面的情况下同步某个工作区的日历,请使用设备代码流程:
GOOGLE_OAUTH_CLIENT_ID=<id> GOOGLE_OAUTH_CLIENT_SECRET=<secret> \ ./build/cli/<os_arch>/bundle/bin/cc_server calendar connect \ --data-dir ./data --workspace <workspace-id>它会打印一个代码和一个 URL,供你在另一台设备上批准,把刷新令牌保存在服务器端,然后退出。从源代码构建的版本不自带 Google 客户端,因此必须提供 id 和 secret 这两个环境变量——不存在 --google-client-id
标志。参见连接 Google Calendar。
上面用到的标志是初次配置所需要的。完整的接口——23 对标志与环境变量、四个子命令以及数据目录布局——都在 cc_server CLI 参考中。
你最有可能需要的是这四个:
| 标志 | 默认值 | 含义 |
|---|---|---|
--data-dir |
操作系统的每用户应用数据目录 | 数据库、密钥、模型和缓存的媒体 |
--port |
9030 |
TCP 端口(0 = 临时端口) |
--bind |
loopback |
loopback,或为所有网络接口指定 any(需要 TLS 或 --insecure) |
--public-url |
根据绑定推导 | 向已配对客户端公布的 RPC URL。在代理、NAT 或隧道之后请显式设置此值 |
默认数据目录在 macOS 上是 ~/Library/Application Support/control-center,
在 Windows 上是 %APPDATA%\control-center,在其他系统上则是 $XDG_DATA_HOME/control-center(否则为
~/.local/share/control-center)。只有在无法解析出主目录或应用数据目录时,才会使用相对于当前工作目录的 .cc_server。
服务器提供哪些服务
标题为“服务器提供哪些服务”的部分cc_server 是完整的产品接口面,而不是一个子集:repo-RPC 数据通路与订阅、位于同一端口的 MCP 工具端点 POST /mcp 和 GET /sse、入站 webhook、媒体与字体代理、单点登录
回调、机群租约协议、按 30 分钟调度在服务器端抓取 RSS,以及向量搜索(sqlite_vector 扩展被注册为
进程级自动扩展,仅在该扩展不可用之处降级为全文搜索)。
调度器是无状态的:不存在按会话的工作区绑定。
每一次限定工作区范围的调用都自带 workspace_id,访问权限由调用方的工作区成员资格和角色决定——而不是由是否持有配对密钥决定。
在把端口暴露到回环之外以前,有两件事需要了解:
- MCP 在主机之外失败即关闭。 没有令牌的 MCP 接口面只应答回环。绑定
any并不会放宽这一点——在你配置令牌之前(mcp.setToken,或 设置 → 服务器 → MCP 服务器),来自主机之外的请求会被以 “MCP requires a bearer token to answer non-loopback clients” 拒绝。 GET /healthz按设计不需要认证,带通配符 CORS,并公布服务器的 id、名称、身份指纹和构建版本。客户端需要该指纹来固定服务器,因此这是有意为之——但它确实意味着任何可达的cc_server都会向任何询问者披露这些信息。