รันเซิร์ฟเวอร์แบบไม่มีส่วนติดต่อ
รัน 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 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 ของเซิร์ฟเวอร์, ID อุปกรณ์ และคีย์จับคู่ของคุณ รีเลย์สัญญาณก็เช่นกัน ต้องการแค่พอร์ตของมัน (แก้ทับได้ด้วย
SIGNALING_HOST/SIGNALING_PORT) มันไม่มีสถานะ ไม่เก็บ ข้อมูลลับ และเป็นทางเลือกสำรอง — โทรศัพท์จะใช้ลูปแบ็ก, LAN, tailnet หรือ เส้นทางwss://ตรง ๆ เมื่อมีเส้นทางใดเข้าถึงได้ และจะถอยไปใช้รีเลย์เฉพาะ เมื่อไม่มีเลย ข้ามมันได้หากไคลเอนต์ของคุณเข้าถึงเซิร์ฟเวอร์โดยตรงเสมอ
ส่วนที่เหลือของคู่มือนี้บิลด์เซิร์ฟเวอร์จากซอร์ส แฟล็กและพฤติกรรมที่มันกล่าวถึง เป็นชุดเดียวกับที่ตัวแปรสภาพแวดล้อมของอิมเมจป้อนเข้าไป
สิ่งที่ต้องมีก่อน
หัวข้อที่มีชื่อว่า “สิ่งที่ต้องมีก่อน”- Dart SDK รีโปนี้ตรึง SDK ไว้ด้วย fvm จึงต้องนำหน้าทุกคำสั่งด้วย
fvm - รีโปนี้ที่เช็กเอาต์ไว้แล้ว
- ชุดเครื่องมือเนทีฟที่สคริปต์จัดเตรียมต้องการ — Rust/
cargoรวมถึงmeson,ninja,pkg-config,gitและคอมไพเลอร์ C++ สคริปต์บิลด์แต่ละตัวจะระบุสิ่งที่มันหาไม่พบและวิธีติดตั้ง - โฮสต์ที่เข้าถึงได้: ลูปแบ็กสำหรับใช้ในเครื่อง หรือที่อยู่ 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 จะส่งสิ่งที่จัดเตรียมไว้ตรงนั้นซ้ำเข้าไปในบันเดิล
การข้ามขั้นตอนนี้จึงทำให้บิลด์ล้มเหลวไปเลย หรือ — ด้วยช่องทางเฉพาะด้านล่าง —
สร้างไบนารีที่การตรวจสอบก่อนบูตปฏิเสธที่จะเริ่มและระบุชื่อไลบรารีที่ขาดหาย
ขั้นตอนที่ 2: บิลด์ไบนารี
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 2: บิลด์ไบนารี”cd apps/cc_serverfvm 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; ID ยังเลือกได้ว่า
ระดับของอุปกรณ์เป็นอะไร — 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 ไม่มีเวลาหมดอายุ ส่วนที่สร้างในแอป
จะหมดอายุหลัง 30 วันเสมอ เพิกถอนอุปกรณ์ที่สร้างจาก CLI ได้จาก
การตั้งค่า → คุณ → อุปกรณ์ของคุณ เมื่อใช้เสร็จแล้ว
ขั้นตอนที่ 4: เริ่มเซิร์ฟเวอร์
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 4: เริ่มเซิร์ฟเวอร์”บนลูปแบ็ก — ค่าเริ่มต้นและเพียงพอสำหรับไคลเอนต์บนเครื่องเดียวกัน:
./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--insecure จะถูกเพิกเฉยเมื่อตั้งทั้ง --tls-cert และ --tls-key — TLS
ชนะเสมอ เซิร์ฟเวอร์ประกาศสถานะข้อความล้วนเป็น insecure: true ใน
GET /healthz และไคลเอนต์จะติดป้ายการเชื่อมต่อแบบนั้นว่าไม่ปลอดภัย
เซิร์ฟเวอร์จะรันจนถึง SIGINT/SIGTERM แล้วปิดตัวลงอย่างสะอาด
ขั้นตอนที่ 5: เชื่อมต่อไคลเอนต์
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 5: เชื่อมต่อไคลเอนต์”ชี้ไคลเอนต์ไปยัง URL ของเซิร์ฟเวอร์และคีย์จับคู่จากขั้นตอนที่ 3:
- แอปเดสก์ท็อป: บนหน้าจอเปิดครั้งแรก (Control Center ควรรันอย่างไร?) ให้กรอกช่องเชื่อมต่อแทน รันในแอปนี้ หรือเพิ่มเซิร์ฟเวอร์ภายหลังที่ การตั้งค่า → เซิร์ฟเวอร์ → การเชื่อมต่อและสถานะ ดูเชื่อมต่อเซิร์ฟเวอร์ระยะไกล
- ไคลเอนต์เว็บ: เปิด app.usectrl.dev (หรือสำเนาบิลด์เว็บของคุณเอง) แล้วป้อน URL ของเซิร์ฟเวอร์, ID อุปกรณ์ และคีย์จับคู่
- โทรศัพท์: จับคู่แอป Remote จากไคลเอนต์ที่กำลังรัน — ดูจับคู่อุปกรณ์
ขั้นตอนที่ 6: เชื่อมต่อ Google Calendar (ตัวเลือก)
หัวข้อที่มีชื่อว่า “ขั้นตอนที่ 6: เชื่อมต่อ Google Calendar (ตัวเลือก)”หากต้องการซิงค์ปฏิทินของเวิร์กสเปซโดยไม่ต้องมี GUI ให้ใช้ขั้นตอน device code:
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 มาให้ จึงต้องใช้ตัวแปรสภาพแวดล้อม
id และ secret — ไม่มีแฟล็ก --google-client-id ดูเชื่อมต่อ Google Calendar
การตั้งค่า
หัวข้อที่มีชื่อว่า “การตั้งค่า”แฟล็กที่ใช้ข้างต้นคือชุดที่การตั้งค่าครั้งแรกต้องใช้ พื้นผิวฉบับเต็ม — แฟล็กและ
ตัวแปรสภาพแวดล้อม 23 คู่ คำสั่งย่อยสี่คำสั่ง และเลย์เอาต์ของไดเรกทอรีข้อมูล —
อยู่ในเอกสารอ้างอิง CLI ของ cc_server
สี่ตัวที่คุณน่าจะต้องใช้มากที่สุด:
| แฟล็ก | ค่าเริ่มต้น | ความหมาย |
|---|---|---|
--data-dir |
ไดเรกทอรีข้อมูลแอปพลิเคชันต่อผู้ใช้ของระบบปฏิบัติการ | ฐานข้อมูล ข้อมูลลับ โมเดล และสื่อที่แคชไว้ |
--port |
9030 |
พอร์ต TCP (0 = ชั่วคราว) |
--bind |
loopback |
loopback หรือ any สำหรับทุกอินเทอร์เฟซ (ต้องมี TLS หรือ --insecure) |
--public-url |
อนุมานจากค่าที่ผูก | URL ของ RPC ที่ประกาศให้ไคลเอนต์ที่จับคู่ไว้ ตั้งค่านี้อย่างชัดเจนเมื่ออยู่หลังพร็อกซี, NAT หรือทันเนล |
ไดเรกทอรีข้อมูลเริ่มต้นคือ ~/Library/Application Support/control-center บน macOS,
%APPDATA%\control-center บน Windows และ $XDG_DATA_HOME/control-center
(หรือ ~/.local/share/control-center) ที่อื่น ๆ จะใช้ .cc_server สัมพัทธ์กับ
cwd ก็เฉพาะเมื่อหาไดเรกทอรีโฮมหรือไดเรกทอรีข้อมูลแอปไม่เจอเท่านั้น
สิ่งที่เซิร์ฟเวอร์ให้บริการ
หัวข้อที่มีชื่อว่า “สิ่งที่เซิร์ฟเวอร์ให้บริการ”cc_server คือพื้นผิวผลิตภัณฑ์ทั้งหมด ไม่ใช่ส่วนย่อย: เส้นทางข้อมูล repo-RPC
และการสมัครรับข้อมูล, ปลายทางเครื่องมือ MCP ที่ POST /mcp และ GET /sse
บนพอร์ตเดียวกัน, เว็บฮุกขาเข้า, พร็อกซีสื่อและฟอนต์, คอลแบ็กการลงชื่อเข้าใช้
ครั้งเดียว, โปรโตคอลลีสของฟลีต, การดึง RSS ฝั่งเซิร์ฟเวอร์ตามกำหนดทุก 30 นาที
และการค้นหาแบบเวกเตอร์ (ส่วนขยาย sqlite_vector ลงทะเบียนเป็นส่วนขยายอัตโนมัติ
ระดับโปรเซส โดยลดลงเป็นการค้นหาแบบข้อความเต็มเฉพาะที่ส่วนขยายใช้ไม่ได้)
ดิสแพตเชอร์ไม่มีสถานะ: ไม่มีการผูกเวิร์กสเปซต่อเซสชัน ทุกการเรียกที่อยู่ใน
ขอบเขตเวิร์กสเปซจะพา workspace_id ของตัวเองมา และการเข้าถึงตัดสินจาก
สมาชิกภาพเวิร์กสเปซและบทบาทของผู้เรียก — ไม่ใช่จากการถือคีย์จับคู่
สองเรื่องที่ควรรู้ก่อนเปิดพอร์ตพ้นลูปแบ็ก:
- MCP ปิดกั้นแบบล้มเหลวปลอดภัยนอกโฮสต์ พื้นผิว MCP ที่ไม่มีโทเค็นจะตอบเฉพาะลูปแบ็ก การผูกกับ
anyไม่ได้ขยายขอบเขตนั้น — คำขอจากนอกโฮสต์จะถูกปฏิเสธด้วย “MCP requires a bearer token to answer non-loopback clients” จนกว่าจะตั้งค่าไว้ (mcp.setTokenหรือ การตั้งค่า → เซิร์ฟเวอร์ → เซิร์ฟเวอร์ MCP) GET /healthzไม่ต้องยืนยันตัวตนโดยการออกแบบ เปิด CORS แบบไวลด์การ์ด และเปิดเผย id, ชื่อ, ลายนิ้วมือตัวตน และเวอร์ชันบิลด์ของเซิร์ฟเวอร์ ไคลเอนต์ต้องใช้ลายนิ้วมือเพื่อปักหมุดเซิร์ฟเวอร์ จึงเป็นเจตนา — แต่มันหมายความว่าcc_serverใดก็ตามที่เข้าถึงได้จะเปิดเผยข้อมูลเท่านั้นให้ใครก็ตามที่ถาม