Compare commits

...

10 Commits

Author SHA1 Message Date
Haileyesus
22f2b92215 fix: add fallback for SERVER_PORT using PORT environment variable 2026-03-12 22:01:17 +03:00
Haileyesus
03269a33f5 Merge branch 'fix/network-setup-improvements' of https://github.com/siteboon/claudecodeui into fix/network-setup-improvements 2026-03-12 13:13:53 +03:00
Haileyesus
e644f5cb7d fix: add legacy PORT env fallback for server port configuration 2026-03-12 13:13:30 +03:00
Simos Mikelatos
f41383f3a6 Merge branch 'main' into fix/network-setup-improvements 2026-03-12 11:05:29 +01:00
Simos Mikelatos
ea844b110c Merge branch 'main' into fix/network-setup-improvements 2026-03-12 10:28:18 +01:00
Haileyesus
1995a411d2 chore: add comments explaining host normalization 2026-03-11 16:05:26 +03:00
Haileyesus
468ab599be refactor: rename PORT to SERVER_PORT for clarity 2026-03-11 15:55:42 +03:00
Haileyesus
f3b25bbbab fix: use shared network hosts configuration for better proxy setup
- Normalize all localhost variants to 'localhost' for consistent proxy
configuration in Vite and server setup.
- use one source of truth for network hosts functions by moving them to
a shared
- log production and development urls
2026-03-11 15:45:11 +03:00
Haileyesus
0049ff51ee fix: use src hostname for redirecting to Vite in development
Previously, the server redirected to Vite using `localhost` as the hostname.
Even if the user was using HOST="0.0.0.0", if they connected to server from
another device on the same network using `http://<host_ip>:3001`, the
server would redirect them to `http://localhost:5173`, which would not
work since `localhost` would resolve to the client's machine instead of the server.
2026-03-11 14:35:44 +03:00
Haileyesus
6fa552f157 fix: remove --host from npm run server command
Running `vite --host` exposes the dev server on all interfaces. However,
we should expose it on all interfaces only when `HOST` is set to `0.0.0.0`.
Otherwise, we should assume the user wants to bind to a host of their choice
and not expose the server on the network.
2026-03-11 13:55:28 +03:00
10 changed files with 68 additions and 33 deletions

View File

@@ -17,7 +17,7 @@
# Backend server port (Express API + WebSocket server) # Backend server port (Express API + WebSocket server)
#API server #API server
PORT=3001 SERVER_PORT=3001
#Frontend port #Frontend port
VITE_PORT=5173 VITE_PORT=5173

View File

