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,34 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import type { UpdateSnapshot, ExecutionResult } from "./types.js";
|
||||
|
||||
describe("rétro-compatibilité des contrats", () => {
|
||||
it("un snapshot jalon 1 (sans blocs optionnels) reste valide", () => {
|
||||
const snap: UpdateSnapshot = {
|
||||
machineId: "m1", hostname: "h", os: { family: "debian", version: "12" },
|
||||
checkedAt: "2026-06-05T10:00:00Z", status: "ok",
|
||||
apt: { enabled: true, count: 0, rebootRequired: false, packages: [] },
|
||||
};
|
||||
expect(snap.apt.count).toBe(0);
|
||||
});
|
||||
|
||||
it("une exécution jalon 1 (mode manual, sans blocs) reste valide", () => {
|
||||
const exec: ExecutionResult = {
|
||||
executionId: "e1", machineId: "m1", startedAt: "a", finishedAt: "b",
|
||||
mode: "manual", action: "apt_full_upgrade", status: "ok",
|
||||
rebootRequiredAfterRun: false, importantLogLines: [], rawLogRef: "r", reportRef: "rr",
|
||||
};
|
||||
expect(exec.action).toBe("apt_full_upgrade");
|
||||
});
|
||||
|
||||
it("accepte les nouveaux blocs optionnels", () => {
|
||||
const snap: UpdateSnapshot = {
|
||||
machineId: "m1", hostname: "h", os: { family: "proxmox", version: "8" },
|
||||
checkedAt: "t", status: "updates_available",
|
||||
apt: { enabled: true, count: 1, rebootRequired: false, packages: [], status: "updates_available" },
|
||||
schemaVersion: 1, kind: "apt_update_analyze", machineKind: "proxmox_host",
|
||||
docker: { enabled: false, installed: false, count: 0, stacks: [] },
|
||||
errors: [],
|
||||
};
|
||||
expect(snap.docker?.installed).toBe(false);
|
||||
});
|
||||
});
|
||||
+222
-10
@@ -1,15 +1,143 @@
|
||||
// shared/types.ts
|
||||
export type OsFamily = "debian" | "ubuntu" | "unknown";
|
||||
export type OsFamily = "debian" | "ubuntu" | "proxmox" | "raspbian" | "unknown";
|
||||
export type MachineStatus = "unknown" | "ok" | "updates_available" | "error" | "running";
|
||||
export type AptProxyMode = "direct" | "runtime";
|
||||
export type ActionType = "apt_full_upgrade" | "reboot";
|
||||
export type AptProxyMode = "direct" | "runtime" | "persistent";
|
||||
export type ActionType =
|
||||
| "apt_full_upgrade" | "reboot"
|
||||
| "apt_update_analyze" | "apt_upgrade" | "apt_dist_upgrade"
|
||||
| "apt_autoremove" | "apt_clean" | "reboot_verified"
|
||||
| "docker_scan" | "docker_inspect_current" | "docker_pull_check"
|
||||
| "docker_compose_apply" | "docker_prune_images" | "docker_compose_down"
|
||||
| "machine_probe" | "post_install";
|
||||
export type ExecutionStatus = "ok" | "warning" | "error";
|
||||
export type ApiClientScope = "read" | "operate" | "admin" | "debug";
|
||||
export type MachineKind =
|
||||
| "physical" | "vm" | "proxmox_host" | "lxc"
|
||||
| "raspberry_pi" | "workstation" | "unknown";
|
||||
export type SnapshotStatus = "ok" | "updates_available" | "warning" | "error";
|
||||
|
||||
export interface ServerCapabilities {
|
||||
app: "system_update";
|
||||
apiVersion: "1";
|
||||
generatedAt: string;
|
||||
features: {
|
||||
machines: boolean;
|
||||
machineSnapshots: boolean;
|
||||
actions: boolean;
|
||||
aptFullUpgrade: boolean;
|
||||
reboot: boolean;
|
||||
reports: boolean;
|
||||
terminalOutput: boolean;
|
||||
interactiveSsh: boolean;
|
||||
docker: boolean;
|
||||
postInstall: boolean;
|
||||
hermes: boolean;
|
||||
settings: boolean;
|
||||
scheduledJobs: boolean;
|
||||
authTokens: boolean;
|
||||
};
|
||||
endpoints: {
|
||||
capabilities: string;
|
||||
systemStatus: string;
|
||||
systemMetrics: string;
|
||||
machines: string;
|
||||
machineSnapshot: string;
|
||||
machineRefresh: string;
|
||||
machineActions: string;
|
||||
machineExecutions: string;
|
||||
executionReport: string;
|
||||
terminalOutputWs: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface SystemStatus {
|
||||
app: "system_update";
|
||||
version: string;
|
||||
apiVersion: "1";
|
||||
serverTime: string;
|
||||
uptimeSeconds: number;
|
||||
}
|
||||
|
||||
export interface SystemMetrics {
|
||||
collectedAt: string;
|
||||
process: {
|
||||
uptimeSeconds: number;
|
||||
rssMb: number;
|
||||
heapUsedMb: number;
|
||||
heapTotalMb: number;
|
||||
};
|
||||
host: {
|
||||
loadAverage1m: number;
|
||||
loadAverage5m: number;
|
||||
loadAverage15m: number;
|
||||
totalMemoryMb: number;
|
||||
freeMemoryMb: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface AptPackage {
|
||||
name: string;
|
||||
currentVersion: string | null;
|
||||
targetVersion: string;
|
||||
origin: string | null;
|
||||
arch?: string;
|
||||
operation?: "upgrade" | "install" | "remove" | "hold";
|
||||
severityHint?: "normal" | "security";
|
||||
}
|
||||
|
||||
export interface AptSnapshotDetail {
|
||||
enabled: boolean;
|
||||
count: number;
|
||||
rebootRequired: boolean;
|
||||
packages: AptPackage[];
|
||||
status?: SnapshotStatus;
|
||||
upgradeCount?: number;
|
||||
distUpgradeCount?: number;
|
||||
installed?: AptPackage[];
|
||||
removed?: AptPackage[];
|
||||
held?: string[];
|
||||
rebootPkgs?: string[];
|
||||
}
|
||||
|
||||
export interface DockerSnapshotService {
|
||||
serviceName: string;
|
||||
image: string;
|
||||
currentImageId?: string | null;
|
||||
currentDigest?: string | null;
|
||||
candidateImageId?: string | null;
|
||||
candidateDigest?: string | null;
|
||||
currentVersion?: string | null;
|
||||
candidateVersion?: string | null;
|
||||
sourceUrl?: string | null;
|
||||
status?: "up_to_date" | "updates_available" | "warning" | "error";
|
||||
}
|
||||
|
||||
export interface DockerSnapshotStack {
|
||||
name: string;
|
||||
workingDir: string;
|
||||
composeFiles: string[];
|
||||
projectName?: string | null;
|
||||
status: "candidate" | "enabled" | "ignored" | "error";
|
||||
detectedBy?: "root_scan" | "label" | "manual";
|
||||
services: DockerSnapshotService[];
|
||||
}
|
||||
|
||||
export interface DockerSnapshot {
|
||||
enabled: boolean;
|
||||
installed: boolean;
|
||||
count: number;
|
||||
declaredRoots?: string[];
|
||||
stacks: DockerSnapshotStack[];
|
||||
status?: SnapshotStatus;
|
||||
}
|
||||
|
||||
export interface SnapshotError {
|
||||
source: "apt" | "docker" | "post_install" | "ssh" | "system";
|
||||
kind: string;
|
||||
severity: "info" | "warning" | "error";
|
||||
message: string;
|
||||
remediation?: string;
|
||||
importantLines?: string[];
|
||||
}
|
||||
|
||||
export interface UpdateSnapshot {
|
||||
@@ -18,27 +146,95 @@ export interface UpdateSnapshot {
|
||||
os: { family: OsFamily; version: string };
|
||||
checkedAt: string; // ISO 8601
|
||||
status: MachineStatus;
|
||||
apt: {
|
||||
enabled: boolean;
|
||||
count: number;
|
||||
rebootRequired: boolean;
|
||||
packages: AptPackage[];
|
||||
};
|
||||
apt: AptSnapshotDetail;
|
||||
schemaVersion?: number;
|
||||
kind?: "apt_update_analyze" | "docker_scan" | "reboot_check" | "combined";
|
||||
machineKind?: MachineKind;
|
||||
docker?: DockerSnapshot;
|
||||
errors?: SnapshotError[];
|
||||
rawHints?: { logImportantLines: string[] };
|
||||
}
|
||||
|
||||
export interface AptChange {
|
||||
name: string;
|
||||
arch?: string;
|
||||
fromVersion: string | null;
|
||||
toVersion: string | null;
|
||||
operation: "upgraded" | "installed" | "removed" | "unchanged";
|
||||
origin?: string | null;
|
||||
}
|
||||
|
||||
export interface AptExecutionResult {
|
||||
planned: AptPackage[];
|
||||
applied: AptChange[];
|
||||
installed: AptChange[];
|
||||
removed: AptChange[];
|
||||
held: string[];
|
||||
errors?: SnapshotError[];
|
||||
rebootRequiredAfterRun: boolean;
|
||||
}
|
||||
|
||||
export interface DockerImageChange {
|
||||
stack: string;
|
||||
serviceName?: string;
|
||||
imageRef?: string;
|
||||
fromImageId?: string | null;
|
||||
toImageId?: string | null;
|
||||
fromDigest?: string | null;
|
||||
toDigest?: string | null;
|
||||
operation: "pulled" | "recreated" | "pruned";
|
||||
}
|
||||
|
||||
export interface DockerExecutionResult {
|
||||
pull?: { changes: DockerImageChange[]; errors?: SnapshotError[] };
|
||||
up?: { recreated: string[]; running: string[]; exited: string[]; errors?: SnapshotError[] };
|
||||
prune?: { imagesDeleted: string[]; bytesReclaimed: number; errors?: SnapshotError[] };
|
||||
errors?: SnapshotError[];
|
||||
}
|
||||
|
||||
export interface RebootResult {
|
||||
beforeBootId: string | null;
|
||||
afterBootId: string | null;
|
||||
requestedAt: string;
|
||||
sshWentDownAt: string | null;
|
||||
sshCameBackAt: string | null;
|
||||
waitedSeconds: number;
|
||||
status: "ok" | "reboot_command_failed" | "ssh_never_went_down"
|
||||
| "machine_did_not_return" | "boot_id_unchanged" | "timeout";
|
||||
lastRebootDurationSeconds?: number;
|
||||
nextRecommendedWaitSeconds?: number;
|
||||
errors?: SnapshotError[];
|
||||
}
|
||||
|
||||
export interface PostInstallResult {
|
||||
profilesRun: string[];
|
||||
variablesUsed: Record<string, string | number | boolean>;
|
||||
filesModified: string[];
|
||||
packagesInstalled: string[];
|
||||
servicesEnabled: string[];
|
||||
rebootsRequested: boolean;
|
||||
networkChange?: { oldEndpoint: string | null; newEndpoint: string | null; reconnectHost: string | null };
|
||||
errors?: SnapshotError[];
|
||||
}
|
||||
|
||||
export interface ExecutionResult {
|
||||
executionId: string;
|
||||
machineId: string;
|
||||
startedAt: string;
|
||||
finishedAt: string;
|
||||
mode: "manual";
|
||||
mode: "manual" | "scheduled" | "hermes_requested";
|
||||
action: ActionType;
|
||||
status: ExecutionStatus;
|
||||
rebootRequiredAfterRun: boolean;
|
||||
importantLogLines: string[];
|
||||
rawLogRef: string;
|
||||
reportRef: string;
|
||||
schemaVersion?: number;
|
||||
apt?: AptExecutionResult;
|
||||
docker?: DockerExecutionResult;
|
||||
reboot?: RebootResult;
|
||||
postInstall?: PostInstallResult;
|
||||
errors?: SnapshotError[];
|
||||
}
|
||||
|
||||
/** Vue machine renvoyée par l'API — NE CONTIENT JAMAIS de secret. */
|
||||
@@ -54,3 +250,19 @@ export interface MachineView {
|
||||
status: MachineStatus;
|
||||
lastCheckedAt: string | null;
|
||||
}
|
||||
|
||||
/** Client API local/Hermes — ne contient jamais le token brut. */
|
||||
export interface ApiClientView {
|
||||
id: string;
|
||||
name: string;
|
||||
tokenPrefix: string;
|
||||
scopes: ApiClientScope[];
|
||||
createdAt: string;
|
||||
lastUsedAt: string | null;
|
||||
revokedAt: string | null;
|
||||
}
|
||||
|
||||
export interface CreatedApiClient {
|
||||
client: ApiClientView;
|
||||
token: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user