Add drift detection and removal as a real deployment

A drift deployment scans each server's sp/ tree for files not in the
server_scripts inventory (untracked scripts left over from before EC was
managing the fleet), deletes them, and records the removed bytes in
deployment_files for audit. The existing sweep then runs the standard
wait-for-empty → Komodo restart cycle on each affected server.

To make that cycle safer, the sweep now has a second phase: after Komodo
restart succeeds we wait for komodoInspectHealthy + RCON to both come
back green before marking the deployment_server row 'done'. If either
stays broken the row stays pending and keeps retrying.

New: /api/drift (GET scan, POST run), drift panel on /deployments.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-09-12 13:44:52 -04:00
co-authored by Claude Opus 4.7
parent 590f803d93
commit edfe4dfc09
5 changed files with 440 additions and 49 deletions
+32
View File
@@ -0,0 +1,32 @@
import { json } from '@sveltejs/kit';
import { runDriftDeployment, scanFleetDrift } from '$lib/server/fleet/drift.js';
import { kick } from '$lib/server/pipeline/worker.js';
// GET returns the current drift plan for the whole fleet without touching disk.
export async function GET() {
const entries = await scanFleetDrift();
const byServer = new Map<string, Array<{ relPath: string; size: number; sha256: string }>>();
for (const e of entries) {
const arr = byServer.get(e.server) ?? [];
arr.push({ relPath: e.relPath, size: e.size, sha256: e.sha256 });
byServer.set(e.server, arr);
}
return json({
totalDrift: entries.length,
servers: [...byServer.entries()].map(([server, files]) => ({ server, files }))
});
}
// POST removes drift and creates a drift deployment. Body: { servers?: string[], dryRun?: boolean }
export async function POST({ request }: { request: Request }) {
const body = (await request
.json()
.catch(() => ({}))) as { servers?: string[]; dryRun?: boolean };
const result = await runDriftDeployment({
servers: body.servers,
dryRun: body.dryRun ?? false,
triggeredBy: 'ui'
});
if (result.deploymentId) void kick();
return json({ ok: true, result });
}