@@ -70,7 +70,7 @@
npx @siteboon/claude-code-ui npx @siteboon/claude-code-ui
``` ```
サーバーが起動し、`http://localhost:3001`(または設定した PORTでアクセスできます。 サーバーが起動し、`http://localhost:3001`(または設定した SERVER_PORTでアクセスできます。
**再起動**: サーバーを停止した後、同じ `npx` コマンドを再度実行するだけです **再起動**: サーバーを停止した後、同じ `npx` コマンドを再度実行するだけです
### グローバルインストール(定期的に使用する場合) ### グローバルインストール(定期的に使用する場合)

View File

@@ -70,7 +70,7 @@
npx @siteboon/claude-code-ui npx @siteboon/claude-code-ui
``` ```
서버가 시작되면 `http://localhost:3001` (또는 설정한 PORT)에서 접근할 수 있습니다. 서버가 시작되면 `http://localhost:3001` (또는 설정한 SERVER_PORT)에서 접근할 수 있습니다.
**재시작**: 서버를 중지한 후 동일한 `npx` 명령을 다시 실행하면 됩니다 **재시작**: 서버를 중지한 후 동일한 `npx` 명령을 다시 실행하면 됩니다
### 전역 설치 (정기적 사용 시) ### 전역 설치 (정기적 사용 시)

View File

@@ -70,7 +70,7 @@
npx @siteboon/claude-code-ui npx @siteboon/claude-code-ui
``` ```
服务器将启动并可通过 `http://localhost:3001`(或您配置的 PORT)访问。 服务器将启动并可通过 `http://localhost:3001`(或您配置的 SERVER_PORT)访问。
**重启**: 停止服务器后只需再次运行相同的 `npx` 命令 **重启**: 停止服务器后只需再次运行相同的 `npx` 命令

View File

@@ -26,7 +26,7 @@
"scripts": { "scripts": {
"dev": "concurrently --kill-others \"npm run server\" \"npm run client\"", "dev": "concurrently --kill-others \"npm run server\" \"npm run client\"",
"server": "node server/index.js", "server": "node server/index.js",
"client": "vite --host", "client": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"typecheck": "tsc --noEmit -p tsconfig.json", "typecheck": "tsc --noEmit -p tsconfig.json",

View File

@@ -110,7 +110,7 @@ function showStatus() {
// Environment variables // Environment variables
console.log(`\n${c.info('[INFO]')} Configuration:`); console.log(`\n${c.info('[INFO]')} Configuration:`);
console.log(` PORT: ${c.bright(process.env.PORT || '3001')} ${c.dim(process.env.PORT ? '' : '(default)')}`); console.log(` SERVER_PORT: ${c.bright(process.env.SERVER_PORT || process.env.PORT || '3001')} ${c.dim(process.env.SERVER_PORT || process.env.PORT ? '' : '(default)')}`);
console.log(` DATABASE_PATH: ${c.dim(process.env.DATABASE_PATH || '(using default location)')}`); console.log(` DATABASE_PATH: ${c.dim(process.env.DATABASE_PATH || '(using default location)')}`);
console.log(` CLAUDE_CLI_PATH: ${c.dim(process.env.CLAUDE_CLI_PATH || 'claude (default)')}`); console.log(` CLAUDE_CLI_PATH: ${c.dim(process.env.CLAUDE_CLI_PATH || 'claude (default)')}`);
console.log(` CONTEXT_WINDOW: ${c.dim(process.env.CONTEXT_WINDOW || '160000 (default)')}`); console.log(` CONTEXT_WINDOW: ${c.dim(process.env.CONTEXT_WINDOW || '160000 (default)')}`);
@@ -134,7 +134,7 @@ function showStatus() {
console.log(` ${c.dim('>')} Use ${c.bright('cloudcli --port 8080')} to run on a custom port`); console.log(` ${c.dim('>')} Use ${c.bright('cloudcli --port 8080')} to run on a custom port`);
console.log(` ${c.dim('>')} Use ${c.bright('cloudcli --database-path /path/to/db')} for custom database`); console.log(` ${c.dim('>')} Use ${c.bright('cloudcli --database-path /path/to/db')} for custom database`);
console.log(` ${c.dim('>')} Run ${c.bright('cloudcli help')} for all options`); console.log(` ${c.dim('>')} Run ${c.bright('cloudcli help')} for all options`);
console.log(` ${c.dim('>')} Access the UI at http://localhost:${process.env.PORT || '3001'}\n`); console.log(` ${c.dim('>')} Access the UI at http://localhost:${process.env.SERVER_PORT || process.env.PORT || '3001'}\n`);
} }
// Show help // Show help
@@ -169,7 +169,8 @@ Examples:
$ cloudcli status # Show configuration $ cloudcli status # Show configuration
Environment Variables: Environment Variables:
PORT Set server port (default: 3001) SERVER_PORT Set server port (default: 3001)
PORT Set server port (default: 3001) (LEGACY)
DATABASE_PATH Set custom database location DATABASE_PATH Set custom database location
CLAUDE_CLI_PATH Set custom Claude CLI path CLAUDE_CLI_PATH Set custom Claude CLI path
CONTEXT_WINDOW Set context window size (default: 160000) CONTEXT_WINDOW Set context window size (default: 160000)
@@ -260,9 +261,9 @@ function parseArgs(args) {
const arg = args[i]; const arg = args[i];
if (arg === '--port' || arg === '-p') { if (arg === '--port' || arg === '-p') {
parsed.options.port = args[++i]; parsed.options.serverPort = args[++i];
} else if (arg.startsWith('--port=')) { } else if (arg.startsWith('--port=')) {
parsed.options.port = arg.split('=')[1]; parsed.options.serverPort = arg.split('=')[1];
} else if (arg === '--database-path') { } else if (arg === '--database-path') {
parsed.options.databasePath = args[++i]; parsed.options.databasePath = args[++i];
} else if (arg.startsWith('--database-path=')) { } else if (arg.startsWith('--database-path=')) {
@@ -285,8 +286,10 @@ async function main() {
const { command, options } = parseArgs(args); const { command, options } = parseArgs(args);
// Apply CLI options to environment variables // Apply CLI options to environment variables
if (options.port) { if (options.serverPort) {
process.env.PORT = options.port; process.env.SERVER_PORT = options.serverPort;
} else if (!process.env.SERVER_PORT && process.env.PORT) {
process.env.SERVER_PORT = process.env.PORT;
} }
if (options.databasePath) { if (options.databasePath) {
process.env.DATABASE_PATH = options.databasePath; process.env.DATABASE_PATH = options.databasePath;

View File

@@ -31,7 +31,7 @@ const c = {
dim: (text) => `${colors.dim}${text}${colors.reset}`, dim: (text) => `${colors.dim}${text}${colors.reset}`,
}; };
console.log('PORT from env:', process.env.PORT); console.log('SERVER_PORT from env:', process.env.SERVER_PORT);
import express from 'express'; import express from 'express';
import { WebSocketServer, WebSocket } from 'ws'; import { WebSocketServer, WebSocket } from 'ws';
@@ -69,6 +69,7 @@ import { startEnabledPluginServers, stopAllPlugins } from './utils/plugin-proces
import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js'; import { initializeDatabase, sessionNamesDb, applyCustomSessionNames } from './database/db.js';
import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js'; import { validateApiKey, authenticateToken, authenticateWebSocket } from './middleware/auth.js';
import { IS_PLATFORM } from './constants/config.js'; import { IS_PLATFORM } from './constants/config.js';
import { getConnectableHost } from '../shared/networkHosts.js';
const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini']; const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini'];
@@ -2401,7 +2402,8 @@ app.get('*', (req, res) => {
res.sendFile(indexPath); res.sendFile(indexPath);
} else { } else {
// In development, redirect to Vite dev server only if dist doesn't exist // In development, redirect to Vite dev server only if dist doesn't exist
res.redirect(`http://localhost:${process.env.VITE_PORT || 5173}`); const redirectHost = getConnectableHost(req.hostname);
res.redirect(`${req.protocol}://${redirectHost}:${VITE_PORT}`);
} }
}); });
@@ -2489,10 +2491,10 @@ async function getFileTree(dirPath, maxDepth = 3, currentDepth = 0, showHidden =
}); });
} }
const PORT = process.env.PORT || 3001; const SERVER_PORT = process.env.SERVER_PORT || 3001;
const HOST = process.env.HOST || '0.0.0.0'; const HOST = process.env.HOST || '0.0.0.0';
// Show localhost in URL when binding to all interfaces (0.0.0.0 isn't a connectable address) const DISPLAY_HOST = getConnectableHost(HOST);
const DISPLAY_HOST = HOST === '0.0.0.0' ? 'localhost' : HOST; const VITE_PORT = process.env.VITE_PORT || 5173;
// Initialize database and start server // Initialize database and start server
async function startServer() { async function startServer() {
@@ -2506,13 +2508,15 @@ async function startServer() {
// Log Claude implementation mode // Log Claude implementation mode
console.log(`${c.info('[INFO]')} Using Claude Agents SDK for Claude integration`); console.log(`${c.info('[INFO]')} Using Claude Agents SDK for Claude integration`);
console.log(`${c.info('[INFO]')} Running in ${c.bright(isProduction ? 'PRODUCTION' : 'DEVELOPMENT')} mode`); console.log('');
if (!isProduction) { if (isProduction) {
console.log(`${c.warn('[WARN]')} Note: Requests will be proxied to Vite dev server at ${c.dim('http://localhost:' + (process.env.VITE_PORT || 5173))}`); console.log(`${c.info('[INFO]')} To run in production mode, go to http://${DISPLAY_HOST}:${SERVER_PORT}`);
} }
server.listen(PORT, HOST, async () => { console.log(`${c.info('[INFO]')} To run in development mode with hot-module replacement, go to http://${DISPLAY_HOST}:${VITE_PORT}`);
server.listen(SERVER_PORT, HOST, async () => {
const appInstallPath = path.join(__dirname, '..'); const appInstallPath = path.join(__dirname, '..');
console.log(''); console.log('');
@@ -2520,7 +2524,7 @@ async function startServer() {
console.log(` ${c.bright('Claude Code UI Server - Ready')}`); console.log(` ${c.bright('Claude Code UI Server - Ready')}`);
console.log(c.dim('═'.repeat(63))); console.log(c.dim('═'.repeat(63)));
console.log(''); console.log('');
console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://' + DISPLAY_HOST + ':' + PORT)}`); console.log(`${c.info('[INFO]')} Server URL: ${c.bright('http://' + DISPLAY_HOST + ':' + SERVER_PORT)}`);
console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`); console.log(`${c.info('[INFO]')} Installed at: ${c.dim(appInstallPath)}`);
console.log(`${c.tip('[TIP]')} Run "cloudcli status" for full configuration details`); console.log(`${c.tip('[TIP]')} Run "cloudcli status" for full configuration details`);
console.log(''); console.log('');

View File

@@ -529,7 +529,7 @@ router.get('/next/:projectName', async (req, res) => {
// Fallback to loading tasks and finding next one locally // Fallback to loading tasks and finding next one locally
// Use localhost to bypass proxy for internal server-to-server calls // Use localhost to bypass proxy for internal server-to-server calls
const tasksResponse = await fetch(`http://localhost:${process.env.PORT || 3001}/api/taskmaster/tasks/${encodeURIComponent(projectName)}`, { const tasksResponse = await fetch(`http://localhost:${process.env.SERVER_PORT || process.env.PORT || '3001'}/api/taskmaster/tasks/${encodeURIComponent(projectName)}`, {
headers: { headers: {
'Authorization': req.headers.authorization 'Authorization': req.headers.authorization
} }

22
shared/networkHosts.js Normal file
View File

@@ -0,0 +1,22 @@
export function isWildcardHost(host) {
return host === '0.0.0.0' || host === '::';
}
export function isLoopbackHost(host) {
return host === 'localhost' || host === '127.0.0.1' || host === '::1' || host === '[::1]';
}
export function normalizeLoopbackHost(host) {
if (!host) {
return host;
}
return isLoopbackHost(host) ? 'localhost' : host;
}
// Use localhost for connectable loopback and wildcard addresses in browser-facing URLs.
export function getConnectableHost(host) {
if (!host) {
return 'localhost';
}
return isWildcardHost(host) || isLoopbackHost(host) ? 'localhost' : host;
}

View File

@@ -1,15 +1,21 @@
import { defineConfig, loadEnv } from 'vite' import { defineConfig, loadEnv } from 'vite'
import react from '@vitejs/plugin-react' import react from '@vitejs/plugin-react'
import { getConnectableHost, normalizeLoopbackHost } from './shared/networkHosts.js'
export default defineConfig(({ command, mode }) => { export default defineConfig(({ mode }) => {
// Load env file based on `mode` in the current working directory. // Load env file based on `mode` in the current working directory.
const env = loadEnv(mode, process.cwd(), '') const env = loadEnv(mode, process.cwd(), '')
const host = env.HOST || '0.0.0.0' const configuredHost = env.HOST || '0.0.0.0'
// When binding to all interfaces (0.0.0.0), proxy should connect to localhost // if the host is not a loopback address, it should be used directly.
// Otherwise, proxy to the specific host the backend is bound to // This allows the vite server to EXPOSE all interfaces when the host
const proxyHost = host === '0.0.0.0' ? 'localhost' : host // is set to '0.0.0.0' or '::', while still using 'localhost' for browser
const port = env.PORT || 3001 // URLs and proxy targets.
const host = normalizeLoopbackHost(configuredHost)
const proxyHost = getConnectableHost(configuredHost)
// TODO: Remove support for legacy PORT variables in all locations in a future major release, leaving only SERVER_PORT.
const serverPort = env.SERVER_PORT || env.PORT || 3001
return { return {
plugins: [react()], plugins: [react()],
@@ -17,13 +23,13 @@ export default defineConfig(({ command, mode }) => {
host, host,
port: parseInt(env.VITE_PORT) || 5173, port: parseInt(env.VITE_PORT) || 5173,
proxy: { proxy: {
'/api': `http://${proxyHost}:${port}`, '/api': `http://${proxyHost}:${serverPort}`,
'/ws': { '/ws': {
target: `ws://${proxyHost}:${port}`, target: `ws://${proxyHost}:${serverPort}`,
ws: true ws: true
}, },
'/shell': { '/shell': {
target: `ws://${proxyHost}:${port}`, target: `ws://${proxyHost}:${serverPort}`,
ws: true ws: true
} }
} }