Ir al contenido

Ejecutar un servidor headless

Ejecuta cc_server en una máquina que esté siempre encendida y conéctate a él desde tu escritorio, un navegador o tu teléfono. El servidor es un binario nativo de Dart puro —sin motor de Flutter—, así que se ejecuta headless en macOS, Linux o Windows. Es el propietario de la base de datos, ejecuta los agentes y hace todas las llamadas a API externas; los clientes solo dibujan.

Ejecutar en Docker (imágenes precompiladas)

Sección titulada «Ejecutar en Docker (imágenes precompiladas)»

Cada versión publica un stack autoalojado de cuatro imágenes de GHCR —el backend, los dos clientes ligeros estáticos y el relé de emparejamiento opcional— además de una imagen cc-server-demo aparte para el host de demostración público. Una instalación autoalojada no necesita ningún checkout del código fuente. Fija una etiqueta de versión (ghcr.io/samuelalev/<image>:v0.0.1-rc.1) o usa latest.

Imagen Qué es Publica
ghcr.io/samuelalev/cc-server El backend: base de datos, agentes, MCP, webhooks -p 9030:9030
ghcr.io/samuelalev/cc-webapp El cliente web (nginx estático) -p 8080:8080
ghcr.io/samuelalev/cc-remote La PWA complementaria para el teléfono (nginx estático) -p 8081:8080
ghcr.io/samuelalev/cc-signaling-server El relé de emparejamiento WebRTC (opcional) -p 8788:8788

Todo el stack en un archivo de 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:

Cosas que conviene saber antes de exponer algo de esto más allá de loopback:

  • La imagen cc-server incluye CC_SERVER_INSECURE=1 (tiene que enlazar 0.0.0.0 para recibir el tráfico de los puertos publicados), lo cual solo es seguro detrás de un proxy inverso que termine TLS —Caddy, Traefik, nginx o un Cloudflare Tunnel— en una red de confianza. Para terminar TLS dentro del propio proceso, monta un certificado y define CC_SERVER_TLS_CERT / CC_SERVER_TLS_KEY (y quita entonces CC_SERVER_INSECURE). Todo se puede sobrescribir con docker run -e … o con el bloque environment: de Compose.

  • /data es el único estado. Las bases de datos SQLite, los secretos de los dispositivos emparejados, los modelos descargados y el audio de las reuniones viven todos en el volumen: haz una copia de seguridad y el contenedor pasa a ser desechable.

  • Empareja un cliente desde dentro de la imagen antes de la primera conexión, el equivalente en contenedor del paso 3:

    Ventana de terminal
    docker run --rm -it -v cc_data:/data \
    --entrypoint /app/bin/cc_server ghcr.io/samuelalev/cc-server:latest pair
  • Las dos imágenes de cliente son hosts estáticos de nginx: no hay nada que configurar en tiempo de ejecución; el formulario de conexión de cada cliente acepta la URL de tu servidor, el id de dispositivo y la clave de emparejamiento. El relé de señalización también necesita solo su puerto (SIGNALING_HOST / SIGNALING_PORT se pueden sobrescribir); no tiene estado, no guarda ningún secreto y es una alternativa: un teléfono usa loopback, la LAN, la tailnet o una ruta wss:// directa cuando alguna es accesible, y solo recurre al relé cuando ninguna lo es. Omítelo si tus clientes siempre llegan al servidor directamente.

El resto de esta guía compila el servidor desde el código fuente; los flags y el comportamiento que cubre son los mismos que alimentan las variables de entorno de la imagen.

  • El SDK de Dart. Este repo fija su SDK con fvm, así que antepón fvm a todos los comandos.
  • Este repositorio, clonado en local.
  • La cadena de herramientas nativa que necesita el script de preparación —Rust/cargo, además de meson, ninja, pkg-config, git y un compilador de C++. Cada script de compilación indica lo que no encuentra y cómo instalarlo.
  • Un host accesible: loopback para uso local, o una dirección de LAN o pública con TLS para clientes remotos.

