Skip to content

Latest commit

 

History

History
929 lines (691 loc) · 58.4 KB

EMBEDDED-SERVICES.md

File metadata and controls

929 lines (691 loc) · 58.4 KB

Embedded Services (עברית)

🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇧🇦 bs · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW


גרסה: v3.8.44 עדכון אחרון: 2026-09-09 קהל יעד: מהנדסים שמוסיפים, מתחזקים או מנפים שגיאות בשירותים מוטמעים (9Router, CLIProxyAPI, Mux, Bifrost, open-wa).

שירותים מוטמעים הם כלי sidecar המותקנים מקומית כתהליכים, שאותם OmniRoute מתקין, מפקח עליהם וחושף כיעדי ניתוב מהמעלה הראשונה. בניגוד לספקים חיצוניים (שהגישה אליהם מתבצעת דרך האינטרנט באמצעות מפתחות API), שירותים מוטמעים פועלים באותו מחשב שבו פועל OmniRoute ומתקשרים דרך loopback.


תוכן עניינים

  1. סקירה כללית
  2. ארכיטקטורה — 4 שכבות
  3. מכונת מצבי מחזור החיים
  4. מדריך עזר ל-API
  5. אבטחה
  6. הוספת שירות מוטמע חדש
  7. פתרון בעיות
  8. שאלות נפוצות

1. סקירה כללית

מדוע שירותים מוטמעים?

שישה שירותים מוטמעים:

שירות חבילת npm פורט ברירת מחדל מטרה
9Router 9router 20130 נתב AI שבו OmniRoute יכול להשתמש כתת-ספק. המודלים נחשפים בתבנית 9router/{sub}/{model}
CLIProxyAPI קובץ בינארי ממהדורת GitHub (cliproxy) 8317 מתאם proxy מקומי עבור תהליכי אימות של Anthropic CLI. מספק ניתוב חלופי כאשר תוקפי OAuth פגים
Mux mux (mux server ללא ממשק גרפי) 8322 daemon מקומי לתזמור סוכנים (coder/mux). מנוהל רק מבחינת מחזור החיים — אינו יעד ניתוב (ללא העברת LLM דרך proxy).
Bifrost @maximhq/bifrost 8080 backend ממסר לשער AI ב-Go. כאשר הוא פועל, הוא נבחר אוטומטית על ידי נתיב הממסר (/v1/relay/)
Dario @askalf/dario 3456 proxy למינוי Claude — חלופה/גיבוי ל-CLIProxyAPI עבור תעבורה במבנה Claude Code; המפתח המוזרק הופך ל-DARIO_ADMIN_TOKEN, המגן על מישור הבקרה של OAuth תחת /admin/*
open-wa @open-wa/wa-automate 8323 אוטומציה של WhatsApp Web‏ (Chromium ללא ממשק גרפי באמצעות Puppeteer). מנוהל רק מבחינת מחזור החיים — אינו יעד ניתוב.

כל ששת השירותים פועלים לפי אותו מודל פיקוח:

  • OmniRoute מתקין אותם תחת DATA_DIR/services/{name}/ (בבידוד מה-package.json של OmniRoute עצמו)
  • OmniRoute מפעיל ומנטר אותם כתהליכי צאצא
  • OmniRoute מזריק מפתח API ארעי לסביבת תהליך הצאצא ומחליף אותו ללא זמן השבתה (כאשר הדבר רלוונטי)
  • כל נתיבי הניהול (/api/services/*) הם LOCAL_ONLY — נגישים רק מ-loopback (כלל קשיח מס' 17)

החלטות מרכזיות (מתוכנית התכנון)

החלטה ערך
גישת לוח המחוונים לממשק המקורי של 9Router Reverse proxy ב-/dashboard/providers/services/9router/embed/*
מנגנון התקנה npm install {package} באמצעות execFile (ללא אינטרפולציית shell)
מצב צריכה הספק רשום כ-9router/{sub}/{model} במנוע הניתוב
ניהול מפתחות API OmniRoute יוצר, מצפין במנוחה (AES-256-GCM) ומזריק באמצעות משתני סביבה
מיקום לוח המחוונים /dashboard/providers/services (שלוש לשוניות)
הפעלה אוטומטית מתג נפרד לכל שירות, כבוי כברירת מחדל

2. ארכיטקטורה — 4 שכבות

┌────────────────────────────────────────────────────────────────────┐
│  שכבה 1 — ממשק משתמש                                               │
│  /dashboard/providers/services  (לשוניות: CLIProxyAPI | 9Router | Mux)│
│  יומנים בזמן אמת (SSE), הפעלה/עצירה/הפעלה מחדש/עדכון, הגדרות, התקנה│
│                                                                    │
│  src/app/(dashboard)/dashboard/providers/services/                 │
│    ├── page.tsx               מעטפת + ניתוב לשוניות לפי ?tab=      │
│    ├── tabs/                  CliproxyServiceTab, NinerouterServiceTab,│
│    │                          MuxServiceTab                        │
│    └── components/            ServiceStatusCard, ServiceLifecycleButtons,│
│                               ServiceLogsPanel, ApiKeyCard, ...    │
└──────────────────────┬─────────────────────────────────────────────┘
                       │ HTTP ‏(fetch של Next.js)
┌──────────────────────▼─────────────────────────────────────────────┐
│  שכבה 2 — API ‏(LOCAL_ONLY — ממשק loopback בלבד)                   │
│                                                                    │
│  /api/services/9router/{install|start|stop|restart|update|         │
│                          rotate-key|status|auto-start|logs}        │
│  /api/services/cliproxy/{install|start|stop|restart|update|        │
│                           status|auto-start|logs}                  │
│  /api/services/mux/{install|start|stop|restart|update|             │
│                      status|auto-start|logs}                       │
│  /dashboard/providers/services/9router/embed/[...path]             │
│    (פרוקסי HTTP + WebSocket הפוך → שירות 9Router במעלה הזרם)       │
│                                                                    │
│  שער: LOCAL_ONLY_API_PREFIXES כולל את "/api/services/" ואת         │
│        "/dashboard/providers/services/*/embed/"                    │
└──────────────────────┬─────────────────────────────────────────────┘
                       │ קריאות בתוך התהליך
