تخطَّ إلى المحتوى

تشغيل خادم بلا واجهة

شغّل cc_server على جهاز يبقى يعمل، ثم اتصل به من الحاسوب أو المتصفح أو الهاتف. الخادم ثنائي Dart خالص — بلا محرّك Flutter — فيعمل بلا واجهة على macOS أو Linux أو Windows. يملك قاعدة البيانات، ويشغّل الوكلاء، ويجري كل استدعاء لواجهة برمجة خارجية، والعملاء يقتصرون على الرسم.

التشغيل في 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 تطبيق الويب التقدّمي المرافق للهاتف (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:

ما يستحق معرفته قبل أن تكشف أيًا من هذا خارج loopback:

  • صورة cc-server تُشحن وفيها CC_SERVER_INSECURE=1 (لا بد أن تربط 0.0.0.0 لتستقبل حركة المنافذ المنشورة)، وهذا لا يكون آمنًا إلا خلف بروكسي عكسي ينهي TLS — Caddy أو Traefik أو nginx أو Cloudflare Tunnel — على شبكة موثوقة. لإنهاء TLS داخل العملية، اربط شهادة كوحدة تخزين واضبط CC_SERVER_TLS_CERT / CC_SERVER_TLS_KEY (ثم أزل CC_SERVER_INSECURE). كل ذلك قابل للتجاوز بـ docker run -e … أو بكتلة environment: في compose.

  • /data هي الحالة الوحيدة. قواعد SQLite، وأسرار الأجهزة المقترنة، والنماذج المنزَّلة، وصوت الاجتماعات، كلها في وحدة التخزين. انسخها احتياطيًا فتصير الحاوية قابلة للاستبدال.

  • اقرن عميلًا من داخل الصورة قبل أول اتصال، وهو في الحاوية ما يقابل الخطوة 3:

    نافذة طرفيّة
    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 قابلان للتجاوز)؛ وهو بلا حالة، ولا يحمل أسرارًا، وهو احتياط — يستخدم الهاتف loopback أو LAN أو tailnet أو مسار wss:// مباشرًا حين يمكن بلوغ أحدها، ولا يعود إلى المرحّل إلا حين لا يبلغ أيًا منها. تخطَّه إن كان عملاؤك يبلغون الخادم مباشرة دائمًا.

بقية الدليل تبني الخادم من المصدر. ما يغطيه من أعلام وسلوك هو نفسه ما تغذّيه متغيرات بيئة الصورة.

  • Dart SDK. يقيّد هذا المستودع إصدار SDK عبر fvm، فأسبق كل أمر بـ fvm.
  • نسخة محلية من هذا المستودع.
  • سلسلة أدوات البناء الأصيلة التي يحتاجها سكربت التجهيز — Rust/cargo، مع meson وninja وpkg-config وgit ومترجم C++. يسمّي كل سكربت بناء ما لم يجده وكيف يُثبَّت.
  • مضيف يمكن بلوغه: loopback للاستخدام المحلي، أو عنوان على LAN أو عنوان عام مع TLS للعملاء البعيدين.

الخطوة 1: تجهيز المكتبات الأصيلة

قسم بعنوان “الخطوة 1: تجهيز المكتبات الأصيلة”

المكتبات الأصيلة مطلوبة — لا وضع منقوص. ابنِها من جذر المستودع قبل أي شيء آخر:

نافذة طرفيّة
scripts/natives/build_natives.sh

يبني هذا rift وfff وtree-sitter مع قواعده النحوية، وaec وlame وccpty وcc_watcher وcc_inference ومكتبة SAML الأصيلة، داخل build/natives/. خطاف بناء cc_server يعيد تضمين ما جُهّز هناك في الحزمة، فتجاوز هذه الخطوة إما يُفشل البناء مباشرة، أو — مع المخرج أدناه — ينتج ثنائيًا يرفض فحص ما قبل الإقلاع تشغيله ويسمّي المكتبة الناقصة.

نافذة طرفيّة
cd apps/cc_server
fvm dart build cli

تُشحن الحزمة ومعها libsqlite3 والمكتبات الأصيلة المجهّزة إلى جانب الثنائي، فلا حاجة إلى SQLite من النظام ولا إلى محرّك Flutter. يستقر الملف التنفيذي في ./build/cli/<os_arch>/bundle/bin/cc_server.

الخطوة 3: اقتران عميل قبل أول تشغيل

قسم بعنوان “الخطوة 3: اقتران عميل قبل أول تشغيل”

دليل البيانات الجديد بلا جهاز مقترن وبلا مفتاح مشترك مسبقًا، فلا يمكن بعد تلبية مطالبة مفتاح الاقتران في العميل. صُكّ واحدًا بالأمر الفرعي 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 (المضيف الذي يُطبع في العنوان، لأن loopback لا يُبلَغ من جهاز آخر)، و--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 تبقى بلا انتهاء صلاحية، والتي تُصك في التطبيق تنتهي دائمًا بعد 30 يومًا. أبطل الجهاز الذي صُكّ من سطر الأوامر من الإعدادات ← أنت ← أجهزتك حين تنتهي منه.