Las bibliotecas nativas son obligatorias: no existe ningún modo degradado. Compílalas desde la raíz del repo antes que nada:

Ventana de terminal
scripts/natives/build_natives.sh

Esto compila rift, fff, tree-sitter y sus gramáticas, aec, lame, ccpty, cc_watcher, cc_inference y la biblioteca nativa de SAML en build/natives/. El hook de compilación de cc_server vuelca de nuevo en el bundle todo lo que se haya preparado ahí, así que saltarte este paso hace que la compilación falle directamente o —con la vía de escape de abajo— genere un binario cuya comprobación previa de arranque se niega a iniciarse y nombra la biblioteca que falta.

Ventana de terminal
cd apps/cc_server
fvm dart build cli

El bundle incluye libsqlite3 y las bibliotecas nativas preparadas junto al binario: no hacen falta el SQLite del sistema ni el motor de Flutter. El ejecutable queda en ./build/cli/<os_arch>/bundle/bin/cc_server.

Paso 3: Emparejar un cliente antes del primer arranque

Sección titulada «Paso 3: Emparejar un cliente antes del primer arranque»

Un directorio de datos recién creado no tiene ningún dispositivo emparejado ni ninguna clave precompartida, así que todavía no se puede satisfacer la petición de clave de emparejamiento de un cliente. Genera una con el subcomando pair. Escribe global.db y secrets.json directamente; un servidor que ya esté sirviendo ese directorio de datos detecta el nuevo dispositivo sin reiniciarse:

Ventana de terminal
./build/cli/<os_arch>/bundle/bin/cc_server pair --data-dir ./data --port 9030

Imprime tres valores que puedes pegar en el formulario de conexión de un cliente:

Connect a thin client (paste into its connect form):
Server ws://localhost:9030/rpc
Device id web-client
Pairing key <a freshly generated key>

Flags útiles: --device (valor predeterminado web-client; el id también determina el nivel del dispositivo —desktop, ios, android, cualquier otro valor es un cliente web—), --label (de forma predeterminada, el nombre de la plataforma más el nombre de host de esta máquina), --host (el host que se imprime en la URL, ya que loopback no es accesible desde otra máquina) y --client-url. Volver a ejecutar pair rota la clave de ese dispositivo y corta sus sesiones activas. El emparejamiento nunca crea un espacio de trabajo: la configuración inicial del cliente que se conecta le pone nombre y crea el primero.

Con --client-url también imprime un QR que se puede escanear desde el terminal y que abre el cliente web ya rellenado:

Ventana de terminal
./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

Una credencial generada por cc_server pair no caduca; una generada en la app caduca siempre a los 30 días. Cuando ya no necesites un dispositivo generado por la CLI, revócalo desde Ajustes → Tú → Tus dispositivos.

En loopback, el valor predeterminado y todo lo que necesitas para un cliente en la misma máquina:

Ventana de terminal
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030

Para llegar a él desde otra máquina, enlaza todas las interfaces. Un enlace fuera de loopback y sin TLS se rechaza al arrancar (Refusing to bind non-loopback address … without TLS), así que proporciona un certificado:

Ventana de terminal
./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

O bien termina TLS en un proxy inverso de una red privada de confianza y opta por un enlace en texto plano detrás de él:

Ventana de terminal
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030 \
--bind any --insecure --public-url wss://server.example/rpc

--insecure se ignora cuando --tls-cert y --tls-key están definidos los dos: TLS siempre gana. El servidor anuncia el estado en texto plano como insecure: true en GET /healthz, y los clientes marcan ese tipo de conexión como insegura.

El servidor se ejecuta hasta que recibe SIGINT/SIGTERM, y luego se apaga de forma limpia.

