// server/services/aptRepositories.ts import { getMachineRow, getCreds } from "./machines.js"; import { renderTemplate } from "../templates/render.js"; import { runScriptSudo } from "../ssh/client.js"; import type { AptRepositoriesAnalysis, OsFamily } from "@shared/types.js"; function section(raw: string, start: string, end?: string): string { const i = raw.indexOf(start); if (i < 0) return ""; const from = i + start.length; const j = end ? raw.indexOf(end, from) : -1; return raw.slice(from, j < 0 ? undefined : j).trim(); } interface Repo { uri: string; suite: string; components: string[]; } /** Parse les lignes `deb [opts] URI suite comp...` (format une-ligne). */ function parseDebLines(block: string): Repo[] { const repos: Repo[] = []; for (const line of block.split("\n")) { const t = line.trim(); if (!t.startsWith("deb ") && !t.startsWith("deb\t")) continue; // retire le mot-clé deb et les options [arch=...] const rest = t.replace(/^deb\s+/, "").replace(/^\[[^\]]*\]\s*/, ""); const parts = rest.split(/\s+/).filter(Boolean); if (parts.length < 2) continue; const [uri, suite, ...components] = parts; repos.push({ uri: uri!, suite: suite!, components }); } return repos; } export function analyzeRepositories(osFamily: OsFamily, raw: string): AptRepositoriesAnalysis { const repos = parseDebLines(section(raw, "===SU:REPO_DEB===", "===SU:REPO_DEB822===")); const components = [...new Set(repos.flatMap((r) => r.components))].sort(); const warnings: AptRepositoriesAnalysis["warnings"] = []; const notes: string[] = []; if (osFamily === "proxmox") { const enterprise = repos.some((r) => /enterprise\.proxmox\.com/.test(r.uri)); const noSubscription = repos.some((r) => /download\.proxmox\.com/.test(r.uri) && r.components.includes("pve-no-subscription")); if (enterprise && !noSubscription) { warnings.push({ kind: "pve_enterprise_without_subscription", message: "Dépôt PVE entreprise actif sans dépôt no-subscription : `apt update` échouera sans abonnement.", }); } if (!enterprise && !noSubscription) { warnings.push({ kind: "pve_repo_missing", message: "Aucun dépôt PVE détecté (ni enterprise ni no-subscription)." }); } return { osFamily, components, repos, proxmox: { enterprise, noSubscription }, warnings, notes }; } if (osFamily === "debian") { for (const comp of ["contrib", "non-free", "non-free-firmware"]) { if (!components.includes(comp)) notes.push(`Composant « ${comp} » absent (requis pour firmware/drivers propriétaires).`); } } else if (osFamily === "ubuntu") { for (const comp of ["universe", "restricted", "multiverse"]) { if (!components.includes(comp)) notes.push(`Composant « ${comp} » absent (drivers/paquets supplémentaires indisponibles).`); } } if (repos.length === 0) warnings.push({ kind: "no_sources", message: "Aucune source APT détectée." }); return { osFamily, components, repos, warnings, notes }; } /** Analyse les dépôts APT d'une machine via SSH (lecture seule). */ export async function analyzeMachineRepositories(machineId: string): Promise { const m = getMachineRow(machineId); if (!m) throw new Error("Machine introuvable"); const script = renderTemplate("apt/repositories.sh.tpl", {}); const res = await runScriptSudo(getCreds(m), script, () => {}); return analyzeRepositories(m.osFamily as OsFamily, res.stdout); }