Gå til innholdet

Kjør en headless server

Kjør cc_server på en maskin som står på hele tiden, og koble deg deretter til den fra skrivebordet, en nettleser eller telefonen. Serveren er en native binærfil i ren Dart — uten Flutter-motor — så den kjører headless på macOS, Linux og Windows. Den eier databasen, kjører agentene og gjør hvert eneste eksterne API-kall; klientene gjengir bare.

Hver utgivelse publiserer en selvhostet stakk med fire GHCR-imager — backend-en, de to statiske tynne klientene og det valgfrie paringsreléet — pluss et eget cc-server-demo-image for den offentlige demoverten. En selvhostet installasjon trenger ingen utsjekking av kildekoden. Fest en versjonstagg (ghcr.io/samuelalev/<image>:v0.0.1-rc.1) eller følg latest.

Image Hva det er Publiserer
ghcr.io/samuelalev/cc-server Backend-en: database, agenter, MCP, webhooker -p 9030:9030
ghcr.io/samuelalev/cc-webapp Nettklienten (statisk nginx) -p 8080:8080
ghcr.io/samuelalev/cc-remote Telefonfølgeappen (PWA, statisk nginx) -p 8081:8080
ghcr.io/samuelalev/cc-signaling-server WebRTC-paringsreléet (valgfritt) -p 8788:8788

Hele stakken som en compose-fil:

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:

Noen ting det er verdt å vite før du eksponerer noe av dette utenfor loopback:

  • cc-server-imaget leveres med CC_SERVER_INSECURE=1 (det må binde 0.0.0.0 for å ta imot trafikk på publiserte porter), noe som bare er trygt bak en TLS-avsluttende omvendt proxy — Caddy, Traefik, nginx eller en Cloudflare Tunnel — på et klarert nettverk. Vil du avslutte TLS i prosessen i stedet, monterer du et sertifikat og setter CC_SERVER_TLS_CERT / CC_SERVER_TLS_KEY (og fjerner deretter CC_SERVER_INSECURE). Alt kan overstyres med docker run -e … eller environment:-blokken i compose.

  • /data er den eneste tilstanden. SQLite-databasene, hemmelighetene til parede enheter, nedlastede modeller og møtelyd ligger alle i volumet — sikkerhetskopier det, og containeren blir engangs.

  • Par en klient fra inne i imaget før den første tilkoblingen; dette er container-ekvivalenten av trinn 3:

    Terminalvindu
    docker run --rm -it -v cc_data:/data \
    --entrypoint /app/bin/cc_server ghcr.io/samuelalev/cc-server:latest pair
  • De to klientimagene er statiske nginx-verter — ingenting å konfigurere ved kjøring; hver klients tilkoblingsskjema tar imot server-URL-en, enhets-id-en og paringsnøkkelen din. Relémegleren trenger på samme måte bare porten sin (SIGNALING_HOST / SIGNALING_PORT kan overstyres); den er tilstandsløs, oppbevarer ingen hemmeligheter og er en reserve — en telefon bruker loopback, LAN, tailnet eller en direkte wss://-vei når en slik er tilgjengelig, og faller bare tilbake til reléet når ingen er det. Hopp over den hvis klientene dine alltid når serveren direkte.

Resten av denne veiledningen bygger serveren fra kildekoden; flaggene og oppførselen den dekker, er de samme som imagets miljøvariabler styrer.

  • Dart SDK. Dette repoet fester SDK-en sin med fvm, så sett fvm foran hver kommando.
  • Dette repoet sjekket ut lokalt.
  • Det native verktøysettet klargjøringsskriptet trenger — Rust/cargo, pluss meson, ninja, pkg-config, git og en C++-kompilator. Hvert byggeskript navngir det det ikke finner, og hvordan det installeres.
  • En vert som kan nås: loopback for lokal bruk, eller en LAN-/offentlig adresse med TLS for eksterne klienter.

De native bibliotekene er påkrevd — det finnes ingen redusert modus. Bygg dem fra repoets rot før alt annet:

Terminalvindu
scripts/natives/build_natives.sh

Dette bygger rift, fff, tree-sitter pluss grammatikkene, aec, lame, ccpty, cc_watcher, cc_inference og SAML-native-biblioteket inn i build/natives/. Byggkroken til cc_server sender videre det som er klargjort der, inn i bunten, så hvis du hopper over dette trinnet, feiler bygget enten helt, eller — med nødutgangen nedenfor — gir en binærfil der forhåndssjekken ved oppstart nekter å starte og navngir det manglende biblioteket.

Terminalvindu
cd apps/cc_server
fvm dart build cli

Bunten leveres med libsqlite3 og de klargjorte native bibliotekene ved siden av binærfilen — ingen system-SQLite og ingen Flutter-motor trengs. Den kjørbare filen lander på ./build/cli/<os_arch>/bundle/bin/cc_server.

En fersk datamappe har ingen paret enhet og ingen forhåndsdelt nøkkel, så en klients forespørsel om paringsnøkkel kan ennå ikke besvares. Utsted én med underkommandoen pair. Den skriver global.db og secrets.json direkte; en server som allerede betjener den datamappen, plukker opp den nye enheten uten omstart:

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

Den skriver ut tre verdier du limer inn i en klients tilkoblingsskjema:

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

