feat(server): support configurable hostname for managed OpenCode server spawn (#599)
* feat(server): support configurable hostname for managed OpenCode server spawn Allow the managed OpenCode server bind hostname to be configured via OPENCHAMBER_OPENCODE_HOSTNAME environment variable (default: 127.0.0.1). This enables LAN/Tailscale access without a reverse proxy by setting the hostname to 0.0.0.0. Closes #597 * fix: address review feedback — input validation, port probe hostname, security docs - Add defensive parsing for OPENCHAMBER_OPENCODE_HOSTNAME with trim/empty check and warning log, matching OPENCODE_HOST validation pattern - Pass configured hostname to resolveManagedOpenCodePort() so port availability is probed on the actual bind address, avoiding EADDRINUSE - Add security note in README docs warning about 0.0.0.0 exposure on untrusted networks
This commit is contained in:
@@ -127,6 +127,11 @@ OPENCODE_PORT=4096 OPENCODE_SKIP_START=true openchamber
|
||||
OPENCODE_HOST=https://myhost:4096 OPENCODE_SKIP_START=true openchamber
|
||||
```
|
||||
|
||||
Bind managed OpenCode server to all interfaces (use only on trusted networks):
|
||||
```bash
|
||||
OPENCHAMBER_OPENCODE_HOSTNAME=0.0.0.0 openchamber --port 3000
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
@@ -26,4 +26,5 @@ services:
|
||||
# OH_MY_OPENCODE: true # enable oh-my-opencode
|
||||
# OPENCODE_HOST: http://172.17.0.1:4096 # Connect to external OpenCode server
|
||||
# OPENCODE_SKIP_START: true # skip start opencode
|
||||
# OPENCHAMBER_OPENCODE_HOSTNAME: 0.0.0.0 # Bind OpenCode to all interfaces
|
||||
restart: unless-stopped
|
||||
|
||||
@@ -46,6 +46,34 @@ openchamber update # Update to latest version
|
||||
- Replacing or stopping a tunnel revokes existing connect links and invalidates remote tunnel sessions.
|
||||
- Connect links are one-time tokens; generating a new link revokes the previous unused link.
|
||||
|
||||
<details>
|
||||
<summary>Connect to external OpenCode server</summary>
|
||||
|
||||
```bash
|
||||
OPENCODE_PORT=4096 OPENCODE_SKIP_START=true openchamber
|
||||
OPENCODE_HOST=https://myhost:4096 OPENCODE_SKIP_START=true openchamber
|
||||
```
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `OPENCODE_HOST` | Full base URL of external server (overrides `OPENCODE_PORT`) |
|
||||
| `OPENCODE_PORT` | Port of external server |
|
||||
| `OPENCODE_SKIP_START` | Skip starting embedded OpenCode server |
|
||||
| `OPENCHAMBER_OPENCODE_HOSTNAME` | Bind hostname for managed OpenCode server (default: `127.0.0.1`, use `0.0.0.0` for LAN/remote access — trusted networks only) |
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Bind managed OpenCode to LAN / Tailscale</summary>
|
||||
|
||||
```bash
|
||||
OPENCHAMBER_OPENCODE_HOSTNAME=0.0.0.0 openchamber --port 3000
|
||||
```
|
||||
|
||||
**Security note:** binding to `0.0.0.0` exposes the server on all network interfaces — use only on trusted networks and protect with firewall rules or `--ui-password`.
|
||||
|
||||
</details>
|
||||
|
||||
**Optional env vars:**
|
||||
```yaml
|
||||
environment:
|
||||
|
||||
@@ -864,6 +864,7 @@ ENVIRONMENT:
|
||||
OPENCODE_HOST External OpenCode server base URL, e.g. http://hostname:4096
|
||||
OPENCODE_PORT Port of external OpenCode server to connect to
|
||||
OPENCODE_SKIP_START Skip starting OpenCode, use external server
|
||||
OPENCHAMBER_OPENCODE_HOSTNAME Bind hostname for managed OpenCode server (default: 127.0.0.1)
|
||||
|
||||
EXAMPLES:
|
||||
openchamber # Start in daemon mode on default port 3000 (or free port)
|
||||
|
||||
@@ -3788,6 +3788,21 @@ const ENV_CONFIGURED_OPENCODE_HOST = (() => {
|
||||
// OPENCODE_HOST takes precedence over OPENCODE_PORT when both are set
|
||||
const ENV_EFFECTIVE_PORT = ENV_CONFIGURED_OPENCODE_HOST?.port ?? ENV_CONFIGURED_OPENCODE_PORT;
|
||||
|
||||
const ENV_CONFIGURED_OPENCODE_HOSTNAME = (() => {
|
||||
const raw = process.env.OPENCHAMBER_OPENCODE_HOSTNAME;
|
||||
if (typeof raw !== 'string') {
|
||||
return '127.0.0.1';
|
||||
}
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
console.warn(
|
||||
`[config] Ignoring OPENCHAMBER_OPENCODE_HOSTNAME=${JSON.stringify(raw)}: empty after trimming`,
|
||||
);
|
||||
return '127.0.0.1';
|
||||
}
|
||||
return trimmed;
|
||||
})();
|
||||
|
||||
const ENV_SKIP_OPENCODE_START = process.env.OPENCODE_SKIP_START === 'true' ||
|
||||
process.env.OPENCHAMBER_SKIP_OPENCODE_START === 'true';
|
||||
const ENV_DESKTOP_NOTIFY = process.env.OPENCHAMBER_DESKTOP_NOTIFY === 'true';
|
||||
@@ -6049,7 +6064,7 @@ async function createManagedOpenCodeServerProcess({
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveManagedOpenCodePort(requestedPort) {
|
||||
async function resolveManagedOpenCodePort(requestedPort, hostname = '127.0.0.1') {
|
||||
if (typeof requestedPort === 'number' && Number.isFinite(requestedPort) && requestedPort > 0) {
|
||||
return requestedPort;
|
||||
}
|
||||
@@ -6079,13 +6094,13 @@ async function resolveManagedOpenCodePort(requestedPort) {
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(0, '127.0.0.1');
|
||||
server.listen(0, hostname);
|
||||
});
|
||||
}
|
||||
|
||||
async function startOpenCode() {
|
||||
const desiredPort = ENV_CONFIGURED_OPENCODE_PORT ?? 0;
|
||||
const spawnPort = await resolveManagedOpenCodePort(desiredPort);
|
||||
const spawnPort = await resolveManagedOpenCodePort(desiredPort, ENV_CONFIGURED_OPENCODE_HOSTNAME);
|
||||
console.log(
|
||||
desiredPort > 0
|
||||
? `Starting OpenCode on requested port ${desiredPort}...`
|
||||
@@ -6100,7 +6115,7 @@ async function startOpenCode() {
|
||||
|
||||
try {
|
||||
const serverInstance = await createManagedOpenCodeServerProcess({
|
||||
hostname: '127.0.0.1',
|
||||
hostname: ENV_CONFIGURED_OPENCODE_HOSTNAME,
|
||||
port: spawnPort,
|
||||
timeout: 30000,
|
||||
cwd: openCodeWorkingDirectory,
|
||||
|
||||
Reference in New Issue
Block a user