Перейти к содержимому

Запустить headless-сервер

Запустите 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, вебхуки -p 9030:9030
ghcr.io/samuelalev/cc-webapp Веб-клиент (статический 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:

Что стоит знать, прежде чем открывать что-либо из этого за пределы loopback:

  • Образ cc-server поставляется с CC_SERVER_INSECURE=1 (он должен привязываться к 0.0.0.0, чтобы принимать трафик опубликованных портов), а это безопасно только за обратным прокси, терминирующим TLS — Caddy, Traefik, nginx или туннелем Cloudflare, — в доверенной сети. Чтобы вместо этого терминировать TLS внутри процесса, смонтируйте сертификат и задайте CC_SERVER_TLS_CERT / CC_SERVER_TLS_KEY (затем снимите CC_SERVER_INSECURE). Всё это можно переопределить с помощью docker run -e … или блока environment: в Compose.

  • /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 сервера, идентификатор устройства и ключ сопряжения. Сигнальному реле, так же, нужен только его порт (SIGNALING_HOST / SIGNALING_PORT можно переопределить); оно не хранит состояния, не содержит секретов и служит запасным путём: телефон использует loopback, локальную сеть, tailnet или прямой путь wss://, когда хоть один из них доступен, и переключается на реле, только когда недоступны все. Пропустите его, если ваши клиенты всегда достигают сервера напрямую.

Остальная часть этого руководства собирает сервер из исходного кода; флаги и поведение, о которых здесь идёт речь, — те же, что задают переменные окружения образа.

  • Dart SDK. Этот репозиторий закрепляет свою версию SDK с помощью fvm, поэтому предваряйте каждую команду fvm.
  • Этот репозиторий, склонированный на вашу машину.
  • Нативный набор инструментов, необходимый скрипту подготовки, — Rust/cargo, а также meson, ninja, pkg-config, git и компилятор C++. Каждый скрипт сборки называет то, что не смог найти, и способ это установить.
  • Доступный хост: loopback для локального использования либо адрес в локальной сети или публичный адрес с TLS для удалённых клиентов.

Нативные библиотеки обязательны — режима с ограниченной функциональностью не существует. Соберите их из корня репозитория прежде всего остального:

Окно терминала
scripts/natives/build_natives.sh

Эта команда собирает rift, fff, tree-sitter с его грамматиками, aec, lame, ccpty, cc_watcher, cc_inference и нативную библиотеку SAML в build/natives/. Хук сборки cc_server переносит в бандл всё, что там подготовлено, поэтому пропуск этого шага либо приводит к явному провалу сборки, либо — с обходным путём ниже — даёт бинарник, чья предстартовая проверка при загрузке отказывается запускаться и называет недостающую библиотеку.

Окно терминала
cd apps/cc_server
fvm dart build cli

В бандле рядом с бинарником поставляются 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; идентификатор также задаёт уровень устройства — desktop, ios, android, всё остальное считается веб-клиентом), --label (по умолчанию — название платформы плюс имя хоста этой машины), --host (хост, который выводится в URL, поскольку loopback недоступен с другой машины) и --client-url. Повторный запуск pair меняет ключ этого устройства и завершает его активные сеансы. Сопряжение никогда не создаёт рабочее пространство: первое создаёт и называет онбординг подключающегося клиента.

С --client-url она также выводит QR-код, считываемый из терминала, который открывает уже заполненный веб-клиент:

Окно терминала
./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, в Настройки → Вы → Ваши устройства, когда оно больше не нужно.

На loopback — это значение по умолчанию, и его достаточно для клиента на той же машине:

Окно терминала
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030

Чтобы добраться до него с другой машины, привяжитесь ко всем интерфейсам. Привязка не к loopback без 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

--insecure игнорируется, если заданы и --tls-cert, и --tls-key, — TLS всегда побеждает. Сервер сообщает о состоянии без шифрования как insecure: true в GET /healthz, и клиенты помечают такое соединение как небезопасное.

Сервер работает до SIGINT/SIGTERM, после чего корректно завершает работу.

Направьте клиент на URL сервера и ключ сопряжения из шага 3:

  • Приложение для компьютера: на экране первого запуска (Как запускать Control Center?) заполните поля подключения вместо Запустить в этом приложении или добавьте сервер позже в Настройки → Сервер → Подключение и состояние. См. Подключиться к удалённому серверу.
  • Веб-клиент: откройте app.usectrl.dev (или собственную копию веб-сборки) и введите URL сервера, идентификатор устройства и ключ сопряжения.
  • Телефон: сопрягите приложение 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, поэтому переменные окружения с идентификатором и секретом обязательны — флага --google-client-id не существует. См. Подключить Google Calendar.

Флаги, использованные выше, — те, что нужны при первой настройке. Полный набор — 23 пары «флаг и переменная окружения», четыре подкоманды и структура каталога данных — приведён в справочнике по CLI cc_server.

Четыре, которые, скорее всего, понадобятся:

Флаг Значение по умолчанию Значение
--data-dir каталог данных приложений текущего пользователя ОС Базы данных, секреты, модели и кэшированные медиа
--port 9030 Порт TCP (0 — эфемерный)
--bind loopback loopback либо any для всех интерфейсов (требуется TLS или --insecure)
--public-url выводится из привязки URL RPC, объявляемый сопряжённым клиентам. Задайте его явно за прокси, NAT или туннелем

Каталог данных по умолчанию — ~/Library/Application Support/control-center в macOS, %APPDATA%\control-center в Windows и $XDG_DATA_HOME/control-center (иначе ~/.local/share/control-center) в остальных системах. Относительный к текущему рабочему каталогу .cc_server используется, только когда не удаётся определить ни домашний каталог, ни каталог данных приложений.

cc_server — это вся поверхность продукта, а не её подмножество: путь данных и подписки repo-RPC, эндпоинт инструментов MCP по POST /mcp и GET /sse на том же порту, входящие вебхуки, прокси медиа и шрифтов, обратные вызовы единого входа, протокол аренды флота, получение RSS на стороне сервера по расписанию раз в 30 минут и векторный поиск (расширение sqlite_vector регистрируется как авто-расширение, глобальное для процесса, и переходит к полнотекстовому поиску только там, где расширение недоступно).

Диспетчер не хранит состояния: привязки рабочего пространства к сеансу нет. Каждый вызов в рамках рабочего пространства несёт собственный workspace_id, а доступ определяется членством и ролью вызывающего в рабочем пространстве — а не обладанием ключом сопряжения.

Две вещи, которые стоит знать, прежде чем открывать порт за пределы loopback:

  • MCP отказывает закрыто за пределами хоста. Поверхность MCP без токена отвечает только на loopback. Привязка any этого не расширяет — запрос извне хоста отклоняется с сообщением «MCP requires a bearer token to answer non-loopback clients», пока вы не настроите токен (mcp.setToken или Настройки → Сервер → MCP-серверы).
  • GET /healthz намеренно не требует аутентификации: у него wildcard-CORS, и он публикует идентификатор, имя, отпечаток идентичности и версию сборки сервера. Клиентам нужен отпечаток, чтобы закрепить сервер, поэтому это сделано намеренно — но это значит, что любой доступный cc_server раскрывает эти сведения всякому, кто спросит.