mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 14:19:56 +00:00
fix: restart CLIProxy after dashboard version install
This commit is contained in:
@@ -34,11 +34,7 @@ import {
|
|||||||
} from '../../cliproxy/config-generator';
|
} from '../../cliproxy/config-generator';
|
||||||
import { getProxyStatus as getProxyProcessStatus, stopProxy } from '../../cliproxy/session-tracker';
|
import { getProxyStatus as getProxyProcessStatus, stopProxy } from '../../cliproxy/session-tracker';
|
||||||
import { ensureCliproxyService } from '../../cliproxy/service-manager';
|
import { ensureCliproxyService } from '../../cliproxy/service-manager';
|
||||||
import {
|
import { checkCliproxyUpdate, getInstalledCliproxyVersion } from '../../cliproxy/binary-manager';
|
||||||
checkCliproxyUpdate,
|
|
||||||
getInstalledCliproxyVersion,
|
|
||||||
installCliproxyVersion,
|
|
||||||
} from '../../cliproxy/binary-manager';
|
|
||||||
import {
|
import {
|
||||||
fetchAllVersions,
|
fetchAllVersions,
|
||||||
isNewerVersion,
|
isNewerVersion,
|
||||||
@@ -56,6 +52,7 @@ import {
|
|||||||
canonicalizeModelIdForProvider,
|
canonicalizeModelIdForProvider,
|
||||||
getDeniedModelIdReasonForProvider,
|
getDeniedModelIdReasonForProvider,
|
||||||
} from '../../cliproxy/model-id-normalizer';
|
} from '../../cliproxy/model-id-normalizer';
|
||||||
|
import { installDashboardCliproxyVersion } from '../services/cliproxy-dashboard-install-service';
|
||||||
|
|
||||||
const router = Router();
|
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
|
* POST /api/cliproxy/install - Install specific CLIProxyAPI version
|
||||||
* Body: { version: string, force?: boolean }
|
* 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> => {
|
router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
@@ -967,22 +964,14 @@ router.post('/install', async (req: Request, res: Response): Promise<void> => {
|
|||||||
return;
|
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();
|
const backend = getConfiguredBackend();
|
||||||
await installCliproxyVersion(version, true, backend);
|
const installResult = await installDashboardCliproxyVersion(version, backend);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
|
||||||
version,
|
version,
|
||||||
isFaulty,
|
isFaulty,
|
||||||
isExperimental,
|
isExperimental,
|
||||||
message: `Successfully installed CLIProxy Plus v${version}`,
|
...installResult,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[cliproxy-stats] ${(error as Error).message}`);
|
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 {
|
export interface CliproxyInstallResult {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
version?: string;
|
version?: string;
|
||||||
|
restarted?: boolean;
|
||||||
|
port?: number;
|
||||||
isUnstable?: boolean;
|
isUnstable?: boolean;
|
||||||
requiresConfirmation?: boolean;
|
requiresConfirmation?: boolean;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user