mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 09:24:09 +00:00
33b15d6cba
A control instance now retries fleet-sync pushes that hit a transient failure and backfills the security state on a freshly registered remote without waiting for the next policy edit. New service: - FleetSyncRetryService (singleton, start/stop) wakes 30s after boot and ticks every 5min. For each fleet resource, queries getFailedSyncTargets within a 24h window and re-pushes via FleetSyncService.pushResourceToNode through the same per-node mutex, so a normal fanout in flight serializes naturally with a retry. - After STALE_THRESHOLD_MS (1h) of continuous failure for a previously-working node, dispatches a single warning notification per cooldown window. Brand-new nodes that have never succeeded do not alert via this path; misconfigured remotes are caught by the test-connection affordance at registration time. - Wired into bootstrap startup/shutdown next to AutoHealService. Public surface: - FleetSyncService.pushResourceToNode(node, resource): targeted push to one node that re-uses the per-node mutex. Used by the retry service and any future targeted-resync flow. - routes/nodes.ts POST /api/nodes fires pushResourceAsync for both resources after a remote-proxy node row commits. Tuning constants centralized in fleetSyncConstants.ts: - RETRY_MAX_AGE_MS = 24h - STALE_THRESHOLD_MS = 1h Tests: - 8 vitest cases covering replica skip, retry dispatch, missing-node skip, alert-once-per-cooldown across the threshold window, no-alert for recent failures, no-alert for brand-new never-succeeded nodes, no-alert when the retry itself succeeds, start/stop idempotency. - Full backend suite: 1781 pass / 5 skipped.
77 lines
3.4 KiB
TypeScript
77 lines
3.4 KiB
TypeScript
import type { Server } from 'http';
|
|
import { FileSystemService } from '../services/FileSystemService';
|
|
import { NodeRegistry } from '../services/NodeRegistry';
|
|
import { LicenseService } from '../services/LicenseService';
|
|
import SelfUpdateService from '../services/SelfUpdateService';
|
|
import { MonitorService } from '../services/MonitorService';
|
|
import { AutoHealService } from '../services/AutoHealService';
|
|
import { FleetSyncRetryService } from '../services/FleetSyncRetryService';
|
|
import { DockerEventManager } from '../services/DockerEventManager';
|
|
import TrivyService from '../services/TrivyService';
|
|
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 { sweepStaleTempDirs as sweepStaleGitTempDirs } from '../services/GitSourceService';
|
|
import { PORT } from '../helpers/constants';
|
|
|
|
/**
|
|
* Run the startup sequence: stack-directory migration, service initialization,
|
|
* background watchdogs, then bind the HTTP server. The caller passes the
|
|
* already-constructed server so tests can import the module without binding a
|
|
* port.
|
|
*/
|
|
export async function startServer(server: Server): Promise<void> {
|
|
try {
|
|
console.log('Running stack migration check...');
|
|
const defaultFsService = FileSystemService.getInstance(NodeRegistry.getInstance().getDefaultNodeId());
|
|
await defaultFsService.migrateFlatToDirectory();
|
|
console.log('Migration check completed');
|
|
} catch (error) {
|
|
console.error('Migration failed:', error);
|
|
}
|
|
|
|
// Initialize the license service before any tier-gated code can run.
|
|
LicenseService.getInstance().initialize();
|
|
|
|
// Synchronous starts: schedule background timers and continue. None of
|
|
// these fire their first tick for at least a few seconds, so they
|
|
// safely run alongside the async initializers below.
|
|
MonitorService.getInstance().start();
|
|
AutoHealService.getInstance().start();
|
|
FleetSyncRetryService.getInstance().start();
|
|
ImageUpdateService.getInstance().start();
|
|
SchedulerService.getInstance().start();
|
|
MfaService.getInstance().start();
|
|
MeshService.getInstance().start().catch((err) => {
|
|
console.warn('[Startup] MeshService start failed:', (err as Error).message);
|
|
});
|
|
BlueprintReconciler.getInstance().start();
|
|
|
|
// Async initializers are independent of each other; run in parallel
|
|
// so total boot time is the slowest one rather than the sum.
|
|
await Promise.all([
|
|
SelfUpdateService.getInstance().initialize(),
|
|
DockerEventManager.getInstance().start(),
|
|
TrivyService.getInstance().initialize(),
|
|
]);
|
|
|
|
// Fire-and-forget housekeeping; logged but never awaited.
|
|
sweepStaleGitTempDirs().catch((err) => {
|
|
console.warn('[GitSource] Temp dir sweep failed:', (err as Error).message);
|
|
});
|
|
|
|
const isPilotAgent = process.env.SENCHO_MODE === 'pilot';
|
|
const listenHost = isPilotAgent ? '127.0.0.1' : undefined;
|
|
|
|
server.listen(PORT, listenHost, () => {
|
|
console.log(`Server running on ${listenHost || '0.0.0.0'}:${PORT}${isPilotAgent ? ' (pilot-agent mode)' : ''}`);
|
|
if (isPilotAgent) {
|
|
import('../pilot/agent').then((m) => m.startPilotAgent(PORT)).catch((err) => {
|
|
console.error('[Pilot] Agent startup failed:', err);
|
|
});
|
|
}
|
|
});
|
|
}
|