🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇧🇦 bs · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇮🇳 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
Versjon: v3.8.44 Sist oppdatert: 2026-09-09 Målgruppe: Ingeniører som legger til, vedlikeholder eller feilsøker innebygde tjenester (9Router, CLIProxyAPI, Mux, Bifrost, open-wa).
Innebygde tjenester er lokalt installerte sideverktøy for prosesser som OmniRoute installerer, overvåker og eksponerer som fullverdige rutingsmål. I motsetning til eksterne leverandører (som nås over internett via API-nøkler) kjører innebygde tjenester på samme maskin som OmniRoute og kommuniserer via loopback.
- Oversikt
- Arkitektur — 4 lag
- Tilstandsmaskin for livssyklus
- API-referanse
- Sikkerhet
- Legge til en ny innebygd tjeneste
- Feilsøking
- Vanlige spørsmål
Seks tjenester er innebygd:
| Tjeneste | npm-pakke | Standardport | Formål |
|---|---|---|---|
| 9Router | 9router |
20130 | AI-ruter som OmniRoute kan bruke som en underleverandør. Modeller eksponeres som 9router/{sub}/{model} |
| CLIProxyAPI | Binærfil fra GitHub-utgivelse (cliproxy) |
8317 | Lokal proxyadapter for autentiseringsflyter i Anthropic CLI. Tilbyr reserveruting når OAuth-tokener utløper |
| Mux | mux (hodeløs mux server) |
8322 | Lokal daemon for agentorkestrering (coder/mux). Kun livssyklusadministrert — ikke et rutingsmål (ingen LLM-proxying). |
| Bifrost | @maximhq/bifrost |
8080 | Go-basert AI-gateway-relébackend. Når den kjører, velges den automatisk av reléruten (/v1/relay/) |
| Dario | @askalf/dario |
3456 | Proxy for Claude-abonnement — alternativ/reserveløsning til CLIProxyAPI for trafikk utformet som Claude Code; den injiserte nøkkelen blir DARIO_ADMIN_TOKEN, som beskytter OAuth-kontrollplanet /admin/* |
| open-wa | @open-wa/wa-automate |
8323 | WhatsApp Web-automatisering (hodeløs Chromium via Puppeteer). Kun livssyklusadministrert — ikke et rutingsmål. |
Alle seks følger samme administrasjonsmodell:
- OmniRoute installerer dem under
DATA_DIR/services/{name}/(isolert fra OmniRoutes egenpackage.json) - OmniRoute starter og overvåker dem som underprosesser
- OmniRoute injiserer en midlertidig API-nøkkel i underprosessens miljø og roterer den uten nedetid (der det er aktuelt)
- Alle administrasjonsruter (
/api/services/*) er LOCAL_ONLY — kun tilgjengelige fra loopback (ufravikelig regel nr. 17)
| Beslutning | Verdi |
|---|---|
| Dashboardtilgang til 9Routers innebygde brukergrensesnitt | Omvendt proxy på /dashboard/providers/services/9router/embed/* |
| Installasjonsmekanisme | npm install {package} via execFile (ingen skallinterpolering) |
| Bruksmodus | Leverandør registrert som 9router/{sub}/{model} i rutingsmotoren |
| Administrasjon av API-nøkler | OmniRoute genererer, krypterer ved lagring (AES-256-GCM) og injiserer via miljøvariabler |
| Plassering i dashboardet | /dashboard/providers/services (tre faner) |
| Automatisk oppstart | Bryter per tjeneste, AV som standard |
┌────────────────────────────────────────────────────────────────────┐
│ Lag 1 — Brukergrensesnitt │
│ /dashboard/providers/services (faner: CLIProxyAPI | 9Router | Mux)│
│ Direktelogger (SSE), start/stopp/omstart/oppdater, innstillinger, installer│
│ │
│ src/app/(dashboard)/dashboard/providers/services/ │
│ ├── page.tsx Ramme + faneruting via ?tab= │
│ ├── tabs/ CliproxyServiceTab, NinerouterServiceTab,│
│ │ MuxServiceTab │
│ └── components/ ServiceStatusCard, ServiceLifecycleButtons,│
│ ServiceLogsPanel, ApiKeyCard, ... │
└──────────────────────┬─────────────────────────────────────────────┘
│ HTTP (Next.js fetch)
┌──────────────────────▼─────────────────────────────────────────────┐
│ Lag 2 — API (LOCAL_ONLY — kun tilbakekobling) │
│ │
│ /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] │
│ (omvendt HTTP- og WebSocket-proxy → 9Router-oppstrøm) │
│ │
│ Sperre: LOCAL_ONLY_API_PREFIXES inkluderer "/api/services/" og │
│ "/dashboard/providers/services/*/embed/" │
└──────────────────────┬─────────────────────────────────────────────┘
│ kall i samme prosess
┌──────────────────────▼─────────────────────────────────────────────┐
│ Lag 3 — ServiceSupervisor (src/lib/services/) │
│ │
│ ServiceSupervisor.ts Generisk overvåker (child_process.spawn) │
│ ├── installer: execFile('npm', ['install', pkg, '--prefix']) │
│ ├── starter: spawn(node, [entrypoint], {env, cwd}) │
│ ├── API-nøkkel: crypto.randomBytes(32) → env NINEROUTER_API_KEY │
│ ├── port: 20130 for 9Router (konfigurerbar) │
│ ├── logger: stdio-ringbuffer på 5 MB → SSE-hendelser │
│ ├── helse: HTTP GET /health hvert 2.–5. s, behovsstyrt gjenoppretting│
│ └── livssyklus: SIGTERM 15 s → SIGKILL │
│ │
│ registry.ts getSupervisor(name) / registerSupervisor() │
│ bootstrap.ts Initialiserer alle SERVICES[] ved prosesstart │
│ apiKey.ts getOrCreateApiKey(), generateServiceApiKey() │
│ modelSync.ts Periodisk GET /v1/models → service_models-tabell│
│ ringBuffer.ts Sirkulær loggbuffer (5 MB per tjeneste) │
│ healthCheck.ts Regelmessig HTTP-helsekontroll │
│ installers/ ninerouter.ts, cliproxy.ts, mux.ts, openwa.ts │
│ (installasjonsadaptere) │
└──────────────────────┬─────────────────────────────────────────────┘
│ OpenAI-kompatibel HTTP (tilbakekobling)
┌──────────────────────▼─────────────────────────────────────────────┐
│ Lag 4 — Leverandør/ruting │
│ │
│ open-sse/executors/ninerouter.ts │
│ Slår opp port og API-nøkkel på nytt per forespørsel (ingen hurtigbuffer).│
│ Fjerner prefikset "9router/" fra modell-ID-en før videresending.│
│ Returnerer 503 service_not_running hvis overvåkeren ikke er i "running".│
│ │
│ src/shared/constants/providers.ts │
│ Oppføring for "9router": isEmbeddedService: true │
│ │
│ open-sse/config/providerRegistry.ts │
│ Modeller lagres som "9router/{sub}/{model}" (med prefiks). │
│ Synkroniseres hvert 5. minutt av modelSync.ts. │
│ │
│ Mux er KUN livssyklusadministrert (lag 1–3) — det er en daemon for│
│ agentorkestrering, ikke en LLM-proxy, og har derfor ingen kjører-/│
│ leverandøroppføring i lag 4 og er aldri et rutingsmål. │
└────────────────────────────────────────────────────────────────────┘
| Fil | Rolle |
|---|---|
src/lib/services/ServiceSupervisor.ts |
Kjerneklasse: livssyklus, lås, tilstand, ringbuffer |
src/lib/services/bootstrap.ts |
Registrering på prosessnivå og automatisk oppstart |
src/lib/services/registry.ts |
Singleton-kart verktøy → supervisor |
src/lib/services/apiKey.ts |
Nøkkelgenerering, AES-256-GCM-kryptering ved lagring |
src/lib/services/modelSync.ts |
Periodisk modellsynkronisering (5 min) + ved behov |
src/lib/services/ringBuffer.ts |
5 MB sirkulær loggbuffer med SSE-abonnement |
src/lib/services/healthCheck.ts |
HTTP-tilstandssjekk (konfigurerbart intervall) |
src/lib/services/installers/ninerouter.ts |
npm-installasjon/-oppdatering/-avinstallering for 9Router |
src/lib/services/installers/cliproxy.ts |
npm-installasjon/-oppdatering/-avinstallering for CLIProxyAPI |
src/lib/services/installers/mux.ts |
npm-installasjon/-oppdatering/-avinstallering for Mux |
src/lib/services/installers/openwa.ts |
npm-installasjon/-oppdatering/-avinstallering for open-wa |
src/app/api/services/9router/_lib.ts |
Hjelpefunksjonen getOrInitSupervisor() |
src/app/api/services/[name]/logs/route.ts |
Delt endepunkt for SSE-logger |
open-sse/executors/ninerouter.ts |
Leverandøreksekutor (lag 4) |
install()
┌─────────────┐ ──────────► ┌─────────────┐
│ not_installed│ │ stopped │◄──────────────────┐
└─────────────┘ └──────┬──────┘ │
│ start() │
▼ │ stop()
┌──────────┐ │
│ starting │ │
└────┬─────┘ │
helsesjekk ok │ krasj / SIGTERM │
┌────▼─────┐ (avsluttes innen 5s)│
│ running │──── krasj ──────────►┤
└────┬─────┘ ┌─▼────┐
stop() │ │error │
▼ └──────┘
┌──────────┐
│ stopping │
└──────────┘
Tilstander lagres i DB-tabellen version_manager (status-kolonnen) og speiles
i minnetilstanden til ServiceSupervisor. Minnetilstanden er autoritativ for
en kjørende prosess. DB-tilstanden er den varige reserveløsningen ved oppstart.
| Fra | Hendelse | Til |
|---|---|---|
not_installed |
install() lykkes |
stopped |
stopped |
start() kalles |
starting |
starting |
helsesjekken returnerer 200 | running |
starting |
prosessen avsluttes før den er frisk | error |
running |
stop() kalles |
stopping → stopped |
running |
prosessen avsluttes uventet (< 5 s) | error (rask krasj) |
running |
prosessen avsluttes uventet (> 5 s) | error |
error |
start() kalles |
starting |
| alle | stop() under stopping |
ingen operasjon |
ServiceSupervisor serialiserer livssyklusoperasjoner gjennom en asynkron operasjonslås
(withLock()). Samtidige kall til start() på samme supervisor fører til nøyaktig
én opprettelse av en prosess. Den andre kalleren venter og returnerer den eksisterende statusen. Dette forhindrer
kappløpstilstander når for eksempel automatisk oppstart og en knapp i brukergrensesnittet utløses samtidig.
Alle ruter under /api/services/ er LOCAL_ONLY (kun loopback, absolutt regel nr. 17).
Forespørsler som ikke kommer fra loopback, mottar 403 LOCAL_ONLY uavhengig av autentiseringstoken.
Installer 9Router fra npm. Oppretter DATA_DIR/services/9router/ med sin egen
package.json og node_modules/. Dette kommer ikke i konflikt med OmniRoutes egne avhengigheter.
Forespørselsinnhold (alle felter er valgfrie):
{ "version": "latest" }| Felt | Type | Standardverdi | Beskrivelse |
|---|---|---|---|
version |
string |
"latest" |
npm-versjonstagg eller semver som skal installeres |
Svar:
| Status | Beskrivelse |
|---|---|
200 |
{ ok: true, installedVersion: "x.y.z", path: "..." } |
400 |
Ugyldig forespørselsinnhold (Zod-valideringsfeil) |
409 |
Installasjon pågår allerede (lås er aktiv) |
500 |
npm-installasjonen mislyktes — se message for en forståelig feilmelding |
Merknader: Bruker execFile('npm', [...]) — ingen shell, ingen interpolasjon (absolutt regel nr. 13).
EACCES-feil vises som forståelige feilmeldinger.
Start 9Router. Registrerer en supervisor hvis en ikke allerede er registrert, og kaller deretter
supervisor.start(). Idempotent hvis tjenesten allerede kjører.
Forespørselsinnhold: ingen
Svar:
| Status | Beskrivelse |
|---|---|
200 |
ServiceStatus-objekt (se skjemaet nedenfor) |
409 |
9Router er ikke installert (status: "not_installed") |
503 |
Oppstart mislyktes (prosessfeil — se lastError) |
ServiceStatus-skjema:
{
"tool": "9router",
"state": "running",
"pid": 12345,
"port": 20130,
"health": "healthy",
"startedAt": "2026-05-25T10:00:00.000Z",
"lastError": null
}Stopp 9Router kontrollert. Sender SIGTERM, venter i 15 s og sender deretter SIGKILL hvis prosessen fortsatt kjører. Idempotent hvis tjenesten allerede er stoppet.
Forespørselsinnhold: ingen
Svar:
| Status | Beskrivelse |
|---|---|
200 |
ServiceStatus (state: "stopped") |
503 |
Stopp mislyktes uventet |
Tilsvarer stop() etterfulgt av start() under operasjonslåsen.
Forespørselsinnhold: ingen
Svar: samme som for start (returnerer endelig ServiceStatus).
Oppdaterer 9Router til en nyere npm-versjon. Hvis tjenesten kjører, stoppes den først, npm install kjøres (og installerer den nyere versjonen på stedet), og deretter startes tjenesten på nytt.
Forespørselsinnhold (alle felter er valgfrie):
{ "version": "latest" }Svar:
| Status | Beskrivelse |
|---|---|
200 |
{ ok: true, previousVersion: "...", installedVersion: "..." } |
400 |
Ugyldig innhold |
500 |
npm-oppdateringen mislyktes |
Genererer en ny API-nøkkel for 9Router, krypterer den ved lagring og starter tjenesten på nytt (hvis den kjører), slik at den henter den nye nøkkelen fra miljøet. Den gamle nøkkelen ugyldiggjøres umiddelbart.
Forespørselsinnhold: ingen
Svar:
| Status | Beskrivelse |
|---|---|
200 |
{ keyRotated: true, restarted: boolean } |
500 |
Nøkkelrotasjon mislyktes |
Sikkerhet: Den nye nøkkelen returneres aldri i svaret (ingen lekkasje av legitimasjon).
Den lagres kryptert (AES-256-GCM) i version_manager-tabellen.
Returnerer kombinert sanntids- og DB-status, inkludert versjonsmetadata og forhåndsvisning av API-nøkkelen.
Svar:
| Status | Beskrivelse |
|---|---|
200 |
Se skjemaet nedenfor |
500 |
Lesing av status mislyktes |
Svarskjema:
{
"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
}Slå automatisk oppstart av eller på. Når enabled: true, starter tjenesten automatisk
neste gang OmniRoute starter (hvis tjenesten er installert).
Forespørselsinnhold:
{ "enabled": true }Svar:
| Status | Beskrivelse |
|---|---|
200 |
{ autoStart: true } |
400 |
Ugyldig innhold |
SSE-strøm med sanntidslogger fra ringbufferen for stdout/stderr i 9Router.
Spørringsparametere:
| Parameter | Type | Standardverdi | Beskrivelse |
|---|---|---|---|
tail |
integer |
200 | Antall historiske linjer som skal sendes først (maks. 1000) |
filter |
string |
ingen | Delstrengfilter uten skille mellom store og små bokstaver (ingen regex — ReDoS-sikkert) |
SSE-hendelser:
| Hendelse | Data | Beskrivelse |
|---|---|---|
snapshot |
LogLine[] |
Innledende historisk utdrag |
log |
LogLine |
Logglinje i sanntid |
heartbeat |
{} |
Hold forbindelsen aktiv hvert 15. s |
LogLine-skjema:
{
"ts": 1716633600000,
"stream": "stdout",
"line": "[9router] Listening on :20130"
}Svar:
| Status | Beskrivelse |
|---|---|
200 |
text/event-stream |
400 |
Parameteren filter er for lang (> 200 tegn) |
404 |
Tjenesten ble ikke funnet (supervisor ikke registrert) |
CLIProxyAPI har samme endepunktstruktur som 9Router, bortsett fra rotate-key, i tillegg til
accounts, provider-expose og auto-restart-adopted. Den mottar nå en
dedikert API-nøkkel for dataplanet som injiseres ved oppstart (needsApiKey: true i
bootstrap.ts, brukt til modellsynkronisering); status inneholder færre felt.
| Metode | Bane | Beskrivelse |
|---|---|---|
POST |
/api/services/cliproxy/install |
Installer CLIProxyAPI fra npm |
POST |
/api/services/cliproxy/start |
Start CLIProxyAPI |
POST |
/api/services/cliproxy/stop |
Stopp CLIProxyAPI |
POST |
/api/services/cliproxy/restart |
Start CLIProxyAPI på nytt |
POST |
/api/services/cliproxy/update |
Oppdater til en nyere versjon |
GET |
/api/services/cliproxy/status |
Sanntids- + DB-status (uten apiKeyMasked) |
POST |
/api/services/cliproxy/auto-start |
Slå automatisk start av eller på |
Det delte endepunktet GET /api/services/{name}/logs (se §4.1) fungerer for alle
de fire tjenestene ved hjelp av det dynamiske segmentet [name].
Mux har samme endepunktstruktur som CLIProxyAPI — ingen rotate-key-rute i API-
grensesnittet (bearer-tokenet genereres på samme måte som for 9Router via
getOrCreateApiKey("mux") og injiseres via miljøvariabelen MUX_SERVER_AUTH_TOKEN, men
det finnes ennå ikke noe dedikert endepunkt for rotasjon). Mux administreres kun gjennom livssyklusen: i motsetning til
9Router har den ingen lag 4-eksekutor og registreres aldri som en rutingstilbyder.
| Metode | Bane | Beskrivelse |
|---|---|---|
POST |
/api/services/mux/install |
Installer Mux fra npm (npm i mux) |
POST |
/api/services/mux/start |
Start Mux (mux server) |
POST |
/api/services/mux/stop |
Stopp Mux |
POST |
/api/services/mux/restart |
Start Mux på nytt |
POST |
/api/services/mux/update |
Oppdater til en nyere npm-versjon |
GET |
/api/services/mux/status |
Sanntids- + DB-status |
POST |
/api/services/mux/auto-start |
Slå automatisk start av eller på |
Bifrost er en AI-gateway-relétjeneste skrevet i Go (@maximhq/bifrost). Den bruker samme
endepunktstruktur som CLIProxyAPI (ingen rotate-key — Bifrost administrerer sine egne tilbyder-
nøkler i config.json under sin -app-dir).
| Metode | Bane | Beskrivelse |
|---|---|---|
POST |
/api/services/bifrost/install |
Installer Bifrost fra npm (@maximhq/bifrost) |
POST |
/api/services/bifrost/start |
Start Bifrost på port 8080 (standard) |
POST |
/api/services/bifrost/stop |
Stopp Bifrost |
POST |
/api/services/bifrost/restart |
Start Bifrost på nytt |
POST |
/api/services/bifrost/update |
Oppdater til en nyere versjon |
GET |
/api/services/bifrost/status |
Sanntids- + DB-status |
POST |
/api/services/bifrost/auto-start |
Slå automatisk start av eller på |
GET |
/api/services/bifrost/logs |
SSE-logghale (via den delte dynamiske ruten [name]/logs) |
Rutingstilkobling: Når BIFROST_BASE_URL ikke er angitt og den overvåkede Bifrost-
instansen kjører, bruker getBifrostRoutingConfig() (i routingBackend.ts) automatisk
http://127.0.0.1:{port} som reléets grunn-URL. En eksplisitt angitt miljøvariabel BIFROST_BASE_URL
har alltid forrang.
Samme livssyklusstruktur som de andre tjenestene (install, start, stop, restart,
update, status, auto-start, auto-restart-adopted), i tillegg til et tokenbeskyttet OAuth-
kontrollplan under admin/: admin/accounts, admin/import-from-omniroute,
admin/login-start, admin/login-complete (alle beskyttet av DARIO_ADMIN_TOKEN).
open-wa (@open-wa/wa-automate) styrer en hodeløs Chromium-instans (via
Puppeteer) for å automatisere WhatsApp Web. Den bruker samme endepunktstruktur som Mux (ingen
rotate-key-rute ennå). Den administreres kun gjennom livssyklusen — den er ikke et rutingsmål
og har ingen lag 4-eksekutor-/tilbyderoppføring.
| Metode | Bane | Beskrivelse |
|---|---|---|
POST |
/api/services/openwa/install |
Installer open-wa fra npm (@open-wa/wa-automate) |
POST |
/api/services/openwa/start |
Start open-wa på port 8323 (standard) |
POST |
/api/services/openwa/stop |
Stopp open-wa |
POST |
/api/services/openwa/restart |
Start open-wa på nytt |
POST |
/api/services/openwa/update |
Oppdater til en nyere versjon |
GET |
/api/services/openwa/status |
Sanntidsstatus + DB-status |
POST |
/api/services/openwa/auto-start |
Slå automatisk oppstart av/på |
GET |
/api/services/openwa/logs |
Løpende SSE-logg (via den delte dynamiske ruten [name]/logs) |
API-nøkkel: injiseres som WA_KEY — open-was generiske miljøoverstyring
med WA_*-prefiks tilordner den til CLI-alternativet --key/-k
(dist/cli/setup.js::envArgs(), verifisert mot den installerte
pakken 4.76.0). Får prefikset ow_ når den genereres av
generateServiceApiKey(). open-wa leser nøkkelen fra en key/api_key-
HTTP-header (ikke Authorization: Bearer); /api-docs* er eksplisitt unntatt
fra kontrollen (setupAuthenticationLayer i dist/cli/server.js), så
helsesjekken trenger ingen autentiseringsheader.
Paring: open-wa er uoffisiell og ikke tilknyttet WhatsApp — det tilkoblede nummeret risikerer å bli utestengt av WhatsApps egen deteksjon av automatisering. Ved første oppstart skrives QR-koden for paring til stdout og gjøres tilgjengelig via det eksisterende loggpanelet/SSE-strømmen — denne integrasjonen har foreløpig ikke noe eget endepunkt for QR-bilder.
Kontrollpanelet bygger inn 9Routers webgrensesnitt i en iframe via en intern omvendt proxy på:
GET|POST|... /dashboard/providers/services/9router/embed/[...path]
Denne proxyen:
- Videresender forespørselen til
http://127.0.0.1:{port}/{path}(kun loopback) - Fjerner innkommende
cookie- ogauthorization-headere (ingen lekkasje av OmniRoute-økten) - Injiserer
Authorization: Bearer {apiKey}for 9Router-autentisering - Fjerner
set-cookie,content-security-policy,x-frame-options,cross-origin-*fra responsen - Omskriver HTML-responser for å injisere
<base href>og normalisere absolutte baner (/foo→/dashboard/.../embed/foo)
WebSocket-oppgraderinger for det innebygde kontrollpanelet håndteres av en
tilhørende server på en dedikert port (se src/lib/services/embedWsProxy.ts).
Sikkerhet: Proxy-rutene for innebygging er klassifisert under
LOCAL_ONLY_API_PREFIXES og kan bare nås fra loopback. En angriper som får tak
i et JWT via en Cloudflare/Ngrok-tunnel, kan ikke bruke proxyen for å nå
innebygde tjenester.
Alle ruter under /api/services/ og /dashboard/providers/services/*/embed/ er
klassifisert som LOCAL_ONLY i src/server/authz/routeGuard.ts. Tilbakesløyfekontrollen
kjøres ubetinget før enhver autentiseringsgren:
forespørselen ankommer
→ isLocalOnlyPath(path)?
→ ikke-tilbakesløyfe → 403 LOCAL_ONLY (alltid, før autentiseringskontroll)
→ tilbakesløyfe → fortsett til normal autentisering
Dette hindrer at en lekket JWT (f.eks. via en tunnel) utløser npm install eller
prosessoppstart. Se docs/security/ROUTE_GUARD_TIERS.md for den fullstendige
nivåmatrisen.
9Router og Mux krever en API-nøkkel/bærertoken for sine egne HTTP-endepunkter. OmniRoute:
- Genererer en nøkkel via
crypto.randomBytes(32).toString("base64url")med et tjenestespesifikt prefiks (nr_for 9Router,mx_for Mux). - Krypterer den ved lagring med AES-256-GCM (samme kryptering som brukes for leverandørlegitimasjon).
- Dekrypterer og injiserer den som en miljøvariabel ved prosessoppstart —
NINEROUTER_API_KEYfor 9Router,MUX_SERVER_AUTH_TOKENfor Mux (aldri et CLI-flagg, slik at tokenet aldri vises ips/prosesslister). - Returnerer aldri nøkkelen i klartekst i noe HTTP-svar.
CLIProxyAPI mottar en dedikert dataplannøkkel som injiseres ved prosessoppstart
(needsApiKey: true — brukes til modellsynkronisering mot adapteren).
Den omvendte HTTP-proxyen (/dashboard/.../embed/[...path]) er hardkodet til kun å
videresende til http://127.0.0.1:{port}. Den følger aldri omdirigeringer til
destinasjoner utenfor tilbakesløyfen. Biblioteket ssrf-req-filter brukes til å avvise
enhver oppstrøms-URL som løses til en adresse utenfor tilbakesløyfeområdet.
npm install startes via execFile('npm', ['install', pkg, '--prefix', dir]) —
ingen malstrenger, intet skall og ingen interpolering av eksterne stier i
kommandostrengen. Kjøretidsverdier (porter, API-nøkler) sendes via underprosessens
env-objekt.
Alle feilsvar fra /api/services/* går gjennom buildErrorBody() eller
sanitizeErrorMessage(). Rå err.stack og err.message returneres aldri
ordrett til klienten.
Følg disse 8 trinnene. Les de eksisterende implementasjonene i src/lib/services/installers/
og src/app/api/services/ som den autoritative referansen.
Opprett src/lib/services/installers/{name}.ts etter mønster av ninerouter.ts:
export const NAME_PACKAGE = "your-npm-package";
export const NAME_DEFAULT_PORT = 20132; // velg en ledig port
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> { ... }Bruk runNpm(['install', NAME_PACKAGE, '--prefix', dir]) fra installers/utils.ts
— aldri execSync eller skallinterpolering.
Legg til en ServiceEntry i SERVICES-tabellen i 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 hvis ingen API-nøkkel er nødvendig
}Utvid buildSpawnArgsFactory() for å håndtere cfg.tool === "myservice".
src/lib/services/providerPlugins/ introduserer en ServiceProviderPlugin-kontrakt som
samler feltene fra ServiceEntry i en backend-tjenestes bootstrap.ts og
manifestmalfeltene i serviceBackends.ts i ett objekt, i stedet for at formen til
samme backend uttrykkes separat i to filer uten tilknytning til hverandre. På
skrivetidspunktet er bare 9router migrert — bootstrap.ts utleder sin
SERVICES[]-oppføring fra getServiceProviderPlugin("9router")
(src/lib/services/providerPlugins/registry.ts) og utløser en oppstartsfeil hvis
programtillegget noen gang mangler. cliproxy, mux og bifrost bruker fortsatt de
eksisterende innebygde SERVICES[]-literalene uendret.
open-sse/config/providerPluginManifest.ts har også fått en additiv
createServiceBackendManifestEntry(pluginId, template)-hjelpefunksjon som bygger en
velformet ProviderPluginManifestEntry fra en SERVICE_BACKEND_MANIFEST_TEMPLATE-oppføring
— den er ennå ikke koblet til noen aktiv forespørselsflyt (verken
generateProviderPluginManifestFromRegistry() eller /v1/providers/[provider]/models);
dette gjenstår som en oppfølging når kontrakten er utprøvd for en andre backend.
Utsatt til oppfølgende PR-er, sporet under sak #7333: migrering av cliproxyapi gjennom
det samme registeret, generalisering av mux/bifrost inn i unionen
ServiceBackendPluginId, innlemming av spesialhåndteringen for eksekveringsruting
(open-sse/executors/index.ts, open-sse/handlers/chatCore/executorProxy.ts) i
programtilleggskontrakten og tilkobling av createServiceBackendManifestEntry() til
en aktiv kodeflyt for manifest/modeller.
Sørg for at tjenesten har en rad i version_manager via en migrering i
src/lib/db/migrations/. Raden skal inneholde:
INSERT OR IGNORE INTO version_manager (tool, status, auto_start, provider_expose)
VALUES ('myservice', 'not_installed', 0, 0);Under src/app/api/services/{name}/:
_lib.ts hjelpefunksjonen getOrInitSupervisor()
install/route.ts POST — kaller installer.install()
start/route.ts POST — kaller supervisor.start()
stop/route.ts POST — kaller supervisor.stop()
restart/route.ts POST — kaller supervisor.restart()
update/route.ts POST — kaller installer.update()
status/route.ts GET — slår sammen direkte- og DB-status
auto-start/route.ts POST — veksler auto_start-flagget
Den delte ruten GET /api/services/[name]/logs er allerede koblet opp — ingen endringer
er nødvendige der.
Deleger alle feilresponser gjennom createErrorResponse() / buildErrorBody().
I src/server/authz/routeGuard.ts, kontroller at /api/services/ allerede er oppført.
Hvis du introduserer et nytt prefiks (f.eks. /api/tools/), legger du det til i både
LOCAL_ONLY_API_PREFIXES og, hvis det starter prosesser, i SPAWN_CAPABLE_PREFIXES.
Legg til en test i tests/unit/authz/routeGuard.test.ts.
Opprett src/app/(dashboard)/dashboard/providers/services/tabs/{Name}ServiceTab.tsx.
Gjenbruk delte komponenter:
ServiceStatusCard— sanntidsstatus + helsetilstandsmerkeServiceLifecycleButtons— Start / Stopp / Start på nytt / OppdaterServiceLogsPanel— SSE-logghale (kobler til/api/services/{name}/logs)ApiKeyCard— visning + rotering av nøkkel (hvisneedsApiKey: true)
Registrer fanen i ServicesPageShell.tsx.
Hvis den innebygde tjenesten tilbyr et OpenAI-kompatibelt endepunkt på /v1/chat/completions:
- Legg til en leverandøroppføring i
src/shared/constants/providers.tsmedisEmbeddedService: true. - Opprett
open-sse/executors/{name}.tssom utviderBaseExecutor. Slå opp porten og API-nøkkelen på nytt for hver forespørsel (aldri bufre dem i konstruktøren). Returner en503 service_not_running-respons når tilsynsprosessens status ikke er"running". - Registrer modeller i
open-sse/config/providerRegistry.tsmed tjenesteprefikset (f.eks.myservice/sub/model).modelSync.tsholder dem oppdatert.
- Oppdater
docs/frameworks/EMBEDDED-SERVICES.md(denne filen) — legg til tjenesten i tabellen i §1 og eventuelle nye endepunkter i §4. - Legg til enhetstester i
tests/unit/services/(livssyklus, installasjonsprogram, API-struktur). - Legg til en integrasjonstest i
tests/integration/services/(bakRUN_SERVICES_INT=1). - Oppdater
docs/openapi.yamlmed de nye endepunktene.
Symptomer: Startknappen returnerer 503, og tilstanden forblir "error" eller "starting".
Sjekkliste:
- Sjekk
GET /api/services/{name}/logs(eller loggpanelet i kontrollpanelet). Se etter linjer somError: ENOENT,address already in useellerCannot find module. - Kontroller at
npmfinnes i PATH: Kjørwhich npmfra den samme brukerkontoen som kjører OmniRoute. - Kontroller at tjenesten er installert: Sjekk
GET /api/services/{name}/statusforinstalledVersion. Hvis verdien ernull, må du kjøre installasjonen først. - Kontroller at
DATA_DIR/services/{name}/node_modules/finnes og ikke er tom. - Sjekk feltet
lastErrori statusresponsen for den rensede avslutningsårsaken.
Symptomer: Tilstanden forblir "starting" lenge før den endres til "running" eller "error".
Forklaring: Kaldstart av 9Router omfatter import av store avhengighetstrær (DNS-, tunnel- og MITM-moduler). Standardintervallet for helsesjekk er 2 s med 3 forsøk før overvåkeren erklærer tidsavbrudd (men fortsetter å sjekke).
Løsning: healthIntervalMs og tidsavbruddet for waitForHealthy
(healthIntervalMs * 3) kan konfigureres i bootstrap.ts. For tjenester med lengre
oppstartstid kan du øke healthIntervalMs til 5000 og stopTimeoutMs til 30 000.
Symptomer: Loggene viser address already in use :::20130.
Årsaker:
- En annen prosess bruker allerede port 20130.
- En tidligere 9Router-prosess ble ikke fullstendig stoppet (zombie-PID).
Løsning:
- Endre standardporten via miljøvariabelen
NINEROUTER_PORTi.env. - Finn og avslutt prosessen som forårsaker konflikten:
lsof -ti :20130 | xargs kill -9. - Porten kan konfigureres per tjeneste i
bootstrap.tsvia feltetport.
Merk: 9Router bruker port 20130 som standard, spesielt for å unngå konflikt med OmniRoutes standardport 20128.
Symptomer: Installasjonen returnerer 500, og loggene viser EACCES eller permission denied.
Årsaker:
DATA_DIReller den overordnede katalogen er ikke skrivbar for OmniRoute-prosessen.- Kjøring i Docker uten root-tilgang og uten skrivetilgang til det tilordnede volumet.
Løsning:
- Sjekk
DATA_DIR(standard:~/.omniroute/):ls -la ~/.omniroute/ - Sørg for at brukeren som kjører OmniRoute-prosessen, eier katalogen:
chown -R $USER ~/.omniroute/ - I Docker må du sørge for at volummonteringen har riktige tillatelser for containerbrukeren.
Symptomer: Oppdateringen returnerer 500 med InstallError, og loggene viser tidsavbrudd for nettverket.
Sjekkliste:
- Bekreft at npm-registeret er tilgjengelig:
npm ping. - Se etter bedriftsproxy:
npm config get proxy,npm config get https-proxy. - Prøv installasjonen manuelt:
npm install {package}@latest --prefix ~/.omniroute/services/{name}/. - Hvis systemet er isolert fra nettverket, forhåndslast ned tarball-filen og bruk
npm install /path/to/tarball.tgz.
Symptomer: Tilstanden endres fra "starting" til "error" på under 5 sekunder.
lastError viser "Fast crash (exited with code 1)".
Sjekkliste:
- Les hele slutten av loggen:
GET /api/services/{name}/logs?tail=500. - Vanlig årsak: manglende miljøvariabler som tjenesten forventer.
- For 9Router: Kontroller at
NINEROUTER_DISABLE_MITM=trueogNINEROUTER_DISABLE_TUNNEL=truefinnes i miljøet som sendes ved oppstart (seinstallers/ninerouter.tsresolveSpawnArgs).
S: Kan jeg eksponere endepunktene for de innebygde tjenestene for klienter utenfor loopback?
Nei. LOCAL_ONLY-nivået er tilsiktet (ufravikelig regel nr. 17). Ruter som kan kjøre
npm install eller starte node-prosesser, må ikke være tilgjengelige for trafikk
utenfor loopback, fordi en lekket JWT via en tunnel (Cloudflare, Ngrok, Tailscale)
ellers ville tillate vilkårlig oppstart av prosesser. Det finnes ikke noe unntak for
/api/services/ — i motsetning til /api/mcp/ er den utelatt fra listen over
omgåelser for administrasjonstilgang. Se docs/security/ROUTE_GUARD_TIERS.md.
S: Vil 9Router og CLIProxyAPI være tilgjengelige i produksjons-/skyimplementeringer?
Ja. Begge tjenestene følger den samme lokal-først-modellen som OmniRoute selv. De kjører på samme maskin og kommuniserer via loopback. «Produksjon» betyr her VPS-en eller den lokale serveren der OmniRoute er distribuert, ikke en ekstern skyleverandør.
S: Hvordan feilsøker jeg prosessovervåkeren?
- Følg SSE-loggstrømmen:
curl -N http://localhost:20128/api/services/9router/logs. - Kontroller strukturerte logger i OmniRoutes pino-utdata, filtrert etter
navnerommet
service:supervisor. - Undersøk DB-raden:
sqlite3 ~/.omniroute/omniroute.db "SELECT * FROM version_manager WHERE tool='9router'". - Bruk
GET /api/services/9router/statusfor å se gjeldende sanntidstilstand, PID, helsestatus oglastErrori ett kall.
S: Prosessovervåkeren viser health: "degraded" eller health: "unknown", men tilstanden er "running". Er det et problem?
"degraded" betyr at helsesjekken returnerte et svar som ikke var 200. "unknown"
betyr at ingen sjekk er fullført ennå (kappløp med første spørring). Begge deler er
midlertidig under oppstart. Hvis helsetilstanden forblir "degraded" i mer enn
healthIntervalMs * 3 ms etter "running", kjører den innebygde tjenesten, men
HTTP-API-et svarer ikke. Kontroller om porten er riktig i statussvaret, og om
tjenesten faktisk lytter på den porten.
S: Kan jeg endre API-nøkkelen for 9Router uten en fullstendig omstart?
Nei. API-nøkkelen sendes til 9Router via en miljøvariabel når prosessen startes.
Miljøvariabler kan ikke endres i en kjørende prosess. POST .../rotate-key stopper
og starter tjenesten automatisk på nytt for å ta i bruk den nye nøkkelen.
Nøkkelrotasjonen trer i kraft innen tjenestens stopTimeoutMs (standardverdi 15 s)
pluss oppstartstiden.
S: Hva er grensen for ringbufferen, og hva skjer når den blir full?
Hver tjeneste har en egen ringbuffer på 5 MB. Når bufferen er full, fjernes de eldste
logglinjene for å gi plass til nye. SSE-hendelsen snapshot returnerer de nyeste
linjene innenfor tail-grensen. Logger lagres ikke på disk med mindre
logsBufferPath er angitt i DB-raden.
docs/security/ROUTE_GUARD_TIERS.md— detaljer om LOCAL_ONLY-nivåetdocs/architecture/CODEBASE_DOCUMENTATION.md— §3.2 modultilordning for innebygde tjenesterdocs/architecture/ARCHITECTURE.md— kontekst på systemnivådocs/openapi.yaml— maskinlesbare endepunktdefinisjonerCLAUDE.md§«Adding a New Embedded Service» — sjekkliste for hurtigreferanse