feat: socle BDD (tâche 1.9 Phase 1-2) + moteur APT (tâche 2 SJ-0→3) + WIP capabilities/auth/Rust
Checkpoint multi-chantiers (arbre vert : tsc 0 erreur, 70 tests, build OK). - tâche 1.9 Phase 1 : schéma socle (machine_state/events/reports/raw_artifacts/ hardware/metrics + colonnes étendues) + wiring refresh/execute. Migration 0002. - tâche 1.9 Phase 2 : machine_credentials + machine_host_keys (non destructif, dual-read + backfill). Migration 0003. Fix séquence journal de migration. - tâche 2 : SJ-0 (types étendus rétro-compatibles, réducteur Docker, resolveTemplate), SJ-1 (update-analyze enrichi), SJ-2 (apply + diff dpkg + timeout inactivité SSH), SJ-3 (reboot vérifié boot_id). - WIP parallèle inclus : /api/capabilities, auth/apiTokens/apiClients, system metrics, scaffold app_rust, ajustements frontend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
// server/services/credentials.ts
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db, schema } from "../db/client.js";
|
||||
|
||||
interface EncPair { encPassword: string | null; encSudoPassword: string | null; }
|
||||
|
||||
/** Résout la source des secrets : machine_credentials prioritaire, sinon legacy machines (fonction pure). */
|
||||
export function resolveCreds(legacy: EncPair, creds: EncPair | null): EncPair {
|
||||
if (creds && creds.encPassword) return { encPassword: creds.encPassword, encSudoPassword: creds.encSudoPassword };
|
||||
return { encPassword: legacy.encPassword, encSudoPassword: legacy.encSudoPassword };
|
||||
}
|
||||
|
||||
/** Écrit (insert/replace) la ligne machine_credentials pour une machine (secrets déjà chiffrés). */
|
||||
export function writeCredentials(input: {
|
||||
machineId: string;
|
||||
encPassword: string | null;
|
||||
encSudoPassword: string | null;
|
||||
}): void {
|
||||
const now = new Date().toISOString();
|
||||
db.insert(schema.machineCredentials)
|
||||
.values({
|
||||
machineId: input.machineId,
|
||||
authMethod: "password",
|
||||
encPassword: input.encPassword,
|
||||
encSudoPassword: input.encSudoPassword,
|
||||
sudoMode: input.encSudoPassword ? "separate" : "same_as_ssh",
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
status: "unknown",
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: schema.machineCredentials.machineId,
|
||||
set: { encPassword: input.encPassword, encSudoPassword: input.encSudoPassword, updatedAt: now },
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
/** Lit la ligne machine_credentials (ou null). */
|
||||
export function readCredentials(machineId: string): EncPair | null {
|
||||
const row = db.select().from(schema.machineCredentials)
|
||||
.where(eq(schema.machineCredentials.machineId, machineId)).get();
|
||||
return row ? { encPassword: row.encPassword, encSudoPassword: row.encSudoPassword } : null;
|
||||
}
|
||||
|
||||
/** Backfill idempotent : crée une ligne machine_credentials pour chaque machine qui n'en a pas. */
|
||||
export function backfillCredentials(): number {
|
||||
const machines = db.select().from(schema.machines).all();
|
||||
let created = 0;
|
||||
for (const m of machines) {
|
||||
if (readCredentials(m.id)) continue;
|
||||
writeCredentials({ machineId: m.id, encPassword: m.encPassword, encSudoPassword: m.encSudoPassword });
|
||||
created++;
|
||||
}
|
||||
return created;
|
||||
}
|
||||
Reference in New Issue
Block a user