跳转到内容

运行无界面服务器

在一台常开的机器上运行 cc_server,然后从桌面端、浏览器或手机连接它。该服务器是纯 Dart 原生二进制文件——不含 Flutter 引擎——因此可在 macOS、Linux 或 Windows 上无界面运行。它掌控数据库、运行智能体,并发出所有外部 API 调用;客户端只负责渲染。

每个版本都会发布一套自托管技术栈,共四个 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/公网地址。

原生库是必需的——不存在降级模式。在做其他任何事情之前,请先从仓库根目录构建它们:

终端窗口
scripts/natives/build_natives.sh

这会把 rift、fff、tree-sitter 及其语法、aec、lame、 ccpty、cc_watcher、cc_inference 以及 SAML 原生库构建到 build/natives/ 中。cc_server 的构建 钩子会把暂存在那里的内容重新写入 bundle,因此跳过这一步要么会直接导致构建失败,要么——借助下面的应急开关——产出一个二进制文件,其启动预检会拒绝启动并指出缺失的库。

终端窗口
cd apps/cc_server
fvm dart build cli

该 bundle 会把 libsqlite3 和已暂存的原生库与二进制文件一起打包——无需系统 SQLite,也无需 Flutter 引擎。可执行文件位于 ./build/cli/<os_arch>/bundle/bin/cc_server。

全新的数据目录既没有已配对设备,也没有预共享密钥,因此客户端弹出的配对密钥输入框还无法满足。请用 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 生成的设备。

在回环地址上——这是默认值,也是同一台机器上的客户端唯一需要的:

终端窗口
./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,然后干净地关闭。

让客户端指向第 3 步中的服务器 URL 和配对密钥:

  • 桌面应用:在首次运行界面(Control Center 应如何运行?)中填写连接字段,而不是选择在此应用中运行,也可以稍后在 设置 → 服务器 → 连接与状态中添加该服务器。参见连接到远程服务器。
  • Web 客户端:打开 app.usectrl.dev(或你自己的 Web 构建副本),然后输入服务器 URL、设备 ID 和配对密钥。
  • 手机:从正在运行的客户端中配对 Remote 应用——参见配对设备。

要在没有图形界面的情况下同步某个工作区的日历,请使用设备代码流程:

终端窗口
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 都会向任何询问者披露这些信息。