اجرای سرور بدون رابط
cc_server را روی ماشینی که همیشه روشن میماند اجرا کنید، سپس از رومیزی، مرورگر
یا تلفنتان به آن متصل شوید. این سرور یک باینری بومی Dart خالص است — بدون موتور
Flutter — پس بدون رابط روی macOS، Linux یا Windows اجرا میشود. پایگاهداده در
اختیار آن است، عاملها را اجرا میکند و هر فراخوانی API خارجی را انجام میدهد؛
کلاینتها فقط رندر میکنند.
اجرا در Docker (ایمیجهای پیشساخته)
بخش با عنوان “اجرا در Docker (ایمیجهای پیشساخته)”هر انتشار یک پشتهٔ خودمیزبان از چهار ایمیج GHCR منتشر میکند — بکاند، دو کلاینت
سبک ایستا و رلهٔ جفتسازی اختیاری — بهعلاوهٔ یک ایمیج جداگانهٔ cc-server-demo
برای میزبان دموی عمومی. نصب خودمیزبان به چکاوت کد منبع نیازی ندارد. یک تگ نسخه را
پین کنید (ghcr.io/samuelalev/<image>:v0.0.1-rc.1) یا از latest پیروی کنید.
| ایمیج | چیست | منتشر میکند |
|---|---|---|
ghcr.io/samuelalev/cc-server |
بکاند: پایگاهداده، عاملها، MCP، وبهوکها | -p 9030:9030 |
ghcr.io/samuelalev/cc-webapp |
کلاینت وب (nginx ایستا) | -p 8080:8080 |
ghcr.io/samuelalev/cc-remote |
PWA همراه تلفن (nginx ایستا) | -p 8081:8080 |
ghcr.io/samuelalev/cc-signaling-server |
رلهٔ جفتسازی WebRTC (اختیاری) | -p 8788:8788 |
کل پشته بهصورت یک فایل 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:نکتههایی که پیش از بیرونبردن هر یک از اینها از لوپبک ارزش دانستن دارند:
-
ایمیج
cc-serverمقدارCC_SERVER_INSECURE=1را همراه دارد (باید0.0.0.0را بایند کند تا ترافیک پورت منتشرشده را دریافت کند)، که تنها پشت یک پروکسی معکوس پایاندهندهٔ TLS — Caddy، Traefik، nginx یا یک تونل Cloudflare — روی یک شبکهٔ قابلاعتماد ایمن است. برای پایاندادن TLS درون خود فرایند، بهجایش یک گواهی مانت کنید وCC_SERVER_TLS_CERT/CC_SERVER_TLS_KEYرا تنظیم کنید (سپسCC_SERVER_INSECUREرا حذف کنید). همهچیز باdocker run -e …یا بلوکenvironment:در compose قابل بازنویسی است. -
/dataتنها وضعیت است. پایگاهدادههای SQLite، کلیدهای محرمانهٔ دستگاههای جفتشده، مدلهای دانلودشده و صدای جلسهها همگی در این والیوم زندگی میکنند — از آن پشتیبان بگیرید و کانتینر یکبارمصرف میشود. -
یک کلاینت را از داخل ایمیج جفت کنید پیش از نخستین اتصال؛ معادل کانتینریِ گام ۳:
پنجرهٔ ترمینال docker run --rm -it -v cc_data:/data \--entrypoint /app/bin/cc_server ghcr.io/samuelalev/cc-server:latest pair -
دو ایمیج کلاینت میزبانهای nginx ایستا هستند — چیزی برای پیکربندی در زمان اجرا ندارند؛ فرم اتصال هر کلاینت، URL سرور، شناسهٔ دستگاه و کلید جفتسازی شما را میگیرد. رلهٔ سیگنالینگ هم به همین ترتیب فقط به پورت خودش نیاز دارد (
SIGNALING_HOST/SIGNALING_PORTقابل بازنویسی)؛ بیحالت است، هیچ رازی نگه نمیدارد و یک جایگزین است — تلفن وقتی مسیری از لوپبک، LAN، tailnet یا مسیر مستقیمwss://در دسترس باشد از آن استفاده میکند و تنها وقتی هیچکدام در دسترس نباشد به رله بازمیگردد. اگر کلاینتهایتان همیشه مستقیماً به سرور میرسند، از آن صرفنظر کنید.
بقیهٔ این راهنما سرور را از کد منبع میسازد؛ فلگها و رفتارهایی که پوشش میدهد همانهایی هستند که متغیرهای محیطی ایمیج تغذیهشان میکنند.
پیشنیازها
بخش با عنوان “پیشنیازها”- Dart SDK. این مخزن SDK خود را با fvm تثبیت میکند، پس هر فرمان را با
fvmآغاز کنید. - این مخزن چکاوتشده باشد.
- زنجیرهٔ ابزار بومی که اسکریپت آمادهسازی لازم دارد — Rust/
cargo، بهعلاوهٔmeson،ninja،pkg-config،gitو یک کامپایلر C++. هر اسکریپت ساخت، هر چه را پیدا نکند و روش نصبش را نام میبرد. - یک میزبان قابلدسترسی: لوپبک برای استفادهٔ محلی، یا یک نشانی LAN/عمومی همراه TLS برای کلاینتهای راه دور.
گام ۱: کتابخانههای بومی را آماده کنید
بخش با عنوان “گام ۱: کتابخانههای بومی را آماده کنید”کتابخانههای بومی الزامی هستند — هیچ حالت تنزلیافتهای وجود ندارد. پیش از هر کار دیگری آنها را از ریشهٔ مخزن بسازید:
scripts/natives/build_natives.shاین کار rift، fff، tree-sitter بههمراه گرامرهایش، aec، lame،
ccpty، cc_watcher، cc_inference و بومیِ SAML را در
build/natives/ میسازد. هوک ساخت cc_server
هر آنچه را که آنجا آماده شده باشد دوباره در بسته جای میدهد، پس رد کردن این گام
یا ساخت را مستقیماً شکست میدهد یا — با راه فرار زیر — باینریای تولید میکند که
پیشبررسی راهاندازیاش از شروع سر باز میزند و کتابخانهٔ گمشده را نام میبرد.
گام ۲: ساخت باینری
بخش با عنوان “گام ۲: ساخت باینری”cd apps/cc_serverfvm dart build cliبستهٔ خروجی libsqlite3 و کتابخانههای بومی آمادهشده را در کنار باینری ارائه
میکند — نه SQLite سیستمی لازم است و نه موتور Flutter. فایل اجرایی در
./build/cli/<os_arch>/bundle/bin/cc_server مینشیند.
گام ۳: پیش از نخستین راهاندازی یک کلاینت را جفت کنید
بخش با عنوان “گام ۳: پیش از نخستین راهاندازی یک کلاینت را جفت کنید”یک پوشهٔ دادهٔ تازه هیچ دستگاه جفتشده و هیچ کلید پیشاشتراکی ندارد، پس درخواست
کلید جفتسازی یک کلاینت هنوز برآوردهشدنی نیست. با زیرفرمان pair یکی بسازید.
این فرمان global.db و secrets.json را مستقیماً مینویسد؛ سروری که از قبل آن
پوشهٔ داده را سرو میکند، دستگاه تازه را بدون راهاندازی مجدد برمیدارد:
./build/cli/<os_arch>/bundle/bin/cc_server pair --data-dir ./data --port 9030سه مقدار چاپ میکند تا در فرم اتصال یک کلاینت بچسبانید:
Connect a thin client (paste into its connect form): Server ws://localhost:9030/rpc Device id web-client Pairing key <a freshly generated key>فلگهای مفید: --device (پیشفرض web-client؛ شناسه ردهٔ دستگاه را هم تعیین
میکند — desktop، ios، android و هر چیز دیگری یک کلاینت وب است)، --label
(بهطور پیشفرض نام سیستمعامل بههمراه نام میزبان این ماشین)، --host (میزبانی
که در URL چاپ میشود، چون لوپبک از ماشینی دیگر قابلدسترسی نیست) و --client-url.
اجرای دوبارهٔ pair کلید آن دستگاه را میچرخاند و نشستهای زندهٔ آن را میاندازد.
جفتسازی هرگز فضای کاری نمیسازد: راهاندازی اولیهٔ کلاینت متصل، اولین فضای کاری
را نامگذاری و ایجاد میکند.
با --client-url همچنین یک QR قابلاسکن در ترمینال چاپ میکند که کلاینت وب را
از قبل پرشده باز میکند:
./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اعتبارنامهای که cc_server pair میسازد هیچ انقضایی ندارد؛ اعتبارنامهای که
در برنامه ساخته میشود همیشه پس از ۳۰ روز منقضی میشود. وقتی کارتان با دستگاهی که
CLI ساخته تمام شد، آن را از تنظیمات ← شما ← دستگاههای شما ابطال کنید.
گام ۴: راهاندازی سرور
بخش با عنوان “گام ۴: راهاندازی سرور”روی لوپبک — مقدار پیشفرض و هر آنچه برای کلاینتی روی همان ماشین لازم دارید:
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030برای دسترسی از ماشینی دیگر، همهٔ رابطهای شبکه را بایند کنید. بایند غیرلوپبک
بدون TLS در زمان راهاندازی رد میشود (Refusing to bind non-loopback address … without TLS)، پس یک گواهی تأمین کنید:
./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یا TLS را در یک پروکسی معکوس روی یک شبکهٔ خصوصی قابلاعتماد پایان دهید و پشت آن یک بایند متن ساده را انتخاب کنید:
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030 \ --bind any --insecure --public-url wss://server.example/rpcوقتی هم --tls-cert و هم --tls-key تنظیم شده باشند، --insecure نادیده گرفته
میشود — TLS همیشه برنده است. سرور حالت متن ساده را در GET /healthz بهصورت
insecure: true اعلام میکند و کلاینتها چنین اتصالی را ناایمن نشان میدهند.
سرور تا SIGINT/SIGTERM اجرا میشود، سپس پاک خاموش میشود.
گام ۵: اتصال یک کلاینت
بخش با عنوان “گام ۵: اتصال یک کلاینت”یک کلاینت را به URL سرور و کلید جفتسازی از گام ۳ اشاره دهید:
- برنامهٔ رومیزی: در صفحهٔ نخستین اجرا (Control Center چطور اجرا شود؟) بهجای اجرا در این برنامه فیلدهای اتصال را پر کنید، یا بعداً سرور را در تنظیمات ← سرور ← اتصال و وضعیت اضافه کنید. اتصال به سرور راه دور را ببینید.
- کلاینت وب: app.usectrl.dev (یا نسخهٔ خودتان از ساخت وب) را باز کنید و URL سرور، شناسهٔ دستگاه و کلید جفتسازی را وارد کنید.
- تلفن: اپ Remote را از یک کلاینت در حال اجرا جفت کنید — جفتکردن دستگاه را ببینید.
گام ۶: اتصال یک Google Calendar (اختیاری)
بخش با عنوان “گام ۶: اتصال یک Google Calendar (اختیاری)”برای همگامسازی تقویم یک فضای کاری بدون هیچ رابط گرافیکی متصل، از جریان کد دستگاه استفاده کنید:
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>یک کد و یک URL برای تأیید روی دستگاهی دیگر چاپ میکند، توکن تازهسازی را سمت سرور
ذخیره میکند و خارج میشود. نسخهای که از کد منبع ساخته میشود هیچ کلاینت Google
همراه ندارد، پس متغیرهای محیطی شناسه و کلید محرمانه الزامیاند — هیچ فلگ
--google-client-id وجود ندارد. اتصال Google Calendar را ببینید.
پیکربندی
بخش با عنوان “پیکربندی”فلگهای استفادهشده در بالا همانهایی هستند که یک راهاندازی نخست لازم دارد. سطح
کامل — ۲۳ جفت فلگ و متغیر محیطی، چهار زیرفرمان و چیدمان پوشهٔ داده — در
مرجع CLI مربوط به cc_server است.
آن چهارتایی که بیشتر از همه به آنها نیاز پیدا میکنید:
| فلگ | پیشفرض | معنا |
|---|---|---|
--data-dir |
پوشهٔ دادهٔ برنامهٔ هر کاربر در سیستمعامل | پایگاهدادهها، کلیدهای محرمانه، مدلها و رسانهٔ کششده |
--port |
9030 |
پورت TCP (0 = موقت) |
--bind |
loopback |
loopback، یا any برای همهٔ رابطها (نیازمند TLS یا --insecure) |
--public-url |
برگرفته از بایند | URL مربوط به RPC که به کلاینتهای جفتشده اعلام میشود. پشت پروکسی، NAT یا تونل این را صریح تنظیم کنید |
پوشهٔ دادهٔ پیشفرض روی macOS مسیر ~/Library/Application Support/control-center،
روی Windows مسیر %APPDATA%\control-center و در جاهای دیگر
$XDG_DATA_HOME/control-center (وگرنه ~/.local/share/control-center) است. یک
.cc_server نسبی به cwd تنها وقتی استفاده میشود که هیچ پوشهٔ خانه یا پوشهٔ دادهٔ
برنامهای حل نشود.
سرور چه چیزی سرو میکند
بخش با عنوان “سرور چه چیزی سرو میکند”cc_server تمام سطح محصول است، نه زیرمجموعهای از آن: مسیر داده و اشتراکهای
repo-RPC، نقطهٔ پایانی ابزار MCP در POST /mcp و GET /sse روی همان پورت،
وبهوکهای ورودی، پروکسیهای رسانه و قلم، بازخوانهای ورود یکپارچه، پروتکل اجارهٔ
ناوگان، واکشی RSS سمت سرور با زمانبندی ۳۰ دقیقهای و جستجوی برداری (افزونهٔ
sqlite_vector بهصورت یک افزونهٔ خودکار سراسریِ فرایند ثبت میشود و تنها جایی که
آن افزونه در دسترس نباشد به جستجوی متن کامل تنزل میکند).
توزیعکننده بیحالت است: هیچ اتصال فضای کاری بهازای هر نشست وجود ندارد. هر
فراخوانی در محدودهٔ فضای کاری، workspace_id خودش را حمل میکند و دسترسی بر پایهٔ
عضویت و نقش فراخوان در فضای کاری تعیین میشود — نه با در دست داشتن کلید جفتسازی.
دو نکته که پیش از بیرونبردن پورت از لوپبک باید بدانید:
- MCP بیرون از میزبان بسته شکست میخورد. یک سطح MCP بدون توکن فقط به لوپبک پاسخ میدهد. بایند کردن
anyآن را گسترده نمیکند — درخواستی از بیرون میزبان با “MCP requires a bearer token to answer non-loopback clients” رد میشود تا وقتی یکی را پیکربندی کنید (mcp.setToken، یا تنظیمات ← سرور ← سرورهای MCP). GET /healthzطبق طراحی بدون احراز هویت است، با CORS عام، و شناسه، نام، اثر انگشت هویت و نسخهٔ ساخت سرور را منتشر میکند. کلاینتها برای پین کردن سرور به اثر انگشت نیاز دارند، پس این عمدی است — اما یعنی هرcc_serverقابلدسترسی، همین مقدار را به هر کسی که بپرسد افشا میکند.