Apunta un cliente a la URL del servidor y a la clave de emparejamiento del paso 3:

  • App de escritorio: en la pantalla del primer arranque (¿Cómo debe ejecutarse Control Center?) rellena los campos de conexión en lugar de Ejecutar en esta aplicación, o añade el servidor más tarde en Ajustes → Servidor → Conexión y estado. Consulta Conectar a un servidor remoto.
  • Cliente web: abre app.usectrl.dev (o tu propia copia de la compilación web) e introduce la URL del servidor, el id de dispositivo y la clave de emparejamiento.
  • Teléfono: empareja la app Remote desde un cliente en ejecución; consulta Emparejar un dispositivo.

Paso 6: Conectar un Google Calendar (opcional)

Sección titulada «Paso 6: Conectar un Google Calendar (opcional)»

Para sincronizar el calendario de un espacio de trabajo sin ninguna interfaz gráfica conectada, usa el flujo de código de dispositivo:

Ventana de terminal
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>

Imprime un código y una URL para aprobarlos en otro dispositivo, guarda el token de refresco en el lado del servidor y termina. Una compilación desde el código fuente no incluye ningún cliente de Google, así que las variables de entorno del id y el secreto son obligatorias: no existe ningún flag --google-client-id. Consulta Conectar un Google Calendar.

Los flags usados arriba son los que necesita una primera configuración. La superficie completa —23 pares de flag y variable de entorno, los cuatro subcomandos y la estructura del directorio de datos— está en la referencia de la CLI de cc_server.

Los cuatro que más probablemente necesites:

Flag Valor predeterminado Significado
--data-dir el directorio de datos de aplicación por usuario del sistema operativo Bases de datos, secretos, modelos y medios en caché
--port 9030 Puerto TCP (0 = efímero)
--bind loopback loopback, o any para todas las interfaces (necesita TLS o --insecure)
--public-url derivado del enlace La URL de RPC que se anuncia a los clientes emparejados. Defínela explícitamente detrás de un proxy, un NAT o un túnel

El directorio de datos predeterminado es ~/Library/Application Support/control-center en macOS, %APPDATA%\control-center en Windows y $XDG_DATA_HOME/control-center (o, si no, ~/.local/share/control-center) en el resto de sistemas. Solo se usa un .cc_server relativo al directorio de trabajo actual cuando no se resuelve ningún directorio home ni de datos de aplicación.

cc_server es toda la superficie del producto, no un subconjunto: la ruta de datos y las suscripciones de RPC del repo, el endpoint de herramientas MCP en POST /mcp y GET /sse en el mismo puerto, los webhooks entrantes, los proxies de medios y de fuentes, las devoluciones de llamada del inicio de sesión único, el protocolo de lease de la flota, la obtención de RSS en el lado del servidor con una programación de 30 minutos y la búsqueda vectorial (la extensión sqlite_vector se registra como autoextensión global del proceso y solo recurre a la búsqueda de texto completo allí donde la extensión no está disponible).

El despachador carece de estado: no hay ningún vínculo de espacio de trabajo por sesión. Cada llamada con ámbito de espacio de trabajo lleva su propio workspace_id, y el acceso lo deciden la pertenencia al espacio de trabajo y el rol de quien llama, no el hecho de tener una clave de emparejamiento.

Dos cosas que conviene saber antes de exponer el puerto más allá de loopback:

  • MCP falla en cerrado fuera del host. Una superficie MCP sin token solo responde en loopback. Enlazar any no la amplía: una petición desde fuera del host se rechaza con «MCP requires a bearer token to answer non-loopback clients» hasta que configures uno (mcp.setToken, o Ajustes → Servidor → Servidores MCP).
  • GET /healthz no requiere autenticación por diseño, tiene CORS comodín y publica el id, el nombre, la huella de identidad y la versión de compilación del servidor. Los clientes necesitan la huella para fijar el servidor, así que es deliberado, pero significa que cualquier cc_server accesible revela esos datos a quien se los pida.