Salta ai contenuti

Esegui un server headless

Esegui cc_server su una macchina sempre accesa, poi connettiti dal desktop, da un browser o dal telefono. Il server è un binario nativo scritto in puro Dart — nessun engine Flutter — quindi gira headless su macOS, Linux o Windows. Possiede il database, esegue gli agent e fa ogni chiamata alle API esterne; i client si limitano a mostrare.

Ogni release pubblica uno stack self-hosted di quattro immagini GHCR — il backend, i due thin client statici e il relay di abbinamento opzionale — più una immagine separata cc-server-demo per l’host della demo pubblica. Un’installazione self-hosted non richiede alcun checkout del sorgente. Fissa un tag di versione (ghcr.io/samuelalev/<image>:v0.0.1-rc.1) oppure segui latest.

Immagine Cos’è Pubblica
ghcr.io/samuelalev/cc-server Il backend: database, agent, MCP, webhook -p 9030:9030
ghcr.io/samuelalev/cc-webapp Il client web (nginx statico) -p 8080:8080
ghcr.io/samuelalev/cc-remote La PWA complementare per telefono (nginx statico) -p 8081:8080
ghcr.io/samuelalev/cc-signaling-server Il relay di abbinamento WebRTC (opzionale) -p 8788:8788

L’intero stack come file 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:

Cose che vale la pena sapere prima di esporre tutto questo oltre il loopback:

  • L’immagine cc-server include CC_SERVER_INSECURE=1 (deve fare il bind su 0.0.0.0 per ricevere il traffico della porta pubblicata), condizione sicura solo dietro un reverse proxy che termina il TLS — Caddy, Traefik, nginx o un Cloudflare Tunnel — su una rete affidabile. Per terminare invece il TLS nel processo, monta un certificato e imposta CC_SERVER_TLS_CERT / CC_SERVER_TLS_KEY (poi rimuovi CC_SERVER_INSECURE). Tutto è sovrascrivibile con docker run -e … o con il blocco environment: del compose.

  • /data è l’unico stato. I database SQLite, i segreti dei dispositivi associati, i modelli scaricati e l’audio delle riunioni vivono tutti nel volume: fanne un backup e il container diventa usa e getta.

  • Associa un client dall’interno dell’immagine prima della prima connessione, l’equivalente in container del passaggio 3:

    Finestra del terminale
    docker run --rm -it -v cc_data:/data \
    --entrypoint /app/bin/cc_server ghcr.io/samuelalev/cc-server:latest pair
  • Le due immagini client sono host nginx statici — niente da configurare a runtime; il modulo di connessione di ogni client accetta l’URL del server, l’ID dispositivo e la chiave di abbinamento. Anche il relay di signaling ha bisogno solo della sua porta (SIGNALING_HOST / SIGNALING_PORT sovrascrivibili); è senza stato, non conserva segreti ed è un fallback: un telefono usa il loopback, la LAN, il tailnet o un percorso wss:// diretto quando uno è raggiungibile, e ripiega sul relay solo quando nessuno lo è. Salta il relay se i tuoi client raggiungono sempre il server direttamente.

Il resto di questa guida compila il server dal sorgente; i flag e il comportamento che descrive sono gli stessi alimentati dalle variabili d’ambiente dell’immagine.

  • Il Dart SDK. Questo repo fissa la sua SDK con fvm, quindi anteponi fvm a ogni comando.
  • Questo repository con il checkout effettuato.
  • La toolchain nativa richiesta dallo script di preparazione — Rust/cargo, più meson, ninja, pkg-config, git e un compilatore C++. Ogni script di build indica ciò che non riesce a trovare e come installarlo.
  • Un host raggiungibile: il loopback per l’uso locale, oppure un indirizzo LAN/pubblico con TLS per i client remoti.

Le librerie native sono obbligatorie: non esiste una modalità degradata. Compilale dalla radice del repo prima di ogni altra cosa:

Finestra del terminale
scripts/natives/build_natives.sh

Questa compila rift, fff, tree-sitter con le sue grammatiche, aec, lame, ccpty, cc_watcher, cc_inference e il nativo SAML in build/natives/. L’hook di build di cc_server riversa nel bundle qualunque cosa sia stata preparata lì, quindi saltare questo passaggio fa fallire la build del tutto oppure — con la scappatoia qui sotto — produce un binario il cui preflight di avvio si rifiuta di partire e indica la libreria mancante.

Finestra del terminale
cd apps/cc_server
fvm dart build cli

Il bundle include libsqlite3 e le librerie native preparate accanto al binario — non servono SQLite di sistema né l’engine Flutter. L’eseguibile finisce in ./build/cli/<os_arch>/bundle/bin/cc_server.

Passaggio 3: associa un client prima del primo avvio

Sezione intitolata “Passaggio 3: associa un client prima del primo avvio”

Una directory dati appena creata non ha alcun dispositivo associato e nessuna chiave precondivisa, quindi la richiesta della chiave di abbinamento di un client non può ancora essere soddisfatta. Generane una con il sottocomando pair. Scrive direttamente global.db e secrets.json; un server che sta già servendo quella directory dati rileva il nuovo dispositivo senza riavvio:

Finestra del terminale
./build/cli/<os_arch>/bundle/bin/cc_server pair --data-dir ./data --port 9030

Stampa tre valori da incollare nel modulo di connessione di un client:

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

