diff --git a/README.md b/README.md index fb0ca0181..af04c1830 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,8 @@ OpenCLI is not only for websites. It can also: | Variable | Default | Description | |----------|---------|-------------| | `OPENCLI_DAEMON_PORT` | `19825` | HTTP port for the daemon-extension bridge | +| `OPENCLI_DAEMON_HOST` | `127.0.0.1` | Host the CLI uses to reach the daemon. Set to e.g. `host.docker.internal` to call a daemon running on the host from inside a container. | +| `OPENCLI_DAEMON_BIND` | `127.0.0.1` | Interface the daemon binds to. Default is loopback; set to `0.0.0.0` to accept connections from other hosts. The daemon has no built-in authentication, so restrict access with a firewall when binding off-loopback. | | `OPENCLI_PROFILE` | — | Browser Bridge profile alias/contextId to use when multiple Chrome profiles are connected | | `OPENCLI_WINDOW` | command default | Set to `foreground` or `background` to override Browser Bridge window placement. Browser-backed commands also accept `--window `. | | `OPENCLI_KEEP_TAB` | command default | Set to `true` or `false` to keep or release the browser tab lease after a browser-backed adapter command. Browser-backed adapter commands also accept `--keep-tab `. | diff --git a/README.zh-CN.md b/README.zh-CN.md index 303346dfa..87077e404 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -181,6 +181,8 @@ OpenCLI 不只是网站 CLI,还可以: | 变量 | 默认值 | 说明 | |------|--------|------| | `OPENCLI_DAEMON_PORT` | `19825` | daemon-extension 通信端口 | +| `OPENCLI_DAEMON_HOST` | `127.0.0.1` | CLI 连接 daemon 时使用的主机。例如在容器内调用宿主上的 daemon 时,设为 `host.docker.internal` | +| `OPENCLI_DAEMON_BIND` | `127.0.0.1` | daemon 监听的网卡地址。默认仅 loopback;设为 `0.0.0.0` 可接受外部连接。daemon 不带鉴权,绑非 loopback 时务必用防火墙限制访问 | | `OPENCLI_WINDOW` | 命令默认值 | 设为 `foreground` 或 `background` 来覆盖 Browser Bridge 窗口位置。浏览器型命令也支持 `--window ` | | `OPENCLI_KEEP_TAB` | 命令默认值 | 设为 `true` 或 `false` 来控制浏览器型 adapter 命令结束后是否保留 tab lease。浏览器型 adapter 命令也支持 `--keep-tab ` | | `OPENCLI_BROWSER_CONNECT_TIMEOUT` | `30` | 浏览器连接超时(秒) | diff --git a/src/browser/daemon-client.test.ts b/src/browser/daemon-client.test.ts index 10bfc234c..f67f02bc4 100644 --- a/src/browser/daemon-client.test.ts +++ b/src/browser/daemon-client.test.ts @@ -49,6 +49,31 @@ describe('daemon-client', () => { await expect(fetchDaemonStatus()).resolves.toBeNull(); }); + it('targets 127.0.0.1 by default', async () => { + const fetchMock = vi.mocked(fetch); + fetchMock.mockResolvedValue({ + ok: true, + json: () => Promise.resolve({}), + } as Response); + + await fetchDaemonStatus(); + + expect(fetchMock.mock.calls[0][0]).toMatch(/^http:\/\/127\.0\.0\.1:/); + }); + + it('targets OPENCLI_DAEMON_HOST when set', async () => { + vi.stubEnv('OPENCLI_DAEMON_HOST', 'host.docker.internal'); + const fetchMock = vi.mocked(fetch); + fetchMock.mockResolvedValue({ + ok: true, + json: () => Promise.resolve({}), + } as Response); + + await fetchDaemonStatus(); + + expect(fetchMock.mock.calls[0][0]).toMatch(/^http:\/\/host\.docker\.internal:/); + }); + it('requestDaemonShutdown POSTs to the shared shutdown endpoint', async () => { const fetchMock = vi.mocked(fetch); fetchMock.mockResolvedValue({ ok: true } as Response); diff --git a/src/browser/daemon-client.ts b/src/browser/daemon-client.ts index f2a2c0672..89a472022 100644 --- a/src/browser/daemon-client.ts +++ b/src/browser/daemon-client.ts @@ -9,10 +9,14 @@ import { sleep } from '../utils.js'; import { classifyBrowserError } from './errors.js'; import { resolveProfileContextId } from './profile.js'; -const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10); -const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`; const OPENCLI_HEADERS = { 'X-OpenCLI': '1' }; +function getDaemonUrl(): string { + const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10); + const host = process.env.OPENCLI_DAEMON_HOST ?? '127.0.0.1'; + return `http://${host}:${port}`; +} + let _idCounter = 0; function generateId(): string { @@ -112,7 +116,7 @@ async function requestDaemon(pathname: string, init?: RequestInit & { timeout?: const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeout); try { - return await fetch(`${DAEMON_URL}${pathname}`, { + return await fetch(`${getDaemonUrl()}${pathname}`, { ...rest, headers: { ...OPENCLI_HEADERS, ...headers }, signal: controller.signal, diff --git a/src/daemon.ts b/src/daemon.ts index 803d3cba5..ad2af8d64 100644 --- a/src/daemon.ts +++ b/src/daemon.ts @@ -431,8 +431,10 @@ wss.on('connection', (ws: WebSocket) => { // ─── Start ─────────────────────────────────────────────────────────── -httpServer.listen(PORT, '127.0.0.1', () => { - log.info(`[daemon] Listening on http://127.0.0.1:${PORT}`); +const BIND = process.env.OPENCLI_DAEMON_BIND ?? '127.0.0.1'; + +httpServer.listen(PORT, BIND, () => { + log.info(`[daemon] Listening on http://${BIND}:${PORT}`); }); httpServer.on('error', (err: NodeJS.ErrnoException) => {