mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-12 11:47:11 +00:00
feat(blueprints): backend foundation for fleet-wide compose templates (#860)
* feat(blueprints): add backend foundation for fleet-wide compose templates
Introduces the Blueprint Model: a docker-compose.yml plus a node selector
(labels or explicit IDs) that Sencho reconciles across the fleet. Backend
foundation only; the frontend tab and documentation follow.
Schema (DatabaseService):
- node_labels table for fleet-level orchestration tagging
- blueprints table with compose content, selector, drift_mode, classification
- blueprint_deployments table for per-node materialized state
- New idempotent migrate methods following the existing pattern
Services:
- BlueprintAnalyzer: pure compose-YAML classifier (stateless / stateful /
unknown) with 17 covered cases including named volumes, bind mounts,
external volumes, and tmpfs
- NodeLabelService: label CRUD plus selector matching helper (any/all/ids)
- BlueprintService: local + remote deploy/withdraw orchestration, marker
file management, name-conflict guard, per-(blueprint,node) lock
- BlueprintReconciler: 60-second loop with three-mode drift policy
(observe/suggest/enforce), state-aware guards, and Enforce-downgrade for
volume-destroying drift
Routes (gated requirePaid + requireAdmin on mutations):
- /api/blueprints (CRUD + apply + withdraw + accept + preview + analyze)
- /api/node-labels (CRUD + listAll + listDistinct)
Notifications: four new categories registered in NotificationService for
deploy/failure/drift events.
Bootstrap: reconciler start/stop wired in startup and shutdown.
Tests: 45 new Vitest cases covering selector matching, classifier rules,
state-aware guards, drift-mode branching, and marker parsing. Full backend
suite (1625 tests) passes; tsc clean.
* fix(lint): replace bare Function type in blueprint reconciler tests
Replace 8 occurrences of `as unknown as { computeDecision: Function }`
with a properly typed `ReconcilerWithCompute` alias that mirrors the
real method signature. Export `ReconcileDecision` from
BlueprintReconciler so the test can reference it.
Resolves @typescript-eslint/no-unsafe-function-type errors that were
failing the Backend (Lint) CI step.
This commit is contained in:
@@ -8,6 +8,7 @@ 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';
|
||||
|
||||
/**
|
||||
* Wire graceful shutdown handlers. Docker sends SIGTERM when the container
|
||||
@@ -43,6 +44,9 @@ export function installShutdownHandlers(server: Server): void {
|
||||
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 { DatabaseService.getInstance().flushAuditLogBuffer(); } catch (e) {
|
||||
console.warn('[Shutdown] Audit log flush failed:', (e as Error).message);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
|
||||
@@ -42,6 +43,7 @@ export async function startServer(server: Server): Promise<void> {
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user