على loopback — الافتراضي، وكل ما يلزم لعميل على الجهاز نفسه:

نافذة طرفيّة
./build/cli/<os_arch>/bundle/bin/cc_server --data-dir ./data --port 9030

لبلوغه من جهاز آخر، اربط كل الواجهات. ربط غير loopback من غير 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

يُتجاهل --insecure حين يُضبط --tls-cert و--tls-key معًا — TLS يغلب دائمًا. يعلن الخادم حالة النص الواضح على أنها insecure: true في GET /healthz، ويضع العملاء على هذا الاتصال شارة غير آمن.

يبقى الخادم يعمل حتى SIGINT/SIGTERM، ثم يتوقف بإيقاف نظيف.

وجّه عميلًا إلى عنوان URL للخادم ومفتاح الاقتران من الخطوة 3:

  • تطبيق الحاسوب: في شاشة أول تشغيل (كيف ينبغي أن يعمل Control Center؟) املأ حقول الاتصال بدل التشغيل داخل هذا التطبيق، أو أضف الخادم لاحقًا من الإعدادات ← الخادم ← الاتصال والحالة. انظر الاتصال بخادم بعيد.
  • عميل الويب: افتح app.usectrl.dev (أو نسختك من حزمة الويب) وأدخل عنوان URL للخادم ومعرّف الجهاز ومفتاح الاقتران.
  • الهاتف: اقرن تطبيق Remote من عميل يعمل — انظر اقتران جهاز.

الخطوة 6: ربط Google Calendar (اختياري)

قسم بعنوان “الخطوة 6: ربط 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.

الأعلام المستخدمة أعلاه هي ما يحتاجه الإعداد الأول. السطح الكامل — 23 زوجًا، كل منها علم ومتغير بيئة، والأوامر الفرعية الأربعة، وتخطيط دليل البيانات — في مرجع واجهة سطر أوامر cc_server.

الأربعة التي ستحتاجها في الغالب:

العلم الافتراضي المعنى
--data-dir دليل بيانات التطبيق لكل مستخدم في نظام التشغيل قواعد البيانات، والأسرار، والنماذج، والوسائط المخزَّنة مؤقتًا
--port 9030 منفذ TCP (0 = مؤقت)
--bind loopback loopback، أو any لكل واجهة (يحتاج TLS أو --insecure)
--public-url يُشتق من الربط عنوان RPC المُعلَن للعملاء المقترنين. اضبطه صراحة خلف بروكسي أو NAT أو نفق

دليل البيانات الافتراضي هو ~/Library/Application Support/control-center على macOS، و%APPDATA%\control-center على Windows، و$XDG_DATA_HOME/control-center (وإلا ~/.local/share/control-center) في سواهما. لا يُستخدم .cc_server النسبي إلى دليل العمل الحالي إلا إذا لم يُعثر على دليل المنزل ولا على دليل بيانات التطبيق.

cc_server هو سطح المنتج كله، لا شريحة منه: مسار بيانات repo-RPC والاشتراكات، ونقطة نهاية أدوات MCP عند POST /mcp وGET /sse على المنفذ نفسه، وخطافات الويب الواردة، وبروكسيات الوسائط والخطوط، واستدعاءات تسجيل الدخول الموحّد الراجعة، وبروتوكول إيجار الأسطول، وجلب RSS من جهة الخادم بجدول كل 30 دقيقة، والبحث المتجهي (تُسجَّل إضافة sqlite_vector إضافةً تلقائية على نطاق العملية، فيتراجع إلى البحث بالنص الكامل وحده حيث لا تتوفر الإضافة).

الموزّع بلا حالة: لا ربط لمساحة عمل على مستوى الجلسة. كل استدعاء في نطاق مساحة عمل يحمل workspace_id الخاص به، ويُحسم الوصول بحسب عضوية المستدعي في مساحة العمل ودوره. حيازة مفتاح الاقتران ليست حد الوصول.

أمران قبل أن تكشف المنفذ خارج loopback:

  • MCP يفشل مغلقًا خارج المضيف. سطح MCP بلا رمز لا يجيب إلا على loopback. ربط any لا يوسّعه — فيُرفض الطلب القادم من خارج المضيف بالرسالة «MCP requires a bearer token to answer non-loopback clients» إلى أن تضبط رمزًا (mcp.setToken، أو الإعدادات ← الخادم ← خوادم MCP).
  • GET /healthz بلا مصادقة عن قصد، مع CORS عام، وينشر معرّف الخادم واسمه وبصمة هويته وإصدار البناء. يحتاج العملاء البصمة ليثبّتوا الخادم، فالإفصاح مقصود، ويعني أن أي cc_server يمكن بلوغه يكشف هذا القدر لمن يسأل.