コンテンツにスキップ

ヘッドレスサーバーを実行

常時稼働するマシンで cc_server を実行し、デスクトップ、ブラウザー、またはスマートフォンから接続します。サーバーは純粋な Dart のネイティブバイナリで、Flutter エンジンを必要としないため、macOS、Linux、Windows 上でヘッドレスに動作します。サーバーがデータベースを所有し、 エージェントを実行し、すべての外部 API 呼び出しを行います。クライアントは描画するだけです。

各リリースでは、4 つの GHCR イメージからなるセルフホスト用スタック — バックエンド、 2 つの静的なシンクライアント、任意のペアリングリレー — と、公開デモホスト用の別個の 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
  • 2 つのクライアントイメージは静的な nginx ホストです — 実行時に設定するものは 何もありません。各クライアントの接続フォームに、サーバー URL、デバイス ID、 ペアリングキーを入力します。シグナリングリレーも同様に、必要なのはポートだけです (SIGNALING_HOST / SIGNALING_PORT は上書き可能)。リレーはステートレスでシークレットを 保持せず、あくまでフォールバックです — スマートフォンは到達可能な経路(ループバック、LAN、 tailnet、直接の wss://)があればそれを使い、どれも使えないときにだけリレーに フォールバックします。クライアントが常にサーバーに直接到達できるなら、リレーは省略してください。

このガイドの残りでは、サーバーをソースからビルドします。ここで扱うフラグと挙動は、 イメージの環境変数が渡すものと同じです。

  • Dart SDK。このリポジトリは SDK を fvm で固定しているため、すべてのコマンドの先頭に 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 のビルド フックは、そこでステージングされたものをそのままバンドルに再出力するため、このステップを飛ばすと ビルドがそのまま失敗するか、後述の回避手段を使った場合は、起動前チェックで不足している ライブラリ名が示されて起動が拒否されるバイナリができあがります。

ターミナルウィンドウ
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

クライアントの接続フォームに貼り付ける 3 つの値が出力されます:

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 クライアントを開く、ターミナルでスキャン可能な 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 で発行したデバイスは、使い終わったら 設定 → 自分 → 自分のデバイスから取り消してください。

ループバックの場合 — 既定であり、同じマシン上のクライアントにはこれだけで十分です:

ターミナルウィンドウ
./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 アプリをペアリングします — デバイスをペアリングを参照してください。

GUI を伴わずにワークスペースのカレンダーを同期するには、デバイスコードフローを使います:

ターミナルウィンドウ
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 組のフラグと環境変数、4 つのサブコマンド、データディレクトリの レイアウト — は cc_server CLI リファレンスにあります。

もっとも必要になる可能性が高い 4 つ:

フラグ 既定値 意味
--data-dir OS のユーザーごとのアプリケーションデータディレクトリ データベース、シークレット、モデル、キャッシュされたメディア
--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 は製品サーフェスの一部ではなく、その全体です。リポジトリ RPC のデータ経路 とサブスクリプション、同じポート上の POST /mcp と GET /sse にある MCP ツール エンドポイント、受信 Webhook、メディアとフォントのプロキシ、シングルサインオン のコールバック、フリートリースプロトコル、30 分間隔のサーバー側 RSS 取得、そして ベクター検索(sqlite_vector 拡張は プロセス全体の自動拡張として登録され、拡張が利用できない場合にのみ 全文検索にフォールバックします)です。

ディスパッチャーはステートレスです。セッションごとのワークスペースの紐づけは ありません。ワークスペースを対象とするすべての呼び出しが自身の workspace_id を持ち、アクセスは 呼び出し元のワークスペースのメンバーシップとロールによって決まります — ペアリングキーを 保持しているかどうかでは決まりません。

ポートをループバックの外に公開する前に知っておくべき 2 つのこと:

  • MCP はホスト外でフェイルクローズします。 トークンのない MCP サーフェスはループバックにしか応答しません。any にバインドしてもこの範囲は広がりません — トークンを設定するまで(mcp.setToken、または 設定 → サーバー → MCP サーバー)、ホスト外のリクエストは “MCP requires a bearer token to answer non-loopback clients” で拒否されます。
  • GET /healthz は設計上認証を必要としません。 ワイルドカード CORS を備え、サーバーの ID、名前、アイデンティティフィンガープリント、ビルドバージョンを公開します。クライアントはサーバーをピン留めするためにフィンガープリントを必要とするため、これは意図的なものです — ただし、到達可能な cc_server は誰に対してもそれだけの情報を開示するという意味でもあります。