mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
fix(cliproxy): respect configured local port instead of hardcoding 8317
All call sites that spawn or probe CLIProxyApiPlus now read cliproxy_server.local.port from config via resolveLifecyclePort() instead of using the hardcoded CLIPROXY_DEFAULT_PORT constant. - Move resolveLifecyclePort helper to src/cliproxy/config/port-manager.ts - Fix 7 call sites: ccs.ts, config-command.ts, copilot-executor.ts, lifecycle.ts, binary-manager.ts, cliproxy-stats-routes.ts, cliproxy-local-proxy.ts - Remove duplicate resolveLocalCliproxyPort helper - Cache port resolution in /proxy-status handler to avoid repeated I/O Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -22,7 +22,7 @@ import {
|
|||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
} from './cliproxy';
|
} from './cliproxy';
|
||||||
import { getEffectiveEnvVars, getCompositeEnvVars } from './cliproxy/config/env-builder';
|
import { getEffectiveEnvVars, getCompositeEnvVars } from './cliproxy/config/env-builder';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from './cliproxy/config/port-manager';
|
import { resolveLifecyclePort } from './cliproxy/config/port-manager';
|
||||||
import {
|
import {
|
||||||
ensureWebSearchMcpOrThrow,
|
ensureWebSearchMcpOrThrow,
|
||||||
displayWebSearchStatus,
|
displayWebSearchStatus,
|
||||||
@@ -929,7 +929,7 @@ async function main(): Promise<void> {
|
|||||||
}
|
}
|
||||||
const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles
|
const customSettingsPath = profileInfo.settingsPath; // undefined for hardcoded profiles
|
||||||
const variantPort = profileInfo.port; // variant-specific port for isolation
|
const variantPort = profileInfo.port; // variant-specific port for isolation
|
||||||
const cliproxyPort = variantPort || CLIPROXY_DEFAULT_PORT;
|
const cliproxyPort = variantPort || resolveLifecyclePort();
|
||||||
|
|
||||||
if (resolvedTarget !== 'claude') {
|
if (resolvedTarget !== 'claude') {
|
||||||
const adapter = targetAdapter;
|
const adapter = targetAdapter;
|
||||||
@@ -1385,7 +1385,7 @@ async function main(): Promise<void> {
|
|||||||
};
|
};
|
||||||
} else if (imageAnalysisStatus.proxyReadiness === 'stopped') {
|
} else if (imageAnalysisStatus.proxyReadiness === 'stopped') {
|
||||||
const ensureServiceResult = await ensureCliproxyService(
|
const ensureServiceResult = await ensureCliproxyService(
|
||||||
CLIPROXY_DEFAULT_PORT,
|
resolveLifecyclePort(),
|
||||||
verboseProxyLaunch
|
verboseProxyLaunch
|
||||||
);
|
);
|
||||||
if (!ensureServiceResult.started) {
|
if (!ensureServiceResult.started) {
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { info, warn } from '../utils/ui';
|
import { info, warn } from '../utils/ui';
|
||||||
import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config/config-generator';
|
import { getBinDir } from './config/config-generator';
|
||||||
|
import { resolveLifecyclePort } from './config/port-manager';
|
||||||
import { BinaryInfo, BinaryManagerConfig } from './types';
|
import { BinaryInfo, BinaryManagerConfig } from './types';
|
||||||
import {
|
import {
|
||||||
BACKEND_CONFIG,
|
BACKEND_CONFIG,
|
||||||
@@ -340,7 +341,7 @@ export async function installCliproxyVersion(
|
|||||||
const result = await stopProxyFn();
|
const result = await stopProxyFn();
|
||||||
if (result.stopped) {
|
if (result.stopped) {
|
||||||
// Wait for port to be fully released
|
// Wait for port to be fully released
|
||||||
const portFree = await waitForPortFreeFn(CLIPROXY_DEFAULT_PORT, 5000);
|
const portFree = await waitForPortFreeFn(resolveLifecyclePort(), 5000);
|
||||||
if (!portFree && verbose) {
|
if (!portFree && verbose) {
|
||||||
console.log(formatWarn('Port did not free up in time, proceeding anyway...'));
|
console.log(formatWarn('Port did not free up in time, proceeding anyway...'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import {
|
|||||||
import { downloadAndInstall, deleteBinary, getBinaryPath } from './installer';
|
import { downloadAndInstall, deleteBinary, getBinaryPath } from './installer';
|
||||||
import { info, warn } from '../../utils/ui';
|
import { info, warn } from '../../utils/ui';
|
||||||
import { isCliproxyRunning } from '../services/stats-fetcher';
|
import { isCliproxyRunning } from '../services/stats-fetcher';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from '../config/config-generator';
|
import { resolveLifecyclePort } from '../config/port-manager';
|
||||||
import {
|
import {
|
||||||
CLIPROXY_MAX_STABLE_VERSION,
|
CLIPROXY_MAX_STABLE_VERSION,
|
||||||
CLIPROXY_FAULTY_RANGE,
|
CLIPROXY_FAULTY_RANGE,
|
||||||
@@ -82,7 +82,7 @@ async function handleAutoUpdate(config: BinaryManagerConfig, verbose: boolean):
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyRunning = await isCliproxyRunning(CLIPROXY_DEFAULT_PORT);
|
const proxyRunning = await isCliproxyRunning(resolveLifecyclePort());
|
||||||
const latestNote = isAboveMaxStable(latestVersion) ? ` (latest v${latestVersion} unstable)` : '';
|
const latestNote = isAboveMaxStable(latestVersion) ? ` (latest v${latestVersion} unstable)` : '';
|
||||||
const updateMsg = `${backendLabel} update: v${currentVersion} -> v${targetVersion}${latestNote}`;
|
const updateMsg = `${backendLabel} update: v${currentVersion} -> v${targetVersion}${latestNote}`;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
* Handles port number validation and default port resolution
|
* Handles port number validation and default port resolution
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
||||||
|
import type { UnifiedConfig } from '../../config/unified-config-types';
|
||||||
|
|
||||||
/** Default CLIProxy port */
|
/** Default CLIProxy port */
|
||||||
export const CLIPROXY_DEFAULT_PORT = 8317;
|
export const CLIPROXY_DEFAULT_PORT = 8317;
|
||||||
|
|
||||||
@@ -57,3 +60,13 @@ export function normalizeProtocol(protocol: string | undefined): 'http' | 'https
|
|||||||
// Invalid protocol (e.g., 'ftp') - default to http
|
// Invalid protocol (e.g., 'ftp') - default to http
|
||||||
return 'http';
|
return 'http';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the local CLIProxy lifecycle port from unified config.
|
||||||
|
* Falls back to default port when unset/invalid.
|
||||||
|
*/
|
||||||
|
export function resolveLifecyclePort(
|
||||||
|
config: Pick<UnifiedConfig, 'cliproxy_server'> = loadOrCreateUnifiedConfig()
|
||||||
|
): number {
|
||||||
|
return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import { initUI, header, color, dim, ok, warn, info } from '../../utils/ui';
|
import { initUI, header, color, dim, ok, warn, info } from '../../utils/ui';
|
||||||
import { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services';
|
import { getProxyStatus, startProxy, stopProxy } from '../../cliproxy/services';
|
||||||
import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector';
|
import { detectRunningProxy } from '../../cliproxy/proxy/proxy-detector';
|
||||||
import { resolveLifecyclePort } from './resolve-lifecycle-port';
|
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
|
||||||
|
|
||||||
export async function handleStart(verbose = false): Promise<void> {
|
export async function handleStart(verbose = false): Promise<void> {
|
||||||
await initUI();
|
await initUI();
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { CLIPROXY_DEFAULT_PORT, validatePort } from '../../cliproxy/config/port-manager';
|
|
||||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
|
||||||
import type { UnifiedConfig } from '../../config/unified-config-types';
|
|
||||||
|
|
||||||
type LifecyclePortConfig = Pick<UnifiedConfig, 'cliproxy_server'>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the local CLIProxy lifecycle port from unified config.
|
|
||||||
* Falls back to default port when unset/invalid.
|
|
||||||
*/
|
|
||||||
export function resolveLifecyclePort(
|
|
||||||
config: LifecyclePortConfig = loadOrCreateUnifiedConfig()
|
|
||||||
): number {
|
|
||||||
return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT);
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import open from 'open';
|
|||||||
import { startServer } from '../web-server';
|
import { startServer } from '../web-server';
|
||||||
import { setupGracefulShutdown } from '../web-server/shutdown';
|
import { setupGracefulShutdown } from '../web-server/shutdown';
|
||||||
import { ensureCliproxyService } from '../cliproxy/service-manager';
|
import { ensureCliproxyService } from '../cliproxy/service-manager';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/config-generator';
|
import { resolveLifecyclePort } from '../cliproxy/config/port-manager';
|
||||||
import { getDashboardAuthConfig } from '../config/unified-config-loader';
|
import { getDashboardAuthConfig } from '../config/unified-config-loader';
|
||||||
import { initUI, header, ok, info, warn, fail } from '../utils/ui';
|
import { initUI, header, ok, info, warn, fail } from '../utils/ui';
|
||||||
import { resolveNamedCommand, type NamedCommandRoute } from './named-command-router';
|
import { resolveNamedCommand, type NamedCommandRoute } from './named-command-router';
|
||||||
@@ -137,7 +137,7 @@ export async function handleConfigCommand(
|
|||||||
|
|
||||||
// Ensure CLIProxy service is running for dashboard features
|
// Ensure CLIProxy service is running for dashboard features
|
||||||
console.log(deps.info('Starting CLIProxy service...'));
|
console.log(deps.info('Starting CLIProxy service...'));
|
||||||
const cliproxyResult = await deps.ensureCliproxyService(CLIPROXY_DEFAULT_PORT, verbose);
|
const cliproxyResult = await deps.ensureCliproxyService(resolveLifecyclePort(), verbose);
|
||||||
logger.info('cliproxy.ensure_result', 'Config command checked CLIProxy availability', {
|
logger.info('cliproxy.ensure_result', 'Config command checked CLIProxy availability', {
|
||||||
started: cliproxyResult.started,
|
started: cliproxyResult.started,
|
||||||
alreadyRunning: cliproxyResult.alreadyRunning,
|
alreadyRunning: cliproxyResult.alreadyRunning,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { CopilotConfig } from '../config/unified-config-types';
|
|||||||
import { getGlobalEnvConfig } from '../config/unified-config-loader';
|
import { getGlobalEnvConfig } from '../config/unified-config-loader';
|
||||||
import { ensureCliproxyService } from '../cliproxy';
|
import { ensureCliproxyService } from '../cliproxy';
|
||||||
import { getEffectiveApiKey } from '../cliproxy/auth/auth-token-manager';
|
import { getEffectiveApiKey } from '../cliproxy/auth/auth-token-manager';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from '../cliproxy/config/port-manager';
|
import { resolveLifecyclePort } from '../cliproxy/config/port-manager';
|
||||||
import { checkAuthStatus, isCopilotApiInstalled } from './copilot-auth';
|
import { checkAuthStatus, isCopilotApiInstalled } from './copilot-auth';
|
||||||
import { isDaemonRunning, startDaemon } from './copilot-daemon';
|
import { isDaemonRunning, startDaemon } from './copilot-daemon';
|
||||||
import { ensureCopilotApi } from './copilot-package-manager';
|
import { ensureCopilotApi } from './copilot-package-manager';
|
||||||
@@ -142,7 +142,7 @@ export async function resolveCopilotImageAnalysisEnv(
|
|||||||
|
|
||||||
if (status.proxyReadiness === 'stopped') {
|
if (status.proxyReadiness === 'stopped') {
|
||||||
const ensureServiceResult = await resolvedDeps.ensureCliproxyService(
|
const ensureServiceResult = await resolvedDeps.ensureCliproxyService(
|
||||||
CLIPROXY_DEFAULT_PORT,
|
resolveLifecyclePort(),
|
||||||
verbose
|
verbose
|
||||||
);
|
);
|
||||||
if (!ensureServiceResult.started) {
|
if (!ensureServiceResult.started) {
|
||||||
|
|||||||
@@ -9,8 +9,7 @@
|
|||||||
|
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { Request, Response, Router } from 'express';
|
import { Request, Response, Router } from 'express';
|
||||||
import { CLIPROXY_DEFAULT_PORT, validatePort } from '../../cliproxy/config/port-manager';
|
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
|
||||||
import { loadOrCreateUnifiedConfig } from '../../config/unified-config-loader';
|
|
||||||
import { requireLocalAccessWhenAuthDisabled } from '../middleware/auth-middleware';
|
import { requireLocalAccessWhenAuthDisabled } from '../middleware/auth-middleware';
|
||||||
|
|
||||||
export interface CliproxyLocalProxyDeps {
|
export interface CliproxyLocalProxyDeps {
|
||||||
@@ -22,15 +21,6 @@ export interface CliproxyLocalProxyDeps {
|
|||||||
/** Proxy request timeout in milliseconds (30 seconds) */
|
/** Proxy request timeout in milliseconds (30 seconds) */
|
||||||
const PROXY_TIMEOUT_MS = 30_000;
|
const PROXY_TIMEOUT_MS = 30_000;
|
||||||
|
|
||||||
function resolveLocalCliproxyPort(): number {
|
|
||||||
try {
|
|
||||||
const config = loadOrCreateUnifiedConfig();
|
|
||||||
return validatePort(config.cliproxy_server?.local?.port ?? CLIPROXY_DEFAULT_PORT);
|
|
||||||
} catch {
|
|
||||||
return CLIPROXY_DEFAULT_PORT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isJsonContentType(contentType: string | string[] | undefined): boolean {
|
function isJsonContentType(contentType: string | string[] | undefined): boolean {
|
||||||
const values = Array.isArray(contentType) ? contentType : [contentType];
|
const values = Array.isArray(contentType) ? contentType : [contentType];
|
||||||
return values.some((value) => value?.toLowerCase().includes('application/json') === true);
|
return values.some((value) => value?.toLowerCase().includes('application/json') === true);
|
||||||
@@ -82,7 +72,7 @@ export function createCliproxyLocalProxyRouter(deps: CliproxyLocalProxyDeps = {}
|
|||||||
'CLIProxy local proxy requires localhost access when dashboard auth is disabled.'
|
'CLIProxy local proxy requires localhost access when dashboard auth is disabled.'
|
||||||
));
|
));
|
||||||
const createRequest = deps.request ?? http.request;
|
const createRequest = deps.request ?? http.request;
|
||||||
const resolveTargetPort = deps.resolveTargetPort ?? resolveLocalCliproxyPort;
|
const resolveTargetPort = deps.resolveTargetPort ?? resolveLifecyclePort;
|
||||||
|
|
||||||
router.use((req: Request, res: Response, next) => {
|
router.use((req: Request, res: Response, next) => {
|
||||||
if (enforceAccess(req, res)) {
|
if (enforceAccess(req, res)) {
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ import {
|
|||||||
CLIPROXY_MAX_STABLE_VERSION,
|
CLIPROXY_MAX_STABLE_VERSION,
|
||||||
CLIPROXY_FAULTY_RANGE,
|
CLIPROXY_FAULTY_RANGE,
|
||||||
} from '../../cliproxy/binary/platform-detector';
|
} from '../../cliproxy/binary/platform-detector';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from '../../cliproxy/config/port-manager';
|
import { resolveLifecyclePort } from '../../cliproxy/config/port-manager';
|
||||||
import {
|
import {
|
||||||
MODEL_ENV_VAR_KEYS,
|
MODEL_ENV_VAR_KEYS,
|
||||||
canonicalizeModelIdForProvider,
|
canonicalizeModelIdForProvider,
|
||||||
@@ -321,7 +321,7 @@ router.get('/usage', handleStatsRequest);
|
|||||||
*/
|
*/
|
||||||
router.get('/status', async (_req: Request, res: Response): Promise<void> => {
|
router.get('/status', async (_req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const running = await isCliproxyRunning();
|
const running = await isCliproxyRunning(resolveLifecyclePort());
|
||||||
res.json({ running });
|
res.json({ running });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
||||||
@@ -345,15 +345,16 @@ router.get('/proxy-status', async (_req: Request, res: Response): Promise<void>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const port = resolveLifecyclePort();
|
||||||
// Session tracker says not running, but proxy might be running without session tracking
|
// Session tracker says not running, but proxy might be running without session tracking
|
||||||
// (e.g., started before session persistence was implemented)
|
// (e.g., started before session persistence was implemented)
|
||||||
const actuallyRunning = await isCliproxyRunning();
|
const actuallyRunning = await isCliproxyRunning(port);
|
||||||
|
|
||||||
if (actuallyRunning) {
|
if (actuallyRunning) {
|
||||||
// Proxy running but no session lock - legacy/untracked instance
|
// Proxy running but no session lock - legacy/untracked instance
|
||||||
res.json({
|
res.json({
|
||||||
running: true,
|
running: true,
|
||||||
port: CLIPROXY_DEFAULT_PORT,
|
port,
|
||||||
sessionCount: 0, // Unknown sessions
|
sessionCount: 0, // Unknown sessions
|
||||||
// No pid/startedAt since we don't have session lock
|
// No pid/startedAt since we don't have session lock
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { describe, expect, it } from 'bun:test';
|
import { describe, expect, it } from 'bun:test';
|
||||||
import { CLIPROXY_DEFAULT_PORT } from '../../../src/cliproxy/config/port-manager';
|
import { CLIPROXY_DEFAULT_PORT, resolveLifecyclePort } from '../../../src/cliproxy/config/port-manager';
|
||||||
import { resolveLifecyclePort } from '../../../src/commands/cliproxy/resolve-lifecycle-port';
|
|
||||||
|
|
||||||
describe('resolveLifecyclePort', () => {
|
describe('resolveLifecyclePort', () => {
|
||||||
it('uses configured cliproxy_server.local.port', () => {
|
it('uses configured cliproxy_server.local.port', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user