Sari la conținut

Rulează un server headless

Rulează cc_server pe o mașină care rămâne pornită, apoi conectează-te la el de pe desktop, dintr-un browser sau de pe telefon. Serverul este un binar nativ scris în pur Dart — fără motor Flutter —, așa că rulează headless pe macOS, Linux sau Windows. El deține baza de date, rulează agenții și face fiecare apel API extern; clienții doar afișează.

Fiecare versiune publică un stack auto-găzduit de patru imagini GHCR — backendul, cele două clienturi subțiri statice și releul de asociere opțional — plus o imagine separată cc-server-demo pentru gazda publică de demo. O instalare auto-găzduită nu are nevoie de o copie a codului sursă. Fixează un tag de versiune (ghcr.io/samuelalev/<image>:v0.0.1-rc.1) sau mergi pe latest.

Imagine Ce este Publică
ghcr.io/samuelalev/cc-server Backendul: baza de date, agenții, MCP, webhook-uri -p 9030:9030
ghcr.io/samuelalev/cc-webapp Clientul web (nginx static) -p 8080:8080
ghcr.io/samuelalev/cc-remote PWA-ul companion de pe telefon (nginx static) -p 8081:8080
ghcr.io/samuelalev/cc-signaling-server Releul de asociere WebRTC (opțional) -p 8788:8788

Tot stackul ca fișier 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:

Lucruri pe care merită să le știi înainte de a expune toate acestea dincolo de loopback:

  • Imaginea cc-server vine cu CC_SERVER_INSECURE=1 (trebuie să se lege la 0.0.0.0 ca să primească traficul din porturile publicate), ceea ce este sigur doar în spatele unui proxy invers care termină TLS — Caddy, Traefik, nginx sau un Cloudflare Tunnel —, într-o rețea de încredere. Ca să termini TLS în proces, montează în schimb un certificat și setează CC_SERVER_TLS_CERT / CC_SERVER_TLS_KEY (apoi scoate CC_SERVER_INSECURE). Totul poate fi suprascris cu docker run -e … sau cu blocul environment: din compose.

  • /data este singura stare. Bazele de date SQLite, secretele dispozitivelor asociate, modelele descărcate și audio-ul întâlnirilor stau toate în volum — fă-i un backup, iar containerul devine eliminabil.

  • Asociază un client din interiorul imaginii înainte de prima conexiune, echivalentul în container al pasului 3:

    Fereastră terminal
    docker run --rm -it -v cc_data:/data \
    --entrypoint /app/bin/cc_server ghcr.io/samuelalev/cc-server:latest pair
  • Cele două imagini de client sunt gazde nginx statice — nimic de configurat la runtime; formularul de conectare al fiecărui client preia URL-ul serverului, ID-ul de dispozitiv și cheia de asociere. Releul de semnalizare, la fel, are nevoie doar de portul său (SIGNALING_HOST / SIGNALING_PORT pot fi suprascrise); este fără stare, nu ține niciun secret și este o soluție de rezervă — un telefon folosește loopback, LAN-ul, tailnet-ul sau o cale wss:// directă atunci când una este accesibilă și trece pe releu doar când niciuna nu este. Sari peste el dacă clienții tăi ajung întotdeauna direct la server.

Restul acestui ghid construiește serverul din surse; flagurile și comportamentul pe care le acoperă sunt aceleași pe care le alimentează variabilele de mediu ale imaginii.

  • SDK-ul Dart. Acest depozit își fixează SDK-ul cu fvm, așa că pune prefixul fvm înaintea fiecărei comenzi.
  • Acest depozit clonat local.
  • Lanțul de unelte native de care are nevoie scriptul de pregătire — Rust/cargo, plus meson, ninja, pkg-config, git și un compilator C++. Fiecare script de build numește ce nu găsește și cum se instalează.
  • O gazdă accesibilă: loopback pentru uz local sau o adresă LAN/publică cu TLS pentru clienții remote.

Bibliotecile native sunt obligatorii — nu există un mod degradat. Compilează-le din rădăcina depozitului înainte de orice altceva:

Fereastră terminal
scripts/natives/build_natives.sh

Aceasta compilează rift, fff, tree-sitter plus gramaticile sale, aec, lame, ccpty, cc_watcher, cc_inference și biblioteca nativă SAML în build/natives/. Hook-ul de build al cc_server reemite în bundle tot ce este pregătit acolo, așa că sărirea peste acest pas fie eșuează direct buildul, fie — cu portița de scăpare de mai jos — produce un binar a cărui verificare prealabilă de la pornire refuză să pornească și numește biblioteca lipsă.

Fereastră terminal
cd apps/cc_server
fvm dart build cli

Bundle-ul include libsqlite3 și bibliotecile native pregătite alături de binar — nu e nevoie de SQLite de sistem și nici de motorul Flutter. Executabilul ajunge în ./build/cli/<os_arch>/bundle/bin/cc_server.

Un director de date nou nu are niciun dispozitiv asociat și nicio cheie prepartajată, așa că solicitarea cheii de asociere a unui client nu poate fi satisfăcută încă. Emite una cu subcomanda pair. Ea scrie direct global.db și secrets.json; un server care servește deja acel director de date preia noul dispozitiv fără repornire:

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

Afișează trei valori pe care să le lipești în formularul de conectare al unui 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>