┌──────────────────────▼─────────────────────────────────────────────┐
│  שכבה 3 — ServiceSupervisor ‏(src/lib/services/)                   │
│                                                                    │
│  ServiceSupervisor.ts   מפקח כללי (child_process.spawn)            │
│    ├── התקנה:     execFile('npm', ['install', pkg, '--prefix'])     │
│    ├── הפעלה:     spawn(node, [entrypoint], {env, cwd})            │
│    ├── מפתח API:  crypto.randomBytes(32) → env NINEROUTER_API_KEY  │
│    ├── פורט:      20130 עבור 9Router (ניתן להגדרה)                 │
│    ├── יומנים:    מאגר טבעתי של stdio בגודל 5 MB → אירועי SSE      │
│    ├── תקינות:    HTTP GET /health כל 2–5 שניות, התאוששות עצלה     │
│    └── מחזור חיים: SIGTERM‏ 15 שניות → SIGKILL                     │
│                                                                    │
│  registry.ts        getSupervisor(name) / registerSupervisor()     │
│  bootstrap.ts       מאתחל את כל SERVICES[] בעת הפעלת התהליך        │
│  apiKey.ts          getOrCreateApiKey(), generateServiceApiKey()   │
│  modelSync.ts       GET /v1/models מחזורי → הטבלה service_models   │
│  ringBuffer.ts      מאגר יומנים מעגלי (5 MB לכל שירות)             │
│  healthCheck.ts     בדיקת תקינות HTTP בתשאול מחזורי                │
│  installers/        ninerouter.ts, cliproxy.ts, mux.ts, openwa.ts  │
│                      (מתאמי התקנה)                                 │
└──────────────────────┬─────────────────────────────────────────────┘
                       │ HTTP תואם OpenAI ‏(loopback)
┌──────────────────────▼─────────────────────────────────────────────┐
│  שכבה 4 — ספק / ניתוב                                              │
│                                                                    │
│  open-sse/executors/ninerouter.ts                                  │
│    מאתר מחדש את הפורט ומפתח ה-API בכל בקשה (ללא מטמון).           │
│    מסיר את הקידומת "9router/" ממזהה המודל לפני העברה בפרוקסי.      │
│    מחזיר 503 service_not_running אם המפקח אינו במצב "running".     │
│                                                                    │
│  src/shared/constants/providers.ts                                 │
│    רשומה עבור "9router": isEmbeddedService: true                   │
│                                                                    │
│  open-sse/config/providerRegistry.ts                               │
│    המודלים נשמרים בתור "9router/{sub}/{model}" (עם קידומת).        │
│    מסונכרנים כל 5 דקות על ידי modelSync.ts.                        │
│                                                                    │
│  Mux מנוהל מבחינת מחזור החיים בלבד (שכבות 1–3) — זהו daemon        │
│  לתזמור סוכנים, ולא פרוקסי LLM, ולכן אין לו executor/רשומת ספק     │
│  בשכבה 4 והוא לעולם אינו יעד ניתוב.                                │
└────────────────────────────────────────────────────────────────────┘

קובצי מקור מרכזיים

קובץ תפקיד
src/lib/services/ServiceSupervisor.ts מחלקת ליבה: מחזור חיים, נעילה, תקינות, מאגר מעגלי
src/lib/services/bootstrap.ts רישום ברמת התהליך והפעלה אוטומטית
src/lib/services/registry.ts מפת יחידון tool → supervisor
src/lib/services/apiKey.ts יצירת מפתחות, הצפנת AES-256-GCM במצב מנוחה
src/lib/services/modelSync.ts סנכרון מודלים תקופתי (5 דקות) + לפי דרישה
src/lib/services/ringBuffer.ts מאגר יומן מעגלי בנפח 5 MB עם הרשמת SSE
src/lib/services/healthCheck.ts בדיקת תקינות HTTP (מרווח זמן ניתן להגדרה)
src/lib/services/installers/ninerouter.ts התקנה/עדכון/הסרה באמצעות npm עבור 9Router
src/lib/services/installers/cliproxy.ts התקנה/עדכון/הסרה באמצעות npm עבור CLIProxyAPI
src/lib/services/installers/mux.ts התקנה/עדכון/הסרה באמצעות npm עבור Mux
src/lib/services/installers/openwa.ts התקנה/עדכון/הסרה באמצעות npm עבור open-wa
src/app/api/services/9router/_lib.ts פונקציית העזר getOrInitSupervisor()
src/app/api/services/[name]/logs/route.ts נקודת קצה משותפת ליומני SSE
open-sse/executors/ninerouter.ts מפעיל ספק (שכבה 4)

3. מכונת מצבי מחזור חיים

                    install()
  ┌─────────────┐ ──────────► ┌─────────────┐
  │ not_installed│             │   stopped   │◄──────────────────┐
  └─────────────┘             └──────┬──────┘                   │
                                     │ start()                   │
                                     ▼                           │ stop()
                               ┌──────────┐                      │
                               │ starting │                      │
                               └────┬─────┘                     │
             בדיקת התקינות הצליחה   │         קריסה / SIGTERM   │
                               ┌────▼─────┐  (יציאה בתוך 5 שנ׳) │
                               │ running  │──── קריסה ──────────►┤
                               └────┬─────┘                   ┌─▼────┐
                             stop() │                          │error │
                                    ▼                          └──────┘
                               ┌──────────┐
                               │ stopping │
                               └──────────┘

המצבים מאוחסנים בטבלת מסד הנתונים version_manager (בעמודה status) ומשוקפים במצב שבזיכרון של ServiceSupervisor. המצב שבזיכרון הוא הסמכות הקובעת עבור תהליך פועל; מצב מסד הנתונים הוא גיבוי מתמיד בעת האתחול.

מעברי מצבים