Flag utili: --device (predefinito web-client; l’id determina anche la fascia del dispositivo — desktop, ios, android, qualsiasi altro valore è un client web), --label (predefinito al nome della piattaforma più l’hostname di questa macchina), --host (l’host da stampare nell’URL, dato che il loopback non è raggiungibile da un’altra macchina) e --client-url. Rieseguire pair ruota la chiave di quel dispositivo e chiude le sue sessioni attive. L’abbinamento non crea mai uno spazio di lavoro: l’onboarding del client che si connette nomina e crea il primo.

Con --client-url stampa anche un codice QR scansionabile dal terminale che apre il client web già compilato:

Finestra del terminale
./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 credenziale generata da cc_server pair non ha alcuna scadenza; una generata nell’app scade sempre dopo 30 giorni. Revoca un dispositivo creato dalla CLI da Impostazioni → Tu → I tuoi dispositivi quando hai finito di usarlo.

Su loopback — l’impostazione predefinita e tutto ciò che serve per un client sulla stessa macchina:

Finestra del terminale
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030

Per raggiungerlo da un’altra macchina, fai il bind su ogni interfaccia. Un bind non di loopback senza TLS viene rifiutato all’avvio (Refusing to bind non-loopback address … without TLS), quindi fornisci un certificato:

Finestra del terminale
./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

Oppure termina il TLS in un reverse proxy su una rete privata affidabile e attiva un bind in chiaro dietro di esso:

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

--insecure viene ignorato quando --tls-cert e --tls-key sono entrambi impostati: vince sempre il TLS. Il server dichiara lo stato in chiaro come insecure: true in GET /healthz e i client contrassegnano una connessione di questo tipo come non sicura.

Il server resta in esecuzione fino a SIGINT/SIGTERM, poi si arresta in modo pulito.

Punta un client all’URL del server e alla chiave di abbinamento del passaggio 3:

  • App desktop: nella schermata di primo avvio (Come eseguire Control Center?) compila i campi di connessione invece di Esegui in questa app, oppure aggiungi il server in un secondo momento in Impostazioni → Server → Connessione e stato. Vedi Connettiti a un server remoto.
  • Client web: apri app.usectrl.dev (o la tua copia della build web) e inserisci l’URL del server, l’ID dispositivo e la chiave di abbinamento.
  • Telefono: associa l’app Remote da un client in esecuzione — vedi Associa un dispositivo.

Passaggio 6: collega un Google Calendar (opzionale)

Sezione intitolata “Passaggio 6: collega un Google Calendar (opzionale)”

Per sincronizzare il calendario di uno spazio di lavoro senza alcuna GUI collegata, usa il flusso con codice dispositivo:

Finestra del terminale
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>

Stampa un codice e un URL da approvare su un altro dispositivo, memorizza il refresh token lato server ed esce. Una build dal sorgente non include alcun client Google, quindi le variabili d’ambiente con id e secret sono obbligatorie: non esiste un flag --google-client-id. Vedi Collega un Google Calendar.

I flag usati sopra sono quelli necessari a una prima configurazione. La superficie completa — 23 coppie di flag e variabili d’ambiente, i quattro sottocomandi e la struttura della directory dati — è nel riferimento della CLI di cc_server.

I quattro che ti serviranno più probabilmente:

Flag Predefinito Significato
--data-dir la directory dei dati applicazione per utente del sistema operativo Database, segreti, modelli e media in cache
--port 9030 Porta TCP (0 = effimera)
--bind loopback loopback, oppure any per ogni interfaccia (richiede TLS o --insecure)
--public-url derivato dal bind L’URL RPC annunciato ai client associati. Impostalo esplicitamente dietro un proxy, un NAT o un tunnel

La directory dati predefinita è ~/Library/Application Support/control-center su macOS, %APPDATA%\control-center su Windows e $XDG_DATA_HOME/control-center (altrimenti ~/.local/share/control-center) altrove. Un .cc_server relativo alla cwd viene usato solo quando non si risolve alcuna home o directory di dati applicazione.

cc_server è l’intera superficie del prodotto, non un sottoinsieme: il percorso dati e le sottoscrizioni repo-RPC, l’endpoint degli strumenti MCP su POST /mcp e GET /sse sulla stessa porta, i webhook in ingresso, i proxy di media e font, i callback di single sign-on, il protocollo di lease della flotta, il recupero RSS lato server con una pianificazione di 30 minuti e la ricerca vettoriale (l’estensione sqlite_vector è registrata come auto-extension globale del processo, e degrada alla sola ricerca full-text dove l’estensione non è disponibile).

Il dispatcher è senza stato: non esiste alcun binding di spazio di lavoro per sessione. Ogni chiamata con ambito di spazio di lavoro porta con sé il proprio workspace_id e l’accesso è deciso dall’appartenenza e dal ruolo del chiamante nello spazio di lavoro, non dal possesso di una chiave di abbinamento.

Due cose da sapere prima di esporre la porta oltre il loopback:

  • L’MCP adotta un comportamento restrittivo fuori dall’host. Una superficie MCP senza token risponde solo al loopback. Il bind any non la amplia: una richiesta esterna all’host viene rifiutata con “MCP requires a bearer token to answer non-loopback clients” finché non ne configuri uno (mcp.setToken, oppure Impostazioni → Server → Server MCP).
  • GET /healthz non è autenticato per scelta, ha CORS con wildcard e pubblica l’id, il nome, l’impronta dell’identità e la versione di build del server. I client hanno bisogno dell’impronta per fare il pin del server, quindi è deliberato — ma significa che qualsiasi cc_server raggiungibile divulga tutte queste informazioni a chiunque le chieda.