mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 22:21:20 +00:00
fix: restart CLIProxy after dashboard version install
This commit is contained in:
@@ -34,11 +34,7 @@ import {
|
||||
} from '../../cliproxy/config-generator';
|
||||
import { getProxyStatus as getProxyProcessStatus, stopProxy } from '../../cliproxy/session-tracker';
|
||||
import { ensureCliproxyService } from '../../cliproxy/service-manager';
|
||||
import {
|
||||
checkCliproxyUpdate,
|
||||
getInstalledCliproxyVersion,
|
||||
installCliproxyVersion,
|
||||
} from '../../cliproxy/binary-manager';
|
||||
import { checkCliproxyUpdate, getInstalledCliproxyVersion } from '../../cliproxy/binary-manager';
|
||||
import {
|
||||
fetchAllVersions,
|
||||
isNewerVersion,
|
||||
@@ -56,6 +52,7 @@ import {
|
||||
canonicalizeModelIdForProvider,
|
||||
getDeniedModelIdReasonForProvider,
|
||||
} from '../../cliproxy/model-id-normalizer';
|
||||
import { installDashboardCliproxyVersion } from '../services/cliproxy-dashboard-install-service';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@@ -928,7 +925,7 @@ router.get('/versions', async (_req: Request, res: Response): Promise<void> => {
|
||||
/**
|
||||
* POST /api/cliproxy/install - Install specific CLIProxyAPI version
|
||||
* Body: { version: string, force?: boolean }
|
||||
* Returns: { success, requiresConfirmation?, message? }
|
||||
* Returns: { success, restarted?, port?, requiresConfirmation?, message? }
|
||||
*/
|
||||
router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
||||
try {
|
||||
@@ -967,22 +964,14 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop proxy first if running
|
||||
await stopProxy();
|
||||
|
||||
// Small delay to ensure port is released
|
||||
await new Promise((r) => setTimeout(r, 500));
|
||||
|
||||
// Install the version
|
||||
const backend = getConfiguredBackend();
|
||||
await installCliproxyVersion(version, true, backend);
|
||||
const installResult = await installDashboardCliproxyVersion(version, backend);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
version,
|
||||
isFaulty,
|
||||
isExperimental,
|
||||
message: `Successfully installed CLIProxy Plus v${version}`,
|
||||
...installResult,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { installCliproxyVersion } from '../../cliproxy/binary-manager';
|
||||
import { ensureCliproxyService, type ServiceStartResult } from '../../cliproxy/service-manager';
|
||||
import { getProxyStatus as getProxyProcessStatus } from '../../cliproxy/session-tracker';
|
||||
import { isCliproxyRunning } from '../../cliproxy/stats-fetcher';
|
||||
import type { CLIProxyBackend } from '../../cliproxy/types';
|
||||
|
||||
interface ProxyStatusLike {
|
||||
running: boolean;
|
||||
}
|
||||
|
||||
interface InstallDashboardCliproxyVersionDeps {
|
||||
getProxyStatus: () => ProxyStatusLike;
|
||||
isCliproxyRunning: () => Promise<boolean>;
|
||||
installCliproxyVersion: (
|
||||
version: string,
|
||||
verbose?: boolean,
|
||||
backend?: CLIProxyBackend
|
||||
) => Promise<void>;
|
||||
ensureCliproxyService: () => Promise<ServiceStartResult>;
|
||||
}
|
||||
|
||||
const defaultDeps: InstallDashboardCliproxyVersionDeps = {
|
||||
getProxyStatus: getProxyProcessStatus,
|
||||
isCliproxyRunning,
|
||||
installCliproxyVersion,
|
||||
ensureCliproxyService: () => ensureCliproxyService(),
|
||||
};
|
||||
|
||||
export interface DashboardCliproxyInstallResult {
|
||||
success: boolean;
|
||||
restarted: boolean;
|
||||
port?: number;
|
||||
message: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
async function wasProxyRunning(deps: InstallDashboardCliproxyVersionDeps): Promise<boolean> {
|
||||
const status = deps.getProxyStatus();
|
||||
if (status.running) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return deps.isCliproxyRunning();
|
||||
}
|
||||
|
||||
export async function installDashboardCliproxyVersion(
|
||||
version: string,
|
||||
backend: CLIProxyBackend,
|
||||
deps: InstallDashboardCliproxyVersionDeps = defaultDeps
|
||||
): Promise<DashboardCliproxyInstallResult> {
|
||||
const backendLabel = backend === 'plus' ? 'CLIProxy Plus' : 'CLIProxy';
|
||||
const shouldRestoreService = await wasProxyRunning(deps);
|
||||
|
||||
await deps.installCliproxyVersion(version, true, backend);
|
||||
|
||||
if (!shouldRestoreService) {
|
||||
return {
|
||||
success: true,
|
||||
restarted: false,
|
||||
message: `Successfully installed ${backendLabel} v${version}`,
|
||||
};
|
||||
}
|
||||
|
||||
const startResult = await deps.ensureCliproxyService();
|
||||
if (!startResult.started && !startResult.alreadyRunning) {
|
||||
return {
|
||||
success: false,
|
||||
restarted: false,
|
||||
error: startResult.error || `Installed ${backendLabel} v${version}, but restart failed`,
|
||||
message: `Installed ${backendLabel} v${version}, but failed to restart it`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
restarted: true,
|
||||
port: startResult.port,
|
||||
message: `Successfully installed ${backendLabel} v${version} and restarted it on port ${startResult.port}`,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { describe, expect, it } from 'bun:test';
|
||||
import type { CLIProxyBackend } from '../../../src/cliproxy/types';
|
||||
import {
|
||||
installDashboardCliproxyVersion,
|
||||
type DashboardCliproxyInstallResult,
|
||||
} from '../../../src/web-server/services/cliproxy-dashboard-install-service';
|
||||
|
||||
function createDeps(
|
||||
overrides: {
|
||||
sessionRunning?: boolean;
|
||||
remoteRunning?: boolean;
|
||||
startResult?: { started: boolean; alreadyRunning: boolean; port: number; error?: string };
|
||||
} = {}
|
||||
) {
|
||||
const calls = {
|
||||
isCliproxyRunning: 0,
|
||||
installCliproxyVersion: 0,
|
||||
ensureCliproxyService: 0,
|
||||
};
|
||||
|
||||
const deps = {
|
||||
getProxyStatus: () => ({ running: overrides.sessionRunning ?? false }),
|
||||
isCliproxyRunning: async () => {
|
||||
calls.isCliproxyRunning += 1;
|
||||
return overrides.remoteRunning ?? false;
|
||||
},
|
||||
installCliproxyVersion: async (
|
||||
_version: string,
|
||||
_verbose?: boolean,
|
||||
_backend?: CLIProxyBackend
|
||||
) => {
|
||||
calls.installCliproxyVersion += 1;
|
||||
},
|
||||
ensureCliproxyService: async () => {
|
||||
calls.ensureCliproxyService += 1;
|
||||
return (
|
||||
overrides.startResult ?? {
|
||||
started: true,
|
||||
alreadyRunning: false,
|
||||
port: 8317,
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
return { deps, calls };
|
||||
}
|
||||
|
||||
describe('installDashboardCliproxyVersion', () => {
|
||||
it('restarts the proxy after install when it was already running', async () => {
|
||||
const { deps, calls } = createDeps({ sessionRunning: true });
|
||||
|
||||
const result = await installDashboardCliproxyVersion('6.7.1', 'plus', deps);
|
||||
|
||||
expect(result).toEqual<DashboardCliproxyInstallResult>({
|
||||
success: true,
|
||||
restarted: true,
|
||||
port: 8317,
|
||||
message: 'Successfully installed CLIProxy Plus v6.7.1 and restarted it on port 8317',
|
||||
});
|
||||
expect(calls.isCliproxyRunning).toBe(0);
|
||||
expect(calls.installCliproxyVersion).toBe(1);
|
||||
expect(calls.ensureCliproxyService).toBe(1);
|
||||
});
|
||||
|
||||
it('keeps the proxy stopped after install when it was not running beforehand', async () => {
|
||||
const { deps, calls } = createDeps({ sessionRunning: false, remoteRunning: false });
|
||||
|
||||
const result = await installDashboardCliproxyVersion('6.7.1', 'plus', deps);
|
||||
|
||||
expect(result).toEqual<DashboardCliproxyInstallResult>({
|
||||
success: true,
|
||||
restarted: false,
|
||||
message: 'Successfully installed CLIProxy Plus v6.7.1',
|
||||
});
|
||||
expect(calls.isCliproxyRunning).toBe(1);
|
||||
expect(calls.installCliproxyVersion).toBe(1);
|
||||
expect(calls.ensureCliproxyService).toBe(0);
|
||||
});
|
||||
|
||||
it('reports a restart failure after a successful install when the proxy had been running', async () => {
|
||||
const { deps, calls } = createDeps({
|
||||
sessionRunning: false,
|
||||
remoteRunning: true,
|
||||
startResult: {
|
||||
started: false,
|
||||
alreadyRunning: false,
|
||||
port: 8317,
|
||||
error: 'Port 8317 is blocked by another process',
|
||||
},
|
||||
});
|
||||
|
||||
const result = await installDashboardCliproxyVersion('6.7.1', 'original', deps);
|
||||
|
||||
expect(result).toEqual<DashboardCliproxyInstallResult>({
|
||||
success: false,
|
||||
restarted: false,
|
||||
error: 'Port 8317 is blocked by another process',
|
||||
message: 'Installed CLIProxy v6.7.1, but failed to restart it',
|
||||
});
|
||||
expect(calls.isCliproxyRunning).toBe(1);
|
||||
expect(calls.installCliproxyVersion).toBe(1);
|
||||
expect(calls.ensureCliproxyService).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -606,6 +606,8 @@ export interface CliproxyVersionsResponse {
|
||||
export interface CliproxyInstallResult {
|
||||
success: boolean;
|
||||
version?: string;
|
||||
restarted?: boolean;
|
||||
port?: number;
|
||||
isUnstable?: boolean;
|
||||
requiresConfirmation?: boolean;
|
||||
message?: string;
|
||||
|
||||
Reference in New Issue
Block a user