מ־ אירוע אל
not_installed install() מסתיים בהצלחה stopped
stopped מתבצעת קריאה ל־start() starting
starting בדיקת התקינות מחזירה 200 running
starting התהליך מסתיים לפני שנהיה תקין error
running מתבצעת קריאה ל־stop() stopping → stopped
running התהליך מסתיים באופן בלתי צפוי (< 5 שנ׳) error (קריסה מהירה)
running התהליך מסתיים באופן בלתי צפוי (> 5 שנ׳) error
error מתבצעת קריאה ל־start() starting
כל מצב stop() בזמן stopping ללא פעולה

נעילת פעולות

ServiceSupervisor מסדר פעולות של מחזור החיים באופן סדרתי באמצעות נעילת פעולות אסינכרונית (withLock()). קריאות מקבילות ל־start() באותו מפקח מובילות ליצירת תהליך אחד בדיוק; הקורא השני ממתין ומחזיר את המצב הקיים. כך נמנעים מצבי מרוץ כאשר, לדוגמה, הפעלה אוטומטית וכפתור בממשק המשתמש מופעלים בו־זמנית.


4. מדריך עזר ל־API

כל הנתיבים תחת /api/services/ הם LOCAL_ONLY (ממשק loopback בלבד, כלל קשיח מס׳ 17). בקשות שאינן מ־loopback מקבלות 403 LOCAL_ONLY ללא תלות באסימון האימות.

4.1 נקודות קצה של 9Router‏ (11 נתיבים)

POST /api/services/9router/install

התקנת 9Router מ־npm. יוצרת את DATA_DIR/services/9router/ עם package.json ו־node_modules/ משלו. אינה מתנגשת עם יחסי התלות של OmniRoute.

גוף הבקשה (כל השדות אופציונליים):

{ "version": "latest" }
שדה סוג ברירת מחדל תיאור
version string "latest" תג גרסת npm או semver להתקנה

תגובות:

סטטוס תיאור
200 { ok: true, installedVersion: "x.y.z", path: "..." }
400 גוף בקשה לא תקין (כשל אימות של Zod)
409 התקנה כבר מתבצעת (הנעילה מוחזקת)
500 התקנת npm נכשלה — ראו message לקבלת שגיאה ידידותית

הערות: נעשה שימוש ב־execFile('npm', [...]) — ללא מעטפת וללא אינטרפולציה (כלל קשיח מס׳ 13). שגיאות EACCES מוצגות כהודעות ידידותיות.


POST /api/services/9router/start

הפעלת 9Router. רושמת מפקח אם עדיין לא נרשם, ולאחר מכן קוראת ל־supervisor.start(). הפעולה אידמפוטנטית כאשר השירות כבר פועל.

גוף הבקשה: אין

תגובות:

סטטוס תיאור
200 אובייקט ServiceStatus (ראו את הסכמה להלן)
409 9Router אינו מותקן (status: "not_installed")
503 ההפעלה נכשלה (שגיאת תהליך — ראו lastError)

סכמת ServiceStatus:

{
  "tool": "9router",
  "state": "running",
  "pid": 12345,
  "port": 20130,
  "health": "healthy",
  "startedAt": "2026-05-25T10:00:00.000Z",
  "lastError": null
}

POST /api/services/9router/stop

עצירה מסודרת של 9Router. שולחת SIGTERM, ממתינה 15 שניות ולאחר מכן שולחת SIGKILL אם התהליך עדיין פעיל. הפעולה אידמפוטנטית כאשר השירות כבר עצור.

גוף הבקשה: אין

תגובות:

סטטוס תיאור
200 ServiceStatus (מצב: "stopped")
503 העצירה נכשלה באופן בלתי צפוי

POST /api/services/9router/restart

שקול ל־stop() ולאחר מכן start() תחת נעילת הפעולות.

גוף הבקשה: אין

תגובות: זהות לאלו של start (מוחזר ServiceStatus הסופי).


POST /api/services/9router/update

מעדכן את 9Router לגרסת npm חדשה יותר. אם השירות פועל, הוא נעצר תחילה, התקנת npm מופעלת (והגרסה החדשה יותר מותקנת במקום), ולאחר מכן השירות מופעל מחדש.

גוף הבקשה (כל השדות אופציונליים):

{ "version": "latest" }

תגובות:

סטטוס תיאור
200 { ok: true, previousVersion: "...", installedVersion: "..." }
400 גוף לא תקין
500 עדכון npm נכשל

POST /api/services/9router/rotate-key

יוצר מפתח API חדש עבור 9Router, מצפין אותו במצב מנוחה ומפעיל מחדש את השירות (אם הוא פועל), כדי שיטען את המפתח החדש מהסביבה שלו. המפתח הישן מבוטל באופן מיידי.

גוף הבקשה: ללא

תגובות:

סטטוס תיאור
200 { keyRotated: true, restarted: boolean }
500 סבב החלפת המפתח נכשל

אבטחה: המפתח החדש לעולם אינו מוחזר בתגובה (אין דליפת פרטי גישה). הוא מאוחסן כשהוא מוצפן (AES-256-GCM) בטבלה version_manager.


GET /api/services/9router/status

מחזיר סטטוס משולב בזמן אמת + ממסד הנתונים, כולל מטא-נתוני גרסה ותצוגה מקדימה של מפתח ה-API.

תגובות:

סטטוס תיאור
200 ראו את הסכמה להלן
500 קריאת הסטטוס נכשלה

סכמת התגובה:

{
  "tool": "9router",
  "state": "running",
  "pid": 12345,
  "port": 20130,
  "health": "healthy",
  "startedAt": "2026-05-25T10:00:00.000Z",
  "lastError": null,
  "installedVersion": "1.2.3",
  "latestVersion": "1.2.4",
  "updateAvailable": true,
  "apiKeyMasked": "nr_****abcd",
  "autoStart": false,
  "providerExpose": false
}

POST /api/services/9router/auto-start

