mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 09:24:09 +00:00
63213c0960
* feat: add service-scoped Compose update and restore Allow updating or rebuilding one declared Compose service on multi-service stacks without recreating siblings, with recovery snapshots, health-gate observation, and prune holds for rollback images. Full-stack update paths and single-service UX stay unchanged. * fix: sanitize service-scoped update log messages for CodeQL * fix: address service-scoped update audit findings B-01 through B-07 * fix: complete service-scoped update audit metadata and surfaces * test: wrap Updates readiness tests for deploy-feedback context * fix: keep service recovery reachable without Deploy Progress Make failed service-gate recovery discoverable when Deploy Progress is disabled or dismissed, suppress stale image-scan notification side effects, normalize ComposeService line endings, and add focused regression coverage. * fix: resurface ContainersHealth density and expand on multi-service stacks Service grouping hid the summary strip and Compact/Detailed/Expand controls that still applied to multi-container stacks.
83 lines
4.4 KiB
TypeScript
83 lines
4.4 KiB
TypeScript
import type { Server } from 'http';
|
|
import { DatabaseService } from '../services/DatabaseService';
|
|
import { LicenseService } from '../services/LicenseService';
|
|
import { MonitorService } from '../services/MonitorService';
|
|
import { AutoHealService } from '../services/AutoHealService';
|
|
import { HealthGateService } from '../services/HealthGateService';
|
|
import { ServiceUpdateRecoveryService } from '../services/ServiceUpdateRecoveryService';
|
|
import { FleetSyncRetryService } from '../services/FleetSyncRetryService';
|
|
import { DockerEventManager } from '../services/DockerEventManager';
|
|
import { ImageUpdateService } from '../services/ImageUpdateService';
|
|
import { SchedulerService } from '../services/SchedulerService';
|
|
import { MfaService } from '../services/MfaService';
|
|
import { MeshService } from '../services/MeshService';
|
|
import { BlueprintReconciler } from '../services/BlueprintReconciler';
|
|
import { CveIntelService } from '../services/CveIntelService';
|
|
import { PilotMetrics } from '../services/PilotMetrics';
|
|
|
|
/**
|
|
* Wire graceful shutdown handlers. Docker sends SIGTERM when the container
|
|
* stops; Ctrl-C sends SIGINT in dev. We allow in-flight requests to finish,
|
|
* then cleanly stop background services and close the SQLite connection
|
|
* before exiting. A 10 s force-exit timer guards against hung connections.
|
|
*/
|
|
export function installShutdownHandlers(server: Server): void {
|
|
const gracefulShutdown = (signal: string): void => {
|
|
console.log(`[Shutdown] ${signal} received - shutting down gracefully…`);
|
|
|
|
server.close(() => {
|
|
console.log('[Shutdown] HTTP server closed');
|
|
try { LicenseService.getInstance().destroy(); } catch (e) {
|
|
console.warn('[Shutdown] LicenseService cleanup failed:', (e as Error).message);
|
|
}
|
|
try { MonitorService.getInstance().stop(); } catch (e) {
|
|
console.warn('[Shutdown] MonitorService cleanup failed:', (e as Error).message);
|
|
}
|
|
try { AutoHealService.getInstance().stop(); } catch (e) { console.warn('[Shutdown] AutoHealService cleanup failed:', (e as Error).message); }
|
|
try { HealthGateService.getInstance().stop(); } catch (e) { console.warn('[Shutdown] HealthGateService cleanup failed:', (e as Error).message); }
|
|
try { ServiceUpdateRecoveryService.getInstance().stop(); } catch (e) { console.warn('[Shutdown] ServiceUpdateRecoveryService cleanup failed:', (e as Error).message); }
|
|
try { FleetSyncRetryService.getInstance().stop(); } catch (e) { console.warn('[Shutdown] FleetSyncRetryService cleanup failed:', (e as Error).message); }
|
|
try { DockerEventManager.getInstance().stop(); } catch (e) {
|
|
console.warn('[Shutdown] DockerEventManager cleanup failed:', (e as Error).message);
|
|
}
|
|
try { ImageUpdateService.getInstance().stop(); } catch (e) {
|
|
console.warn('[Shutdown] ImageUpdateService cleanup failed:', (e as Error).message);
|
|
}
|
|
try { SchedulerService.getInstance().stop(); } catch (e) {
|
|
console.warn('[Shutdown] SchedulerService cleanup failed:', (e as Error).message);
|
|
}
|
|
try { MfaService.getInstance().stop(); } catch (e) {
|
|
console.warn('[Shutdown] MfaService cleanup failed:', (e as Error).message);
|
|
}
|
|
MeshService.getInstance().stop().catch((e) => {
|
|
console.warn('[Shutdown] MeshService cleanup failed:', (e as Error).message);
|
|
});
|
|
try { BlueprintReconciler.getInstance().stop(); } catch (e) {
|
|
console.warn('[Shutdown] BlueprintReconciler cleanup failed:', (e as Error).message);
|
|
}
|
|
try { CveIntelService.getInstance().stop(); } catch (e) {
|
|
console.warn('[Shutdown] CveIntelService cleanup failed:', (e as Error).message);
|
|
}
|
|
try { PilotMetrics.flush(); } catch (e) {
|
|
console.warn('[Shutdown] PilotMetrics flush failed:', (e as Error).message);
|
|
}
|
|
try { DatabaseService.getInstance().flushAuditLogBuffer(); } catch (e) {
|
|
console.warn('[Shutdown] Audit log flush failed:', (e as Error).message);
|
|
}
|
|
try { DatabaseService.getInstance().getDb().close(); } catch (e) {
|
|
console.warn('[Shutdown] Database close failed:', (e as Error).message);
|
|
}
|
|
console.log('[Shutdown] Done - exiting');
|
|
process.exit(0);
|
|
});
|
|
|
|
setTimeout(() => {
|
|
console.error('[Shutdown] Timed out waiting for connections - forcing exit');
|
|
process.exit(1);
|
|
}, 10_000).unref();
|
|
};
|
|
|
|
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
}
|