Flaguri utile: --device (implicit web-client; ID-ul alege și nivelul dispozitivului — desktop, ios, android, orice altceva este un client web), --label (implicit numele platformei plus numele gazdă al acestei mașini), --host (gazda de afișat în URL, pentru că loopback nu este accesibil de pe altă mașină) și --client-url. Rularea din nou a lui pair rotește cheia acelui dispozitiv și elimină sesiunile lui active. Asocierea nu creează niciodată un spațiu de lucru: onboarding-ul clientului care se conectează numește și creează primul spațiu.

Cu --client-url afișează și un cod QR care poate fi scanat din terminal și care deschide clientul web deja completat:

Fereastră 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

O acreditare emisă de cc_server pair nu are nicio expirare; una emisă în aplicație expiră întotdeauna după 30 de zile. Revocă un dispozitiv emis din CLI din Setări → Tu → Dispozitivele tale când ai terminat cu el.

Pe loopback — valoarea implicită și tot ce ai nevoie pentru un client de pe aceeași mașină:

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

Ca să ajungi la el de pe altă mașină, leagă-l la toate interfețele. O legare non-loopback fără TLS este refuzată la pornire (Refusing to bind non-loopback address … without TLS), așa că furnizează un certificat:

Fereastră 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

Sau termină TLS într-un proxy invers, într-o rețea privată de încredere, și optează pentru o legare în text simplu în spatele lui:

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

--insecure este ignorat când --tls-cert și --tls-key sunt ambele setate — TLS câștigă întotdeauna. Serverul anunță starea de text simplu ca insecure: true în GET /healthz, iar clienții marchează o astfel de conexiune ca nesigură.

Serverul rulează până la SIGINT/SIGTERM, apoi se oprește curat.

Îndreaptă un client către URL-ul serverului și cheia de asociere de la pasul 3:

  • Aplicația desktop: pe ecranul de prima rulare (Cum ar trebui să ruleze Control Center?) completează câmpurile de conectare în loc de Rulează în această aplicație, sau adaugă serverul mai târziu din Setări → Server → Conexiune și stare. Vezi Conectează-te la un server remote.
  • Client web: deschide app.usectrl.dev (sau propria copie a versiunii web) și introdu URL-ul serverului, ID-ul de dispozitiv și cheia de asociere.
  • Telefon: asociază aplicația Remote dintr-un client care rulează — vezi Asociază un dispozitiv.

Ca să sincronizezi calendarul unui spațiu de lucru fără nicio interfață grafică atașată, folosește fluxul cu cod de dispozitiv:

Fereastră 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>

Afișează un cod și un URL de aprobat pe alt dispozitiv, stochează tokenul de reîmprospătare pe partea de server și iese. O compilare din surse nu include niciun client Google, așa că variabilele de mediu pentru ID și secret sunt obligatorii — nu există un flag --google-client-id. Vezi Conectează un Google Calendar.

Flagurile folosite mai sus sunt cele de care are nevoie o primă configurare. Suprafața completă — 23 de perechi de flaguri și variabile de mediu, cele patru subcomenzi și structura directorului de date — se află în referința CLI cc_server.

Cele patru de care vei avea cel mai probabil nevoie:

Flag Implicit Semnificație
--data-dir directorul de date de aplicație per utilizator al sistemului de operare Bazele de date, secretele, modelele și media din cache
--port 9030 Port TCP (0 = efemer)
--bind loopback loopback sau any pentru toate interfețele (necesită TLS sau --insecure)
--public-url derivat din legare URL-ul RPC anunțat clienților asociați. Setează-l explicit în spatele unui proxy, NAT sau tunel

Directorul de date implicit este ~/Library/Application Support/control-center pe macOS, %APPDATA%\control-center pe Windows și $XDG_DATA_HOME/control-center (altfel ~/.local/share/control-center) în restul cazurilor. Un .cc_server relativ la directorul de lucru este folosit doar când nu se rezolvă niciun director home sau de date de aplicație.

cc_server este întreaga suprafață a produsului, nu un subset: calea de date repo-RPC și abonamentele, endpointul de instrumente MCP la POST /mcp și GET /sse pe același port, webhook-urile primite, proxy-urile de media și de fonturi, callback-urile de autentificare unică, protocolul de concesionare al flotei, preluarea RSS pe partea de server la un interval de 30 de minute și căutarea vectorială (extensia sqlite_vector este înregistrată ca auto-extensie globală de proces, degradând la căutare full-text doar acolo unde extensia nu este disponibilă).

Dispatcherul este fără stare: nu există nicio legătură de spațiu de lucru per sesiune. Fiecare apel limitat la un spațiu de lucru își poartă propriul workspace_id, iar accesul este decis de apartenența și rolul apelantului în spațiul de lucru — nu de deținerea unei chei de asociere.

Două lucruri de știut înainte de a expune portul dincolo de loopback:

  • MCP eșuează închis în afara gazdei. O suprafață MCP fără token răspunde doar pe loopback. Legarea la any nu o lărgește — o cerere din afara gazdei este refuzată cu „MCP requires a bearer token to answer non-loopback clients” până când configurezi unul (mcp.setToken, sau Setări → Server → Servere MCP).
  • GET /healthz este neautentificat prin proiectare, cu CORS wildcard, și publică ID-ul, numele, amprenta de identitate și versiunea de build ale serverului. Clienții au nevoie de amprentă ca să fixeze serverul, deci este deliberat — dar înseamnă că orice cc_server accesibil dezvăluie atât oricui întreabă.