מחליף את מצב דגל ההפעלה האוטומטית. כאשר enabled: true, השירות יופעל אוטומטית בפעם הבאה ש-OmniRoute יאותחל (אם השירות מותקן).

גוף הבקשה:

{ "enabled": true }

תגובות:

סטטוס תיאור
200 { autoStart: true }
400 גוף לא תקין

GET /api/services/9router/logs

זרם SSE של יומנים בזמן אמת ממאגר הטבעת של stdout/stderr של 9Router.

פרמטרים של השאילתה:

פרמטר סוג ברירת מחדל תיאור
tail integer 200 מספר השורות ההיסטוריות שיישלחו תחילה (מקסימום 1000)
filter string ללא מסנן מחרוזת משנה שאינו תלוי רישיות (ללא regex — מוגן מ-ReDoS)

אירועי SSE:

אירוע נתונים תיאור
snapshot LogLine[] הסיומת ההיסטורית הראשונית
log LogLine שורת יומן בזמן אמת
heartbeat {} שמירת חיבור פעיל כל 15 שניות

סכמת LogLine:

{
  "ts": 1716633600000,
  "stream": "stdout",
  "line": "[9router] Listening on :20130"
}

תגובות:

סטטוס תיאור
200 text/event-stream
400 הפרמטר filter ארוך מדי (> 200 תווים)
404 השירות לא נמצא (המפקח אינו רשום)

4.2 נקודות קצה של CLIProxyAPI‏ (10 נתיבים)

ל-CLIProxyAPI יש אותו מבנה נקודות קצה כמו ל-9Router, למעט rotate-key, ובתוספת accounts,‏ provider-expose ו-auto-restart-adopted. כעת הוא מקבל מפתח API ייעודי למישור הנתונים שמוזרק בעת יצירת התהליך (needsApiKey: true בתוך bootstrap.ts, ומשמש לסנכרון מודלים); status כולל פחות שדות.

מתודה נתיב תיאור
POST /api/services/cliproxy/install התקנת CLIProxyAPI מ-npm
POST /api/services/cliproxy/start הפעלת CLIProxyAPI
POST /api/services/cliproxy/stop עצירת CLIProxyAPI
POST /api/services/cliproxy/restart הפעלה מחדש של CLIProxyAPI
POST /api/services/cliproxy/update עדכון לגרסה חדשה יותר
GET /api/services/cliproxy/status סטטוס בזמן אמת + ממסד הנתונים (ללא apiKeyMasked)
POST /api/services/cliproxy/auto-start החלפת מצב ההפעלה האוטומטית

נקודת הקצה המשותפת GET /api/services/{name}/logs (ראו §4.1) פועלת עבור כל ארבעת השירותים באמצעות המקטע הדינמי [name].


4.3 נקודות קצה של Mux‏ (8 נתיבים)

ל-Mux יש אותו מבנה נקודות קצה כמו ל-CLIProxyAPI — אין נתיב rotate-key בממשק ה-API (אסימון ה-bearer נוצר באותו אופן כמו זה של 9Router באמצעות getOrCreateApiKey("mux") ומוזרק דרך משתנה הסביבה MUX_SERVER_AUTH_TOKEN, אך עדיין אין נקודת קצה ייעודית להחלפתו). Mux מנוהל רק מבחינת מחזור החיים: בניגוד ל-9Router, אין לו מבצע שכבה 4 והוא לעולם אינו נרשם כספק ניתוב.

מתודה נתיב תיאור
POST /api/services/mux/install התקנת Mux מ-npm‏ (npm i mux)
POST /api/services/mux/start הפעלת Mux‏ (mux server)
POST /api/services/mux/stop עצירת Mux
POST /api/services/mux/restart הפעלה מחדש של Mux
POST /api/services/mux/update עדכון לגרסת npm חדשה יותר
GET /api/services/mux/status סטטוס בזמן אמת + ממסד הנתונים
POST /api/services/mux/auto-start החלפת מצב ההפעלה האוטומטית

4.4 נקודות קצה של Bifrost‏ (8 נתיבים)

Bifrost הוא backend ממסר של שער AI שנכתב ב-Go‏ (@maximhq/bifrost). הוא משתמש באותו מבנה נקודות קצה כמו CLIProxyAPI (ללא rotate-key —‏ Bifrost מנהל את מפתחות הספקים שלו בעצמו בתוך config.json תחת -app-dir).

מתודה נתיב תיאור
POST /api/services/bifrost/install התקנת Bifrost מ־npm (@maximhq/bifrost)
POST /api/services/bifrost/start הפעלת Bifrost בפורט 8080 (ברירת המחדל)
POST /api/services/bifrost/stop עצירת Bifrost
POST /api/services/bifrost/restart הפעלה מחדש של Bifrost
POST /api/services/bifrost/update עדכון לגרסה חדשה יותר
GET /api/services/bifrost/status מצב בזמן אמת + מצב מסד הנתונים
POST /api/services/bifrost/auto-start הפעלה או השבתה של הפעלה אוטומטית
GET /api/services/bifrost/logs זנב יומן דרך SSE (באמצעות הנתיב הדינמי המשותף [name]/logs)

חיווט ניתוב: כאשר BIFROST_BASE_URL אינו מוגדר ומופע Bifrost המנוהל פועל, getBifrostRoutingConfig() (בתוך routingBackend.ts) משתמש אוטומטית ב־http://127.0.0.1:{port} ככתובת ה־URL הבסיסית של הממסר. משתנה הסביבה BIFROST_BASE_URL שמוגדר במפורש תמיד מקבל עדיפות.


4.5 נקודות קצה של Dario ‏(12 נתיבים)

אותו מבנה מחזור חיים כמו בשירותים האחרים (install, start, stop, restart, update, status, auto-start, auto-restart-adopted), ובנוסף מישור בקרה של OAuth המוגן באסימון תחת admin/:‏ admin/accounts,‏ admin/import-from-omniroute, admin/login-start,‏ admin/login-complete (כולם מוגנים באמצעות DARIO_ADMIN_TOKEN).