Nyttige flagg: --device (standard web-client; id-en velger også enhetskategorien — desktop, ios, android, alt annet er en nettklient), --label (standard er plattformnavnet pluss vertsnavnet til denne maskinen), --host (verten som skrives ut i URL-en, siden loopback ikke kan nås fra en annen maskin) og --client-url. Kjører du pair på nytt, roteres nøkkelen til den enheten, og de aktive øktene dens avsluttes. Paring oppretter aldri et arbeidsområde: onboardingen til klienten som kobler til, navngir og oppretter det første.

Med --client-url skriver den også ut en QR-kode som kan skannes fra terminalen, og som åpner nettklienten ferdig utfylt:

Terminalvindu
./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

En legitimasjon utstedt av cc_server pair har ingen utløpstid; en som er utstedt i appen, utløper alltid etter 30 dager. Tilbakekall en enhet utstedt fra CLI-et fra Innstillinger → Deg → Dine enheter når du er ferdig med den.

På loopback — standarden, og alt du trenger for en klient på samme maskin:

Terminalvindu
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030

For å nå den fra en annen maskin, bind alle grensesnitt. En binding utenfor loopback uten TLS blir avvist ved oppstart (Refusing to bind non-loopback address … without TLS), så oppgi et sertifikat:

Terminalvindu
./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

Eller avslutt TLS i en omvendt proxy på et klarert privat nettverk og velg en klartekstbinding bak den:

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

--insecure ignoreres når både --tls-cert og --tls-key er satt — TLS vinner alltid. Serveren annonserer klarteksttilstanden som insecure: true i GET /healthz, og klienter merker en slik tilkobling som usikker.

Serveren kjører til SIGINT/SIGTERM, og avsluttes deretter ryddig.

Pek en klient mot server-URL-en og paringsnøkkelen fra trinn 3:

  • Skrivebordsappen: på førstegangsskjermen (Hvordan skal Control Center kjøre?) fyller du ut tilkoblingsfeltene i stedet for Kjør i denne appen, eller legger til serveren senere under Innstillinger → Server → Tilkobling og status. Se Koble til en ekstern server.
  • Nettklient: åpne app.usectrl.dev (eller din egen kopi av nettbygget) og skriv inn server-URL-en, enhets-id-en og paringsnøkkelen.
  • Telefon: par Remote-appen fra en klient som kjører — se Koble til en enhet.

For å synkronisere kalenderen til et arbeidsområde uten noe grafisk grensesnitt, bruk enhetskodeflyten:

Terminalvindu
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>

Den skriver ut en kode og en URL du godkjenner på en annen enhet, lagrer oppdateringstokenet på serversiden og avslutter. Et bygg fra kildekoden leveres uten Google-klient, så miljøvariablene for id og hemmelighet er påkrevd — det finnes ikke noe --google-client-id-flagg. Se Koble til en Google Calendar.

Flaggene som er brukt ovenfor, er de en første oppsett trenger. Den komplette flaten — 23 flagg- og miljøvariabelpar, de fire underkommandoene og oppsettet av datamappen — står i CLI-referansen for cc_server.

De fire du mest sannsynlig trenger:

Flagg Standard Betydning
--data-dir operativsystemets appdatamappe per bruker Databaser, hemmeligheter, modeller og hurtiglagret media
--port 9030 TCP-port (0 = efemær)
--bind loopback loopback, eller any for alle grensesnitt (krever TLS eller --insecure)
--public-url utledet fra bindingen RPC-URL-en som annonseres til parede klienter. Sett denne eksplisitt bak en proxy, NAT eller tunnel

Standard datamappe er ~/Library/Application Support/control-center på macOS, %APPDATA%\control-center på Windows og $XDG_DATA_HOME/control-center (ellers ~/.local/share/control-center) andre steder. En .cc_server relativ til arbeidsmappen brukes bare når verken hjemmemappen eller appdatamappen lar seg finne.

cc_server er hele produktflaten, ikke et utvalg: RPC-datastien og abonnementene for repoet, MCP-verktøyendepunktet på POST /mcp og GET /sse på samme port, innkommende webhooker, medie- og skriftproxyene, tilbakekallinger for enkel pålogging, flåte-leaseprotokollen, RSS-henting på serversiden hvert 30. minutt og vektorsøk (sqlite_vector-utvidelsen registreres som en prosessglobal auto-utvidelse og faller ned til fulltekstsøk bare der utvidelsen ikke er tilgjengelig).

Dispatcheren er tilstandsløs: det finnes ingen arbeidsområdebinding per økt. Hvert arbeidsområdespesifikt kall bærer sin egen workspace_id, og tilgang avgjøres av medlemskapet og rollen den som kaller har i arbeidsområdet — ikke av å holde en paringsnøkkel.

To ting du bør vite før du eksponerer porten utenfor loopback:

  • MCP svikter lukket utenfor verten. En MCP-flate uten token svarer bare på loopback. Å binde any utvider den ikke — en forespørsel utenfra avvises med «MCP requires a bearer token to answer non-loopback clients» til du konfigurerer ett (mcp.setToken, eller Innstillinger → Server → MCP-servere).
  • GET /healthz er uautentisert med vilje, med jokertegn i CORS, og publiserer serverens id, navn, identitetsfingeravtrykk og byggeversjon. Klienter trenger fingeravtrykket for å feste serveren, så dette er med vilje — men det betyr at enhver cc_server som kan nås, avslører så mye til hvem som helst som spør.