fix: restart CLIProxy after dashboard version install

This commit is contained in:
Tam Nhu Tran
2026-03-07 10:45:29 +07:00
parent 15c6435685
commit e14df1fe05
4 changed files with 192 additions and 16 deletions
+5 -16
View File
@@ -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);
});
});
+2
View File
@@ -606,6 +606,8 @@ export interface CliproxyVersionsResponse {
export interface CliproxyInstallResult {
success: boolean;
version?: string;
restarted?: boolean;
port?: number;
isUnstable?: boolean;
requiresConfirmation?: boolean;
message?: string;