// server/services/aptParse.ts import type { AptPackage, AptSnapshotDetail, SnapshotStatus, AptChange, AptExecutionResult } from "@shared/types.js"; // Exemple de ligne: // Inst pve-manager [8.4-1] (8.4-3 Proxmox VE:8.x [amd64]) // Inst newpkg (1.0.0 Debian:11.6/stable [all]) const INST_RE = /^Inst (\S+) (?:\[([^\]]+)\] )?\((\S+) (.+?) \[[^\]]+\]\)\s*$/; export function parseAptSimulate(raw: string): AptPackage[] { const out: AptPackage[] = []; for (const line of raw.split("\n")) { const m = INST_RE.exec(line.trimEnd()); if (!m) continue; out.push({ name: m[1]!, currentVersion: m[2] ?? null, targetVersion: m[3]!, origin: (m[4] ?? "").trim() || null, }); } return out; } export function parseRebootRequired(raw: string): boolean { return /REBOOT_REQUIRED=1/.test(raw); } const REMV_RE = /^Remv (\S+)(?: \[([^\]]+)\])?/; export function parseAptRemovals(raw: string): { name: string; currentVersion: string | null }[] { const out: { name: string; currentVersion: string | null }[] = []; for (const line of raw.split("\n")) { const m = REMV_RE.exec(line.trimEnd()); if (m) out.push({ name: m[1]!, currentVersion: m[2] ?? null }); } return out; } export function parseHeld(raw: string): string[] { return raw.split("\n").map((l) => l.trim()).filter((l) => l.length > 0); } export function parseRebootDetail(raw: string): { rebootRequired: boolean; pkgs: string[] } { const rebootRequired = /REBOOT_REQUIRED=1/.test(raw); const pkgs: string[] = []; for (const line of raw.split("\n")) { const m = /^PKG=(.+)$/.exec(line.trim()); if (m) pkgs.push(m[1]!.trim()); } return { rebootRequired, pkgs }; } export interface AptSections { upgradeSim: string; distUpgradeSim: string; heldRaw: string; rebootRaw: string; updateFailed: boolean; } export function parseDpkgList(raw: string): Record { const out: Record = {}; for (const line of raw.split("\n")) { const parts = line.split("\t"); if (parts.length < 3) continue; const [name, version, arch] = [parts[0]!.trim(), parts[1]!.trim(), parts[2]!.trim()]; if (!name) continue; out[`${name}:${arch}`] = { version, arch }; } return out; } /** Diff dpkg réel before/after → AptExecutionResult (planned/held vides : portés par le snapshot). */ export function buildAptExecutionResult(beforeRaw: string, afterRaw: string, rebootRaw: string): AptExecutionResult { const before = parseDpkgList(beforeRaw); const after = parseDpkgList(afterRaw); const applied: AptChange[] = []; const installed: AptChange[] = []; const removed: AptChange[] = []; for (const key of Object.keys(after)) { const [name] = key.split(":"); const a = after[key]!; const b = before[key]; if (!b) { const change: AptChange = { name: name!, arch: a.arch, fromVersion: null, toVersion: a.version, operation: "installed" }; installed.push(change); applied.push(change); } else if (b.version !== a.version) { applied.push({ name: name!, arch: a.arch, fromVersion: b.version, toVersion: a.version, operation: "upgraded" }); } } for (const key of Object.keys(before)) { if (!after[key]) { const [name] = key.split(":"); const b = before[key]!; const change: AptChange = { name: name!, arch: b.arch, fromVersion: b.version, toVersion: null, operation: "removed" }; removed.push(change); applied.push(change); } } return { planned: [], applied, installed, removed, held: [], rebootRequiredAfterRun: /REBOOT_REQUIRED=1/.test(rebootRaw), }; } export function buildAptSnapshotDetail(s: AptSections): AptSnapshotDetail { const upgradePkgs = parseAptSimulate(s.upgradeSim); const distPkgs = parseAptSimulate(s.distUpgradeSim); const installed: AptPackage[] = distPkgs .filter((p) => p.currentVersion === null) .map((p) => ({ ...p, operation: "install" as const })); const removed: AptPackage[] = parseAptRemovals(s.distUpgradeSim).map((r) => ({ name: r.name, currentVersion: r.currentVersion, targetVersion: "", origin: null, operation: "remove" as const, })); const held = parseHeld(s.heldRaw); const { rebootRequired, pkgs: rebootPkgs } = parseRebootDetail(s.rebootRaw); let status: SnapshotStatus = "ok"; if (s.updateFailed) status = "error"; else if (removed.length > 0 || held.length > 0) status = "warning"; else if (distPkgs.length > 0) status = "updates_available"; return { enabled: true, count: distPkgs.length, rebootRequired, packages: distPkgs, status, upgradeCount: upgradePkgs.length, distUpgradeCount: distPkgs.length, installed, removed, held, rebootPkgs, }; }