Ejecutar un servidor headless
Ejecuta cc_server en una computadora que permanezca encendida y luego 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 renderizan.
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-serverincluyeCC_SERVER_INSECURE=1(tiene que enlazar0.0.0.0para 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 defineCC_SERVER_TLS_CERT/CC_SERVER_TLS_KEY(y quita entoncesCC_SERVER_INSECURE). Todo se puede sobrescribir condocker run -e …o con el bloqueenvironment:de Compose. -
/dataes 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 un respaldo 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_PORTse 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 rutawss://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.
Requisitos previos
Sección titulada «Requisitos previos»- El SDK de Dart. Este repo fija su SDK con fvm, así que antepón
fvma todos los comandos. - Este repositorio, clonado en local.
- La cadena de herramientas nativa que necesita el script de preparación —Rust/
cargo, además demeson,ninja,pkg-config,gity 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.
Paso 1: Preparar las bibliotecas nativas
Sección titulada «Paso 1: Preparar las bibliotecas nativas»Las bibliotecas nativas son obligatorias: no existe ningún modo degradado. Compílalas desde la raíz del repo antes que nada:
scripts/natives/build_natives.shEsto 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 verificación previa de arranque se niega a iniciarlo y nombra la
biblioteca que falta.
Paso 2: Compilar el binario
Sección titulada «Paso 2: Compilar el binario»cd apps/cc_serverfvm dart build cliEl 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 solicitud
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:
./build/cli/<os_arch>/bundle/bin/cc_server pair --data-dir ./data --port 9030Imprime 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 computadora), --host (el host que se
imprime en la URL, ya que loopback no es accesible desde otra computadora) 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:
./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.devUna credencial generada por cc_server pair no caduca; una generada en la
app siempre caduca a los 30 días. Cuando ya no necesites un dispositivo
generado por la CLI, revócalo desde Ajustes → Tú → Tus dispositivos.
Paso 4: Arrancar el servidor
Sección titulada «Paso 4: Arrancar el servidor»En loopback, el valor predeterminado y todo lo que necesitas para un cliente en la misma computadora:
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030Para llegar a él desde otra computadora, 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:
./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/rpcO 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:
./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.
Paso 5: Conectar un cliente
Sección titulada «Paso 5: Conectar un cliente»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 agrega el servidor más tarde en Ajustes → Servidor → Conexión y estado. Consulta Conectarte 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:
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.
Configuración
Sección titulada «Configuración»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 los 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.
Qué sirve el servidor
Sección titulada «Qué sirve el servidor»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
anyno la amplía: una solicitud 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 /healthzno 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 cualquiercc_serveraccesible revela esos datos a quien se los pida.
Guías relacionadas
Sección titulada «Guías relacionadas»- Conectarte a un servidor remoto
- Emparejar un dispositivo
- Ejecutar un worker de flota
- Conectar un Google Calendar