4.6 נקודות קצה של open-wa ‏(7 נתיבים)

open-wa (@open-wa/wa-automate) מפעיל מופע Chromium ללא ממשק גרפי (באמצעות Puppeteer) כדי לבצע אוטומציה של WhatsApp Web. הוא משתמש באותו מבנה נקודות קצה כמו Mux (עדיין אין נתיב rotate-key). הוא מנוהל רק ברמת מחזור החיים — אינו יעד ניתוב, ואין לו רשומת מבצע/ספק בשכבה 4.

מתודה נתיב תיאור
POST /api/services/openwa/install התקנת open-wa מ־npm (@open-wa/wa-automate)
POST /api/services/openwa/start הפעלת open-wa בפורט 8323 (ברירת המחדל)
POST /api/services/openwa/stop עצירת open-wa
POST /api/services/openwa/restart הפעלה מחדש של open-wa
POST /api/services/openwa/update עדכון לגרסה חדשה יותר
GET /api/services/openwa/status מצב בזמן אמת + מצב מסד הנתונים
POST /api/services/openwa/auto-start הפעלה או השבתה של הפעלה אוטומטית
GET /api/services/openwa/logs זנב יומן דרך SSE (באמצעות הנתיב הדינמי המשותף [name]/logs)

מפתח API: מוזרק בתור WA_KEY — דריסת משתני הסביבה הגנרית של open-wa עם הקידומת WA_* ממפה אותו לאפשרות ה־CLI ‏--key/-k (dist/cli/setup.js::envArgs(), אומת מול החבילה המותקנת בגרסה 4.76.0). בעת יצירתו באמצעות generateServiceApiKey(), נוספת לו הקידומת ow_. ‏open-wa קורא את המפתח בחזרה מכותרת HTTP בשם key/api_key (ולא Authorization: Bearer);‏ /api-docs* מוחרג במפורש מהבדיקה (setupAuthenticationLayer בתוך dist/cli/server.js), ולכן בדיקת התקינות אינה זקוקה לכותרת אימות.

צימוד: open-wa אינו רשמי ואינו משויך ל־WhatsApp — המספר המחובר חשוף לסכנת חסימה עקב מנגנון זיהוי האוטומציה של WhatsApp. בהפעלה הראשונה, קוד ה־QR לצימוד מודפס ל־stdout ומוצג דרך לוח היומנים/זרם ה־SSE הקיים — עדיין אין נקודת קצה ייעודית לתמונת QR באינטגרציה זו.


4.7 פרוקסי הפוך (הטמעת לוח הבקרה של 9Router)

לוח הבקרה מטמיע את ממשק האינטרנט של 9Router בתוך iframe באמצעות פרוקסי הפוך פנימי בכתובת:

GET|POST|... /dashboard/providers/services/9router/embed/[...path]

פרוקסי זה:

  • מעביר את הבקשה אל http://127.0.0.1:{port}/{path} (לולאת משוב בלבד)
  • מסיר את הכותרות הנכנסות cookie ו־authorization (ללא דליפה של הפעלת OmniRoute)
  • מזריק Authorization: Bearer {apiKey} לצורך אימות מול 9Router
  • מסיר מהתגובה את set-cookie,‏ content-security-policy,‏ x-frame-options,‏ cross-origin-*
  • משכתב תגובות HTML כדי להזריק <base href> ולנרמל נתיבים מוחלטים (/foo ← /dashboard/.../embed/foo)

שדרוגי WebSocket עבור לוח הבקרה המוטמע מטופלים על ידי שרת נלווה בפורט ייעודי (ראו src/lib/services/embedWsProxy.ts).

אבטחה: נתיבי פרוקסי ההטמעה מסווגים תחת LOCAL_ONLY_API_PREFIXES וניתן לגשת אליהם רק מלולאת המשוב. תוקף שמשיג JWT דרך מנהרת Cloudflare/Ngrok אינו יכול להשתמש בפרוקסי כדי לגשת לשירותים המוטמעים.


5. אבטחה

