mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 10:16:49 +00:00
fix: stop untracked CLIProxy installs safely
This commit is contained in:
@@ -9,7 +9,7 @@ import { info, warn } from '../utils/ui';
|
||||
import { getBinDir, CLIPROXY_DEFAULT_PORT } from './config-generator';
|
||||
import { BinaryInfo, BinaryManagerConfig } from './types';
|
||||
import { BACKEND_CONFIG, DEFAULT_BACKEND, CLIPROXY_MAX_STABLE_VERSION } from './platform-detector';
|
||||
import { isProxyRunning, stopProxy } from './services/proxy-lifecycle-service';
|
||||
import { stopProxy } from './services/proxy-lifecycle-service';
|
||||
import { waitForPortFree } from '../utils/port-utils';
|
||||
import { loadOrCreateUnifiedConfig } from '../config/unified-config-loader';
|
||||
import {
|
||||
@@ -167,19 +167,18 @@ export async function installCliproxyVersion(
|
||||
const effectiveBackend = backend ?? getConfiguredBackend();
|
||||
const manager = new BinaryManager({ version, verbose, forceVersion: true }, effectiveBackend);
|
||||
|
||||
// Check if proxy is running and stop it first
|
||||
if (isProxyRunning()) {
|
||||
if (verbose) console.log(info('Stopping running CLIProxy before update...'));
|
||||
const result = await stopProxy();
|
||||
if (result.stopped) {
|
||||
// Wait for port to be fully released
|
||||
const portFree = await waitForPortFree(CLIPROXY_DEFAULT_PORT, 5000);
|
||||
if (!portFree && verbose) {
|
||||
console.log(warn('Port did not free up in time, proceeding anyway...'));
|
||||
}
|
||||
} else if (verbose && result.error) {
|
||||
console.log(warn(`Could not stop proxy: ${result.error}`));
|
||||
// Always attempt a best-effort stop first so we also catch untracked proxies
|
||||
// that are running without a session lock.
|
||||
if (verbose) console.log(info('Stopping running CLIProxy before update...'));
|
||||
const result = await stopProxy();
|
||||
if (result.stopped) {
|
||||
// Wait for port to be fully released
|
||||
const portFree = await waitForPortFree(CLIPROXY_DEFAULT_PORT, 5000);
|
||||
if (!portFree && verbose) {
|
||||
console.log(warn('Port did not free up in time, proceeding anyway...'));
|
||||
}
|
||||
} else if (verbose && result.error && result.error !== 'No active CLIProxy session found') {
|
||||
console.log(warn(`Could not stop proxy: ${result.error}`));
|
||||
}
|
||||
|
||||
if (manager.isBinaryInstalled()) {
|
||||
|
||||
@@ -51,8 +51,8 @@ export async function installDashboardCliproxyVersion(
|
||||
const backendLabel = backend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
|
||||
const shouldRestoreService = await wasProxyRunning(deps);
|
||||
|
||||
// The installer owns the stop-and-replace lifecycle: it stops a running proxy
|
||||
// and waits for the port to free before swapping the binary.
|
||||
// The installer owns the stop-and-replace lifecycle, including best-effort
|
||||
// shutdown for tracked and untracked proxies before swapping the binary.
|
||||
await deps.installCliproxyVersion(version, true, backend);
|
||||
|
||||
if (!shouldRestoreService) {
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { afterAll, beforeAll, describe, expect, it, mock } from 'bun:test';
|
||||
|
||||
const calls = {
|
||||
stopProxy: 0,
|
||||
waitForPortFree: 0,
|
||||
deleteBinary: 0,
|
||||
ensureBinary: 0,
|
||||
};
|
||||
|
||||
mock.module('../../../src/utils/ui', () => ({
|
||||
info: (message: string) => message,
|
||||
warn: (message: string) => message,
|
||||
}));
|
||||
|
||||
mock.module('../../../src/cliproxy/config-generator', () => ({
|
||||
getBinDir: () => '/tmp/ccs-bin',
|
||||
CLIPROXY_DEFAULT_PORT: 8317,
|
||||
}));
|
||||
|
||||
mock.module('../../../src/cliproxy/platform-detector', () => ({
|
||||
DEFAULT_BACKEND: 'plus',
|
||||
CLIPROXY_MAX_STABLE_VERSION: '6.6.80',
|
||||
BACKEND_CONFIG: {
|
||||
plus: {
|
||||
fallbackVersion: '6.6.80',
|
||||
repo: 'router-for-me/CLIProxyAPIPlus',
|
||||
},
|
||||
original: {
|
||||
fallbackVersion: '0.0.0',
|
||||
repo: 'router-for-me/CLIProxyAPI',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
mock.module('../../../src/cliproxy/services/proxy-lifecycle-service', () => ({
|
||||
stopProxy: async () => {
|
||||
calls.stopProxy += 1;
|
||||
return { stopped: false, error: 'No active CLIProxy session found' };
|
||||
},
|
||||
}));
|
||||
|
||||
mock.module('../../../src/utils/port-utils', () => ({
|
||||
waitForPortFree: async () => {
|
||||
calls.waitForPortFree += 1;
|
||||
return true;
|
||||
},
|
||||
}));
|
||||
|
||||
mock.module('../../../src/config/unified-config-loader', () => ({
|
||||
loadOrCreateUnifiedConfig: () => ({
|
||||
cliproxy: { backend: 'plus' },
|
||||
}),
|
||||
}));
|
||||
|
||||
mock.module('../../../src/cliproxy/binary', () => ({
|
||||
checkForUpdates: async () => ({
|
||||
hasUpdate: false,
|
||||
currentVersion: '6.6.80',
|
||||
latestVersion: '6.6.80',
|
||||
fromCache: false,
|
||||
checkedAt: Date.now(),
|
||||
}),
|
||||
deleteBinary: () => {
|
||||
calls.deleteBinary += 1;
|
||||
},
|
||||
getBinaryPath: () => '/tmp/ccs-bin/plus/cliproxy',
|
||||
isBinaryInstalled: () => false,
|
||||
getBinaryInfo: async () => null,
|
||||
getPinnedVersion: () => null,
|
||||
savePinnedVersion: () => {},
|
||||
clearPinnedVersion: () => {},
|
||||
isVersionPinned: () => false,
|
||||
getVersionPinPath: () => '/tmp/ccs-bin/plus/.version-pin',
|
||||
readInstalledVersion: () => '6.6.80',
|
||||
ensureBinary: async () => {
|
||||
calls.ensureBinary += 1;
|
||||
return '/tmp/ccs-bin/plus/cliproxy';
|
||||
},
|
||||
migrateVersionPin: () => {},
|
||||
}));
|
||||
|
||||
let binaryManager: typeof import('../../../src/cliproxy/binary-manager');
|
||||
|
||||
beforeAll(async () => {
|
||||
binaryManager = await import('../../../src/cliproxy/binary-manager');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
describe('installCliproxyVersion', () => {
|
||||
it('attempts to stop the proxy even when there is no tracked running session', async () => {
|
||||
await binaryManager.installCliproxyVersion('6.7.1', false, 'plus');
|
||||
|
||||
expect(calls.stopProxy).toBe(1);
|
||||
expect(calls.waitForPortFree).toBe(0);
|
||||
expect(calls.deleteBinary).toBe(0);
|
||||
expect(calls.ensureBinary).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user