/** * Proxmox template selection — shared between `service add proxmox` and * `service reconfigure`. Queries Proxmox's live appliance catalog * (`pveam available`) and prompts the user to pick an Ubuntu LXC template by * canonical name. * * Why this exists: Proxmox bumps template revisions over time * (ubuntu-24.04 went from -1 to -2, 25.04 ships as -1.1) and an older home-lab * deployment guide naming `-1` produces 404s on the mirror. Using the catalog * means the canonical filename always matches what `pveam download` accepts. */ import { type ProxmoxAppliance, type ProxmoxCredentials, checkTaskStatus, downloadAppliance, listAvailableAppliances, } from '../../api-clients/proxmox'; import { askSelect, askText } from '../../services/bus-interview'; import { FuelGauge } from '../fuel-gauge'; export interface UbuntuTemplateChoice { /** Canonical filename, e.g. "ubuntu-24.04-standard_24.04-2_amd64.tar.zst" */ template: string; /** * 'catalog' = filename came from Proxmox's aplinfo, so it's known good for * downloadAppliance. 'manual' = user typed it because aplinfo was unavailable; * caller should expect they'll need to `pveam download` themselves. */ source: 'catalog' | 'manual'; } export type TemplateSelectionResult = | { kind: 'selected'; choice: UbuntuTemplateChoice } | { kind: 'cancelled' } | { kind: 'error'; message: string }; interface ParsedUbuntu { appliance: ProxmoxAppliance; major: number; minor: number; isLts: boolean; } function parseUbuntuPackage(appliance: ProxmoxAppliance): ParsedUbuntu | null { const match = appliance.package.match(/^ubuntu-(\d+)\.(\d+)-standard$/); if (!match) return null; const major = Number(match[1]); const minor = Number(match[2]); // Ubuntu LTS = even major, .04 minor (20.04, 22.04, 24.04, 26.04, ...). const isLts = minor === 4 && major % 2 === 0; return { appliance, major, minor, isLts }; } function compareDescending(a: ParsedUbuntu, b: ParsedUbuntu): number { if (a.major !== b.major) return b.major - a.major; return b.minor - a.minor; } /** * Build the option list used by the select prompt and tests. * Exported so tests can assert on filtering/sorting without driving a prompt. */ export function buildUbuntuOptions(appliances: ProxmoxAppliance[]): ParsedUbuntu[] { return appliances .map(parseUbuntuPackage) .filter((entry): entry is ParsedUbuntu => entry !== null) .filter((entry) => (entry.appliance.section ?? 'system') === 'system') .sort(compareDescending); } /** * Pre-select rule: * 1. Exact match on currentTemplate (reconfigure with current revision still on mirror). * 2. Family match on currentTemplate package (e.g. user has 24.04 -1 cached, mirror has -2). * 3. Newest LTS in the catalog. * 4. Newest entry overall. */ export function pickInitialTemplate( options: ParsedUbuntu[], currentTemplate?: string, ): string | undefined { if (options.length === 0) return undefined; if (currentTemplate) { const exact = options.find((e) => e.appliance.template === currentTemplate); if (exact) return exact.appliance.template; const familyName = currentTemplate.match(/^(ubuntu-\d+\.\d+-standard)/)?.[1]; if (familyName) { const family = options.find((e) => e.appliance.package === familyName); if (family) return family.appliance.template; } } const newestLts = options.find((e) => e.isLts); return (newestLts ?? options[0]).appliance.template; } export async function selectUbuntuApplianceFromCatalog( credentials: ProxmoxCredentials, nodeName: string, opts: { scope: string; currentTemplate?: string }, ): Promise { console.log('\nFetching Proxmox template catalog...'); const result = await listAvailableAppliances(credentials, nodeName); if (!result.success) { console.log(`⚠ Could not fetch template catalog from Proxmox: ${result.message}`); console.log(' You can still configure the service by entering a template filename manually.'); console.log( ' After saving, run `pveam download