אכיפת LOCAL_ONLY (כלל קשיח #17)

כל הנתיבים תחת /api/services/ ו-/dashboard/providers/services/*/embed/ מסווגים כ-LOCAL_ONLY ב-src/server/authz/routeGuard.ts. בדיקת ה-loopback מתבצעת ללא תנאי לפני כל ענף אימות:

הבקשה מגיעה
  → isLocalOnlyPath(path)?
      → לא-loopback → 403 LOCAL_ONLY (תמיד, לפני בדיקת האימות)
      → loopback    → המשך לאימות הרגיל

כך נמנע מ-JWT שדלף (למשל, דרך מנהרה) להפעיל npm install או יצירת תהליכים. ראו docs/security/ROUTE_GUARD_TIERS.md למטריצת הרמות המלאה.

הזרקת מפתח API

‏9Router ו-Mux דורשים מפתח API/אסימון bearer עבור נקודות הקצה של HTTP שלהם. OmniRoute:

  1. יוצר מפתח באמצעות crypto.randomBytes(32).toString("base64url") עם קידומת ייעודית לשירות (nr_ עבור 9Router,‏ mx_ עבור Mux).
  2. מצפין אותו במנוחה באמצעות AES-256-GCM (אותו צופן המשמש לפרטי הגישה של הספק).
  3. מפענח ומזריק אותו כמשתנה סביבה בעת יצירת התהליך — NINEROUTER_API_KEY עבור 9Router,‏ MUX_SERVER_AUTH_TOKEN עבור Mux (לעולם לא כדגל CLI, כדי שהאסימון לא יופיע לעולם ב-ps/רשימות תהליכים).
  4. לעולם אינו מחזיר את המפתח הגלוי בתגובת HTTP כלשהי.

CLIProxyAPI מקבל מפתח ייעודי למישור הנתונים, המוזרק בעת יצירת התהליך (needsApiKey: true — משמש לסנכרון מודלים מול המתאם).

הגנה מפני SSRF

פרוקסי ה-HTTP ההפוך (/dashboard/.../embed/[...path]) מוגדר באופן קשיח להעביר רק אל http://127.0.0.1:{port}. הוא לעולם אינו עוקב אחר הפניות ליעדים שאינם loopback. הספרייה ssrf-req-filter משמשת לדחיית כל כתובת URL של upstream שנפתרת לכתובת מחוץ לטווח ה-loopback.

בטיחות מעטפת (כלל קשיח #13)

npm install מופעל באמצעות execFile('npm', ['install', pkg, '--prefix', dir]) — ללא תבניות מחרוזת, ללא מעטפת וללא אינטרפולציה של נתיבים חיצוניים לתוך מחרוזת הפקודה. ערכי זמן ריצה (יציאות, מפתחות API) מועברים דרך אובייקט ה-env של תהליך הבן.

טיהור שגיאות (כלל קשיח #12)

כל תגובות השגיאה מ-/api/services/* עוברות דרך buildErrorBody() או sanitizeErrorMessage(). הערכים הגולמיים של err.stack ו-err.message לעולם אינם מוחזרים כלשונם למבצע הקריאה.


6. הוספת שירות מוטמע חדש

בצעו את 8 השלבים הבאים. קראו את המימושים הקיימים ב-src/lib/services/installers/ וב-src/app/api/services/ כמקור המוסמך.

שלב 1 — יצירת המתקין

צרו את src/lib/services/installers/{name}.ts לפי התבנית של ninerouter.ts:

export const NAME_PACKAGE = "your-npm-package";
export const NAME_DEFAULT_PORT = 20132; // בחרו יציאה פנויה

export async function install(version = "latest"): Promise<InstallResult> { ... }
export async function update(version = "latest"): Promise<InstallResult> { ... }
export async function uninstall(): Promise<void> { ... }
export function resolveSpawnArgs(apiKey: string, port: number): SpawnArgs { ... }
export async function getInstalledVersion(): Promise<string | null> { ... }
export async function getLatestVersion(): Promise<string | null> { ... }

השתמשו ב-runNpm(['install', NAME_PACKAGE, '--prefix', dir]) מתוך installers/utils.ts — לעולם אל תשתמשו ב-execSync או באינטרפולציה של מעטפת.

שלב 2 — רישום באתחול

הוסיפו ServiceEntry למערך SERVICES ב-src/lib/services/bootstrap.ts:

{
  tool: "myservice",
  port: NAME_DEFAULT_PORT,
  healthPath: "/health",
  healthIntervalMs: 5_000,
  stopTimeoutMs: 15_000,
  logsBufferBytes: 5_242_880,
  needsApiKey: true, // false אם אין צורך במפתח API
}

הרחיבו את buildSpawnArgsFactory() כדי לטפל ב-cfg.tool === "myservice".

חוזה תוסף ספק ניתן להרחבה (שלב 1, #7333)

src/lib/services/providerPlugins/ מציג חוזה ServiceProviderPlugin המאחד את שדות ה-ServiceEntry של bootstrap.ts בצד השרת ואת שדות תבנית המניפסט של serviceBackends.ts לאובייקט אחד, במקום שהמבנה של אותו צד שרת יוגדר בנפרד בשני קבצים שאינם קשורים זה לזה. נכון למועד כתיבה זה, רק 9router הועבר — bootstrap.ts גוזר את הרשומה שלו ב-SERVICES[] מתוך getServiceProviderPlugin("9router") (src/lib/services/providerPlugins/registry.ts), וזורק שגיאת הפעלה אם התוסף חסר אי פעם. cliproxy,‏ mux ו-bifrost ממשיכים להשתמש בליטרלים הקיימים של SERVICES[] ללא שינוי.

ל-open-sse/config/providerPluginManifest.ts נוסף גם helper מצטבר בשם createServiceBackendManifestEntry(pluginId, template), שבונה ProviderPluginManifestEntry תקין מתוך רשומת SERVICE_BACKEND_MANIFEST_TEMPLATE — הוא עדיין אינו מחובר לאף נתיב בקשות פעיל (לא ל-generateProviderPluginManifestFromRegistry() ולא ל-/v1/providers/[provider]/models); הדבר נותר להמשך לאחר שהחוזה יוכח עבור צד שרת שני.

נדחה ל-PR-י המשך, במעקב תחת issue #7333: העברת cliproxyapi דרך אותו registry, הכללת mux/bifrost ב-union של ServiceBackendPluginId, שילוב הטיפול המיוחד בניתוב המבצעים (open-sse/executors/index.ts, open-sse/handlers/chatCore/executorProxy.ts) בחוזה התוסף, וחיבור createServiceBackendManifestEntry() לנתיב קוד פעיל של מניפסט/מודלים.

שלב 3 — הוספת מיגרציה ונתוני אתחול למסד הנתונים

ודאו שלשירות קיימת שורה ב-version_manager באמצעות מיגרציה תחת src/lib/db/migrations/. השורה צריכה לכלול:

INSERT OR IGNORE INTO version_manager (tool, status, auto_start, provider_expose)
VALUES ('myservice', 'not_installed', 0, 0);

שלב 4 — יצירת 7 נקודות הקצה של ה-API

תחת src/app/api/services/{name}/:

_lib.ts            helper מסוג getOrInitSupervisor()
install/route.ts   POST — קורא ל-installer.install()
start/route.ts     POST — קורא ל-supervisor.start()
stop/route.ts      POST — קורא ל-supervisor.stop()
restart/route.ts   POST — קורא ל-supervisor.restart()
update/route.ts    POST — קורא ל-installer.update()
status/route.ts    GET  — ממזג סטטוס חי + סטטוס ממסד הנתונים
auto-start/route.ts POST — מחליף את מצב הדגל auto_start

נתיב ה־GET /api/services/[name]/logs המשותף כבר מחובר — אין צורך בשינויים שם.

יש להעביר את כל תגובות השגיאה דרך createErrorResponse() / buildErrorBody().

שלב 5 — הוספה אל LOCAL_ONLY_API_PREFIXES

בקובץ src/server/authz/routeGuard.ts, יש לוודא כי /api/services/ כבר מופיע ברשימה. אם נוסף prefix חדש (למשל, /api/tools/), יש להוסיף אותו גם אל LOCAL_ONLY_API_PREFIXES, ואם הוא מפעיל תהליכים, גם אל SPAWN_CAPABLE_PREFIXES. יש להוסיף בדיקה בקובץ tests/unit/authz/routeGuard.test.ts.

שלב 6 — הוספת לשונית ממשק המשתמש

יש ליצור את src/app/(dashboard)/dashboard/providers/services/tabs/{Name}ServiceTab.tsx. יש לעשות שימוש חוזר ברכיבים המשותפים:

  • ServiceStatusCard — מצב בזמן אמת + תג מצב תקינות
  • ServiceLifecycleButtons — הפעלה / עצירה / הפעלה מחדש / עדכון
  • ServiceLogsPanel — זנב יומן באמצעות SSE (מתחבר אל /api/services/{name}/logs)
  • ApiKeyCard — חשיפת מפתח + החלפה (אם needsApiKey: true)

יש לרשום את הלשונית ב־ServicesPageShell.tsx.

שלב 7 — הוספת רשומת ספק (אם השירות הוא יעד ניתוב)

אם השירות המוטמע חושף נקודת קצה /v1/chat/completions התואמת ל־OpenAI:

  1. יש להוסיף רשומת ספק ב־src/shared/constants/providers.ts עם isEmbeddedService: true.
  2. יש ליצור את open-sse/executors/{name}.ts שמרחיב את BaseExecutor. יש לאחזר מחדש את הפורט ואת מפתח ה־API בכל בקשה (לעולם אין לשמור אותם במטמון בבנאי). יש להחזיר תגובת 503 service_not_running כאשר מצב המפקח אינו "running".
  3. יש לרשום מודלים ב־open-sse/config/providerRegistry.ts עם ה־prefix של השירות (למשל, myservice/sub/model). הקובץ modelSync.ts ישמור אותם מעודכנים.

שלב 8 — תיעוד ובדיקות

  1. יש לעדכן את docs/frameworks/EMBEDDED-SERVICES.md (קובץ זה) — להוסיף את השירות אל הטבלה בסעיף 1 ואת כל נקודות הקצה החדשות לסעיף 4.
  2. יש להוסיף בדיקות יחידה ב־tests/unit/services/ (מחזור חיים, מתקין, מבנה API).
  3. יש להוסיף בדיקת אינטגרציה ב־tests/integration/services/ (מאחורי RUN_SERVICES_INT=1).
  4. יש לעדכן את docs/openapi.yaml בנקודות הקצה החדשות.

7. פתרון בעיות

השירות אינו מופעל

תסמינים: לחצן ההפעלה מחזיר 503, והמצב נשאר "error" או "starting".

רשימת בדיקה:

  1. בדקו את GET /api/services/{name}/logs (או את חלונית היומנים בלוח הבקרה). חפשו שורות כמו Error: ENOENT,‏ address already in use או Cannot find module.
  2. ודאו ש-npm נמצא ב-PATH: הריצו which npm מאותו חשבון משתמש שמפעיל את OmniRoute.
  3. ודאו שהשירות מותקן: בדקו ב-GET /api/services/{name}/status את installedVersion. אם הערך הוא null, בצעו תחילה התקנה.
  4. ודאו שהנתיב DATA_DIR/services/{name}/node_modules/ קיים ואינו ריק.
  5. בדקו את השדה lastError בתגובת הסטטוס כדי לראות את סיבת היציאה המסוננת.

הפעלה קרה איטית (יותר מ-10 שניות עד להגעה ל-running)

תסמינים: המצב נשאר "starting" במשך זמן רב לפני שהוא משתנה ל-"running" או ל-"error".

הסבר: ההפעלה הקרה של 9Router כוללת ייבוא של עצי תלויות גדולים (מודולי DNS, מנהרה ו-MITM). מרווח ברירת המחדל לבדיקת תקינות הוא 2 שניות, עם 3 ניסיונות לפני שהמפקח מכריז על פסק זמן (אך ממשיך לבצע תשאול).

פתרון: ניתן להגדיר את healthIntervalMs ואת פסק הזמן של waitForHealthy (healthIntervalMs * 3) ב-bootstrap.ts. עבור שירותים שזמן ההפעלה שלהם ארוך יותר, הגדילו את healthIntervalMs ל-5000 ואת stopTimeoutMs ל-30 000.


התנגשות יציאות (EADDRINUSE)

תסמינים: ביומנים מופיע address already in use :::20130.

גורמים:

  • תהליך אחר כבר משתמש ביציאה 20130.
  • תהליך קודם של 9Router לא נעצר לחלוטין (PID זומבי).

פתרון:

  1. שנו את יציאת ברירת המחדל באמצעות משתנה הסביבה NINEROUTER_PORT בקובץ .env.
  2. אתרו וסיימו את התהליך המתנגש: lsof -ti :20130 | xargs kill -9.
  3. ניתן להגדיר את היציאה לכל שירות בנפרד ב-bootstrap.ts באמצעות השדה port.

הערה: 9Router משתמש כברירת מחדל ביציאה 20130 במיוחד כדי למנוע התנגשות עם יציאת ברירת המחדל 20128 של OmniRoute.


ההרשאה נדחתה (EACCES) במהלך ההתקנה

תסמינים: ההתקנה מחזירה 500, וביומנים מופיע EACCES או permission denied.

גורמים:

  • DATA_DIR או תיקיית האב שלה אינם ניתנים לכתיבה על ידי התהליך של OmniRoute.
  • הפעלה בתוך Docker ללא הרשאות root וללא גישת כתיבה לאמצעי האחסון הממופה.

פתרון:

  1. בדקו את DATA_DIR (ברירת מחדל: ~/.omniroute/):‏ ls -la ~/.omniroute/
  2. ודאו שמשתמש התהליך של OmniRoute הוא הבעלים של התיקייה: chown -R $USER ~/.omniroute/
  3. ב-Docker, ודאו שלעיגון אמצעי האחסון יש את ההרשאות הנכונות עבור המשתמש בקונטיינר.

העדכון נכשל (פסק זמן של npm install או שגיאת רשת)

תסמינים: העדכון מחזיר 500 עם InstallError, וביומנים מופיע פסק זמן של הרשת.

רשימת בדיקה:

  1. ודאו שניתן לגשת למאגר npm:‏ npm ping.
  2. בדקו אם קיים פרוקסי ארגוני: npm config get proxy,‏ npm config get https-proxy.
  3. נסו לבצע את ההתקנה ידנית: npm install {package}@latest --prefix ~/.omniroute/services/{name}/.
  4. בסביבה מבודדת מהרשת, הורידו מראש את קובץ ה-tarball והשתמשו ב-npm install /path/to/tarball.tgz.

השירות מציג מצב "error" מיד לאחר ההפעלה (קריסה מהירה)

תסמינים: המצב משתנה מ-"starting" ל-"error" בתוך פחות מ-5 שניות. lastError מציג "Fast crash (exited with code 1)".

רשימת בדיקה:

  1. קראו את מלוא קטע הסיום של היומן: GET /api/services/{name}/logs?tail=500.
  2. גורם נפוץ: חסרים משתני סביבה שהשירות מצפה להם.
  3. עבור 9Router: ודאו ש-NINEROUTER_DISABLE_MITM=true ו- NINEROUTER_DISABLE_TUNNEL=true נמצאים בסביבה המועברת בעת יצירת התהליך (ראו installers/ninerouter.ts resolveSpawnArgs).

8. שאלות נפוצות

ש: האם ניתן לחשוף את נקודות הקצה של השירותים המוטמעים ללקוחות שאינם loopback?

לא. רמת LOCAL_ONLY היא מכוונת (כלל קשיח מס' 17). נתיבים שיכולים להפעיל npm install או ליצור תהליכי node אינם יכולים להיות נגישים מתעבורה שאינה loopback, מכיוון ש-JWT שדלף דרך מנהרה (Cloudflare, Ngrok, Tailscale) היה מאפשר אחרת יצירה שרירותית של תהליכים. אין החרגת ביטול עבור /api/services/ — בניגוד ל-/api/mcp/, הוא אינו נכלל ברשימת המעקפים של תחום הניהול. ראו docs/security/ROUTE_GUARD_TIERS.md.


ש: האם 9Router ו-CLIProxyAPI יהיו זמינים בפריסות ייצור/ענן?

כן. שני השירותים פועלים לפי אותו מודל מקומי-תחילה כמו OmniRoute עצמו. הם פועלים באותו מחשב ומתקשרים באמצעות loopback. "ייצור" כאן פירושו ה-VPS או השרת המקומי שבו OmniRoute פרוס, ולא ספק ענן מרוחק.


ש: כיצד ניתן לנפות שגיאות במפקח?

  1. עקבו אחר זרם יומני ה-SSE: curl -N http://localhost:20128/api/services/9router/logs.
  2. בדקו את היומנים המובנים בפלט ה-pino של OmniRoute, המסוננים לפי מרחב השמות service:supervisor.
  3. בדקו את הרשומה במסד הנתונים: sqlite3 ~/.omniroute/omniroute.db "SELECT * FROM version_manager WHERE tool='9router'".
  4. השתמשו ב-GET /api/services/9router/status כדי לראות בקריאה אחת את המצב הפעיל הנוכחי, ה-PID, התקינות ו-lastError.

ש: המפקח מציג health: "degraded" או health: "unknown", אך המצב הוא "running". האם זו בעיה?

"degraded" פירושו שבדיקת התקינות החזירה תשובה שאינה 200. "unknown" פירושו שעדיין לא הושלמה בדיקה (מצב תחרות עם התשאול הראשון). שני המצבים זמניים במהלך האתחול. אם התקינות נשארת במצב "degraded" במשך יותר מ-healthIntervalMs * 3 אלפיות שנייה לאחר "running", השירות המוטמע פועל, אך API ה-HTTP שלו אינו מגיב. בדקו אם הפורט נכון בתגובת הסטטוס ואם השירות אכן מאזין בפורט זה.


ש: האם ניתן לשנות את מפתח ה-API של 9Router ללא הפעלה מחדש מלאה?

לא. מפתח ה-API מועבר אל 9Router באמצעות משתנה סביבה בזמן יצירת התהליך. לא ניתן לשנות משתני סביבה בתהליך פועל. POST .../rotate-key עוצר ומפעיל מחדש את השירות באופן אוטומטי כדי להחיל את המפתח החדש. סבב המפתח נכנס לתוקף בתוך stopTimeoutMs של השירות (ברירת המחדל היא 15 שניות), בתוספת זמן האתחול שלו.


ש: מהי מגבלת המאגר המעגלי ומה קורה כשהוא מתמלא?

לכל שירות יש מאגר מעגלי ייעודי בגודל 5 MB. כאשר המאגר מלא, שורות היומן הישנות ביותר מוסרות כדי לפנות מקום לחדשות. אירוע ה-SSE מסוג snapshot מחזיר את השורות העדכניות ביותר במסגרת מגבלת tail. היומנים אינם נשמרים בדיסק, אלא אם logsBufferPath מוגדר ברשומת מסד הנתונים.


ראו גם

  • docs/security/ROUTE_GUARD_TIERS.md — פרטים על רמת LOCAL_ONLY
  • docs/architecture/CODEBASE_DOCUMENTATION.md — §3.2 מיפוי מודול השירותים המוטמעים
  • docs/architecture/ARCHITECTURE.md — הקשר ברמת המערכת
  • docs/openapi.yaml — הגדרות נקודות קצה הניתנות לקריאה על ידי מכונה
  • CLAUDE.md §"הוספת שירות מוטמע חדש" — רשימת תיוג לעיון מהיר