diff --git a/CLAUDE.md b/CLAUDE.md index 40664601..48e1b9f5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -166,6 +166,7 @@ Use `resolveModelString()` from `@automaker/model-resolver` to convert model ali ## Environment Variables - `ANTHROPIC_API_KEY` - Anthropic API key (or use Claude Code CLI auth) +- `HOST` - Host to bind server to (default: 0.0.0.0) - `PORT` - Server port (default: 3008) - `DATA_DIR` - Data storage directory (default: ./data) - `ALLOWED_ROOT_DIRECTORY` - Restrict file operations to specific directory diff --git a/apps/server/.env.example b/apps/server/.env.example index 68b28395..6ac27145 100644 --- a/apps/server/.env.example +++ b/apps/server/.env.example @@ -44,6 +44,11 @@ CORS_ORIGIN=http://localhost:3007 # OPTIONAL - Server # ============================================ +# Host to bind the server to (default: 0.0.0.0) +# Use 0.0.0.0 to listen on all interfaces (recommended for Docker/remote access) +# Use 127.0.0.1 or localhost to restrict to local connections only +HOST=0.0.0.0 + # Port to run the server on PORT=3008 diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts index 609be945..db371ee2 100644 --- a/apps/server/src/index.ts +++ b/apps/server/src/index.ts @@ -73,6 +73,7 @@ import { getDevServerService } from './services/dev-server-service.js'; dotenv.config(); const PORT = parseInt(process.env.PORT || '3008', 10); +const HOST = process.env.HOST || '0.0.0.0'; const DATA_DIR = process.env.DATA_DIR || './data'; const ENABLE_REQUEST_LOGGING = process.env.ENABLE_REQUEST_LOGGING !== 'false'; // Default to true @@ -551,22 +552,24 @@ terminalWss.on('connection', (ws: WebSocket, req: import('http').IncomingMessage }); // Start server with error handling for port conflicts -const startServer = (port: number) => { - server.listen(port, () => { +const startServer = (port: number, host: string) => { + server.listen(port, host, () => { const terminalStatus = isTerminalEnabled() ? isTerminalPasswordRequired() ? 'enabled (password protected)' : 'enabled' : 'disabled'; const portStr = port.toString().padEnd(4); + const hostDisplay = host === '0.0.0.0' ? 'localhost' : host; logger.info(` ╔═══════════════════════════════════════════════════════╗ ║ Automaker Backend Server ║ ╠═══════════════════════════════════════════════════════╣ -║ HTTP API: http://localhost:${portStr} ║ -║ WebSocket: ws://localhost:${portStr}/api/events ║ -║ Terminal: ws://localhost:${portStr}/api/terminal/ws ║ -║ Health: http://localhost:${portStr}/api/health ║ +║ Listening: ${host}:${port}${' '.repeat(Math.max(0, 34 - host.length - port.toString().length))}║ +║ HTTP API: http://${hostDisplay}:${portStr} ║ +║ WebSocket: ws://${hostDisplay}:${portStr}/api/events ║ +║ Terminal: ws://${hostDisplay}:${portStr}/api/terminal/ws ║ +║ Health: http://${hostDisplay}:${portStr}/api/health ║ ║ Terminal: ${terminalStatus.padEnd(37)}║ ╚═══════════════════════════════════════════════════════╝ `); @@ -600,7 +603,7 @@ const startServer = (port: number) => { }); }; -startServer(PORT); +startServer(PORT, HOST); // Global error handlers to prevent crashes from uncaught errors process.on('unhandledRejection', (reason: unknown, _promise: Promise) => { diff --git a/apps/ui/vite.config.mts b/apps/ui/vite.config.mts index e40bce49..71a70cda 100644 --- a/apps/ui/vite.config.mts +++ b/apps/ui/vite.config.mts @@ -65,6 +65,7 @@ export default defineConfig(({ command }) => { }, }, server: { + host: process.env.HOST || '0.0.0.0', port: parseInt(process.env.TEST_PORT || '3007', 10), }, build: {