diff --git a/web/app/[locale]/changelog/page.tsx b/web/app/[locale]/changelog/page.tsx index 485e50a6..c662111c 100644 --- a/web/app/[locale]/changelog/page.tsx +++ b/web/app/[locale]/changelog/page.tsx @@ -12,14 +12,16 @@ import CopyableCode from "@/components/CopyableCode" // Resolve which CHANGELOG.md to read for the given locale. The canonical // English file lives at the repo root (so GitHub displays it as-is and -// existing RSS / external consumers don't break). Localized versions -// sit under /lang//CHANGELOG.md. Falls back to English if -// the localized file doesn't exist yet — so a partially-translated -// changelog still renders (in EN) instead of 404'ing. +// existing RSS / external consumers don't break). Localized versions sit +// under /web/data/changelog/.md — separate from the +// /lang/ directory which is now reserved for runtime translation JSON +// shipped to the host install. Falls back to English if the localized +// file doesn't exist yet — so a partially-translated changelog still +// renders (in EN) instead of 404'ing. function resolveChangelogPath(locale: string): string { const repoRoot = path.join(process.cwd(), "..") if (locale && locale !== "en") { - const localized = path.join(repoRoot, "lang", locale, "CHANGELOG.md") + const localized = path.join(process.cwd(), "data", "changelog", `${locale}.md`) if (fs.existsSync(localized)) return localized } return path.join(repoRoot, "CHANGELOG.md") diff --git a/web/app/[locale]/docs/backup-restore/creating-backups/page.tsx b/web/app/[locale]/docs/backup-restore/creating-backups/page.tsx new file mode 100644 index 00000000..f7738a87 --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/creating-backups/page.tsx @@ -0,0 +1,473 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import Image from "next/image" +import { Link } from "@/i18n/navigation" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.creatingBackups.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox interactive backup", + "proxmenux backup menu", + "backup profile default custom", + "hb_prepare_staging", + "proxmenux backup wizard", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/creating-backups" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/creating-backups", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type EntryRow = { entry: string; path: string; detail: string } +type MatrixRow = { combo: string; destination: string; profile: string; action: string } +type StepRow = { step: string; name: string; detail: string } +type WritingRow = { topic: string; detail: string } +type WhereNextItem = { label: string; href: string; tail: string } + +export default async function CreatingBackupsPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.creatingBackups" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { creatingBackups: { + entryPoints: { rows: EntryRow[] } + matrix: { rows: MatrixRow[] } + commonPipeline: { steps: StepRow[] } + included: { + globalItems: string[] + rootItems: string[] + proxmenuxItems: string[] + notInProfileItems: string[] + } + writing: { rows: WritingRow[] } + whereNext: { items: WhereNextItem[] } + } } } + } + const cb = messages.docs.backupRestore.creatingBackups + const entryRows = cb.entryPoints.rows + const matrixRows = cb.matrix.rows + const pipelineSteps = cb.commonPipeline.steps + const globalItems = cb.included.globalItems + const rootItems = cb.included.rootItems + const proxmenuxItems = cb.included.proxmenuxItems + const notInProfileItems = cb.included.notInProfileItems + const writingRows = cb.writing.rows + const whereNextItems = cb.whereNext.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + + + {t.rich("intro.body", { code, em, strong })} + + +

+ {t("entryPoints.heading")} +

+ +
+ + + + + + + + + + {entryRows.map((row, idx) => ( + + + + + + ))} + +
Entry pointPathDetail
{row.entry}{row.path}{t.rich(`entryPoints.rows.${idx}.detail`, { code })}
+
+ +

+ {t("modes.heading")} +

+ +

+ {t.rich("modes.body", { code, strong })} +

+ +

+ + {t("modes.seeAlso")} + +

+ +
+ + {t("modes.monitorAlt")} + +
+ {t("modes.monitorCaption")} +
+
+ +

+ {t("matrix.heading")} +

+ +

+ {t("matrix.intro")} +

+ +
+ + + + + + + + + + + {matrixRows.map((row, idx) => ( + + + + + + + ))} + +
#DestinationProfileWhat it does
{row.combo}{row.destination}{row.profile}{t.rich(`matrix.rows.${idx}.action`, { code })}
+
+ +

+ {t("profiles.heading")} +

+ +

+ {t("profiles.defaultTitle")} +

+ +

+ {t.rich("profiles.defaultBody", { code, em })} +

+ +

+ {t("profiles.customTitle")} +

+ +

+ {t.rich("profiles.customBody", { code, em })} +

+ +
+ + {t("profiles.customPickerAlt")} + +
+ {t("profiles.customPickerCaption")} +
+
+ +
+ + {t("profiles.manageCustomAlt")} + +
+ {t("profiles.manageCustomCaption")} +
+
+ +

+ {t("commonPipeline.heading")} +

+ +

+ {t.rich("commonPipeline.intro", { code })} +

+ +
+ + + + + + + + + + {pipelineSteps.map((row, idx) => ( + + + + + + ))} + +
#StepDetail
{row.step}{row.name}{t.rich(`commonPipeline.steps.${idx}.detail`, { code, em })}
+
+ +

+ {t("included.heading")} +

+ +

+ {t.rich("included.intro", { code })} +

+ +

+ {t.rich("included.globalTitle", { code })} +

+ +
    + {globalItems.map((_, idx) => ( +
  • + + {t.rich(`included.globalItems.${idx}`, { code })} +
  • + ))} +
+ +

+ {t.rich("included.rootTitle", { code })} +

+ +

+ {t.rich("included.rootBody", { code })} +

+ +
    + {rootItems.map((_, idx) => ( +
  • + + {t.rich(`included.rootItems.${idx}`, { code })} +
  • + ))} +
+ +

+ {t.rich("included.proxmenuxTitle", { code })} +

+ +

+ {t.rich("included.proxmenuxBody", { code })} +

+ +
    + {proxmenuxItems.map((_, idx) => ( +
  • + + {t.rich(`included.proxmenuxItems.${idx}`, { code })} +
  • + ))} +
+ +

+ {t("included.notInProfileTitle")} +

+ +

+ {t.rich("included.notInProfileBody", { code })} +

+ +
    + {notInProfileItems.map((_, idx) => ( +
  • + + {t.rich(`included.notInProfileItems.${idx}`, { code, strong })} +
  • + ))} +
+ +

+ {t("included.customPathsTitle")} +

+ +

+ {t.rich("included.customPathsBody", { code })} +

+ +

+ {t("archiveStructure.heading")} +

+ +

+ {t.rich("archiveStructure.intro", { code })} +

+ +
+{t("archiveStructure.tree")}
+      
+ +

+ {t("confirmation.heading")} +

+ +

+ {t.rich("confirmation.body", { code })} +

+ +

+ {t("writing.heading")} +

+ +

+ {t("writing.intro")} +

+ +
+ + + + + + + + + {writingRows.map((row, idx) => ( + + + + + ))} + +
TopicDetail
{row.topic}{t.rich(`writing.rows.${idx}.detail`, { code })}
+
+ +

+ {t("finishedScreens.heading")} +

+ +

+ {t("finishedScreens.intro")} +

+ +
+ + {t("finishedScreens.scriptsAlt")} + +
+ {t("finishedScreens.scriptsCaption")} +
+
+ +
+ + {t("finishedScreens.monitorAlt")} + +
+ {t("finishedScreens.monitorCaption")} +
+
+ +

+ {t("whereNext.heading")} +

+ +
    + {whereNextItems.map((item) => ( +
  • + + {item.label} + + {item.tail} +
  • + ))} +
+
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/cross-kernel/page.tsx b/web/app/[locale]/docs/backup-restore/cross-kernel/page.tsx new file mode 100644 index 00000000..3386f69e --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/cross-kernel/page.tsx @@ -0,0 +1,293 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { AlertTriangle } from "lucide-react" +import { Link } from "@/i18n/navigation" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.crossKernel.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox cross-kernel restore", + "bk_older bk_newer safe subset", + "IOMMU VFIO hydration", + "kernel-agnostic restore", + "hb_unsafe_paths_cross_version", + "HB_HYDRATION_APPLIED", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/cross-kernel" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/cross-kernel", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type DirectionRow = { direction: string; condition: string; behavior: string } +type CategoryRow = { category: string; paths: string; reason: string } +type PhaseRow = { phase: string; detail: string } +type ScenarioRow = { scenario: string; detail: string } +type CodeRefRow = { component: string; location: string } +type WhereNextItem = { label: string; href: string; tail: string } + +export default async function CrossKernelPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.crossKernel" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { crossKernel: { + directionCheck: { rows: DirectionRow[] } + safeSubsetFilter: { categoryRows: CategoryRow[] } + hydration: { phaseRows: PhaseRow[] } + concreteExamples: { rows: ScenarioRow[] } + codeReference: { rows: CodeRefRow[] } + whereNext: { items: WhereNextItem[] } + } } } + } + const ck = messages.docs.backupRestore.crossKernel + const directionRows = ck.directionCheck.rows + const categoryRows = ck.safeSubsetFilter.categoryRows + const phaseRows = ck.hydration.phaseRows + const scenarioRows = ck.concreteExamples.rows + const codeRefRows = ck.codeReference.rows + const whereNextItems = ck.whereNext.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + + + {t.rich("intro.body", { code, strong, em })} + + +

+ {t("directionCheck.heading")} +

+ +

+ {t.rich("directionCheck.intro", { code })} +

+ +
+ + + + + + + + + + {directionRows.map((row, idx) => ( + + + + + + ))} + +
DirectionConditionBehaviour
{row.direction}{t.rich(`directionCheck.rows.${idx}.condition`, { code })}{t.rich(`directionCheck.rows.${idx}.behavior`, { code, em })}
+
+ +

+ {t("whyBkNewerIsSafe.heading")} +

+ +

+ {t.rich("whyBkNewerIsSafe.body", { code, em })} +

+ +

+ {t("safeSubsetFilter.heading")} +

+ +

+ {t.rich("safeSubsetFilter.intro", { code })} +

+ +
+ + + + + + + + + + {categoryRows.map((row, idx) => ( + + + + + + ))} + +
CategoryPathsWhy they are skipped
{row.category}{t.rich(`safeSubsetFilter.categoryRows.${idx}.paths`, { code })}{t.rich(`safeSubsetFilter.categoryRows.${idx}.reason`, { code })}
+
+ +

+ {t.rich("safeSubsetFilter.outroBody", { code })} +

+ +

+ {t("hydration.heading")} +

+ +

+ {t.rich("hydration.intro", { code })} +

+ +
+ + + + + + + + + {phaseRows.map((row, idx) => ( + + + + + ))} + +
PhaseDetail
{row.phase}{t.rich(`hydration.phaseRows.${idx}.detail`, { code })}
+
+ +

+ {t("planCommit.heading")} +

+ +

+ {t.rich("planCommit.body", { code, em })} +

+ +

+ {t("flowDiagram.heading")} +

+ +

+ {t("flowDiagram.intro")} +

+ +
+        {t("flowDiagram.diagram")}
+      
+ +

+ {t("concreteExamples.heading")} +

+ +

+ {t("concreteExamples.intro")} +

+ +
+ + + + + + + + + {scenarioRows.map((row, idx) => ( + + + + + ))} + +
ScenarioWhat hydration does
{row.scenario}{t.rich(`concreteExamples.rows.${idx}.detail`, { code })}
+
+ +
+
+
+
+ +

+ {t("codeReference.heading")} +

+ +

+ {t("codeReference.intro")} +

+ +
+ + + + + + + + + {codeRefRows.map((row, idx) => ( + + + + + ))} + +
ComponentLocation
{row.component}{t.rich(`codeReference.rows.${idx}.location`, { code })}
+
+ +

+ {t("whereNext.heading")} +

+ +
    + {whereNextItems.map((item) => ( +
  • + + {item.label} + + {item.tail} +
  • + ))} +
+
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/destinations/borg/page.tsx b/web/app/[locale]/docs/backup-restore/destinations/borg/page.tsx new file mode 100644 index 00000000..6aaacdeb --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/destinations/borg/page.tsx @@ -0,0 +1,396 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { ExternalLink } from "lucide-react" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" +import CopyableCode from "@/components/CopyableCode" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations.borg.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "borg backup proxmox", + "borg repository", + "borg ssh", + "borg repokey", + "borg serve restrict-to-path", + "borg-linux64", + "borgbackup deduplication", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/destinations/borg" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/destinations/borg", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type PriorityRow = { priority: string; source: string; detail: string } +type RepoTypeRow = { type: string; url: string; detail: string } +type StrategyRow = { mode: string; label: string; detail: string } +type FileRow = { file: string; content: string } +type EnvRow = { var: string; value: string; purpose: string } +type ReferenceItem = { label: string; href: string; tail: string } +type RequirementRow = { requirement: string; detail: string } + +export default async function BorgDestinationPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations.borg" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { destinations: { borg: { + binarySourcing: { rows: PriorityRow[] } + repoTypes: { rows: RepoTypeRow[] } + serverSetup: { hostChoicesItems: string[]; requirementsRows: RequirementRow[] } + sshAuth: { strategyRows: StrategyRow[] } + savedTargets: { rows: FileRow[] } + runtimeEnv: { rows: EnvRow[] } + references: { items: ReferenceItem[] } + } } } } + } + const borg = messages.docs.backupRestore.destinations.borg + const binaryRows = borg.binarySourcing.rows + const repoTypeRows = borg.repoTypes.rows + const hostChoicesItems = borg.serverSetup.hostChoicesItems + const requirementsRows = borg.serverSetup.requirementsRows + const strategyRows = borg.sshAuth.strategyRows + const savedTargetRows = borg.savedTargets.rows + const envRows = borg.runtimeEnv.rows + const references = borg.references.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + +

+ {t("aboutBorg.heading")} +

+ +

+ {t.rich("aboutBorg.body", { code })} +

+ + + {t.rich("intro.body", { code, em })} + + +

+ {t("binarySourcing.heading")} +

+ +

+ {t.rich("binarySourcing.intro", { code })} +

+ +
+ + + + + + + + + + {binaryRows.map((row, idx) => ( + + + + + + ))} + +
#SourceDetail
{row.priority}{t.rich(`binarySourcing.rows.${idx}.source`, { code })}{t.rich(`binarySourcing.rows.${idx}.detail`, { code })}
+
+ +

+ {t("repoTypes.heading")} +

+ +

+ {t("repoTypes.intro")} +

+ +
+ + + + + + + + + + {repoTypeRows.map((row, idx) => ( + + + + + + ))} + +
TypeRepository URLDetail
{row.type}{row.url}{t.rich(`repoTypes.rows.${idx}.detail`, { code })}
+
+ +

+ {t("serverSetup.heading")} +

+ +

+ {t.rich("serverSetup.intro", { code, em })} +

+ +

+ {t("serverSetup.hostChoicesTitle")} +

+ +

+ {t("serverSetup.hostChoicesBody")} +

+ +
    + {hostChoicesItems.map((_, idx) => ( +
  • + + {t.rich(`serverSetup.hostChoicesItems.${idx}`, { code, em })} +
  • + ))} +
+ + + {t("serverSetup.lxcWarningBody")} + + +

+ {t("serverSetup.requirementsTitle")} +

+ +
+ + + + + + + + + {requirementsRows.map((row, idx) => ( + + + + + ))} + +
RequirementDetail
{t.rich(`serverSetup.requirementsRows.${idx}.requirement`, { code })}{t.rich(`serverSetup.requirementsRows.${idx}.detail`, { code, em })}
+
+ +

+ {t("serverSetup.minimalSetupTitle")} +

+ +

+ {t("serverSetup.minimalSetupBody")} +

+ + + +

+ {t.rich("serverSetup.minimalSetupNote", { code })} +

+ +

+ {t("sshAuth.heading")} +

+ +

+ {t.rich("sshAuth.intro", { code, strong })} +

+ +

+ {t("sshAuth.strategiesTitle")} +

+ +

+ {t("sshAuth.strategiesIntro")} +

+ +
+ + + + + + + + + + {strategyRows.map((row, idx) => ( + + + + + + ))} + +
ModeBest forWhat it does
{row.mode}{row.label}{t.rich(`sshAuth.strategyRows.${idx}.detail`, { code })}
+
+ +

+ {t("sshAuth.restrictTitle")} +

+ +

+ {t.rich("sshAuth.restrictBody", { code })} +

+ + + +

+ {t.rich("sshAuth.restrictNote", { code })} +

+ +

+ {t("savedTargets.heading")} +

+ +

+ {t("savedTargets.intro")} +

+ +
+ + + + + + + + + {savedTargetRows.map((row, idx) => ( + + + + + ))} + +
FileContent
{row.file}{t.rich(`savedTargets.rows.${idx}.content`, { code })}
+
+ +

+ {t("savedTargets.outro")} +

+ +

+ {t("encryption.heading")} +

+ +

+ {t.rich("encryption.body", { code })} +

+ +

+ {t("runtimeEnv.heading")} +

+ +

+ {t.rich("runtimeEnv.intro", { code })} +

+ +
+ + + + + + + + + + {envRows.map((row, idx) => ( + + + + + + ))} + +
VariableValuePurpose
{row.var}{row.value}{t.rich(`runtimeEnv.rows.${idx}.purpose`, { code })}
+
+ +

+ {t("archiveFormat.heading")} +

+ +

+ {t("archiveFormat.intro")} +

+ + + +

+ {t.rich("archiveFormat.retentionBody", { code })} +

+ +

+ {t("restoreAccess.heading")} +

+ +

+ {t.rich("restoreAccess.body", { code, em })} +

+ +

+ {t("references.heading")} +

+ +

+ {t("references.intro")} +

+ + +
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/destinations/local/page.tsx b/web/app/[locale]/docs/backup-restore/destinations/local/page.tsx new file mode 100644 index 00000000..68cbb715 --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/destinations/local/page.tsx @@ -0,0 +1,196 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" +import CopyableCode from "@/components/CopyableCode" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations.local.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox local backup", + "tar.zst archive", + "proxmox backup usb", + "proxmenux local destination", + "proxmox backup to usb drive", + "vzdump directory", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/destinations/local" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/destinations/local", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type OptionItem = string +type StateRow = { state: string; shown: string; action: string } + +export default async function LocalDestinationPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations.local" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { destinations: { local: { + targetConfig: { options: OptionItem[] } + usbFlow: { stateRows: StateRow[] } + } } } } + } + const loc = messages.docs.backupRestore.destinations.local + const options = loc.targetConfig.options + const stateRows = loc.usbFlow.stateRows + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + + + {t.rich("intro.body", { code, strong })} + + +

+ {t("targetConfig.heading")} +

+ +

+ {t.rich("targetConfig.intro", { code, strong, em })} +

+ +
    + {options.map((_, idx) => ( +
  • + + {t.rich(`targetConfig.options.${idx}`, { code, strong })} +
  • + ))} +
+ +

+ {t("usbFlow.heading")} +

+ +

+ {t.rich("usbFlow.intro", { code })} +

+ +

+ {t("usbFlow.statesTitle")} +

+ +
+ + + + + + + + + + {stateRows.map((row, idx) => ( + + + + + + ))} + +
StateMenu entryAction
{row.state}{row.shown}{t.rich(`usbFlow.stateRows.${idx}.action`, { code, strong })}
+
+ +

+ {t.rich("usbFlow.notMountedFallback", { code, em })} +

+ +

+ {t("safetyCheck.heading")} +

+ +

+ {t.rich("safetyCheck.body", { code, strong })} +

+ +

+ {t("archiveFormat.heading")} +

+ +

+ {t.rich("archiveFormat.intro", { code, strong })} +

+ + + +

+ {t("archiveFormat.compressionTitle")} +

+ +

+ {t.rich("archiveFormat.compressionBody", { code, strong })} +

+ +

+ {t("archiveFormat.sourceTitle")} +

+ +

+ {t.rich("archiveFormat.sourceBody", { code, strong })} +

+ +

+ {t("sidecar.heading")} +

+ +

+ {t.rich("sidecar.intro", { code, strong })} +

+ +

+ {t("sidecar.contentTitle")} +

+ +

+ {t.rich("sidecar.contentBody", { code })} +

+ +

+ {t.rich("sidecar.whyBody", { code, strong })} +

+ +

+ {t("restoreAccess.heading")} +

+ +

+ {t.rich("restoreAccess.body", { code, strong })} +

+
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/destinations/page.tsx b/web/app/[locale]/docs/backup-restore/destinations/page.tsx new file mode 100644 index 00000000..e8974907 --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/destinations/page.tsx @@ -0,0 +1,180 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { Link } from "@/i18n/navigation" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" +import CopyableCode from "@/components/CopyableCode" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox backup destinations", + "proxmox local backup", + "proxmox backup server", + "borg backup proxmox", + "tar zst backup", + "pbs snapshot", + "borg repository", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/destinations" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/destinations", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type ComparisonRow = { + feature: string + local: string + pbs: string + borg: string +} +type WhereNextItem = { label: string; href: string; tail: string } + +export default async function DestinationsIndexPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { destinations: { + comparison: { rows: ComparisonRow[] } + whereNext: { items: WhereNextItem[] } + } } } + } + const dest = messages.docs.backupRestore.destinations + const rows = dest.comparison.rows + const whereNextItems = dest.whereNext.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + + + {t.rich("intro.body", { code, strong })} + + +

+ {t("comparison.heading")} +

+ +

+ {t.rich("comparison.intro", { strong })} +

+ +
+ + + + + + + + + + + {rows.map((row, idx) => ( + + + + + + + ))} + +
+ {t("comparison.captionCode")} + + {t("comparison.captionLocal")} + + {t("comparison.captionPbs")} + + {t("comparison.captionBorg")} +
{row.feature}{t.rich(`comparison.rows.${idx}.local`, { code })}{t.rich(`comparison.rows.${idx}.pbs`, { code })}{t.rich(`comparison.rows.${idx}.borg`, { code })}
+
+ +

+ {t("sameArchive.heading")} +

+ +

+ {t.rich("sameArchive.body", { code, em })} +

+ +

+ {t("extractStandalone.heading")} +

+ +

+ {t.rich("extractStandalone.intro", { code, strong })} +

+ +

+ {t("comparison.captionLocal")} +

+ + +

+ {t("comparison.captionPbs")} +

+ + +

+ {t("comparison.captionBorg")} +

+ + +

+ {t.rich("extractStandalone.note", { em })} +

+ +

+ {t("whereNext.heading")} +

+ +

+ {t.rich("whereNext.intro", { em })} +

+ +
    + {whereNextItems.map((item) => ( +
  • + + {item.label} + + {item.tail} +
  • + ))} +
+
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/destinations/pbs/page.tsx b/web/app/[locale]/docs/backup-restore/destinations/pbs/page.tsx new file mode 100644 index 00000000..d4f6ec85 --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/destinations/pbs/page.tsx @@ -0,0 +1,262 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { Star, ExternalLink } from "lucide-react" +import Image from "next/image" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" +import CopyableCode from "@/components/CopyableCode" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations.pbs.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox backup server", + "pbs encryption keyfile", + "pbs recovery passphrase", + "proxmox-backup-client", + "pxar snapshot", + "pbs chunk deduplication", + "proxmenux pbs", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/destinations/pbs" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/destinations/pbs", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type SourceRow = { source: string; path: string; content: string } +type ReferenceItem = { label: string; href: string; tail: string } + +export default async function PbsDestinationPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.destinations.pbs" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { destinations: { pbs: { + repoSelection: { sourceRows: SourceRow[] } + references: { items: ReferenceItem[] } + } } } } + } + const sourceRows = messages.docs.backupRestore.destinations.pbs.repoSelection.sourceRows + const references = messages.docs.backupRestore.destinations.pbs.references.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + +
+ + +
+ +

+ {t("aboutPbs.heading")} +

+ +

+ {t.rich("aboutPbs.body", { code })} +

+ + + {t.rich("intro.body", { code, em })} + + +

+ {t("repoSelection.heading")} +

+ +

+ {t.rich("repoSelection.intro", { code })} +

+ +
+ + + + + + + + + + {sourceRows.map((row, idx) => ( + + + + + + ))} + +
SourceOn-disk stateContent
{row.source}{row.path}{t.rich(`repoSelection.sourceRows.${idx}.content`, { code, em })}
+
+ +

+ {t("repoSelection.menuTitle")} +

+ +

+ {t.rich("repoSelection.menuBody", { code })} +

+ +

+ {t("backupCommand.heading")} +

+ +

+ {t.rich("backupCommand.intro", { code })} +

+ + + +

+ {t("backupCommand.backupIdTitle")} +

+ +

+ {t.rich("backupCommand.backupIdBody", { code, em })} +

+ +

+ {t("backupCommand.pxarTitle")} +

+ +

+ {t.rich("backupCommand.pxarBody", { code })} +

+ +

+ {t("encryption.heading")} +

+ +

+ {t.rich("encryption.intro", { code, strong })} +

+ +

+ {t("encryption.keyfileTitle")} +

+ +

+ {t.rich("encryption.keyfileBody", { code })} +

+ +

+ {t("encryption.recoveryTitle")} +

+ +

+ {t.rich("encryption.recoveryBody", { code })} +

+ +

+ {t("encryption.blobUploadTitle")} +

+ +

+ {t.rich("encryption.blobUploadBody1", { code })} +

+ +
+ + {t("encryption.blobUploadImageAlt")} + +
+ {t("encryption.blobUploadImageCaption")} +
+
+ +

+ {t("encryption.blobUploadConstraintTitle")} +

+ +

+ {t.rich("encryption.blobUploadConstraintBody", { code, strong, em })} +

+ +

+ {t("encryption.recoverTitle")} +

+ +

+ {t.rich("encryption.recoverBody", { code })} +

+ +

+ {t("restoreAccess.heading")} +

+ +

+ {t.rich("restoreAccess.body", { code })} +

+ +

+ {t("references.heading")} +

+ +

+ {t("references.intro")} +

+ + +
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/how-it-works/page.tsx b/web/app/[locale]/docs/backup-restore/how-it-works/page.tsx new file mode 100644 index 00000000..7192bb8e --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/how-it-works/page.tsx @@ -0,0 +1,310 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.howItWorks.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox backup internals", + "proxmox manifest json", + "proxmox backup collectors", + "packages.manual.list", + "components_status.json", + "proxmenux rootfs", + "proxmox rsync backup", + "apply_cluster_postboot", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/how-it-works" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/how-it-works", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type CategoryRow = { category: string; paths: string; why: string } +type CollectorRow = { collector: string; produces: string; content: string } +type InstallerRow = { component: string; installer: string; action: string } +type StageRow = { stage: string; name: string; reads: string; action: string } + +export default async function HowItWorksPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.howItWorks" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { howItWorks: { + rootfs: { categoryRows: CategoryRow[] } + manifest: { collectorRows: CollectorRow[] } + applications: { installerRows: InstallerRow[] } + restoreFlow: { stageRows: StageRow[] } + } } } + } + const hw = messages.docs.backupRestore.howItWorks + const categoryRows = hw.rootfs.categoryRows + const collectorRows = hw.manifest.collectorRows + const installerRows = hw.applications.installerRows + const stageRows = hw.restoreFlow.stageRows + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + + + {t.rich("intro.body", { code, strong })} + + +

+ {t("layout.heading")} +

+ +

+ {t.rich("layout.intro", { code, strong })} +

+ +
+
+{t("layout.tree")}
+        
+
+ {t("layout.treeCaption")} +
+
+ +

+ {t("rootfs.heading")} +

+ +

+ {t.rich("rootfs.intro", { code, strong, em })} +

+ +

+ {t("rootfs.defaultProfileTitle")} +

+ +

+ {t.rich("rootfs.defaultProfileBody", { code, strong })} +

+ +
+ + + + + + + + + + {categoryRows.map((row, idx) => ( + + + + + + ))} + +
{t("rootfs.categoriesTitle")}PathsWhy
{row.category}{row.paths}{t.rich(`rootfs.categoryRows.${idx}.why`, { code })}
+
+ +

+ {t("rootfs.customTitle")} +

+ +

+ {t.rich("rootfs.customBody", { code, em, strong })} +

+ +

+ {t("rootfs.customExtrasTitle")} +

+ +

+ {t.rich("rootfs.customExtrasBody", { code, em, strong })} +

+ +

+ {t("rootfs.customModeTitle")} +

+ +

+ {t.rich("rootfs.customModeBody", { code, em, strong })} +

+ +

+ {t("rootfs.customMissingTitle")} +

+ +

+ {t.rich("rootfs.customMissingBody", { code })} +

+ +

+ {t("manifest.heading")} +

+ +

+ {t.rich("manifest.intro", { code, strong })} +

+ +
+ + + + + + + + + + {collectorRows.map((row, idx) => ( + + + + + + ))} + +
CollectorProducesContent
{row.collector}{row.produces}{t.rich(`manifest.collectorRows.${idx}.content`, { code, em })}
+
+ +

+ {t("manifest.orchestratorCaption")} +

+ +

+ {t("manifest.schemaTitle")} +

+ +

+ {t.rich("manifest.schemaBody", { code })} +

+ +

+ {t("applications.heading")} +

+ +

+ {t.rich("applications.intro", { code, strong })} +

+ +

+ {t("applications.packagesTitle")} +

+ +

+ {t.rich("applications.packagesBody", { code, em })} +

+ +

+ {t("applications.componentsTitle")} +

+ +

+ {t.rich("applications.componentsBody", { code, em })} +

+ +

+ {t("applications.componentInstallersTitle")} +

+ +

+ {t.rich("applications.componentInstallersBody", { code })} +

+ +
+ + + + + + + + + + {installerRows.map((row, idx) => ( + + + + + + ))} + +
ComponentInstallerAction on --auto-reinstall
{row.component}{row.installer}{t.rich(`applications.installerRows.${idx}.action`, { code })}
+
+ +

+ {t("restoreFlow.heading")} +

+ +

+ {t.rich("restoreFlow.intro", { code, strong })} +

+ +
+ + + + + + + + + + + {stageRows.map((row, idx) => ( + + + + + + + ))} + +
#StageReadsAction
{row.stage}{row.name}{row.reads}{t.rich(`restoreFlow.stageRows.${idx}.action`, { code, em })}
+
+ +

+ {t("restoreFlow.stagesCaption")} +

+ +

+ {t("whyItWorks.heading")} +

+ +

+ {t.rich("whyItWorks.body", { code, strong })} +

+
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/page.tsx b/web/app/[locale]/docs/backup-restore/page.tsx new file mode 100644 index 00000000..55921b8c --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/page.tsx @@ -0,0 +1,162 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { Link } from "@/i18n/navigation" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" +import { DataFlowDiagram } from "@/components/ui/data-flow-diagram" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox backup", + "proxmox host backup", + "proxmox restore", + "proxmox backup server", + "borg backup proxmox", + "cross-kernel restore", + "proxmox host restore", + "vzdump alternative", + "proxmenux backup", + "proxmox migration", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type WhatItIsNotItem = string +type WhereNextItem = { label: string; href: string; tail: string } + +export default async function BackupRestoreOverviewPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { + whatItIsNot: { items: WhatItIsNotItem[] } + whereNext: { items: WhereNextItem[] } + } } + } + const br = messages.docs.backupRestore + const whatItIsNotItems = br.whatItIsNot.items + const whereNextItems = br.whereNext.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + + + {t.rich("intro.body", { code, strong })} + + +

+ {t("whatItIsNot.heading")} +

+ +

+ {t.rich("whatItIsNot.intro", { strong })} +

+ +
    + {whatItIsNotItems.map((_, idx) => ( +
  • + + {t.rich(`whatItIsNot.items.${idx}`, { code, strong })} +
  • + ))} +
+ +

+ {t("threePillars.heading")} +

+ +

+ {t.rich("threePillars.intro", { strong })} +

+ + + +

+ {t("restoreIsUniversal.heading")} +

+ +

+ {t.rich("restoreIsUniversal.body", { strong })} +

+ +

+ {t("twoInterfaces.heading")} +

+ +

+ {t.rich("twoInterfaces.intro", { strong })} +

+ + + +

+ {t("whereNext.heading")} +

+ +

+ {t.rich("whereNext.intro", { em })} +

+ +
    + {whereNextItems.map((item) => ( +
  • + + {item.label} + + {item.tail} +
  • + ))} +
+
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/restoring/page.tsx b/web/app/[locale]/docs/backup-restore/restoring/page.tsx new file mode 100644 index 00000000..627893d1 --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/restoring/page.tsx @@ -0,0 +1,404 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import Image from "next/image" +import { Link } from "@/i18n/navigation" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.restoring.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox restore", + "proxmenux restore", + "hb_compat_check", + "apply_pending_restore", + "apply_cluster_postboot", + "path classification hot reboot dangerous", + "destructive rollback", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/restoring" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/restoring", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type ActionRow = { action: string; detail: string } +type OutputRow = { output: string; detail: string } +type ModeRow = { mode: string; detail: string } +type ClassRow = { class: string; detail: string } +type FileRow = { file: string; content: string } +type TaskRow = { task: string; detail: string } +type TimeRow = { stage: string; time: string; detail: string } +type LogRow = { log: string; detail: string } +type WhereNextItem = { label: string; href: string; tail: string } + +export default async function RestoringPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.restoring" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { restoring: { + threeActions: { actionRows: ActionRow[] } + compatibilityCheck: { outputRows: OutputRow[] } + twoModes: { modeRows: ModeRow[] } + pathClassification: { rows: ClassRow[] } + pendingMachinery: { rows: FileRow[] } + postbootDispatcher: { tasks: TaskRow[] } + tenMinutes: { rows: TimeRow[] } + logs: { rows: LogRow[] } + whereNext: { items: WhereNextItem[] } + } } } + } + const rs = messages.docs.backupRestore.restoring + const actionRows = rs.threeActions.actionRows + const outputRows = rs.compatibilityCheck.outputRows + const modeRows = rs.twoModes.modeRows + const classRows = rs.pathClassification.rows + const pendingRows = rs.pendingMachinery.rows + const postbootTasks = rs.postbootDispatcher.tasks + const timeRows = rs.tenMinutes.rows + const logRows = rs.logs.rows + const whereNextItems = rs.whereNext.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + const asciiDiagram = (text: string, caption: string) => ( +
+
+        {text}
+      
+ {caption && ( +
+ {caption} +
+ )} +
+ ) + + return ( +
+ + + + {t.rich("intro.body", { code, em })} + + +

+ {t("threeActions.heading")} +

+ +

+ {t("threeActions.intro")} +

+ +
+ + + + + + + + + {actionRows.map((row, idx) => ( + + + + + ))} + +
ActionDetail
{row.action}{t.rich(`threeActions.actionRows.${idx}.detail`, { code, em })}
+
+ +

+ {t("compatibilityCheck.heading")} +

+ +

+ {t.rich("compatibilityCheck.intro", { code })} +

+ +
+ + + + + + + + + {outputRows.map((row, idx) => ( + + + + + ))} + +
OutputWhat it drives
{row.output}{t.rich(`compatibilityCheck.outputRows.${idx}.detail`, { code, em })}
+
+ +

+ {t.rich("compatibilityCheck.reportBody", { code })} +

+ +

+ {t("twoModes.heading")} +

+ +

+ {t.rich("twoModes.intro", { em })} +

+ +
+ + + + + + + + + {modeRows.map((row, idx) => ( + + + + + ))} + +
ModeDetail
{row.mode}{t.rich(`twoModes.modeRows.${idx}.detail`, { code, em })}
+
+ +

+ {t("pathClassification.heading")} +

+ +

+ {t.rich("pathClassification.intro", { code })} +

+ +
+ + + + + + + + + {classRows.map((row, idx) => ( + + + + + ))} + +
ClassBehaviour
{row.class}{t.rich(`pathClassification.rows.${idx}.detail`, { code, em, strong })}
+
+ +

+ {t("fullFlow.heading")} +

+ +

+ {t("fullFlow.intro")} +

+ + {asciiDiagram(t("fullFlow.diagram"), "")} + +

+ {t("pendingMachinery.heading")} +

+ +

+ {t.rich("pendingMachinery.intro", { code })} +

+ +
+ + + + + + + + + {pendingRows.map((row, idx) => ( + + + + + ))} + +
FileContent
{row.file}{t.rich(`pendingMachinery.rows.${idx}.content`, { code })}
+
+ +

+ {t.rich("pendingMachinery.unitBody", { code })} +

+ +

+ {t("postbootDispatcher.heading")} +

+ +

+ {t.rich("postbootDispatcher.intro", { code })} +

+ +
+ + + + + + + + + {postbootTasks.map((row, idx) => ( + + + + + ))} + +
TaskDetail
{row.task}{t.rich(`postbootDispatcher.tasks.${idx}.detail`, { code })}
+
+ +

+ {t("postbootExample.heading")} +

+ +

+ {t.rich("postbootExample.body", { code })} +

+ +
+ + {t("postbootExample.imageAlt")} + +
+ {t("postbootExample.imageCaption")} +
+
+ +

+ {t("tenMinutes.heading")} +

+ +

+ {t("tenMinutes.intro")} +

+ +
+ + + + + + + + + + {timeRows.map((row, idx) => ( + + + + + + ))} + +
StageTimeDetail
{row.stage}{row.time}{t.rich(`tenMinutes.rows.${idx}.detail`, { code })}
+
+ +

+ {t.rich("tenMinutes.outroBody", { code })} +

+ +

+ {t("destructiveRollback.heading")} +

+ +

+ {t.rich("destructiveRollback.body", { code, em })} +

+ +

+ {t("logs.heading")} +

+ +
+ + + + + + + + + {logRows.map((row, idx) => ( + + + + + ))} + +
Log pathContent
{row.log}{t.rich(`logs.rows.${idx}.detail`, { code })}
+
+ +

+ {t("whereNext.heading")} +

+ +
    + {whereNextItems.map((item) => ( +
  • + + {item.label} + + {item.tail} +
  • + ))} +
+
+ ) +} diff --git a/web/app/[locale]/docs/backup-restore/scheduled-jobs/page.tsx b/web/app/[locale]/docs/backup-restore/scheduled-jobs/page.tsx new file mode 100644 index 00000000..c097f4ad --- /dev/null +++ b/web/app/[locale]/docs/backup-restore/scheduled-jobs/page.tsx @@ -0,0 +1,272 @@ +import type { Metadata } from "next" +import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" +import { Info } from "lucide-react" +import { Link } from "@/i18n/navigation" +import { DocHeader } from "@/components/ui/doc-header" +import { Callout } from "@/components/ui/callout" + +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}): Promise { + const { locale } = await params + const t = await getTranslations({ locale, namespace: "docs.backupRestore.scheduledJobs.meta" }) + return { + title: t("title"), + description: t("description"), + keywords: [ + "proxmox scheduled backup", + "proxmenux backup job", + "systemd timer backup", + "vzdump attach hook", + "keep-last keep-daily", + "backup retention proxmox", + ], + alternates: { canonical: "https://proxmenux.com/docs/backup-restore/scheduled-jobs" }, + openGraph: { + title: t("ogTitle"), + description: t("ogDescription"), + type: "article", + url: "https://proxmenux.com/docs/backup-restore/scheduled-jobs", + }, + twitter: { + card: "summary_large_image", + title: t("twitterTitle"), + description: t("twitterDescription"), + }, + } +} + +type ModeRow = { mode: string; backends: string; schedule: string; retention: string } +type StepRow = { step: string; detail: string } +type LayoutRow = { path: string; content: string } +type MgmtRow = { action: string; detail: string } +type WhereNextItem = { label: string; href: string; tail: string } + +export default async function ScheduledJobsPage({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params + setRequestLocale(locale) + const t = await getTranslations({ locale, namespace: "docs.backupRestore.scheduledJobs" }) + + const messages = (await getMessages({ locale })) as unknown as { + docs: { backupRestore: { scheduledJobs: { + intro: { modelsList: string[] } + modes: { rows: ModeRow[] } + attachDetail: { steps: StepRow[] } + storageLayout: { rows: LayoutRow[] } + runner: { steps: string[] } + management: { rows: MgmtRow[] } + whereNext: { items: WhereNextItem[] } + } } } + } + const sj = messages.docs.backupRestore.scheduledJobs + const modelsList = sj.intro.modelsList + const modeRows = sj.modes.rows + const attachSteps = sj.attachDetail.steps + const layoutRows = sj.storageLayout.rows + const runnerSteps = sj.runner.steps + const mgmtRows = sj.management.rows + const whereNextItems = sj.whereNext.items + + const code = (chunks: React.ReactNode) => {chunks} + const strong = (chunks: React.ReactNode) => {chunks} + const em = (chunks: React.ReactNode) => {chunks} + + return ( +
+ + +

+ {t("intro.title")} +

+ +

+ {t("intro.body")} +

+ +
    + {modelsList.map((_, idx) => ( +
  • + + {t.rich(`intro.modelsList.${idx}`, { code, strong })} +
  • + ))} +
+ +
+
+
+
+ +

+ {t("modes.heading")} +

+ +
+ + + + + + + + + + + {modeRows.map((row, idx) => ( + + + + + + + ))} + +
ModeBackendsScheduleRetention
{row.mode}{row.backends}{t.rich(`modes.rows.${idx}.schedule`, { code })}{t.rich(`modes.rows.${idx}.retention`, { code })}
+
+ +

+ {t("attachDetail.heading")} +

+ +

+ {t.rich("attachDetail.intro", { code })} +

+ +
+ + + + + + + + + {attachSteps.map((row, idx) => ( + + + + + ))} + +
#Detail
{row.step}{t.rich(`attachDetail.steps.${idx}.detail`, { code })}
+
+ +

+ {t.rich("attachDetail.outroBody", { code })} +

+ +

+ {t("storageLayout.heading")} +

+ +

+ {t("storageLayout.intro")} +

+ +
+ + + + + + + + + {layoutRows.map((row, idx) => ( + + + + + ))} + +
PathContent
{row.path}{t.rich(`storageLayout.rows.${idx}.content`, { code })}
+
+ +

+ {t.rich("runner.heading", { code })} +

+ +

+ {t("runner.intro")} +

+ +
    + {runnerSteps.map((_, idx) => ( +
  1. + {t.rich(`runner.steps.${idx}`, { code, strong })} +
  2. + ))} +
+ +

+ {t("management.heading")} +

+ +

+ {t("management.intro")} +

+ +
+ + + + + + + + + {mgmtRows.map((row, idx) => ( + + + + + ))} + +
ActionDetail
{row.action}{t.rich(`management.rows.${idx}.detail`, { code })}
+
+ +

+ {t("notifications.heading")} +

+ +

+ {t.rich("notifications.body", { code })} +

+ +

+ {t("whereNext.heading")} +

+ +
    + {whereNextItems.map((item) => ( +
  • + + {item.label} + + {item.tail} +
  • + ))} +
+
+ ) +} diff --git a/web/app/[locale]/docs/monitor/dashboard/network/page.tsx b/web/app/[locale]/docs/monitor/dashboard/network/page.tsx index f4c7c021..b2f218af 100644 --- a/web/app/[locale]/docs/monitor/dashboard/network/page.tsx +++ b/web/app/[locale]/docs/monitor/dashboard/network/page.tsx @@ -34,6 +34,7 @@ export default async function NetworkTabPage({ const messages = (await getMessages({ locale })) as unknown as { docs: { monitor: { dashboard: { network: { topRow: { rows: TopRow[] } + flow: { elements: string[]; pulses: string[] } groups: { badges: string[] } drillIn: { rows: DrillRow[] } latency: { @@ -48,6 +49,8 @@ export default async function NetworkTabPage({ } const net = messages.docs.monitor.dashboard.network const topRows = net.topRow.rows + const flowElements = net.flow.elements + const flowPulses = net.flow.pulses const badges = net.groups.badges const drillRows = net.drillIn.rows const targets = net.latency.targets @@ -96,6 +99,42 @@ export default async function NetworkTabPage({ +

{t("flow.heading")}

+

+ {t.rich("flow.intro", { strong })} +

+ +
+ {t("flow.imageAlt")} +
+ {t("flow.imageCaption")} +
+
+ +

{t("flow.elementsTitle")}

+

{t("flow.elementsIntro")}

+
    + {flowElements.map((_, idx) => ( +
  • {t.rich(`flow.elements.${idx}`, { strong })}
  • + ))} +
+ +

{t("flow.pulsesTitle")}

+

{t("flow.pulsesBody")}

+
    + {flowPulses.map((_, idx) => ( +
  • {t.rich(`flow.pulses.${idx}`, { strong, em })}
  • + ))} +
+ + + {t.rich("flow.useBody", { em })} + +

{t("groups.heading")}

{t.rich("groups.intro", { strong })} diff --git a/web/app/[locale]/docs/monitor/dashboard/system-overview/page.tsx b/web/app/[locale]/docs/monitor/dashboard/system-overview/page.tsx index 55d3fea1..f1acd3c7 100644 --- a/web/app/[locale]/docs/monitor/dashboard/system-overview/page.tsx +++ b/web/app/[locale]/docs/monitor/dashboard/system-overview/page.tsx @@ -31,6 +31,7 @@ export default async function SystemOverviewTabPage({ const messages = (await getMessages({ locale })) as unknown as { docs: { monitor: { dashboard: { systemOverview: { topRow: { rows: TopRow[]; thresholdsItems: string[] } + processes: { listItems: string[]; detailItems: string[] } bottom: { storageItems: string[] } refresh: { items: string[] } dataCollected: { rows: DataRow[] } @@ -40,6 +41,8 @@ export default async function SystemOverviewTabPage({ const so = messages.docs.monitor.dashboard.systemOverview const topRows = so.topRow.rows const thresholdsItems = so.topRow.thresholdsItems + const processListItems = so.processes.listItems + const processDetailItems = so.processes.detailItems const storageItems = so.bottom.storageItems const refreshItems = so.refresh.items const dataRows = so.dataCollected.rows @@ -135,6 +138,53 @@ export default async function SystemOverviewTabPage({ {t("topRow.sparklineBody")} +

{t("processes.heading")}

+

+ {t.rich("processes.intro", { code })} +

+ +

{t("processes.listTitle")}

+
    + {processListItems.map((_, idx) => ( +
  • {t.rich(`processes.listItems.${idx}`, { strong })}
  • + ))} +
+ +
+ {t("processes.captureListAlt")} +
+ {t("processes.captureListCaption")} +
+
+ +

{t("processes.detailTitle")}

+

+ {t.rich("processes.detailIntro", { code })} +

+
    + {processDetailItems.map((_, idx) => ( +
  • {t.rich(`processes.detailItems.${idx}`, { strong, code })}
  • + ))} +
+

+ {t.rich("processes.detailRefresh", { em, code })} +

+ +
+ {t("processes.captureDetailAlt")} +
+ {t("processes.captureDetailCaption")} +
+
+

{t("middle.heading")}

{t.rich("middle.body1", { code, em })} diff --git a/web/app/[locale]/docs/post-install/automated/page.tsx b/web/app/[locale]/docs/post-install/automated/page.tsx index a2ca3256..59b1ba66 100644 --- a/web/app/[locale]/docs/post-install/automated/page.tsx +++ b/web/app/[locale]/docs/post-install/automated/page.tsx @@ -79,6 +79,9 @@ export default async function AutomatedPage({ const uninstallLink = (chunks: React.ReactNode) => ( {chunks} ) + const log2ramLink = (chunks: React.ReactNode) => ( + {chunks} + ) return (

@@ -116,7 +119,9 @@ export default async function AutomatedPage({ {i + 1} {o.tool} - {o.what} + + {t.rich(`optimizations.${i}.what`, { link: log2ramLink })} + {chunks} const strong = (chunks: React.ReactNode) => {chunks} @@ -328,6 +330,46 @@ chmod +x "/etc/profile.d/figurine.sh"

{t("figurine.outro")}

+

+ + {t("log2ram.title")} +

+ +

+ {t.rich("log2ram.intro", { code, em })} +

+ +

+ {t("log2ram.upstreamLabel")} + + {t("log2ram.upstreamLinkLabel")} + +

+ +

+ {t("log2ram.doesLabel")} +

+
    + {log2ramItems.map((_, idx) => ( +
  • {t.rich(`log2ram.doesItems.${idx}`, { code, em })}
  • + ))} +
+ +

+ {t("log2ram.howUseLabel")} {t("log2ram.howUseBody")} +

+ +

+ {t("log2ram.verifyLabel")} +

+ +

{t("autoApplication.title")}

{t("autoApplication.body")}

diff --git a/web/app/[locale]/docs/post-install/storage/page.tsx b/web/app/[locale]/docs/post-install/storage/page.tsx index 7d21c57e..c8665852 100644 --- a/web/app/[locale]/docs/post-install/storage/page.tsx +++ b/web/app/[locale]/docs/post-install/storage/page.tsx @@ -64,7 +64,15 @@ export default async function PostInstallStoragePage({ /> - {t.rich("intro.body", { strong })} + {t.rich("intro.body", { + strong, + code, + link: (chunks) => ( + + {chunks} + + ), + })} diff --git a/web/components/DocSidebar.tsx b/web/components/DocSidebar.tsx index 7cb37ef0..7e12219c 100644 --- a/web/components/DocSidebar.tsx +++ b/web/components/DocSidebar.tsx @@ -248,6 +248,31 @@ export const sidebarItems: MenuItem[] = [ ], }, + { + title: "Backup & Restore", + i18nKey: "backupRestore", + href: "/docs/backup-restore", + submenu: [ + { title: "Overview", i18nKey: "backupRestoreOverview", href: "/docs/backup-restore" }, + { title: "How it works", i18nKey: "backupRestoreHowItWorks", href: "/docs/backup-restore/how-it-works" }, + { + title: "Destinations", + i18nKey: "backupRestoreDestinations", + href: "/docs/backup-restore/destinations", + submenu: [ + { title: "Overview", i18nKey: "backupRestoreDestOverview", href: "/docs/backup-restore/destinations" }, + { title: "Local archive", i18nKey: "backupRestoreDestLocal", href: "/docs/backup-restore/destinations/local" }, + { title: "Proxmox Backup Server", i18nKey: "backupRestoreDestPbs", href: "/docs/backup-restore/destinations/pbs" }, + { title: "Borg", i18nKey: "backupRestoreDestBorg", href: "/docs/backup-restore/destinations/borg" }, + ], + }, + { title: "Creating backups", i18nKey: "backupRestoreCreating", href: "/docs/backup-restore/creating-backups" }, + { title: "Scheduled jobs", i18nKey: "backupRestoreJobs", href: "/docs/backup-restore/scheduled-jobs" }, + { title: "Restoring", i18nKey: "backupRestoreRestoring", href: "/docs/backup-restore/restoring" }, + { title: "Cross-kernel restore", i18nKey: "backupRestoreCrossKernel", href: "/docs/backup-restore/cross-kernel" }, + ], + }, + { title: "Commands Reference", i18nKey: "commandsReference", diff --git a/web/data/changelog/es.md b/web/data/changelog/es.md new file mode 100644 index 00000000..09a77865 --- /dev/null +++ b/web/data/changelog/es.md @@ -0,0 +1,1240 @@ + +## 2026-06-02 + +### Nueva versión ProxMenux v1.2.2 — *Consolidación estable del ciclo v1.2.1.x* + +Release estable que lleva al canal principal las cuatro prereleases del ciclo **v1.2.1.x** en un solo movimiento. El trabajo a lo largo de esas cuatro betas se centró en tres temas: hacer del Health Monitor algo realmente configurable en lugar de solo observable (thresholds por categoría, duraciones de dismiss por evento, un audit log de supresiones activas), expandir el stack de notificaciones para cubrir alrededor de 80 servicios a través de Apprise mientras se persisten eventos durante las Quiet Hours, y convertir el propio proceso del Monitor en un ciudadano del sistema más silencioso y predecible en hosts idle. Por encima de eso, esta release entrega detección automática de updates en contenedores LXC, una reescritura end-to-end del instalador de Coral TPU con los últimos drivers upstream, y una larga lista de fixes visibles para el operador — handshake del terminal HTTPS, detección de kernel updates en PVE 9.x, flujo del instalador NVIDIA en Alpine LXC, gestión del audio acompañante en passthrough de GPU mixta, y varias optimizaciones runtime en los bucles de scan del Monitor. Cinco contribuciones de código directas de la comunidad shipean junto con esta release ([@jcastro](https://github.com/jcastro) ×5, [@pespinel](https://github.com/pespinel) ×1) y el trabajo de GPU passthrough lo impulsaron los reports detallados de campo de [@ghosthvj](https://github.com/ghosthvj) — ver los Acknowledgments al final. + +--- + +## 🩺 Health Monitor — Configurable, granular, auditable + +Tres piezas acopladas que juntas permiten al operador ajustar el Health Monitor a la envoltura real de su host en lugar de trabajar alrededor de sus defaults, y gestionar dismisses con el mismo control fino que ya tienen sobre el resto del dashboard. + +### Thresholds Warning / Critical por categoría + +Cada check que corre el Health Monitor está parametrizado por un par de números — un *Warning* y un *Critical* — y ambos están ahora expuestos bajo **Settings → Health Monitor Thresholds**. Los defaults que shipean con ProxMenux son razonables para el host Proxmox medio, pero cada entorno tiene su propia envoltura: + +- Un homelab pequeño con un único SSD quiere paginar antes en capacidad (75 / 90 %) para dejar margen a snapshots. +- Un nodo de datacenter con almacenamiento Ceph redundante puede ser mucho más relajado con los warnings de memoria (un working set del 90 % es normal con ZFS ARC). +- Un mini-PC refrigerado pasivamente necesita thresholds de temperatura más bajos que un servidor con refrigeración forzada — misma clase de disco, distinta envoltura física. + +Los cambios surten efecto en el siguiente scan — el Health Monitor relee los valores desde `/usr/local/share/proxmenux/health_thresholds.json` en cada ciclo, sin restart del servicio. Los mismos números también alimentan los rangos de color de cada widget del dashboard (barras de almacenamiento, anillos de CPU/memoria, chips de temperatura, el punto del modal de disco), de forma que la clasificación visual en cualquier punto del Monitor mapea a un rango definido respecto al par configurado. + +### Duración de dismiss por evento con badge Permanent + +El botón *Dismiss* en cada alerta del Health Monitor abre ahora un pequeño dropdown con tres opciones: + +- **24 hours** — default anterior, se comporta exactamente como antes +- **7 days** — útil para una condición temporal de la que no quieres oír durante una migración de una semana +- **Permanently** — silencia este `error_key` concreto indefinidamente + +Los dismisses permanentes se persisten con `suppression_hours = -1` en la base de datos de persistencia, nunca re-emiten, nunca re-notifican y se marcan con un badge ámbar **Permanent** distinto en el Health Monitor para que el operador siempre sepa qué alertas están silenciadas intencionadamente. La infraestructura backend para el centinela permanente ya existía — solo le faltaba a la UI una forma de fijarlo. El contrato de API es pequeño y backwards-compatible: `POST /api/health/acknowledge` acepta un campo body opcional `suppression_hours` (entero positivo para horas, `-1` para permanente); omitirlo preserva el comportamiento previo y usa la supresión configurada de la categoría. Un segundo endpoint nuevo `POST /api/health/un-acknowledge {error_key}` limpia un acknowledgment previamente registrado para que la alerta vuelva a ser elegible para dispararse — usado por el panel Active Suppressions abajo. + +### Panel Active Suppressions en Settings + +Una nueva sección dentro de **Settings → Health Monitor**, justo debajo de las duraciones de supresión por categoría, lista cada alerta actualmente silenciada — tanto los dismisses con tiempo limitado (con un badge *22h remaining* / *6d remaining*) como los permanentes (con el badge ámbar *Permanent* del dashboard). Cada fila lleva el `error_key`, la categoría, la severidad, el timestamp en que se registró el dismiss, y un botón **Re-enable** que limpia el acknowledgment server-side. Los re-enables están **encolados** — pulsar el botón marca la fila en verde con texto tachado y cambia el botón a *Undo*, y el `POST /api/health/un-acknowledge` real solo se dispara cuando el usuario pulsa **Save**, de modo que un lote de re-enables se entrega atómicamente junto a cualquier cambio pendiente de dropdown por categoría. La acción está protegida por el toggle de *Edit* del Health Monitor en la parte superior de la card. Los dismisses permanentes **solo pueden revertirse desde aquí** — el dashboard no expone intencionadamente un affordance per-alerta de un-dismiss para evitar re-enables accidentales, por lo que el panel de Settings es la superficie deliberada de auditoría + revert para ellos. La lista también se refresca automáticamente cuando se hace un dismiss de una alerta desde el modal del Health Monitor mientras la página de Settings ya está abierta, vía un evento `health-suppression-changed` del navegador más listeners en `window focus` y `document visibilitychange`. + +### Tiers de severidad de Disk I/O + +Una ventana deslizante de 24 h clasifica ahora los errores ATA / SCSI de dmesg en tres buckets: silencioso (0–10 eventos transitorios), WARNING (11–100) y CRITICAL (100+, o cualquier error duro como UNC / Buffer I/O / Sense Key Hardware Error). Los días tranquilos se quedan tranquilos, pero un único evento de Buffer I/O sigue paginando inmediatamente. + +--- + +## 📨 Canal de notificación Apprise — Paridad completa de features + +La integración Apprise que aterrizó como un adapter básico en 1.2.1.4-beta se ha graduado a paridad completa con los canales nativos. Una sola URL de Apprise llega ahora a alrededor de 80 servicios de notificación (Pushover, ntfy, Slack, Matrix, mailto, signal, Pushbullet, Mattermost, Microsoft Teams vía webhooks, …) sin que ProxMenux necesite un adapter dedicado para cada uno. La pestaña Apprise en Notifications expone los mismos controles que Telegram, Gotify, Discord y Email: + +- El bloque **Notification Categories** completo — las mismas 10 categorías con sus sub-toggles por evento, idéntico a los otros canales +- **Quiet Hours** — ventana start/end por canal, con el mismo comportamiento de buffering (los eventos disparados durante la ventana se persisten en SQLite y se liberan como un resumen agrupado cuando la ventana cierra, en lugar de dropearse silenciosamente) +- **Daily Digest** — entrega opt-in de un resumen una vez al día a una hora elegida + +El filtrado per-channel del backend ya aplicaba genéricamente a cada canal incluyendo Apprise vía el bloque `channel_overrides` — la UI simplemente no estaba surfaceando los controles. + +Tres fixes de fiabilidad shipean junto, todos surfaceados después del rollout beta inicial: + +1. **Mobile overflow** en viewports estrechos. La fila de Apprise URL solía romper el diseño — el placeholder empaquetaba cuatro URLs de ejemplo completas en una línea y los `` inline de los ejemplos no tenían regla `break-all`. El placeholder es ahora un único ejemplo conciso (`tgram://bottoken/ChatID`), el wrapper del input URL fuerza `min-w-0 / flex-1 / shrink-0` en sus children, y el párrafo de ejemplos usa `break-all min-w-0` para que envuelva limpiamente a cualquier ancho. + +2. **Regresión del whitelist backend** que rechazaba Apprise con HTTP 400. El conjunto de canales hardcodeado del validador de notifications-test (`{telegram, gotify, discord, email, all}`) tenía a `apprise` ausente, por lo que cada test o send de Apprise devolvía `400 Invalid channel` antes de que la librería fuera siquiera invocada. El whitelist se deriva ahora en vivo desde `notification_channels.CHANNEL_TYPES`, de forma que añadir una nueva implementación de canal en el futuro no puede regresionar silenciosamente este validador otra vez. + +3. **Error reporting opaco** cuando el destino devolvía una respuesta no-2xx. Cuando un destino (`jsons://`, `ntfy://`, `slack://`, …) rechazaba el payload, el operador solo veía un mensaje genérico *"Apprise rejected the notification (transport failure)"*. El canal captura ahora el logger interno de Apprise durante `notify()` y surfacea el HTTP status code real más el response body del destino (capado a 300 caracteres) — de forma que un beta tester debuggeando un webhook custom puede ver inmediatamente si el servidor upstream está rechazando su schema de payload. + +--- + +## 📦 Detección de updates en LXC + +Una nueva sección dedicada en **Settings** (entre *Health Monitor Thresholds* y *Notifications*) con un único toggle que protege el scan per-CT de `apt list --upgradable` / `apk list -u` end-to-end. Default ON. Cuando está OFF el scan para completamente (sin llamadas `pct exec`), cada entrada `type=lxc` se purga inmediatamente del registry de managed-installs, y el toggle de notificación correspondiente en *Notifications → Services* desaparece de la UI mientras preserva su preferencia almacenada. + +El checker lee también el `mtime` de la caché de metadata del package-manager de cada CT y dispara `apt-get update` / `apk update` desde fuera vía `pct exec` si tiene más de 24 h, con timeout de 60 s y fallo silencioso. Los CTs appliance long-running cuyas cachés estaban meses obsoletas surfacean por fin su backlog real upstream — un CT Debian 12 con caché de 524 días pasó de "0 updates" a "117 (12 security)" en hardware de lab. + +--- + +## 🐧 Coral TPU en LXC — Últimos drivers upstream + +El instalador de Coral para LXC (`scripts/gpu_tpu/install_coral_lxc.sh`) se ha reescrito end-to-end para instalar el **último driver `gasket-dkms` upstream** y el **último runtime `libedgetpu1`** (220 líneas añadidas, 150 eliminadas). Los módulos Coral M.2 / mPCIe que antes fallaban al compilar en kernels PVE 9 ahora instalan y bindean limpiamente. Las notificaciones de update registry-driven que aterrizaron en 1.2.1.2 mantienen ambos paquetes frescos en adelante: la pestaña Hardware + Notifications señalizan cuando feranick/gasket-driver publica una release nueva, y el runtime `libedgetpu1` tracked vía `apt` recibe el flujo estándar de System Updates. + +El **path de uninstall del instalador Coral** acompañante también aterriza en este ciclo — espejando el flujo NVIDIA para que una instalación Coral pueda revertirse limpiamente si el usuario re-despliega el host sin aceleración TPU. + +--- + +## ⚡ Optimizaciones de rendimiento del Monitor + +Una corrida de strace + sampling de 10 minutos sobre un host live surfaceó tres sitios donde el scanner de background del Monitor estaba spawning subprocesos más agresivamente de lo necesario. Los tres están arreglados en 1.2.2: + +### Tormenta de subprocesos fail2ban +En hosts donde `fail2ban-client` no estaba instalado, el wrapper de caché alrededor de `_f2b_get_banned_ips()` solo actualizaba su timestamp en éxito. Cada request HTTP al dashboard caía a través del check de caché y disparaba un `execve("fail2ban-client", ...)` fresco que inmediatamente fallaba con `ENOENT` — 250+ llamadas `execve` fallidas en una ventana de 10 minutos. `shutil.which('fail2ban-client')` se resuelve ahora **una vez** al cargar el módulo y el timestamp de caché se actualiza incondicionalmente. Los hosts sin Fail2Ban tienen ahora cero syscalls de `fail2ban-client` por request. + +### Colisión del scheduler de smartctl +El polling de temperatura SMART de discos, la lectura de temperatura CPU y la probe de latency solían dispararse en el mismo offset dentro de cada minuto, produciendo un spike medible de CPU / IO cuando todos sus subprocesos spawneaban juntos. Las polls están ahora staggered (latency primero, luego temperatura CPU, luego SMART de disco) preservando la cadencia per-disco de 60 s — el spike ha desaparecido, el CPU total bajo carga no cambia. + +### Subproceso de inventario LXC +El mount monitor solía llamar `lxc-info -n -p` por cada CT corriendo solo para obtener su PID init. Ahora lee `/proc//task//children` directamente y solo cae a `lxc-info` cuando la lectura de `/proc` falla. Un subproceso por CT por ciclo de scan eliminado — medible en hosts con 20+ contenedores. + +--- + +## 🔌 Handshake del terminal HTTPS + +Cada modal de terminal en el Monitor (terminal del dashboard, terminal LXC, terminal de scripts) solía fallar con *WebSocket connection error* en hosts donde HTTPS estaba habilitado. La root cause era específica al path `gevent + SSL`: el `WebSocketHandler` de gevent-websocket estaba apilado sobre la implementación de protocolo de flask-sock, por lo que el servidor emitía **dos** cabeceras `HTTP/1.1 101 Switching Protocols` consecutivas y el navegador cerraba la conexión como un frame corrupto. Quitar el argumento explícito `handler_class=WebSocketHandler` restaura una única respuesta 101 y el handshake completa con normalidad. El fix es invisible para operadores corriendo en HTTP plano — no estaban afectados — pero desbloquea cada install fronteada por HTTPS (reverse proxies, deployments con certificate-managed, cualquier cosa detrás de nginx/Traefik). + +Adicionalmente, el panel de terminal solía perder su conexión WebSocket cuando el usuario activaba la feature de auto-traducción del navegador (los prompts "translate this page" de Chrome / Edge / Safari). El traductor mueve nodos del DOM que React aún mantiene como refs, y el componente WebSocket React se rompe porque su ref de contenedor apunta a un nodo movido. Añadido `translate="no"` en los divs contenedores del terminal para que el traductor salte el tty embebido por completo — las traducciones en el resto de la página siguen funcionando. + +--- + +## 🐧 Health Monitor — Detección de kernel updates en PVE 9.x (#208) + +En hosts Proxmox VE 9.x, la fila *System Updates → Kernel / PVE* reportaba "Kernel/PVE up to date" incluso cuando un update para el kernel corriendo estaba esperando upstream. Tres causas combinadas, tres fixes combinados: + +1. **La lista de prefijos de kernel-packages** incluye ahora `proxmox-kernel-*` y `proxmox-firmware-*` — PVE 9.x shipea kernels bajo `proxmox-kernel-`, no el prefijo `pve-kernel-` de 7.x / 8.x. El regex anterior nunca matcheaba los paquetes nuevos y por tanto nunca flagueaba ningún update de kernel en 9.x. + +2. **El dry-run cambió de `apt-get upgrade --dry-run` a `apt-get dist-upgrade --dry-run`**. PVE 9 shipea kernel updates empaquetados como instalaciones nuevas (no como upgrades directas de un paquete existente), y el `upgrade --dry-run` plano no considera nuevas instalaciones en absoluto. `dist-upgrade --dry-run` sí. + +3. **La detección del kernel corriendo** lee ahora `uname -r` y flaguea un update como *running-kernel update* cuando el paquete matchea la release corriendo exactamente o su meta-package de branch (p. ej. `proxmox-kernel-6.14` para un host en `6.14.11-4-pve`). El texto de la fila distingue *"Running kernel update available (reboot required)"* de *"N kernel update(s) available (none for running kernel)"* para que el operador sepa si necesita reboot o solo instalar. + +--- + +## 🟢 Instalador NVIDIA + +Varias mejoras impulsadas por los reports detallados de campo de [@ghosthvj](https://github.com/ghosthvj) sobre configuraciones de GPU mixta (ver Acknowledgments): + +- **Ventana de compatibilidad de kernel** — el menú de versiones respeta ahora el rango de drivers compatibles del kernel corriendo, ofreciendo solo branches que no fallarán al compilar contra el kernel del host. +- **Soporte Alpine LXC** — el install de userspace container-side se reescribió para que succeda en hosts Alpine; la detección de espacio libre funciona fiablemente en todos los layouts de almacenamiento (LVM-thin, ZFS, directory, etc). +- **NVENC patch awareness** — cuando el host tiene el patch NVENC aplicado, el menú de versiones se estrecha a drivers soportados por el patch para que reinstalar nunca lo pierda silenciosamente. +- **Feedback de uninstall** — el path de uninstall reporta ahora un mensaje claro de completación en lugar de volver al menú en silencio. + +--- + +## 🌐 Sitio de documentación — Migración i18n completa + +El sitio de documentación acompañante (proxmenux.com) shipea ahora bajo URLs prefijadas por locale (`/en/...` y `/es/...`) con la plumbing de next-intl. Cada doc page es bilingüe — 107 páginas traducidas al español (sin placeholders copy-of-English). El root `/` redirige a `/en/` vía meta-refresh + JS para que la URL apex siga resolviendo a algo útil. Los RSS feeds funcionan per-locale en `/en/rss.xml` y `/es/rss.xml`, con el canonical `/rss.xml` conservado para backwards compatibility con suscriptores de feed existentes. La búsqueda client-side está wireada vía **Pagefind** — el índice se construye fresh en cada deploy de CI desde el HTML output final y se descarga fragmentariamente por el cliente, así que la búsqueda funciona sin un servidor backend. + +Nuevas páginas de documentación cubren la sección **Active Suppressions** en la pestaña Settings y el **dropdown Dismiss por evento** en el modal del Health Monitor, ambas con capturas reflejando la nueva UI. + +--- + +## 🔧 Otras mejoras + +- **Sección AI Enhancement en Notifications** — reescrita de una fila uppercase atenuada que los testers consistentemente scrolleaban sin ver, a un label foreground normal-case con un icono `Sparkles` líder y un badge persistente (verde *Active* cuando IA está habilitada, neutro *Optional* cuando no lo está) para que la feature sea descubrible independientemente del estado. +- **Monitorización de temperatura de discos** — readings mejorados, caching más inteligente entre probes SMART, y un modal de historia rediseñado que abre a 24 h por defecto con estadísticas min / avg / max. +- **Detección de updates de funciones post-install** — el Monitor trackea optimizaciones ProxMenux instaladas (Log2Ram, Memory Settings, System Limits, Logrotate, …) y notifica cuando hay una versión más nueva disponible, con apply one-click desde Settings. +- **Flujo de update de Secure Gateway (Tailscale)** — update one-click de Tailscale desde Settings con indicadores Last-checked / Installed / Latest y notificación cuando se publica una nueva versión. +- **Menú Helper-Scripts** — context más rico e información útil para cada entrada, haciendo más fácil saber qué hace cada script antes de ejecutarlo. +- **Wording de agregación burst** — los resúmenes burst reportan ahora solo los eventos *adicionales* que llegaron después de la alerta individual inicial, de forma que el operador ya no ve el primer evento contado dos veces. +- **Clasificador de errores conocidos** — regex con word-boundary en patrones ATA / UNC para que mensajes de kernel como `nvidia_uvm:FatalError` ya no se clasifiquen mal como problemas de cable ATA. +- **Errores de control de VM / CT** — start / stop / restart fallido surfacea ahora el stderr real de `pvesh` (p. ej. *"no space left on device"*) en el toast de la UI y dispara una notificación `vm_fail` / `ct_fail`, en lugar del bare 500 INTERNAL SERVER ERROR que el operador solía ver. +- **Path de apply de log2ram** — el flujo auto / update reinicia ahora log2ram después de escribir el nuevo size, de forma que un `512M` configurado realmente surte efecto en el tmpfs corriendo sin restart manual. +- **PVE webhook URL** — el webhook de notificación sigue ahora automáticamente el estado SSL activo, cambiando entre `http://` y `https://` cuando toggleas HTTPS en el panel. +- **Cascada de 401 frontend** — la login screen ya no se traga un 401 para siempre tras un estado breve de token rancio; la flag de dedup se limpia al mount y al login exitoso. + +--- + +## 🙏 Acknowledgments + +Esta release incluye contribuciones de código directas de la comunidad y una cantidad sustancial de feedback que dio forma al diseño. Particular agradecimiento a: + +### Contributors de código + +**[@jcastro](https://github.com/jcastro)** entregó cinco mejoras directas que shipean con v1.2.2: + +- **Selección de ISOs de VM desde todos los almacenamientos ISO** — nuevo helper compartido `scripts/global/iso_storage_helpers.sh` más integración en `vm_creator.sh`, `select_linux_iso.sh` y `select_windows_iso.sh`. El picker de ISO lee ahora desde cada almacenamiento Proxmox tagueado como ISO content en lugar de estar pinned a `local`. Commit [`092b548d`](https://github.com/MacRimi/ProxMenux/commit/092b548d). +- **Selector de canal de release en Settings** — un menú proper bajo `scripts/menus/config_menu.sh` para flipear entre los canales de install estable y beta in-place, con la gestión correcta de `version.txt` / `beta_version.txt` en cada lado. Commit [`f8a8c43d`](https://github.com/MacRimi/ProxMenux/commit/f8a8c43d). +- **ZFS autotrim en el auto post-install** — `auto_post_install.sh` habilita ahora `autotrim=on` en pools ZFS root por defecto (con el disable correspondiente en el path de uninstall), de forma que installs SSD-backed reclaman espacio liberado sin intervención manual. Commit [`8877f987`](https://github.com/MacRimi/ProxMenux/commit/8877f987). +- **Detección de webhook loopback + handoff de update** — `flask_notification_routes.py` clasifica correctamente webhooks de `127.0.0.1` / `localhost` como loopback, y el handoff de update del script `menu` ya no flackea en edge cases. Commit [`70ab072c`](https://github.com/MacRimi/ProxMenux/commit/70ab072c). +- **Figurine bumped a 2.0.0** — refresh del banner tool en `customizable_post_install.sh`, con la página de docs actualizada para matchear. Commit [`aba94028`](https://github.com/MacRimi/ProxMenux/commit/aba94028). + +**[@pespinel](https://github.com/pespinel)** arregló una regresión del beta-installer que rompía los paths de servicio tras el move al nuevo layout runtime — `install_proxmenux_beta.sh` resuelve ahora los paths correctos de la unit systemd en first install y en update. Commit [`0daab74a`](https://github.com/MacRimi/ProxMenux/commit/0daab74a). + +### Field reports que dieron forma al trabajo de GPU + Coral + +Los reports detallados y sugerencias de **[@ghosthvj](https://github.com/ghosthvj)** sobre el flujo de hardware passthrough impulsaron las mejoras de scripts de GPU en esta release. Los fixes del instalador NVIDIA, el hardening del lifecycle de GPU + audio acompañante en `switch_gpu_mode.sh`, y el checklist de audio-companion iGPU en `add_gpu_vm.sh::detect_optional_gpu_audio` empezaron todos desde sus reports de edge cases que los paths de código previos manejaban pobremente. + +### Todos los demás + +Un gracias enorme a cada usuario que abrió un issue en GitHub, comentó en [GitHub Discussions](https://github.com/MacRimi/ProxMenux/discussions), reportó un bug en el canal de la comunidad, o pasó a compartir qué funcionaba y qué no en su hardware. **Muchas de las mejoras internas en esta release — el stagger del scheduler de smartctl, el fix de caché de fail2ban, el reemplazo `lxc-info /proc`, el handshake del terminal HTTPS, la detección de kernel-update en PVE 9.x, todo el wiring de Apprise — empezaron como un report de alguien encontrándose con el problema.** Seguid llegando. + +--- + + +## 2026-04-20 + +### Nueva versión ProxMenux v1.2.1 — *SR-IOV Awareness & GPU Passthrough Hardening* + +Release puntual sobre **v1.2.0** que aborda tres áreas reportadas por la comunidad y que necesitaban arreglo antes del siguiente ciclo estable: reconocimiento completo de SR-IOV en todo el subsistema GPU/PCI, gestión robusta de los dispositivos de audio acompañantes de la GPU durante el attach y detach de passthrough (Intel iGPU con audio del chipset, tarjetas discretas con audio HDMI, VMs con GPU mixta), y fixes de compatibilidad para los proveedores de notificaciones con IA (endpoints custom OpenAI-compatible tipo LiteLLM/MLX/LM Studio, modelos de razonamiento de OpenAI, y modelos thinking de Gemini 2.5+/3.x). También incluye mejoras de calidad de vida en el instalador NVIDIA, el Monitor de salud de discos, y los helpers de ciclo de vida de LXC usados por los wizards de passthrough. + +--- + +## 🎛️ SR-IOV Awareness en todo el subsistema GPU + +Intel `i915-sriov-dkms` y AMD MxGPU dividen la Physical Function (PF) de una GPU en Virtual Functions (VFs) que pueden asignarse de forma independiente a LXCs y VMs. Anteriormente ProxMenux no tenía reconocimiento alguno de SR-IOV: trataba VFs y PFs de manera idéntica, lo que podía reescribir `vfio.conf` con el vendor:device ID de la PF, colapsar el árbol de VFs en el siguiente arranque, y dejar a los usuarios sin poder iniciar sus guests. Se ha auditado y endurecido cada ruta que pudiera alterar un árbol de VFs activo. + +### Helpers de detección +- Nuevos `_pci_is_vf`, `_pci_has_active_vfs`, `_pci_sriov_role`, `_pci_sriov_filter_array` en `scripts/global/pci_passthrough_helpers.sh` +- Equivalentes HTTP/JSON en la ruta Flask de GPU — la UI del Monitor lee el estado VF/PF directamente desde sysfs (`physfn`, `sriov_totalvfs`, `sriov_numvfs`, `virtfn*`) + +### Pre-start hook (`gpu_hook_guard_helpers.sh`) +El guard pre-start de las VMs ahora reconoce Virtual Functions. Tanto la rama de sintaxis slot-only (que solía iterar todas las funciones del slot y exigir `vfio-pci` en todas) como la rama full-BDF saltan las VFs, de modo que Proxmox puede realizar su rebind vfio-pci por VF con normalidad. El falso bloqueo "GPU passthrough device is not ready" en VMs SR-IOV ha desaparecido. + +### Los scripts de mode-switch rechazan operaciones SR-IOV +`switch_gpu_mode.sh`, `switch_gpu_mode_direct.sh`, `add_gpu_vm.sh`, `add_gpu_lxc.sh`, `vm_creator.sh`, `synology.sh`, `zimaos.sh` y `add_controller_nvme_vm.sh` rechazan ahora las VFs y las PFs con VFs activas antes de tocar la configuración del host. Un dialog claro "SR-IOV Configuration Detected" explica la situación. Para los wizards invocados en mitad de flujo (creadores de VM) el mensaje se entrega por `whiptail` para que interrumpa limpiamente, seguido de una línea `msg_warn` por dispositivo para dejar rastro en el log. + +### Nuevo estado "SR-IOV active" en la UI del Monitor +La tarjeta GPU de la página Hardware gana un tercer estado visual con un color teal dedicado, una pill in-line `SR-IOV ×N` (o `SR-IOV VF` para una Virtual Function), y ramas LXC y VM en discontinuo/atenuadas. El botón Edit se oculta porque el estado está gestionado por hardware. + +![SR-IOV active card and modal](https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/sriov-indicator.png) + +### Modal dashboard para GPUs SR-IOV +Al abrir el modal de una Physical Function con VFs activas se muestra ahora: +- Banner de métricas agregadas ("Metrics below reflect the Physical Function, aggregate across N VFs") +- Telemetría normal en tiempo real de la GPU para la PF +- Una tabla **Virtual Functions**, una fila por VF, con el driver actual (`i915`, `vfio-pci`, unbound) y la VM o LXC específica que la consume, incluyendo el estado running/stopped — los consumidores se descubren cruzando entradas `hostpci` y líneas de mount `/dev/dri/renderDN` contra el BDF de la VF y el nodo DRM render + +Al abrir el modal de una Virtual Function se muestran su PF padre (clickable para navegar de vuelta al modal de la PF), el driver actual y el consumidor. + +### El popup de VM Conflict Policy ya no se dispara para VFs de SR-IOV +La regex en `detect_affected_vms_for_selected` casaba el slot (`00:02`) contra VMs que tenían una VF (`00:02.1`) asignada, produciendo un dialog confuso "Keep GPU in VM config". Con el gate SR-IOV upstream, el flujo nunca llega a ese camino de código para slots SR-IOV. + +--- + +## 🔊 GPU + Audio Passthrough — Hardening de ciclo de vida completo + +Una ronda de fixes en torno a cómo el passthrough de GPU gestiona su dispositivo de audio acompañante. Anteriormente, solo se recogía automáticamente el hermano `.1` de una GPU discreta; el passthrough de iGPU Intel a una VM — donde el audio vive separado en el chipset en `00:1f.3` y no en `00:02.1` — se saltaba silenciosamente. En el detach, el viejo `sed` que limpiaba líneas hostpci por substring de slot también podía eliminar una GPU no relacionada cuyo BDF contuviera el slot buscado como substring (p. ej. el slot `00:02` casando dentro de `0000:02:00.0`). Ambos caminos son ahora robustos. + +### Checklist de audio-companion de iGPU en el attach +`add_gpu_vm.sh::detect_optional_gpu_audio` mantiene la fast path de auto-include para el clásico hermano `.1` (NVIDIA / AMD discretas con audio HDMI en la tarjeta). Cuando no existe audio `.1`, el script ahora: +- Escanea sysfs en busca de cada controlador PCI de audio del host +- Salta cualquier cosa ya cubierta por el IOMMU group de la GPU +- Pregunta al usuario mediante un `_pmx_checklist` (`dialog` en modo standalone, `whiptail` en modo wizard llamado desde `vm_creator`/`synology`/`zimaos`) qué controladores de audio pasar junto con la GPU +- Muestra cada entrada con su driver actual en el host (`snd_hda_intel`, `snd_hda_codec_*`, etc.) para que la decisión sea informada +- Por defecto **none** — el usuario opta activamente por incluirlos + +### Cascada de audio huérfano en el detach +Cuando el usuario elige "Remove GPU from VM config" durante un mode switch, los scripts hacen ahora un seguimiento con limpieza dirigida: +- `switch_gpu_mode.sh`, `switch_gpu_mode_direct.sh` y `add_gpu_vm.sh::cleanup_vm_config` (limpieza de la VM origen en el flujo "move GPU") llaman todos al helper compartido `_vm_list_orphan_audio_hostpci` +- El helper usa un escaneo en dos pasadas del config de la VM: pasada 1 registra las bases de slot de las entradas hostpci display/3D; pasada 2 clasifica las entradas de audio y **salta cualquier audio cuyo slot aún tenga un hermano display en la misma VM** — protegiendo el audio HDMI de otras dGPUs que queden en la VM +- Antes el simple match por substring habría marcado el `02:00.1` de NVIDIA como huérfano al hacer detach de una iGPU Intel en `00:02.0` +- El flujo interactivo de switch confirma las eliminaciones con un checklist de `dialog` (por defecto ON). La variante web hace auto-remove sin preguntar — el runner no tiene buena forma de renderizar un checklist — y loguea cada BDF que ha tocado + +### Extensión de la cascada a vfio.conf +Para cada audio eliminado por la cascada, los scripts de switch-mode comprueban ahora si su BDF sigue referenciado por cualquier otra VM vía `_pci_bdf_in_any_vm`. Si nada más lo usa, el `vendor:device` se añade a `SELECTED_IOMMU_IDS` antes de que se ejecute el update de `/etc/modprobe.d/vfio.conf`. Eso cierra el bucle para el caso de la iGPU Intel: `8086:51c8` (PCH HD Audio) se retira ahora de `vfio.conf` junto con `8086:46a3` (iGPU) cuando ambos salen del modo VM y ninguna otra VM los referencia. Si otra VM aún usa el audio, el ID se conserva deliberadamente — sin efectos secundarios rompedores sobre otras VMs. `add_gpu_vm.sh` NO extiende la limpieza en el flujo *move*, porque la GPU sigue en uso en otro sitio y sus IDs deben permanecer. + +### Regex precisa de eliminación de hostpci +Cada `sed` inline usado para hacer detach de una GPU del config de una VM casaba antes el slot como substring libre: +``` +/^hostpci[0-9]+:.*${slot}/d +``` +Para `slot=00:02` ese patrón casa la substring dentro de `0000:02:00.0` (una dGPU NVIDIA no relacionada en el slot `02:00`) y borraría ambas tarjetas. El fix ancla el match a la forma BDF real: +``` +/^hostpci[0-9]+:[[:space:]]*(0000:)?${slot}\.[0-7]([,[:space:]]|$)/d +``` +Aplicado en `switch_gpu_mode.sh`, `switch_gpu_mode_direct.sh` y `add_gpu_vm.sh::cleanup_vm_config`. El helper basado en awk en `vm_storage_helpers.sh::_remove_pci_slot_from_vm_config` (usado por los wizards NVMe) ya usaba el patrón correcto y no necesitó cambios. + +--- + +## 🤖 Compatibilidad de proveedores de IA — OpenAI-Compatible, modelos Reasoning y Thinking + +Tres fixes coordinados que desbloquean categorías de modelos previamente rechazadas por el pipeline de mejora de notificaciones. + +### Endpoints OpenAI-compatible +LiteLLM, MLX, LM Studio, vLLM, LocalAI, Ollama-proxy — el `list_models()` del proveedor exigía antes `"gpt"` en cada nombre de modelo, así que los setups locales sirviendo `mlx-community/...`, `Qwen3-...`, `mistralai/...` veían una lista de modelos vacía. Cuando se establece una Custom Base URL, la comprobación de substring `"gpt"` se omite ahora y `EXCLUDED_PATTERNS` (embeddings, whisper, tts, dall-e) es el único filtro. La capa de ruta Flask también deja de intersectar el resultado contra `verified_ai_models.json` para endpoints custom — la lista verificada solo describe los IDs oficiales de modelo de OpenAI y estaba borrando cada modelo local que el usuario realmente servía. + +### Modelos reasoning de OpenAI +`o1`, `o3`, `o3-mini`, `o4-mini`, `gpt-5`, `gpt-5-mini`, `gpt-5.1`, `gpt-5.2-pro`, `gpt-5.4-nano`, etc. (excluyendo las variantes `*-chat-latest`) usan un contrato API más estricto: `max_completion_tokens` en lugar de `max_tokens`, sin `temperature`. Enviar los parámetros clásicos de chat producía HTTP 400 Bad Request para todos ellos. Un detector en `openai_provider.py` bifurca ahora el payload en consecuencia y establece `reasoning_effort: "minimal"` — por defecto estos modelos gastan su presupuesto de output en razonamiento interno y devuelven una respuesta vacía para la breve petición de traducción de notificación. + +### Modelos thinking de Gemini 2.5+ / 3.x +`gemini-2.5-flash`, `2.5-pro`, `gemini-3-pro-preview`, `gemini-3.1-pro-preview`, etc. tienen "thinking" interno activado por defecto. Con el pequeño presupuesto de tokens usado para el enriquecimiento de notificaciones (≤250 tokens), el presupuesto de thinking consumía toda la asignación y el modelo devolvía output vacío con `finishReason: MAX_TOKENS`. `gemini_provider.py` establece ahora `thinkingConfig.thinkingBudget: 0` para variantes no-`lite` de 2.5+ y 3.x, de modo que los tokens disponibles van a la respuesta visible al usuario. Las variantes lite (sin thinking activado) quedan intactas. + +--- + +## 📋 Refresh de Verified AI Models + +`AppImage/config/verified_ai_models.json` refrescado para los proveedores re-testeados contra APIs en vivo. La nueva herramienta privada de mantenimiento (mantenida fuera de la AppImage) re-ejecuta un test estandarizado de translate+explain contra cada modelo que anuncia cada proveedor, clasifica pass / warn / fail, e imprime un snippet JSON listo para pegar. Re-ejecutar antes de cada release de ProxMenux para mantener la lista al día. + +| Provider | New recommended | Notes | +|----------|-----------------|-------| +| **OpenAI** | `gpt-4.1-nano` | `gpt-4.1-nano`, `gpt-4.1-mini`, `gpt-4o-mini`, `gpt-4.1`, `gpt-4o`, `gpt-5-chat-latest`, más `gpt-5.4-nano` / `gpt-5.4-mini` desde 2026-03. Snapshots con fecha y modelos legacy excluidos. Modelos reasoning soportados por el código pero no listados por defecto — más lentos / más caros sin mejorar la calidad de las notificaciones | +| **Gemini** | `gemini-2.5-flash-lite` | `gemini-2.5-flash-lite`, `gemini-2.5-flash` (funciona ahora), `gemini-3-flash-preview`. Aliases `latest` omitidos intencionadamente — resolvían a modelos distintos entre ejecuciones y producían timeouts en algunas regiones. Las variantes Pro rechazan `thinkingBudget=0` y son excesivas para traducción de notificaciones | +| Groq / Anthropic / OpenRouter | *sin cambios* | Marcados con un `_note` — se re-verificarán en cuanto haya keys disponibles | + +--- + +## 🩺 Monitor de salud de discos — Persistencia de observaciones en el journal watcher + +Un bug latente en `notification_events.py::_check_disk_io` hacía que los errores de I/O del kernel en tiempo real capturados por el journal watcher se superficiaran como notificaciones pero nunca se escribieran en la tabla permanente de observaciones por disco. En la práctica el escaneo paralelo periódico de dmesg solía registrar la observación poco después, pero bajo casos límite de timing (ventana de dmesg obsoleta, restart de servicio justo después del error, rotación de buffer) la observación podía perderse. + +El journal watcher registra ahora la observación antes del gate de cooldown de notificación de 24h, usando la misma clasificación de signature por familia (`io__ata_connection_error`, `io__block_io_error`, `io__ata_failed_command`) que el escaneo periódico. Ambos caminos deduplican ahora en la misma fila vía el UPSERT en `record_disk_observation`, de modo que los conteos de ocurrencias son precisos sin importar qué detector disparó primero. + +--- + +## 🔧 Pulido del instalador NVIDIA + +### Race condition de `lsmod` silenciada +Durante la reinstalación, la verificación de unload de módulos en `unload_nvidia_modules` producía errores espurios `lsmod: ERROR: could not open '/sys/module/nvidia_uvm/holders'` porque `lsmod` lee `/proc/modules` y luego abre el directorio `holders/` de cada módulo, que desaparece transitoriamente mientras el módulo está siendo eliminado. La comprobación lee ahora `/proc/modules` directamente e inserta sleeps cortos para dejar que el kernel finalice el unload antes de re-verificar. Aplicado en el mismo espíritu a los otros cuatro call sites de `lsmod` en el script. + +### Dialog → whiptail en el flujo de update LXC +El mensaje "Insufficient Disk Space" en `update_lxc_nvidia` y la confirmación "Update NVIDIA in LXC Containers" usan ahora dialogs estilo `whiptail` consistentes con el resto del messaging in-flow, evitando la rotura visual que `dialog --msgbox` causaba al renderizarse en mitad de la secuencia en la fase de update de contenedores. + +--- + +## 🧵 Helper de ciclo de vida LXC — Stop seguro con timeout + +Un `pct stop` simple puede colgarse indefinidamente cuando el contenedor tiene un lock obsoleto de una operación abortada previa, cuando los procesos de dentro (Plex, Jellyfin, bases de datos) ignoran TERM y caen en uninterruptible-sleep mientras la GPU que estaban usando es arrancada, o cuando `pct shutdown --timeout` no es respetado por pct mismo. Reportes de campo de esperas de 5+ min durante mode switches de GPU hicieron de esto un peligro real de UX. + +Nuevo helper compartido `_pmx_stop_lxc [log_file]` en `pci_passthrough_helpers.sh`: +1. Devuelve 0 inmediatamente si el contenedor no está corriendo +2. `pct unlock` best-effort (silencioso ante fallo) — la mayoría de contenedores no están realmente bloqueados; solo nos importan los casos en que lo están +3. `pct shutdown --forceStop 1 --timeout 30` envuelto en un `timeout 45` externo para no esperar nunca más que eso a la fase graceful, incluso si pct se atasca en I/O del backend +4. Verifica el estado real vía `pct status` — pct puede devolver no-cero mientras el contenedor está de hecho parado +5. Si sigue corriendo, `pct stop` envuelto en `timeout 60`. Verificar de nuevo +6. Devuelve 1 solo si el contenedor está realmente atascado tras ~107 s totales — el wizard continúa en lugar de colgarse + +Cableado en las tres rutas de modo GPU que paran LXCs durante un switch: `switch_gpu_mode.sh`, `switch_gpu_mode_direct.sh`, y `add_gpu_vm.sh::cleanup_lxc_configs`. + +--- + +## ⚙️ Estabilidad del prompt de reboot en `add_gpu_vm.sh` + +El prompt final "Reboot Required" del wizard de asignación GPU-a-VM estaba disparando reboots espurios en ciertas invocaciones de cadena de menú (`menu` → `main_menu` → `hw_grafics_menu` → `add_gpu_vm`). Con el helper `_pmx_yesno` a veces devolvía exit 0 sin que el usuario hubiera confirmado realmente, llamando `reboot` de inmediato. Con un `read` simple en su lugar el proceso quedaba suspendido por SIGTTIN cuando la cadena de menú desligaba el script del grupo de proceso foreground del terminal, dejando `[N]+ Stopped menu` en el shell padre sin posibilidad de responder. + +El prompt usa ahora `whiptail --yesno` invocado directamente (el patrón verificado para funcionar de forma fiable en esa cadena de menú) e inserta una pausa `Press Enter to continue ... read -r` entre la respuesta "Yes" y la llamada real a `reboot` — de modo que un Enter accidental en el botón de confirmar no puede disparar un reboot inmediato sin un paso de confirmación visible primero. + +--- + +### 🙏 Gracias + +Gracias a los usuarios que reportaron los casos de SR-IOV, LiteLLM/MLX y GPU + audio — estas mejoras existen gracias a reportes detallados y reproducibles. No dudéis en seguir reportando issues o sugiriendo mejoras 🙌. + +--- + + +## 2026-04-17 + +### Nueva versión ProxMenux v1.2.0 — *AI-Enhanced Monitoring* + + +![ProxMenux AI](https://raw.githubusercontent.com/MacRimi/ProxMenux/main/images/ProxMenux_ai.png) + +Esta release es la culminación del ciclo beta v1.1.9.1 → v1.1.9.6 e introduce la mayor evolución de **ProxMenux Monitor** hasta la fecha: notificaciones mejoradas con IA, un sistema de notificaciones multicanal rediseñado, una experiencia de hardware y almacenamiento totalmente reelaborada, y mejoras amplias de rendimiento en todo el stack de monitorización. También consolida todo el trabajo reciente en los scripts de Storage, Hardware y GPU/TPU. + +--- + +## 🤖 ProxMenux Monitor — Notificaciones mejoradas con IA + +Las notificaciones pueden mejorarse ahora usando IA para generar mensajes claros y contextuales en lugar del output técnico crudo. + +Ejemplo — en lugar de `backup completed exitcode=0 size=2.3GB`, la IA produce: *"The web server backup completed successfully. Size: 2.3GB"*. + +### Lo que hace la IA +- Transforma notificaciones técnicas en mensajes legibles +- Traduce a tu idioma preferido +- Te deja elegir el nivel de detalle: minimal, standard o detailed +- Funciona con Telegram, Discord, Email, Pushover y Webhooks + +### Lo que la IA NO hace +- **No** es un chatbot ni un asistente +- **No** analiza tu sistema ni toma decisiones +- **No** tiene acceso a datos más allá de la notificación que está procesando +- **No** ejecuta comandos ni modifica el servidor +- **No** almacena historial ni aprende de tus datos + +### Soporte multi-proveedor +Elige entre 6 proveedores de IA, cada uno con su propia API key almacenada de forma independiente: +- **Groq** — inferencia rápida, free tier generoso +- **Google Gemini** — excelente relación calidad/precio, free tier disponible +- **OpenAI** — estándar de la industria +- **Anthropic Claude** — excelente para escritura y traducción +- **OpenRouter** — 300+ modelos con una sola API key +- **Ollama** — ejecución 100% local, sin internet + +### Verified AI Models +Una lista curada de modelos (`verified_ai_models.json`) testeada específicamente para mejora de notificaciones. + +- **Verificación híbrida**: el sistema obtiene los modelos del lado del proveedor y filtra para mostrar solo los testeados que funcionan correctamente +- **Memoria de modelo por proveedor**: el modelo seleccionado se guarda por proveedor, de modo que cambiar de proveedor preserva cada elección +- **Verificación diaria**: una tarea en background comprueba la disponibilidad de modelos y migra automáticamente a una alternativa verificada si el modelo actual desaparece +- **Modelos incompatibles excluidos**: Whisper, TTS, image/video, embeddings, guard models, etc. se filtran por proveedor + +| Provider | Recommended | Also Verified | +|----------|-------------|---------------| +| Gemini | gemini-2.5-flash-lite | gemini-flash-lite-latest | +| OpenAI | gpt-4o-mini | gpt-4.1-mini | +| Groq | llama-3.3-70b-versatile | llama-3.1-70b-versatile, llama-3.1-8b-instant, llama3-70b-8192, llama3-8b-8192, mixtral-8x7b-32768, gemma2-9b-it | +| Anthropic | claude-3-5-haiku-latest | claude-3-5-sonnet-latest, claude-3-opus-latest | +| OpenRouter | meta-llama/llama-3.3-70b-instruct | meta-llama/llama-3.1-70b-instruct, anthropic/claude-3.5-haiku, google/gemini-flash-2.5-flash-lite, openai/gpt-4o-mini, mistralai/mixtral-8x7b-instruct | +| Ollama | (todos los modelos locales) | Sin filtrado — muestra todos los modelos instalados | + +### Custom AI Prompts +Los usuarios avanzados pueden definir su propio prompt para tener control total sobre el formato y la traducción. + +- **Selector de Prompt Mode** — Default Prompt o Custom Prompt +- **Export / Import** — guarda y comparte prompts custom entre instalaciones +- **Example Template** — punto de partida para construir tu propio prompt +- **Community Prompts** — enlace directo a GitHub Discussions para compartir plantillas +- El selector de idioma se oculta en modo Custom Prompt (defines el idioma de salida en el propio prompt) + +### Contexto enriquecido +- El **uptime** del sistema se incluye solo para eventos de error/warning (no informativos) — ayuda a distinguir errores de arranque vs runtime +- Tracking de **frecuencia de eventos** — indica problemas recurrentes vs puntuales +- Datos de **SMART disk health** pasados para errores relacionados con discos +- La base de datos de **errores conocidos de Proxmox** mejora la precisión del diagnóstico +- Instrucciones de prompt más claras para prevenir alucinaciones de la IA + +--- + +## 📨 Rediseño del sistema de notificaciones + +- **Arquitectura multicanal** — canales Telegram, Discord, Pushover, Email y Webhook corriendo simultáneamente +- **Configuración por evento** — habilita/deshabilita tipos de evento específicos por canal +- **Channel Overrides** — personaliza el comportamiento de notificación por canal +- **Endpoint webhook seguro** — sistemas externos pueden enviar notificaciones autenticadas +- **Almacenamiento cifrado** — API keys y datos sensibles guardados cifrados +- **Procesamiento basado en cola** — worker en background con reintento automático para notificaciones fallidas +- **Almacenamiento de config basado en SQLite** — reemplaza el config basado en ficheros para mayor fiabilidad + +### Soporte de Telegram Topics +Envía notificaciones a un topic específico dentro de grupos con Topics habilitado. +- Nuevo campo **Topic ID** en el canal Telegram +- Detección automática de grupos con topics habilitados +- Totalmente retrocompatible + +### Notificaciones de update de ProxMenux +El Monitor detecta ahora cuándo se publica una nueva versión de ProxMenux. +- **Doble canal** — monitoriza tanto stable (`version.txt`) como beta (`beta_version.txt`) +- **Integración con GitHub** — compara versiones locales vs remotas +- **Dashboard Update Indicator** — el logo de ProxMenux cambia a una variante de update cuando se detecta una nueva versión (no intrusivo, sin popups) +- **Estado persistente** — el estado se guarda en `config.json`, reseteado por los scripts de update +- Un único toggle en Settings controla ambos canales (habilitado por defecto) + +--- + +## 🖥️ Panel de Hardware — Detección ampliada + +La página Hardware se ha ampliado significativamente, con mejor detección y detalle por dispositivo más rico. + +- **Controladoras SCSI / SAS / RAID** — modelo, driver y slot PCI mostrados en la sección de storage controllers +- **Detección de PCIe Link Speed** — los drives NVMe muestran la velocidad de link actual (generación PCIe y ancho de carriles), facilitando detectar drives que rinden por debajo por ancho de banda limitado del slot +- **Modal de detalle de disco mejorado** — los drives NVMe, SATA, SAS y USB exponen ahora sus campos específicos (info de link PCIe, versión/velocidad SAS, tipo de interfaz) en lugar de una vista genérica +- **Reconocimiento más inteligente de tipo de disco** — etiquetado uniforme para NVMe SSDs, SATA SSDs, HDDs y discos extraíbles +- **Caching de Hardware Info** (`lspci`, `lspci -vmm`) — la caché de 5 min evita escaneos repetidos de datos que no cambian + +--- + +## 💽 Storage Overview — Salud, observaciones, exclusiones + +El Storage Overview se ha reelaborado en torno al estado en tiempo real y al tracking controlado por el usuario. + +### Alineación del estado de salud de disco +- Los badges reflejan ahora el estado **actual** SMART reportado por Proxmox, no un peor histórico +- **Observaciones preservadas** — los hallazgos históricos siguen accesibles vía el badge "X obs." +- **Recuperación automática** — cuando SMART reporta healthy de nuevo, el disco muestra inmediatamente **Healthy** +- Eliminado el viejo tracking `worst_health` que requería limpieza manual + +### Mejoras del registro de discos +- **Lookup inteligente por serial** — cuando un serial es desconocido el sistema comprueba si existe una entrada con serial antes de insertar una nueva +- **Sin duplicados** — previene entradas separadas para el mismo disco apareciendo con/sin serial +- **Soporte de discos USB** — gestiona drives USB que pueden aparecer bajo nombres de dispositivo distintos entre reboots + +### Exclusiones de Storage e interfaces de Red +- Sección **Storage Exclusions** — excluye drives de la monitorización de salud y notificaciones +- **Network Interface Exclusions** — nueva sección para excluir interfaces (bridges `vmbr`, bonds, NICs físicas, VLANs) de salud y notificaciones; ideal para interfaces deshabilitadas intencionadamente que de otro modo generarían falsas alertas +- **Toggles separados** por ítem para Health monitoring y Notifications + +### Robustez en la detección de discos +- **Validación de Power-On-Hours** — detecta y corrige valores absurdamente grandes (miles de millones de horas) en drives con codificación SMART no estándar +- **Bit masking inteligente** — extrae el valor correcto de drives que empaquetan info extra en bytes altos +- **Fallback elegante** — muestra "N/A" en lugar de números imposibles cuando los datos no pueden parsearse + +--- + +## 🧠 Monitor de salud y ciclo de vida de errores + +### Limpieza de errores obsoletos +Los errores de recursos que ya no existen se resuelven ahora automáticamente. +- **VMs / CTs eliminadas** — los errores relacionados se auto-resuelven cuando se elimina el recurso +- **Discos retirados** — los errores de drives USB desconectados o hot-swap se limpian +- **Cambios de cluster** — los errores de cluster se limpian cuando un nodo abandona el cluster +- **Patrones de log** — los errores basados en logs se auto-resuelven tras 48 horas sin recurrencia +- **Updates de seguridad** — las notificaciones de update se auto-resuelven tras 7 días + +### Sistema de migración de base de datos +- **Detección automática de columnas** — las columnas faltantes se añaden en el arranque +- **Compatibilidad de schema** — funciona tanto con convenciones de nombrado de columna antiguas como nuevas +- **Retrocompatible** — se soportan bases de datos de versiones anteriores de ProxMenux +- **Migración elegante** — sin pérdida de datos durante updates de schema + +--- + +## 🧩 Modal de detalle VM / CT + +El modal de detalle VM/CT se ha rediseñado por completo para mejorar la usabilidad. + +- **Navegación con tabs** — *Overview* (información general, estado, uso de recursos) y *Backups* (historial dedicado) +- **Mejoras visuales** — iconos en todo, jerarquía y espaciado mejorados, mejor distinción VM vs CT +- **Adaptación a móvil** — se adapta correctamente a pantallas móviles tanto en webapp como en acceso directo por navegador, sin más overflow en dispositivos pequeños +- **Controles touch-friendly** — botones y espaciado mayores + +### Modal de Secure Gateway +- **Lista de storage con scroll** cuando hay muchos destinos disponibles +- Layout adaptado a móvil y jerarquía visual mejorada + +### Conexión de terminal +- **Fix de bucle de reconexión** que afectaba a dispositivos móviles +- Manejo mejorado de WebSocket para navegadores móviles +- Recuperación más elegante de timeouts de conexión + +### Gestión de Fail2ban y Lynis +- **Botones de delete** añadidos en Settings para ambas herramientas +- Eliminación limpia de paquetes y ficheros de configuración +- Dialog de confirmación para prevenir borrado accidental + +--- + +## ⚡ Optimizaciones de rendimiento + +Reducción importante de uso de CPU y eliminación de picos en el Monitor. + +### Intervalos de polling escalonados +Los collectors corren ahora en schedules con offset para prevenir ejecución simultánea: + +| Collector | Schedule | +|-----------|----------| +| CPU sampling | Cada 30s en offset 0 | +| Temperature sampling | Cada 15s en offset 7s | +| Latency pings | Cada 60s en offset 25s | +| Temperature record | Cada 60s en offset 40s | +| Health collector | Arranca en offset 55s | +| Notification polling | Health=10s, Updates=30s, ProxMenux=45s, AI=50s | + +### Información de sistema cacheada +Los comandos costosos se cachean ahora para reducir ejecución repetida: + +| Command | Cache TTL | Impact | +|---------|-----------|--------| +| `pveversion` | 6 horas | Elimina picos de CPU del 23%+ por ejecución de Perl | +| `apt list --upgradable` | 6 horas | Reduce consultas del gestor de paquetes | +| `pvesh get /cluster/resources` | 30 segundos | 6 llamadas API por request reducidas a 1 | +| `sensors` | 10 segundos | Lecturas de temperatura cacheadas entre polls | +| `smartctl` (SMART health) | 30 minutos | Health checks de disco reducidos desde cada 5 min | +| `lspci` / `lspci -vmm` | 5 minutos | Info de hardware cacheada (no cambia) | +| `journalctl --since 24h` | 1 hora | Conteo de intentos de login cacheado (92% de reducción) | + +### Timeouts de journalctl aumentados +Previene cascadas de timeout bajo carga del sistema: + +| Query Type | Before | After | +|------------|--------|-------| +| Short-term (3-10 min) | 3s | 10s | +| Medium-term (1 hour) | 5s | 15s | +| Long-term (24 hours) | 5s | 20s | + +### Frecuencia de polling reducida +- Intervalo de `TaskWatcher` subido de **2s → 5s** (60% menos comprobaciones) + +### GitHub Actions +- Todas las actions de workflow actualizadas a **v6** para compatibilidad con Node.js 24 +- Warnings de deprecación eliminados en CI/CD + +--- + +## 🧰 Scripts — Trabajo en Storage, Hardware y GPU/TPU + +Esta release también consolida trabajo significativo en los scripts core de ProxMenux. + +### Scripts de Storage +- **Tests SMART programados** y flujo interactivo de test SMART mejorado con feedback de progreso más claro +- Reelaboración de **formateo de disco** (`format-disk.sh`) con selección de dispositivo más segura y flujo de dialog +- **Disk passthrough** para VMs y CTs — enumeración de dispositivos actualizada, identificación basada en serial, y teardown más limpio +- **Adición de controladora NVMe para VMs** — selección de tipo de controladora y detección de slot mejoradas +- **Import disk image** — validación de path más suave y reporte de progreso +- Refresh de la guía manual de **Disk & storage** + +### Scripts de Hardware / GPU / TPU +- **Coral TPU installer** actualizado para kernels y udev rules actuales (Proxmox VE 8 & VE 9) +- **NVIDIA installer** — instalación de driver más limpia, manejo de kernel headers, y flujo de attachment VM/LXC +- **GPU mode switch** (variantes directa e interactiva) — switching más seguro entre modos iGPU +- **Add GPU to VM / LXC** — dialogs de selección unificados y gestión de permisos +- **Herramientas de GPU Intel / AMD** mantenidas en sync con los nuevos patrones compartidos +- **Hardware & graphics menu** reestructurado para consistencia con el resto de ProxMenux + + +## 2026-03-14 + +### Nueva versión v1.1.9 — *Helper Scripts Catalog Rebuilt* + +### Cambiado + +- **Helper Scripts Menu — Reconstrucción completa del catálogo** + El catálogo de Helper Scripts se ha reconstruido por completo para adaptarse a la nueva arquitectura de datos del proyecto [Community Scripts](https://community-scripts.github.io/ProxmoxVE/). + + La implementación previa dependía de un fichero `metadata.json` que ya no existe en el repositorio upstream. El catálogo conecta ahora directamente a la **API de PocketBase** (`db.community-scripts.org`), que es la nueva fuente de datos oficial del proyecto. + + Un nuevo workflow de GitHub Actions genera un índice local `helpers_cache.json` que reemplaza la antigua dependencia de metadata. Esta nueva caché es más rica, más estructurada, e incluye: + - Tipo de script, slug, descripción, notas y credenciales por defecto + - Variantes de OS por script (p. ej. Debian, Alpine) — cada una mostrada como una opción seleccionable separada en el menú + - URL directa de GitHub y **URL Mirror** (`git.community-scripts.org`) para cada script + - Nombres de categoría embebidos directamente en la caché — sin necesidad de requests externos para construir el menú + - Metadata adicional: puerto por defecto, website, logo, soporte de update, disponibilidad ARM + + Los scripts que soportan múltiples variantes de OS (p. ej. Docker con Alpine y Debian) muestran ahora correctamente **una entrada por OS**, cada una con su propia opción de descarga GitHub y Mirror — restaurando el comportamiento que existía antes de la migración upstream. + +--- + +### 🎖 Reconocimiento especial + +Esta actualización no habría sido posible sin la apertura y colaboración de los mantenedores de **Community Scripts**. + +Cuando la estructura de metadata upstream cambió y rompió el catálogo de ProxMenux, los mantenedores respondieron rápidamente, explicaron la nueva arquitectura en detalle y proporcionaron toda la información necesaria para reconstruir la integración limpiamente. + +Agradecimientos especiales a: + +- **MickLeskCanbiZ ([@MickLesk](https://github.com/MickLesk))** — por documentar la nueva estructura de path de scripts por tipo y slug, y por la guía técnica clara y directa. +- **Michel Roegl-Brunner ([@michelroegl-brunner](https://github.com/michelroegl-brunner))** — por explicar la nueva estructura de colecciones de PocketBase (`script_scripts`, `script_categories`). + +El proyecto Helper Scripts es un recurso extraordinario para la comunidad Proxmox. Los scripts pertenecen enteramente a sus autores y mantenedores — ProxMenux simplemente ofrece una forma guiada de descubrirlos y lanzarlos. Todo el crédito va a la comunidad detrás de [community-scripts/ProxmoxVE](https://github.com/community-scripts/ProxmoxVE). + +## 2025-09-18 + +### Nueva versión v1.1.8 — *ProxMenux Offline Mode* + +![ProxMenux Offline](https://macrimi.github.io/ProxMenux/ProxMenux_offline.png) + +--- + +### Añadido + +- **Modo de ejecución offline (sin dependencia de GitHub)** + Todos los scripts core de ProxMenux se ejecutan ahora **enteramente en local**, sin requerir requests en vivo a GitHub (`raw.githubusercontent.com`). + Este cambio proporciona: + - Mayor estabilidad durante la ejecución + - Sin interrupciones por timeouts de red o bloqueos regionales de GitHub + - Soporte para **entornos offline o aislados** + + ⚠️ Esta actualización resuelve issues recientes donde usuarios en ciertas regiones eran incapaces de ejecutar scripts debido a errores de CDN o filtrado TLS al descargar ficheros `.sh` desde URLs raw de GitHub. + + **🎖 Reconocimiento especial: @cod378** + Esta conversión offline ha sido posible gracias al extraordinario trabajo de **@cod378**, + que rediseñó toda la lógica interna del installer y el updater, refactorizó el sistema de gestión de ficheros, + e implementó el nuevo workflow de ejecución totalmente local. + Sin su colaboración, dedicación y aportación técnica, esta transformación no habría sido posible. + +- **ProxMenux Monitor v1.0.1** + Esta actualización trae un gran salto en la interfaz de **ProxMenux Monitor**. + Nuevas funciones y mejoras: + - `Proxy Support`: Accede a ProxMenux a través de proxies inversos con plena funcionalidad + - `Authentication System`: Asegura tu dashboard con protección por contraseña + - `Two-Factor Authentication (2FA)`: Soporte opcional TOTP para mayor seguridad + - `PCIe Link Speed Detection`: Ver velocidades de conexión NVMe y detectar cuellos de botella de rendimiento + - `Enhanced Storage Display`: Auto-formatea tamaños de disco (GB → TB cuando corresponde) + - `SATA/SAS Interface Info`: Detecta y muestra el tipo de storage (SATA, SAS, NVMe, etc.) + - `Health Monitoring System`: Health check integrado del sistema con alertas descartables + - Renderizado mejorado entre navegadores y mejor rendimiento + +- **Helper Scripts Menu (Mirror Support)** + El menú `Helper Scripts` ahora: + - Detecta **URLs mirror** y muestra opciones de descarga alternativas cuando están disponibles + - Lista las versiones de OS disponibles cuando un helper script depende de la versión (p. ej. instaladores de plantillas) + +--- + +### Arreglado + +- Fixes menores y refinamientos a lo largo del codebase para asegurar compatibilidad offline total y una experiencia de usuario más suave. + + + +## 2025-09-04 + +### Nueva versión v1.1.7 + +### Añadido + +- **ProxMenux Monitor** + Tu nueva herramienta de monitorización para Proxmox. Descubre todas las funciones que te ayudarán a gestionar y supervisar tu infraestructura eficientemente. + + ProxMenux Monitor está diseñado para soportar futuras actualizaciones donde **se puedan disparar acciones sin usar el terminal**, gestionadas a través de una **interfaz amigable** accesible en múltiples formatos y dispositivos. + + Accede en: **http://your-server-ip:8008** + + ![ProxMenux Monitor](https://macrimi.github.io/ProxMenux/monitor/welcome.png) +- **Nuevo método de eliminación de banner** + Una nueva función para deshabilitar el mensaje de suscripción de Proxmox con seguridad mejorada: + - Crea un backup completo antes de modificar ningún fichero + - Muestra un warning claro de que pueden producirse breaking changes con futuras actualizaciones de la GUI + - Si la GUI no carga, el usuario puede revertir los cambios por SSH desde el menú post-install usando la herramienta **"Uninstall Options → Restore Banner"** + + Gracias especiales a **@eryonki** por proporcionar el método mejorado. + +--- + +### Mejorado + +- **CORAL TPU Installer actualizado para PVE 9** + El instalador del driver CORAL TPU soporta ahora tanto **Proxmox VE 8 como VE 9**, asegurando compatibilidad con los kernels y udev rules más recientes. + +- **Instalación e integración de Log2RAM** + - La instalación de Log2RAM es ahora idempotente y puede ejecutarse con seguridad múltiples veces. + - Ajusta automáticamente la configuración de `journald` para alinearse con el tamaño y comportamiento de Log2RAM. + - Asegura que el journaling esté correctamente afinado para evitar overflows o agotamiento de RAM en sistemas con poca memoria. + +- **Función de optimización de Red (LXC + NFS)** + Mejorada para prevenir warnings de "martian source" en setups donde **contenedores LXC comparten storage con VMs** vía NFS dentro del mismo servidor. + +- **Progreso de APT Upgrade** + Al ejecutar actualizaciones completas del sistema vía ProxMenux, se muestra ahora una **barra de progreso en tiempo real**, dando al usuario visibilidad clara del proceso de update. + +--- + +### Arreglado + +- Otras pequeñas mejoras y fixes para optimizar el rendimiento en runtime y eliminar bugs menores. + + + +## 2025-01-10 + +### Nueva versión v1.1.6 + +![Shared Resources Menu](https://macrimi.github.io/ProxMenux/share/main-menu.png) + + +### Añadido + +- **Nuevo menú: Mount and Share Manager** + Introducido un nuevo menú integral para gestionar recursos compartidos entre el host Proxmox y los contenedores LXC: + + **Opciones de configuración del host:** + - **Configure NFS Shared on Host** - Añadir, ver y eliminar recursos NFS compartidos en el servidor Proxmox con gestión automática de exports + - **Configure Samba Shared on Host** - Añadir, ver y eliminar recursos Samba/CIFS compartidos en el servidor Proxmox con configuración de share + - **Configure Local Shared on Host** - Crear y gestionar directorios locales compartidos con los permisos adecuados en el host Proxmox + + **Opciones de integración LXC:** + - **Configure LXC Mount Points (Host ↔ Container)** - **Función core** que permite montar directorios del host dentro de contenedores LXC con gestión automática de permisos. Incluye la capacidad de **ver los mount points existentes** para cada contenedor de forma clara y organizada y **eliminar mount points** con verificación apropiada de que el proceso se completó con éxito. Especialmente optimizado para **contenedores no privilegiados** donde el mapeo UID/GID es crítico. + - **Configure NFS Client in LXC** - Configura un cliente NFS dentro de contenedores privilegiados + - **Configure Samba Client in LXC** - Configura un cliente Samba dentro de contenedores privilegiados + - **Configure NFS Server in LXC** - Instala servidor NFS dentro de contenedores privilegiados + - **Configure Samba Server in LXC** - Instala servidor Samba dentro de contenedores privilegiados + + **Documentación y soporte:** + - **Help & Info (commands)** - Guías integrales con instrucciones manuales paso a paso para todos los escenarios de compartición + + Todo el sistema está construido en torno a la funcionalidad de **LXC Mount Points**, que detecta automáticamente los tipos de filesystem, gestiona el mapeo de permisos entre usuarios del host y del contenedor, y proporciona integración fluida tanto para contenedores privilegiados como no privilegiados. + +--- + +### Mejorado + +- **Mejora de auto-detección de Log2RAM** + En el script automático de post-install, la función de instalación de Log2RAM ahora pregunta al usuario cuando la detección automática de disco ssd/m2 falla. + Esto asegura que Log2RAM pueda instalarse aún en sistemas donde la detección automática de disco no funciona correctamente. + +--- + +### Arreglado + +- **Verificación del repositorio de updates de Proxmox** + Arreglado un issue en la función de update de Proxmox donde los ficheros source vacíos del repositorio causaban errores durante la verificación de conflictos. La función gestiona ahora correctamente ficheros vacíos de `/etc/apt/sources.list.d/` sin lanzar falsos warnings. + + Gracias a **@JF_Car** por reportar este issue. + +--- + +### Reconocimientos + +Gracias especiales a **@JF_Car**, **@ghosthvj** y **@jonatanc** por sus pruebas, feedback valioso y sugerencias que ayudaron a refinar la funcionalidad de recursos compartidos y a mejorar la experiencia general de usuario. + + + +## 2025-08-20 + +### Nueva versión v1.1.5 + +### Añadido + +- **Nuevo script: Upgrade PVE 8 a PVE 9** + Añadida una herramienta de upgrade completa ubicada bajo `Utilities and Tools`. Proporciona: + 1. **Upgrade automático** de PVE 8 a 9 + 2. **Upgrade interactivo** con confirmaciones paso a paso + 3. **Modo check-only** usando `check-pve8to9` + 4. **Instrucciones manuales** mostradas en orden para usuarios que prefieren actualizar manualmente + +- **Nuevas herramientas en System Utilities** + - [`s-tui`](https://github.com/amanusk/s-tui): Monitorización de CPU basada en terminal con gráficas + - [`intel-gpu-tools`](https://gitlab.freedesktop.org/drm/igt-gpu-tools): Útil para diagnósticos de GPU Intel + +--- + +### Mejorado + +- **Gestión de APT Upgrade** + La función de upgrade de PVE bloquea ahora el proceso si algún paquete pide confirmación manual. Esto evita upgrades parciales y asegura consistencia. + +- **Optimización de Red (sysctl)** + - Parámetros de kernel obsoletos eliminados (p. ej. `tcp_tw_recycle`, `nf_conntrack_helper`) para prevenir warnings en **Proxmox 9 / kernel 6.14** + - Ahora genera solo parámetros sysctl válidos y al día + +- **Gestión de Patch de CPU AMD** + - Aplica ahora `idle=nomwait` correcto y opciones KVM (`ignore_msrs=1`, `report_ignored_msrs=0`) + - El warning esperado está ahora documentado y gestionado con seguridad para estabilidad con Ryzen/EPYC + +- **Fixes de Timezone y NTP** + - Detecta automáticamente la timezone usando geolocalización por IP pública + - Fallback a UTC si la detección falla + - Reinicia Postfix tras establecer la timezone → resuelve el warning de mismatch `/var/spool/postfix/etc/localtime` + +- **Lógica del Repository & Package Installer** + - Verifica ahora que existan repositorios funcionales antes de instalar ningún paquete + - Si no hay ninguno disponible, añade un repositorio **Debian stable** como fallback + - Reemplaza el obsoleto `mlocate` por `plocate` (compatible con Debian 13 y Proxmox 9) + +- **Logs y Feedback de usuario mejorados** + - Las acciones que fallan proporcionan ahora mensajes precisos (en lugar de marcarse falsamente como éxito) + - Ayuda a los usuarios a entender claramente qué se ha aplicado o saltado + + + +## 2025-08-06 + +### Nueva versión v1.1.4 + +### Añadido + +- **Preparación de compatibilidad con Proxmox 9** + Esta versión prepara **ProxMenux** para el próximo **Proxmox VE 9**: + - La función para añadir los repositorios oficiales de Proxmox soporta ahora el nuevo formato `.sources` usado en Proxmox 9, manteniendo retrocompatibilidad con Proxmox 8. + - La eliminación de banner se soporta ahora opcionalmente para Proxmox 9. + +- **Detección de xshok-proxmox** + Añadida una comprobación para detectar si el script post-install `xshok-proxmox` ya ha sido ejecutado. + Si se detecta, se muestra un warning para evitar ajustes conflictivos: + + ``` + It appears that you have already executed the xshok-proxmox post-install script on this system. + + If you continue, some adjustments may be duplicated or conflict with those already made by xshok. + + Do you want to continue anyway? + ``` + +--- + +### Mejorado + +- **Eliminación de banner (Proxmox 8.4.9+)** + Actualizada la lógica para eliminar el banner de suscripción en **Proxmox 8.4.9**, debido a cambios en `proxmoxlib.js`. + +- **LXC Disk Passthrough (UUID persistente)** + La función para añadir un disco físico a un contenedor LXC usa ahora **paths persistentes basados en UUID**. + Esto asegura que los discos permanezcan correctamente montados, incluso si el orden `/dev/sdX` cambia por hardware nuevo. + + ```bash + PERSISTENT_DISK=$(get_persistent_path "$DISK") + if [[ "$PERSISTENT_DISK" != "$DISK" ]] ... + ``` + +- **System Utilities Installer** + Comprueba ahora si las sources APT están disponibles antes de instalar las herramientas seleccionadas. + Si una nueva instalación de Proxmox no tiene repos activos, **añadirá automáticamente las sources por defecto** para evitar fallos de instalación. + +- **Activación de IOMMU en sistemas ZFS** + La función que habilita IOMMU para passthrough verifica ahora los parámetros de kernel existentes para evitar duplicación si el usuario ya los ha configurado manualmente. + +--- + +### Arreglado + +- Limpieza de código menor y rendimiento de runtime mejorado en varios módulos. + + + +## 2025-07-20 + +### Cambiado + +- **Eliminación del banner de suscripción (Proxmox 8.4.5+)** + Mejorada la función `remove_subscription_banner` para asegurar compatibilidad con Proxmox 8.4.5, donde el método de eliminación de banner fallaba tras instalaciones limpias. + +- **Detección de Log2RAM mejorada** + Tanto en los scripts post-install automático como personalizable, se ha mejorado la lógica para la instalación de Log2RAM. + Ahora detecta correctamente si Log2RAM ya está configurado y evita disparar errores o reconfiguración. + +- **Instalación de Figurine optimizada** + La función `install_figurine` evita ahora duplicar entradas en `.bashrc` si la personalización del prompt root ya existe. + + +### Añadido + +- **Nueva función: Nombrado persistente de interfaces de Red** + Añadida una nueva función `setup_persistent_network` para crear nombres de interfaz de red estables usando ficheros `.link` basados en direcciones MAC. + Esto evita renombrados impredecibles (p. ej. `enp2s0` convirtiéndose en `enp3s0`) cuando cambia el hardware, se reordena la topología PCI o se aplican configuraciones de passthrough. + + **¿Por qué usar ficheros `.link`?** + Porque los nombres predecibles de interfaz en `systemd` pueden cambiar con reordenamientos o reemplazos de hardware. Usar ficheros `.link` estáticos atados a direcciones MAC asegura consistencia, especialmente en sistemas con múltiples NICs o setups de passthrough. + + Gracias especiales a [@Andres_Eduardo_Rojas_Moya] por contribuir la función de nombrado + de red persistente y por la idea original. + +```bash +[Match] +MACAddress=XX:XX:XX:XX:XX:XX + +[Link] +Name=eth0 +``` + + +## 2025-07-01 + +### Nueva versión v1.1.3 + +![Installer Menu](https://macrimi.github.io/ProxMenux/install/install.png) + +- **Dos modos de instalación para ProxMenux** + El installer ofrece ahora dos modos distintos: + 1. **Versión Lite (sin traducciones):** Solo instala dos paquetes oficiales de Debian (`dialog`, `jq`) para habilitar menús y parsing JSON. No se escriben ficheros más allá del directorio de configuración. + 2. **Versión Full (con traducciones):** Usa un virtual environment y permite seleccionar el idioma de interfaz durante la instalación. + + Al actualizar, si el usuario cambia de full a lite, la versión vieja se **eliminará automáticamente** para una transición limpia. + +### Añadido + +- **Nuevo script: Setup automatizado de post-instalación** + Un nuevo script post-install minimal que realiza el setup esencial automáticamente: + - Upgrade y sync del sistema + - Eliminar el banner enterprise + - Optimizar APT, journald, logrotate, límites del sistema + - Mejorar gestión de kernel panic, ajustes de memoria, entropía, red + - Añadir tweaks a `.bashrc` y **auto-instalación de Log2RAM** (si se detecta SSD/M.2) + +- **Nueva función: Configuración de Log2RAM** + Disponible ahora tanto en los scripts post-install personalizable como automático. + En sistemas con SSD/NVMe, Log2RAM se **habilita automáticamente** para preservar la vida del disco. + +- **Nuevos menús:** + - 🧰 **System Utilities Menu** + Permite a los usuarios seleccionar e instalar herramientas CLI útiles con validación de comando apropiada. + - 🌐 **Network Configuration & Repair** + Un nuevo menú interactivo para analizar y reparar interfaces de red. + +### Mejorado + +- **Lógica del menú Post-Install** + Las opciones están ahora agrupadas más lógicamente para mejor usabilidad. + +- **Menú VM Creation** + Mejorado con soporte mejorado de modelo de CPU y opciones custom. + +- **Script UUP Dump ISO Creator** + - Añadida opción para **personalizar la ubicación de la carpeta temporal** + - Arreglado un issue donde se eliminaba la carpeta temp entera en lugar de solo el contenido + 💡 Sugerido por [@igrokit](https://github.com/igrokit) + [#17](https://github.com/MacRimi/ProxMenux/issues/17), [#11](https://github.com/MacRimi/ProxMenux/issues/11) + +- **Script Physical Disk to LXC** + Gestiona ahora **discos formateados con XFS** correctamente. + ¡Gracias a [@antroxin](https://github.com/antroxin) por reportar y testear! + +- **System Utilities Installer** + Reescrito para **verificar la disponibilidad del comando** tras la instalación, asegurando que las herramientas funcionen como se espera. + 🐛 Fix para [#18](https://github.com/MacRimi/ProxMenux/issues/18) por [@DST73](https://github.com/DST73) + +### Arreglado + +- **Habilitar IOMMU en ZFS** + La detección y configuración para habilitar IOMMU en sistemas basados en ZFS es ahora totalmente funcional. + 🐛 Fix para [#15](https://github.com/MacRimi/ProxMenux/issues/15) por [@troponaut](https://github.com/troponaut) + +### Otros + +- Mejoras de rendimiento y limpieza de código en varios módulos. + + + +## 2025-06-06 + +### Añadido + +- **Nuevo menú: Proxmox PVE Helper Scripts** + Introducido oficialmente el nuevo menú **Proxmox PVE Helper Scripts**, reemplazando el anterior: Esenciales Proxmox. + Este nuevo menú incluye: + - Búsqueda de script por nombre en tiempo real + - Navegación basada en categoría + + Es una forma más limpia, rápida y funcional de acceder a los scripts de la comunidad en Proxmox. + + ![Helper Scripts Menu](https://macrimi.github.io/ProxMenux/menu-helpers-script.png) + + +- **Nuevos modelos de CPU en VM Creation** + El menú de selección de CPU en VM creation ha sido ampliado considerablemente para soportar perfiles avanzados de CPU QEMU y x86-64. + Esto permite mejor compatibilidad con sistemas guest modernos y fine-tuning del rendimiento para workloads específicos, incluyendo virtualización anidada y funciones asistidas por hardware. + + + ![CPU Config](https://macrimi.github.io/ProxMenux/vm/config-cpu.png) + + Gracias a **@Nida Légé (Nidouille)** por sugerir esta mejora. + + +- **Soporte para imágenes de disco `.raw`** + La herramienta de import de disco para VMs soporta ahora ficheros `.raw`, además de `.img`, `.qcow2` y `.vmdk`. + Esto mejora la compatibilidad cuando se trabaja con exports de disco de otros hypervisors o herramientas de backup. + + 💡 Sugerido por **@guilloking** en [GitHub Issue #5](https://github.com/MacRimi/ProxMenux/issues/5) + + +- **Detección de Locale al saltarse idiomas** + La función que deshabilita idiomas extra de APT incluye ahora: + - Detección automática de locale (`LANG`) + - Auto-generación de `en_US.UTF-8` si no se encuentra ninguno + - Previene warnings durante ejecución de scripts debido a locale indefinido + + +### Mejorado + +- **Lógica de saltado de idioma APT** + La gestión mejorada de locale asegura compatibilidad del sistema antes de deshabilitar traducciones: + ```bash + if ! locale -a | grep -qi "^${default_locale//-/_}$"; then + echo "$default_locale UTF-8" >> /etc/locale.gen + locale-gen "$default_locale" + fi + ``` + +- **Velocidad de System Update** + Los upgrades de sistema post-install son ahora más rápidos: + - El proceso de upgrade (`dist-upgrade`) está separado de las actualizaciones de índice de plantillas de contenedor. + - El refresh de índice es ahora una función opcional seleccionada en el script. + + + +## 2025-05-27 + +### Arreglado +- **URL de ISO de Kali Linux actualizada** + Arreglada la URL de descarga incorrecta para el ISO de Kali Linux en el módulo de instalador Linux. El nuevo path correcto es: + ``` + https://cdimage.kali.org/kali-2025.1c/kali-linux-2025.1c-installer-amd64.iso + ``` + +### Mejorado +- **Transiciones de menú dialog más rápidas** + Mejorada la respuesta UI en todos los menús interactivos reemplazando `whiptail` por `dialog`, ofreciendo transiciones más rápidas y navegación más suave. + +- **Soporte Coral USB en LXC** + Mejorada la lógica para configurar passthrough de Coral USB TPU en contenedores LXC: + - Refactorizada la configuración en bloques modulares con mejor estructura y comentarios inline. + - Separación clara de la lógica de Coral USB (`/dev/coral`) y Coral M.2 (`/dev/apex_0`). + - Mantiene retrocompatibilidad con configuraciones LXC existentes. + - Introducido passthrough Coral USB persistente usando una udev rule: + ```bash + # Create udev rule for Coral USB + SUBSYSTEM=="usb", ATTRS{idVendor}=="18d1", ATTRS{idProduct}=="9302", MODE="0666", TAG+="uaccess", SYMLINK+="coral" + + # Map /dev/coral if it exists + if [ -e /dev/coral ]; then + echo "lxc.mount.entry: /dev/coral dev/coral none bind,optional,create=file" >> "$CONFIG_FILE" + fi + ``` + - Gracias especiales a **@Blaspt** por validar el passthrough Coral USB persistente y por sugerir el uso del symlink `/dev/coral`. + + +### Añadido +- **Soporte de passthrough Coral USB persistente** + Añadido soporte de udev rule para dispositivos Coral USB para mapearlos persistentemente como `/dev/coral`, habilitando passthrough consistente entre reboots. Este path se detecta y mapea automáticamente en la configuración del contenedor. + +- **Integración de RSS Feed** + Añadido soporte para generar un RSS feed para el changelog, permitiendo a los usuarios mantenerse informados de updates a través de clientes de noticias. + +- **Automatización del Release Service** + Implementado un nuevo servicio de gestión de release para automatizar la publicación y tagging de versiones, empezando con la versión **v1.1.2**. + + +## 2025-05-13 + +### Arreglado + +- **Fix de startup en versiones recientes de Proxmox**\ + Arreglado un issue donde algunas instalaciones recientes de Proxmox carecían del directorio `/usr/local/bin`, causando errores al instalar el menú de ejecución. El script crea ahora el directorio si no existe antes de descargar el menú principal.\ + Gracias a **@danielmateos** por detectar y reportar este issue. + +### Mejorado + +- **Lógica de instalación de Lynis actualizada en Post-Install Settings**\ + La función `install_lynis()` se ha mejorado para instalar siempre la **última versión** de Lynis clonando el repositorio oficial de GitHub: + ``` + https://github.com/CISOfy/lynis.git + ``` + El proceso de instalación asegura ahora que siempre se obtenga la última versión y se enlace correctamente dentro del path del sistema. + + Gracias a **@Kamunhas** por reportar esta oportunidad de mejora. + +- **Optimización de memoria equilibrada para sistemas con poca memoria** + Mejorados los ajustes de memoria por defecto para soportar mejor sistemas con RAM limitada. La configuración previa podía impedir que servidores de bajas specs arrancaran. Ahora se usa un conjunto más equilibrado de parámetros de kernel, y la compactación de memoria se habilita si el sistema la soporta. + + ```bash + cat <exactly the same functionality. Every backup — manual or scheduled — goes through the same choice matrix (three destinations × two profiles) and invokes the same backend function per cell (_bk_pbs, _bk_borg or _bk_local). The archives produced from either entry point are indistinguishable. Which one to use is a matter of preference: the TUI is SSH-friendly and scriptable; the Monitor offers point-and-click and lives alongside the notification and log tail views." + }, + "entryPoints": { + "heading": "The two entry points", + "rows": [ + { "entry": "ProxMenux Scripts (TUI)", "path": "menu → Utilities → Host Config Backup", "detail": "Dialog-based flow, SSH-friendly. The main menu presents the six options directly. Uses backup_menu in backup_host.sh." }, + { "entry": "ProxMenux Monitor (Web UI)", "path": "Backups tab → Create backup", "detail": "Wizard-style flow. Same six options presented as a two-step form (destination → profile). Same backend functions are invoked over the Flask API." } + ] + }, + "modes": { + "heading": "Manual vs scheduled backups", + "body": "Backups can be produced in two modes: manual (the interactive flow this page documents — the operator picks a destination and a profile from a menu and watches the archive land) or scheduled (an unattended job that runs on a cron-style timer and applies the retention configured on the job). Both modes support the same three destinations and the same two profiles, and both are available from both entry points — the Scripts TUI menu and the Monitor Backups tab. Scheduled jobs use the same backend functions as the manual flow through run_scheduled_backup.sh; the archives produced are indistinguishable.", + "seeAlso": "The scheduled-jobs page covers the full flow, including how to create a job, attach it to an existing PVE vzdump timer, and configure the retention values.", + "monitorAlt": "ProxMenux Monitor Backups tab showing the New scheduled backup dialog with destination, profile, schedule and retention fields.", + "monitorCaption": "Scheduled backup — ProxMenux Monitor. The same wizard-style dialog that creates a manual backup carries the schedule and retention fields at the bottom for the unattended path." + }, + "matrix": { + "heading": "The six-option matrix", + "intro": "The choice determines both which backend runs and what path selection strategy is applied. Cross-reference the destination-specific pages for the configuration details of each cell.", + "rows": [ + { "combo": "1", "destination": "PBS", "profile": "Default", "action": "Uploads the default profile plus persistent extras to a configured PBS repository." }, + { "combo": "2", "destination": "Borg", "profile": "Default", "action": "Creates an archive with the default profile plus persistent extras in the selected Borg repository." }, + { "combo": "3", "destination": "Local", "profile": "Default", "action": "Writes a .tar.zst archive with the default profile plus persistent extras to the configured local target." }, + { "combo": "4", "destination": "PBS", "profile": "Custom", "action": "Opens the path picker before the PBS upload; the operator ticks paths and can add new ones." }, + { "combo": "5", "destination": "Borg", "profile": "Custom", "action": "Opens the path picker before the Borg archive create." }, + { "combo": "6", "destination": "Local", "profile": "Custom", "action": "Opens the path picker before writing the local .tar.zst." } + ] + }, + "profiles": { + "heading": "Default vs Custom profile", + "defaultTitle": "Default profile", + "defaultBody": "The default profile is the curated list from hb_default_profile_paths (documented in How it works under Path categories) plus every entry in the persistent extras file /usr/local/share/proxmenux/backup-extra-paths.txt. The operator confirms the destination and encryption options and the backup proceeds without further path selection.", + "customTitle": "Custom profile", + "customBody": "The custom profile opens a checklist showing every path in the default profile (unchecked) and every persistent extra (pre-checked, prefixed with [+]). The operator ticks the set for this run and can press Add custom path to append a new absolute path. Any path added inline is persisted to backup-extra-paths.txt so future backups pick it up automatically without re-adding it. Removing a persistent extra unticks it for this run but does not delete it from the file — deletion is a separate Manage custom paths action outside the backup flow.", + "customPickerAlt": "Custom profile checklist showing the default-profile paths (unchecked) and persistent extras (pre-checked with a [+] prefix), plus buttons to add a new path or confirm the selection.", + "customPickerCaption": "Custom profile — the path picker. Default-profile paths are unchecked; persistent extras appear pre-checked with a [+] prefix. The operator ticks the set for this run.", + "manageCustomAlt": "Manage custom paths menu showing the list of persistent extras and options to add, remove or edit them.", + "manageCustomCaption": "Manage custom paths — the entry point where persistent extras are added or removed. Every path listed here is included automatically in Default-mode backups without needing to open the Custom picker." + }, + "commonPipeline": { + "heading": "What runs regardless of destination", + "intro": "After the profile is resolved, every backend runs the same staging pipeline before diverging into its own upload path. hb_prepare_staging assembles the archive tree in /tmp/proxmenux-DESTINATION-stage.XXXXXX and populates each of the three payloads.", + "steps": [ + { "step": "1", "name": "rootfs assembly", "detail": "Runs rsync -a for each selected path into staging_root/rootfs/. Excludes volatile subpaths (bash history, caches, trash) from /root/. Paths absent from the source are recorded in metadata/missing_paths.txt without stopping the backup." }, + { "step": "2", "name": "Manifest generation", "detail": "build_manifest.sh orchestrates the six collectors and writes manifest.json at the top of staging. On collector failure, the affected section falls back to a documented empty default; the manifest is still valid." }, + { "step": "3", "name": "Package inventory", "detail": "apt-mark showmanual is captured verbatim into metadata/packages.manual.list. Component state is already inside the restored rootfs (components_status.json) because /usr/local/share/proxmenux/ is part of the default profile." }, + { "step": "4", "name": "Run info", "detail": "metadata/run_info.env records the backup run identity — hostname, timestamp, kernel version — used by the restore's compatibility check to determine the cross-kernel direction." }, + { "step": "5", "name": "Notification (start)", "detail": "hb_notify_lifecycle \"start\" fires. If notifications are configured in the Monitor, an operator-facing Host backup started event is emitted. Silent if no channels are configured." } + ] + }, + "included": { + "heading": "What goes in and what stays out", + "intro": "Every path in the resolved profile (default + persistent extras + custom-mode selection) is copied with rsync -aAXH --numeric-ids. A shared exclusion list applies to every path, and two directories carry additional path-specific exclusions.", + "globalTitle": "Global exclusions (applied to every path)", + "globalItems": [ + "images/ — image dumps.", + "dump/ — vzdump outputs.", + "tmp/ — temporary files.", + "*.log — log files." + ], + "rootTitle": "/root/ exclusions", + "rootBody": "/root/ is part of the default profile so operator scripts and config land in the archive. Volatile subpaths are dropped:", + "rootItems": [ + ".bash_history", + ".cache/", + "tmp/", + ".local/share/Trash/" + ], + "proxmenuxTitle": "/usr/local/share/proxmenux/ exclusions", + "proxmenuxBody": "This directory ships user state only — components_status.json, preferences, post-install cache. Code that the destination will already have from its own ProxMenux install is excluded so a restore does not overwrite the target's current binaries with older ones:", + "proxmenuxItems": [ + "restore-pending/, scripts/, web/", + "monitor-app/, monitor-app.*/, AppImage/", + "images/, json/", + "utils.sh, helpers_cache.json", + "ProxMenux-Monitor.AppImage*, install_proxmenux*.sh" + ], + "notInProfileTitle": "Paths outside the profile", + "notInProfileBody": "Anything not listed in hb_default_profile_paths and not added as a custom or persistent extra is not part of the backup. Notable examples:", + "notInProfileItems": [ + "VM and LXC disks — handled by vzdump, not by this feature. Guest configuration files under /etc/pve/nodes/*/qemu-server/*.conf and lxc/*.conf are captured (they live under /etc/pve) so the restore reproduces the inventory; the disks themselves are re-attached from an existing vzdump/PBS backup.", + "/boot and /boot/efi — kernel binaries, initramfs and the UEFI ESP partition are regenerated by the target's own update-initramfs, update-grub or proxmox-boot-tool refresh after the restore. The bootloader is never copied verbatim.", + "Kernel and system runtime filesystems/proc, /sys, /dev and /run are pseudo-filesystems produced by the kernel and udev; they are not persisted anywhere.", + "Package binaries under /usr/bin, /usr/lib, /lib, /sbin — reinstalled by the target's APT from packages.manual.list.", + "/var/log/, /var/tmp/, /var/cache/ — per-host runtime state, not restored.", + "/home/USER — not in the default profile. Add it as a custom path when a system carries user home directories that must survive a restore." + ], + "customPathsTitle": "How custom paths are handled", + "customPathsBody": "A custom path added inline in Custom mode or persisted in backup-extra-paths.txt goes through the same rsync pipeline as default-profile paths. Global exclusions apply. If the custom path happens to be under /root/ or /usr/local/share/proxmenux/, the path-specific exclusions above still apply. Every archived path — default or custom — is recorded in metadata/paths_archived.txt. Paths that do not exist on the source are recorded in metadata/missing_paths.txt without stopping the backup." + }, + "archiveStructure": { + "heading": "Archive structure", + "intro": "The staging directory produced by every backend follows the same layout regardless of destination. The tarball, PBS .pxar or Borg archive stores this tree verbatim.", + "tree": "backup-[timestamp]/\n├── manifest.json # structured host state (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│ ├── packages.manual.list # output of apt-mark showmanual\n│ ├── run_info.env # hostname, timestamp, kernel version\n│ ├── paths_archived.txt # exact list of paths that reached rootfs/\n│ └── missing_paths.txt # paths from the profile absent on source\n└── rootfs/\n ├── etc/ # /etc/pve, /etc/network, /etc/ssh, /etc/apt, ...\n ├── root/ # /root without volatile subpaths\n ├── usr/local/ # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (state only)\n └── var/ # /var/lib/pve-cluster, /var/spool/cron/crontabs" + }, + "confirmation": { + "heading": "Confirmation summary", + "body": "Before the backend writes anything to the destination, ProxMenux shows a summary dialog with the destination, backup ID or archive name, encryption state, and the list of paths being copied. Cancelling here aborts the backup cleanly — the staging directory is removed by the trap hook set on the backend function and no partial data reaches the destination." + }, + "writing": { + "heading": "Writing to the destination", + "intro": "Once the operator confirms, each backend runs its own write step. The mechanics are covered in the destination pages; the shared surface is the log, the sidecar and the completion notification.", + "rows": [ + { "topic": "Log file", "detail": "Every backend writes its full output to /tmp/proxmenux-DESTINATION-backup-YYYYMMDD_HHMMSS.log and, on failure, offers to open it in a scrollable dialog. The log path is printed in the completion summary only when the file has content." }, + { "topic": "Sidecar (local only)", "detail": "hb_write_archive_sidecar drops a *.proxmenux.json next to the local archive so the Monitor identifies it as a ProxMenux host backup even after moves or renames." }, + { "topic": "Notification (complete/fail)", "detail": "hb_notify_lifecycle \"complete\" or \"fail\" fires with duration, archive size and — for failures — the last error-looking line from the log." } + ] + }, + "finishedScreens": { + "heading": "What a finished backup looks like", + "intro": "The same completion event is surfaced by both entry points. The TUI writes a summary block to the terminal; the Monitor's Backups tab shows the run in the archive list with size, duration and status badges.", + "scriptsAlt": "ProxMenux Scripts TUI showing a finished host backup — destination, backup ID, snapshot path, data size, duration and encryption state.", + "scriptsCaption": "Finished backup — ProxMenux Scripts (TUI). The completion block prints the destination, backup ID, resulting snapshot or archive name, data size, duration and encryption state.", + "monitorAlt": "ProxMenux Monitor Backups tab showing a finished host backup entry with size, duration, method badge and encryption indicator.", + "monitorCaption": "Finished backup — ProxMenux Monitor Backups tab. The new backup appears in the archive list with the destination method badge, size, duration and — when applicable — the encryption lock indicator." + }, + "whereNext": { + "heading": "Where to go next", + "items": [ + { "label": "Destinations", "href": "/docs/backup-restore/destinations", "tail": " — configuration details for Local, PBS and Borg." }, + { "label": "Scheduled jobs", "href": "/docs/backup-restore/scheduled-jobs", "tail": " — running the same backup unattended on a schedule instead of interactively." }, + { "label": "Restoring", "href": "/docs/backup-restore/restoring", "tail": " — the flow that consumes what this page produces." } + ] + } +} diff --git a/web/messages/en/docs/backup-restore/cross-kernel.json b/web/messages/en/docs/backup-restore/cross-kernel.json new file mode 100644 index 00000000..5dbc4709 --- /dev/null +++ b/web/messages/en/docs/backup-restore/cross-kernel.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Cross-kernel restore — direction detection, safe-subset filter, hydration | ProxMenux", + "description": "How ProxMenux restores a host backup on a target running a different major kernel version. Documents how the direction of the mismatch is detected, the safe-subset filter that skips boot-critical paths when the target runs a newer kernel than the backup, and the four-phase kernel-agnostic hydration that reapplies user tuning like IOMMU, VFIO IDs and custom cmdline tokens without copying kernel-tied files verbatim.", + "ogTitle": "ProxMenux Backup — cross-kernel restore and hydration", + "ogDescription": "Direction-aware cross-kernel restore with a safe-subset filter and kernel-agnostic hydration.", + "twitterTitle": "Cross-kernel restore | ProxMenux", + "twitterDescription": "How ProxMenux handles a restore when the target kernel differs from the backup's." + }, + "header": { + "title": "Cross-kernel restore", + "description": "How ProxMenux handles a restore when the target host runs a different kernel from the one recorded in the backup — especially when the target runs a newer kernel. Documents how the difference is detected, how boot-critical paths that could break the target are filtered, and how the user's own configuration is reapplied without copying kernel-tied files verbatim.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Every kernel jump is handled differently", + "body": "A Proxmox host's kernel is identified by a version string like 6.17.13-2-pve. The first two numbers (6.17 in this example) define the major version: when they change, some configuration files written for the previous kernel may not be valid on the new one. When the restore detects that the backup and the target have different major kernel versions, the flow branches based on the direction of the jump — whether the backup is older or newer than the target — because the two cases have opposite failure modes. Backups newer than the target restore cleanly as-is (bench-verified across multiple kernel jumps). Backups older than the target need a filter to avoid breaking the target's boot with configuration written for a kernel that has since changed." + }, + "directionCheck": { + "heading": "The direction check", + "intro": "During the compatibility check, hb_compat_check compares the kernel recorded in the backup's manifest against the target's current kernel (uname -r) and classifies the restore into one of three cases, stored in the internal variable HB_COMPAT_KERNEL_DIRECTION:", + "rows": [ + { "direction": "same", "condition": "The major kernel version matches between backup and target.", "behavior": "The full restore flow runs unchanged. No extra filter, no hydration, no special notice in the UI." }, + { "direction": "bk_newer", "condition": "Backup kernel is NEWER than the target's (for example: the backup was made on kernel 7.0 and the target runs kernel 6.17).", "behavior": "The full restore flow runs unchanged, exactly like same. No filter is applied. Verified empirically: drivers auto-reinstall against the target's kernel, IOMMU config applies cleanly, VMs with GPU passthrough come up without issue." }, + { "direction": "bk_older", "condition": "Backup kernel is OLDER than the target's (for example: the backup was made on kernel 6.17 and the target runs kernel 7.0).", "behavior": "The safe-subset filter and the four-phase hydration below are activated. The restore proceeds — the target reproduces the source, but boot-critical files are not copied verbatim." } + ] + }, + "whyBkNewerIsSafe": { + "heading": "Why a backup with a newer kernel than the target restores unchanged", + "body": "When the restore runs against a target with an older kernel than the one recorded in the backup, every mechanism the restore depends on is independent of the kernel version. GPU driver installers and other PCI-device installers detect the running kernel with uname -r and compile DKMS against whatever the target has. The APT package install pulls binaries built for the target's distribution. IOMMU cmdline tokens like intel_iommu=on are stable across major kernel versions. The backup carries paths written under a newer kernel, but those paths (module blacklists, GRUB defaults, initramfs config) are still valid syntax on the older one — kernels ignore tokens they do not recognise instead of failing. This case is equivalent in practice to a same-kernel restore, and ProxMenux treats it as such." + }, + "safeSubsetFilter": { + "heading": "The safe-subset filter (only when the target kernel is newer)", + "intro": "When the target kernel is newer than the backup's, the compatibility check adds 16 boot-critical paths from hb_unsafe_paths_cross_version to RS_SKIP_PATHS. These paths are excluded from the restore because writing a version of them from an older kernel over a target running a newer one has caused kernel panics in bench tests. The paths cover four categories:", + "categoryRows": [ + { "category": "Bootloader", "paths": "/etc/default/grub, /etc/kernel", "reason": "GRUB defaults tied to the prior kernel order, and proxmox-boot-tool state (cmdline, ESP UUIDs, hooks) that references paths and identifiers from the older install." }, + { "category": "Kernel modules and boot artifacts", "paths": "/etc/modules-load.d, /etc/modprobe.d, /etc/initramfs-tools", "reason": "Autoload lists that may reference modules renamed between kernel majors, module options that may not apply, initramfs hooks written for the older kernel." }, + { "category": "Storage stack and filesystem identity", "paths": "/etc/fstab, /etc/multipath, /etc/iscsi, /etc/udev/rules.d, /etc/zfs", "reason": "UUIDs that may not exist on this install, multipath drivers that change between kernels, iSCSI parameters that evolve, udev rules that may bind to nonexistent subsystems, ZFS state (zpool.cache + hostid) that can lock the pool as foreign." }, + { "category": "APT sources", "paths": "/etc/apt", "reason": "APT source suites may trigger a downgrade of critical packages on the next upgrade." }, + { "category": "systemd", "paths": "/etc/systemd/system, /etc/systemd/journald.conf, /etc/systemd/logind.conf, /etc/systemd/system.conf, /etc/systemd/user.conf", "reason": "Unit overrides and .wants tied to the older systemd major; configuration keys that may not parse on a newer systemd." } + ], + "outroBody": "The filter runs BEFORE the confirmation dialog so the user sees the exact list of paths that will be skipped, categorised by reason. The restore still proceeds — everything else (VMs, LXCs, network, /etc/pve, users, cron, ProxMenux state, packages, drivers, /root) is restored normally." + }, + "hydration": { + "heading": "Kernel-agnostic hydration", + "intro": "The safe-subset filter alone would leave the target without the tuning the user had inside those boot-critical files: IOMMU cmdline for GPU passthrough, VFIO device IDs, custom GRUB_TIMEOUT, nvidia blacklists. The hydration pass re-applies those bits kernel-agnostically. Four phases run when the target kernel is newer than the backup's, each additive (never overwrites a value the target already carries) and idempotent (running twice is a no-op).", + "phaseRows": [ + { "phase": "1a — GRUB path", "detail": "For hosts using GRUB (ext4/lvm installs). _rs_hyd_grub merges every token from the backup's manifest.kernel_params.cmdline_extra into the target's live GRUB_CMDLINE_LINUX_DEFAULT, skipping tokens whose key the target already carries. Then merges whitelisted GRUB_* keys (GRUB_TIMEOUT, GRUB_TIMEOUT_STYLE, GRUB_DEFAULT, GRUB_TERMINAL, GRUB_DISABLE_OS_PROBER, GRUB_SERIAL_COMMAND, GRUB_GFXMODE, GRUB_GFXPAYLOAD_LINUX) from the backup's /etc/default/grub if they differ from the target's." }, + { "phase": "1b — systemd-boot / ZFS path", "detail": "For hosts using systemd-boot (typically ZFS-on-root). _rs_hyd_kernel_cmdline merges operator tokens from cmdline_extra into the target's /etc/kernel/cmdline, keeping the target's own root=, boot= and rootflags= boilerplate intact." }, + { "phase": "2 — /etc/modules merge", "detail": "_rs_hyd_modules appends modules from manifest.kernel_params.modules_loaded_at_boot that are in the whitelist (vfio, vfio_pci, vfio_iommu_type1, vfio_virqfd, kvm, kvm_intel, kvm_amd, nvidia, nvidia_drm, nvidia_modeset, nvidia_uvm, i915, xe) AND not already present in the target's /etc/modules." }, + { "phase": "3 — Whitelisted files copy", "detail": "_rs_hyd_files copies operator-authored files from the staging rootfs to the live target when the content differs. Whitelist covers VFIO/nvidia/blacklist files under /etc/modprobe.d, /etc/modules-load.d, and the ProxMenux VFIO bind rule + nvidia udev rules under /etc/udev/rules.d. Distro-owned files (pve-blacklist.conf, mdadm.conf, nvme.conf) are intentionally excluded — their contents evolve between releases." }, + { "phase": "4 — Force post-boot reflows", "detail": "The four phases write directly to the live target OUTSIDE the normal restore pipeline. To make the merged tokens/modules/files take effect on the next boot, HB_HYDRATION_APPLIED=1 propagates through plan.env to apply_pending_restore.sh, which forces NEEDS_INITRAMFS=1 and NEEDS_GRUB=1 regardless of what was in the apply list. The post-boot dispatcher then regenerates initramfs and refreshes the bootloader." } + ] + }, + "planCommit": { + "heading": "Plan vs commit — the operator sees a preview first", + "body": "The hydration runs in two modes. Before the confirmation dialog, ProxMenux runs _rs_apply_bk_older_hydration in plan mode: it computes exactly what would be merged, populates RS_HYDRATION_SUMMARY with a green block listing each action, and returns without writing. The confirmation dialog shows that green block alongside the amber safe-subset skip list, so the user sees UPFRONT what will be re-applied automatically. After the user confirms, ProxMenux re-runs the same helper in commit mode — same phases, same logic, but this time each phase writes to the live target. Cancelling the confirmation dialog leaves the target untouched." + }, + "flowDiagram": { + "heading": "The flow when the target kernel is newer than the backup's", + "intro": "The steps added when the target kernel is newer sit inside the normal restore flow. Everything else — hot apply, pending prepare, package install, post-boot dispatcher — runs identically to a same-kernel restore.", + "diagram": " ┌────────────────────────────────────────────────────────────┐\n │ hb_compat_check │\n │ HB_COMPAT_KERNEL_DIRECTION = bk_older │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Add 16 boot-critical paths to RS_SKIP_PATHS │\n │ /etc/default/grub, /etc/kernel, /etc/modules-load.d, ... │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ _rs_apply_bk_older_hydration \"plan\" │\n │ Compute what would be merged │\n │ Populate RS_HYDRATION_SUMMARY (green block) │\n │ No writes │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Confirmation dialog │\n │ Amber: paths skipped by safe-subset filter │\n │ Green: tokens/files reapplied by hydration │\n │ User accepts or cancels │\n └───────────────────────────┬────────────────────────────────┘\n │ (accepted)\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ _rs_apply_bk_older_hydration \"commit\" │\n │ Phase 1a/1b: merge cmdline tokens + GRUB keys │\n │ Phase 2: append modules to /etc/modules │\n │ Phase 3: copy whitelisted vfio/nvidia files │\n │ Set HB_HYDRATION_APPLIED=1 │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Rest of the restore runs normally │\n │ _rs_apply hot (skips RS_SKIP_PATHS) │\n │ _rs_prepare_pending_restore (writes plan.env with │\n │ HB_HYDRATION_APPLIED=1) │\n │ packages.manual.list install │\n │ Reboot │\n │ apply_pending_restore.sh (forces NEEDS_INITRAMFS=1, │\n │ NEEDS_GRUB=1 because of the hydration flag) │\n │ apply_cluster_postboot.sh │\n │ update-initramfs -u -k all │\n │ update-grub / proxmox-boot-tool refresh │\n │ component --auto-reinstall │\n └────────────────────────────────────────────────────────────┘" + }, + "concreteExamples": { + "heading": "Concrete examples", + "intro": "The hydration pass is not abstract — it produces observable, correct outcomes for common scenarios. Two examples that hydration handles automatically:", + "rows": [ + { "scenario": "GPU passthrough (VFIO)", "detail": "Source had intel_iommu=on iommu=pt in the cmdline, vfio/vfio_pci/vfio_iommu_type1 in /etc/modules, an /etc/modprobe.d/vfio.conf with options vfio-pci ids=10de:2216, and a /etc/modprobe.d/blacklist-nvidia.conf. Hydration merges the cmdline tokens into the target's GRUB or kernel cmdline, appends the vfio modules to /etc/modules, and copies the two user-authored modprobe files. On the next boot, IOMMU is active, VFIO modules load, the GPU is bound to vfio-pci and the VM comes up with passthrough working." }, + { "scenario": "Custom GRUB defaults", "detail": "Source had GRUB_TIMEOUT=1 and GRUB_DISABLE_OS_PROBER=true. Hydration reads both keys from the backup's /etc/default/grub, sees they differ from the fresh install's defaults, and rewrites those two lines in the target's file (leaving everything else, including GRUB_DISTRIBUTOR, untouched)." } + ] + }, + "callout": { + "warningTitle": "What hydration does not reapply when the target kernel is newer", + "warningBody": "Hydration reapplies only the configuration ProxMenux knows to be safe across kernel versions: IOMMU tokens, VFIO IDs, whitelisted GRUB keys, and modules from a fixed list (vfio*, nvidia*, i915, xe, kvm*). Anything outside that set — custom initramfs hooks under /etc/initramfs-tools/hooks/, non-whitelisted files under /etc/modprobe.d/, user-authored systemd unit overrides — remains excluded. The reason is concrete: those files can call kernel-internal interfaces (module APIs, /sys layout, udev hooks) that change between major versions, and applying them verbatim over the newer kernel can prevent the target from booting. When exact reproduction of the boot chain is required, the restore must run on a host with the same major kernel version as the backup." + }, + "codeReference": { + "heading": "Where the mechanisms live", + "intro": "For developers looking to trace or extend the cross-kernel behaviour:", + "rows": [ + { "component": "Direction detection", "location": "hb_compat_check in lib_host_backup_common.sh. Sets HB_COMPAT_KERNEL_DIRECTION." }, + { "component": "Safe-subset path list", "location": "hb_unsafe_paths_cross_version in lib_host_backup_common.sh. Emits path\\treason lines used by both the CLI filter and the Web endpoint /api/host-backups/restore/prepare." }, + { "component": "Hydration phases", "location": "_rs_hyd_grub, _rs_hyd_kernel_cmdline, _rs_hyd_modules, _rs_hyd_files in backup_host.sh. Orchestrated by _rs_apply_bk_older_hydration." }, + { "component": "Post-boot reflow forcing", "location": "apply_pending_restore.sh reads HB_HYDRATION_APPLIED from plan.env and forces NEEDS_INITRAMFS=1/NEEDS_GRUB=1." }, + { "component": "Web preview", "location": "/api/host-backups/restore/prepare in flask_server.py. Sources the helper library, runs _rs_apply_bk_older_hydration in plan mode, returns the actions to the Web modal." } + ] + }, + "whereNext": { + "heading": "Where to go next", + "items": [ + { "label": "Restoring", "href": "/docs/backup-restore/restoring", "tail": " — the full restore pipeline this page's mechanisms plug into." }, + { "label": "How it works", "href": "/docs/backup-restore/how-it-works", "tail": " — the manifest and collectors that produce the kernel_params block hydration reads from." } + ] + } +} diff --git a/web/messages/en/docs/backup-restore/destinations/borg.json b/web/messages/en/docs/backup-restore/destinations/borg.json new file mode 100644 index 00000000..78d5a3ec --- /dev/null +++ b/web/messages/en/docs/backup-restore/destinations/borg.json @@ -0,0 +1,148 @@ +{ + "meta": { + "title": "Borg destination — repository types, SSH auth, encryption | ProxMenux", + "description": "The Borg destination writes ProxMenux host backups to a Borg repository — local, on a mounted external disk, or on a remote server accessed via SSH. Covers the borg binary resolution chain, the four SSH key strategies, repokey encryption, saved target configuration and per-scheduled-job retention.", + "ogTitle": "ProxMenux Backup — Borg destination", + "ogDescription": "How ProxMenux writes host backups to Borg repositories, with SSH auth strategies and repokey encryption.", + "twitterTitle": "Borg backup destination | ProxMenux", + "twitterDescription": "Local, USB and SSH-served Borg repositories for host backups." + }, + "header": { + "title": "Borg", + "description": "The Borg destination writes host backups to a Borg repository. Three repository types are supported: local filesystem path, mounted external disk, or remote server via SSH. Chunk-level deduplication across all archives in the repository and optional repokey encryption.", + "section": "Backup & Restore" + }, + "aboutBorg": { + "heading": "What Borg is", + "body": "Borg (also spelled BorgBackup) is an open-source deduplicating backup tool maintained by the Borg Backup community. It stores every archive as a set of variable-length chunks inside a repository; chunks are shared across archives so re-backing up unchanged data has near-zero storage cost. Borg is not tied to Proxmox — it is a general-purpose backup tool used across many environments. ProxMenux uses it as one of the three destinations for host backups; every Borg-specific mechanism described on this page (repository types, borg-serve over SSH, repokey encryption, prune) is standard Borg behaviour." + }, + "intro": { + "title": "Chunk-based deduplication with local or SSH-served repositories", + "body": "A Borg repository stores chunks that are shared across every archive it contains, so re-backing up an unchanged file transfers and stores nothing new. ProxMenux invokes borg create against a repository at backup time and borg extract at restore time. The repository can live on the same filesystem as the source, on a mounted external disk, or on a remote host accessed over SSH." + }, + "binarySourcing": { + "heading": "The borg binary", + "intro": "Borg is not a base dependency of the ProxMenux installer. When a backup runs, hb_ensure_borg resolves the binary from four sources in order and returns the first that works:", + "rows": [ + { "priority": "1", "source": "System borg", "detail": "If the host already has borg installed via APT (which borg resolves), that binary is used." }, + { "priority": "2", "source": "State-dir cache", "detail": "/usr/local/share/proxmenux/borg — kept from a prior GitHub download on this host." }, + { "priority": "3", "source": "Monitor AppImage bundle", "detail": "/usr/local/share/proxmenux/monitor-app/usr/bin/borg — the AppImage bundles a signed borg-linux64. This is the offline-safe path: a host with no internet still has a working borg." }, + { "priority": "4", "source": "GitHub download", "detail": "wget against the pinned borg-linux64 URL under github.com/borgbackup/borg/releases/, verified against a constant SHA-256 in lib_host_backup_common.sh (HB_BORG_LINUX64_SHA256). On checksum mismatch the binary is discarded and the backup aborts. The downloaded file is cached in the state dir so subsequent backups use path 2." } + ] + }, + "repoTypes": { + "heading": "Repository types", + "intro": "The repository type is chosen when a target is added. Each type resolves to a different repository URL that Borg understands.", + "rows": [ + { "type": "remote", "url": "ssh://USER@HOST/RPATH", "detail": "Repository lives on a remote server that runs borg serve. Requires an SSH connection to that server." }, + { "type": "usb", "url": "/mnt/MOUNTPOINT/borgbackup", "detail": "Repository lives on a mounted external disk (typically USB). The mount is resolved via hb_prompt_mounted_path, which detects, mounts or formats USB partitions as needed." }, + { "type": "local", "url": "/backup/borgbackup (any absolute path)", "detail": "Repository lives on a local directory. Meaningful only when the target directory is on a separate physical disk — a repository on the same disk as the source protects against operator error but not against disk failure." } + ] + }, + "serverSetup": { + "heading": "Preparing the Borg server (server side)", + "intro": "The remote repository type expects a working Borg server accessible over SSH. ProxMenux does not bootstrap the server itself — it only authorises a key against an existing account on it. This section documents what the server needs before ProxMenux can connect.", + "hostChoicesTitle": "Where the server can live", + "hostChoicesBody": "Any Linux host with SSH access qualifies. Common setups:", + "hostChoicesItems": [ + "A dedicated NAS or backup box (Debian, Ubuntu, TrueNAS SCALE with a shell).", + "An LXC container inside a Proxmox node. Small footprint, isolated from the host running the backups.", + "Another Proxmox host on the same LAN, or a VM anywhere reachable over SSH." + ], + "lxcWarningTitle": "LXC as Borg server", + "lxcWarningBody": "If the Borg server runs inside an LXC, the container must have a user account with a password.", + "requirementsTitle": "Requirements on the server", + "requirementsRows": [ + { "requirement": "borg binary at /usr/bin/borg", "detail": "The command=\"/usr/bin/borg serve ...\" line ProxMenux writes into authorized_keys hard-codes that path. Installing via APT (apt install borgbackup) puts it there. A standalone binary must be symlinked to /usr/bin/borg." }, + { "requirement": "A dedicated user account (typically borg)", "detail": "Owns the repository directory and receives incoming SSH connections. Does not need sudo or shell access — the authorized_keys line disables interactive shells anyway." }, + { "requirement": "A writable repository directory", "detail": "The path the operator gives ProxMenux (for example /backup/borgbackup) must exist on the server and be owned by the borg user." }, + { "requirement": "SSH daemon accepting the borg user", "detail": "PubkeyAuthentication yes (default). Password authentication is only needed for the one-shot generate-auto flow — after the key is installed, the server can disable password auth entirely." } + ], + "minimalSetupTitle": "Minimal server setup", + "minimalSetupBody": "On a Debian or Ubuntu Borg server, a working baseline is four commands as root:", + "minimalSetupCmd": "apt install borgbackup\nuseradd -m -d /home/borg -s /bin/bash borg\nmkdir -p /backup/borgbackup\nchown borg:borg /backup/borgbackup", + "minimalSetupNote": "After this, ProxMenux's generate-auto mode can connect using the borg user's password once, install its own SSH key, and every subsequent backup uses that key. No further server-side configuration is needed — the key restricts itself to borg serve on that path." + }, + "sshAuth": { + "heading": "SSH authentication (client side)", + "intro": "Remote Borg repositories are accessed over SSH. The connection runs from the ProxMenux host to a user account on the Borg server that runs borg serve. This user is typically named borg, NOT the admin/root user of the server — the borg-serve command line locks the connection to that specific repository path (see the key strategies below).", + "strategiesTitle": "The four key strategies", + "strategiesIntro": "When a remote target is added, ProxMenux prompts for the SSH user, host and remote path, then asks how to authenticate. Four modes:", + "strategyRows": [ + { "mode": "generate-auto", "label": "Recommended", "detail": "ProxMenux generates a new ed25519 keypair at ~/.ssh/borg_proxmenux_HOST_ed25519, then uses sshpass to log in ONCE to the server with the admin password and append the public key to ~borg/.ssh/authorized_keys. The admin password is only used for this one call — it is never stored." }, + { "mode": "generate-manual", "label": "No admin password on this host", "detail": "ProxMenux generates the keypair as above but displays the full authorized_keys line for the operator to paste manually into the server. The admin password never leaves the ProxMenux host because it is never asked for." }, + { "mode": "generate-pct", "label": "Borg server is a PVE LXC", "detail": "The Borg server runs inside an LXC on a PVE node. ProxMenux authorises the key via pct exec from the PVE host — root on the PVE host writes into the LXC's ~borg/.ssh/authorized_keys without needing SSH into the LXC itself." }, + { "mode": "existing", "label": "Use an existing key", "detail": "ProxMenux scans /root/.ssh/ and $HOME/.ssh/ for parseable ed25519/RSA private keys and lists them. The operator picks one or browses manually to a non-standard path." }, + { "mode": "none", "label": "Default SSH config", "detail": "No custom key — Borg relies on the host's default SSH configuration (typically ~/.ssh/id_rsa or an SSH agent)." } + ], + "restrictTitle": "The authorized_keys line", + "restrictBody": "For every generated key, the authorized_keys line ProxMenux writes on the server locks the key to a single borg-serve invocation against the configured repository path:", + "restrictLine": "command=\"/usr/bin/borg serve --restrict-to-path RPATH\",restrict PUBKEY", + "restrictNote": "The command= forces every SSH session using this key to run only that borg-serve command; restrict disables port forwarding, agent forwarding, X11 forwarding and PTY allocation. The key cannot be used to open an interactive shell on the server or to access any other repository path, even if the account has broader privileges." + }, + "savedTargets": { + "heading": "Saved targets", + "intro": "A saved target persists the repository configuration under a friendly name so the details do not have to be re-entered. Storage layout in the ProxMenux state directory:", + "rows": [ + { "file": "borg-targets.txt", "content": "One line per target: NAME|REPO|SSH_KEY_PATH|ENCRYPT_MODE. Read by hb_collect_borg_configs to populate the target-selection menu." }, + { "file": "borg-pass-NAME.txt", "content": "Passphrase for the target NAMEd above (chmod 600). Only present when the operator chose repokey encryption." } + ], + "outro": "Saving is optional — the operator can decline the save prompt for a one-shot backup that leaves no credentials on the host." + }, + "encryption": { + "heading": "Encryption", + "body": "Repositories are initialised with an encryption mode via hb_borg_init_if_needed: the modern borg repo-create -e MODE when available, or the legacy borg init --encryption=MODE. ProxMenux exposes two modes: repokey (default) and none. In repokey mode Borg stores the encryption key inside the repository itself; access requires a passphrase, which ProxMenux prompts for twice with match validation and stores at borg-pass-NAME.txt. A mandatory acknowledgement dialog is shown after the passphrase is saved: the passphrase is the only way to access the encrypted archives, and losing it makes every archive in the repository unrecoverable." + }, + "runtimeEnv": { + "heading": "Runtime environment", + "intro": "ProxMenux exports the following environment variables before invoking borg. They configure the connection and unlock the repository without embedding secrets in the command line.", + "rows": [ + { "var": "BORG_RSH", "value": "ssh -i SSH_KEY_PATH -o StrictHostKeyChecking=accept-new", "purpose": "The remote-shell command Borg uses for SSH-served repositories. Set only when a custom key was selected; otherwise unset so Borg uses the default SSH configuration." }, + { "var": "BORG_PASSPHRASE", "value": "(passphrase from borg-pass-NAME.txt)", "purpose": "Unlocks the repokey. Set only when the target uses repokey encryption. Never appears in the process argument list." }, + { "var": "BORG_ENCRYPT_MODE", "value": "repokey | none", "purpose": "Used by hb_borg_init_if_needed when initialising a repository that does not yet exist. Ignored by borg create on existing repositories." }, + { "var": "BORG_RELOCATED_REPO_ACCESS_IS_OK", "value": "yes", "purpose": "Suppresses the interactive prompt Borg raises when the repository URL differs from the URL it was originally reached from (common after a mount-point rename or SSH-host reachable via new address)." }, + { "var": "BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK", "value": "yes", "purpose": "Suppresses the interactive prompt Borg raises the first time an unencrypted repository is accessed from a new client." } + ] + }, + "archiveFormat": { + "heading": "Archive naming and retention", + "intro": "Every backup creates a new archive inside the repository.", + "namePattern": "hostcfg-HOSTNAME-YYYYMMDD_HHMMSS", + "retentionBody": "For scheduled jobs, run_scheduled_backup.sh runs borg prune against the repository after each successful backup with the retention values configured on the job (--keep-last, --keep-daily, --keep-weekly). Interactive backups do not prune." + }, + "restoreAccess": { + "heading": "Retrieval on the restore side", + "body": "The restore flow lists archives with borg list REPO and extracts the selected one with borg extract REPO::ARCHIVE-NAME into a staging directory that _rs_check_layout feeds to the standard restore pipeline. For manual retrieval outside ProxMenux the same commands work; the extracted tree is the standard three-payload layout described in How it works." + }, + "references": { + "heading": "References", + "intro": "Official Borg documentation for the components ProxMenux relies on.", + "items": [ + { + "label": "Borg documentation", + "href": "https://borgbackup.readthedocs.io/", + "tail": " — main entry point covering concepts, deployment, quickstart and every command." + }, + { + "label": "borg create", + "href": "https://borgbackup.readthedocs.io/en/stable/usage/create.html", + "tail": " — the command ProxMenux invokes on every backup, with all supported flags." + }, + { + "label": "Repository encryption", + "href": "https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-mode", + "tail": " — the encryption modes Borg supports, including repokey which ProxMenux uses by default." + }, + { + "label": "borg serve and SSH deployment", + "href": "https://borgbackup.readthedocs.io/en/stable/deployment/central-backup-server.html", + "tail": " — how to set up a central Borg server accessed over SSH, including the borg-serve command and the restrict-to-path flag ProxMenux writes into authorized_keys." + }, + { + "label": "borg prune", + "href": "https://borgbackup.readthedocs.io/en/stable/usage/prune.html", + "tail": " — the retention model behind --keep-last / --keep-daily / --keep-weekly that ProxMenux applies per scheduled job." + } + ] + } +} diff --git a/web/messages/en/docs/backup-restore/destinations/index.json b/web/messages/en/docs/backup-restore/destinations/index.json new file mode 100644 index 00000000..a1d262a2 --- /dev/null +++ b/web/messages/en/docs/backup-restore/destinations/index.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Backup destinations — Local, Proxmox Backup Server, Borg | ProxMenux", + "description": "Three storage backends receive the same ProxMenux backup payload: a local .tar.zst archive on any writeable filesystem, a PBS backup in a Proxmox Backup Server datastore, or a Borg archive in a local or remote Borg repository. Each backend has its own compression, deduplication, encryption and network characteristics.", + "ogTitle": "ProxMenux Backup Destinations", + "ogDescription": "Local, Proxmox Backup Server and Borg — three storage backends for the same host backup payload.", + "twitterTitle": "ProxMenux Backup Destinations | ProxMenux", + "twitterDescription": "Local archive, Proxmox Backup Server and Borg backends for host backups." + }, + "header": { + "title": "Destinations", + "description": "Three storage backends supported by ProxMenux Backup: local archive, Proxmox Backup Server (recommended) and Borg. Each receives the same three-payload archive; they differ in storage format, deduplication, encryption model and network requirements.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Same payload, three backends", + "body": "Every backup produces the same three payloads (rootfs, manifest, applications). What changes between destinations is how those payloads are stored and how the operator retrieves them for a restore. Each backend is implemented as a separate function in backup_host.sh_bk_local, _bk_pbs, _bk_borg — but all four share the same staging step (hb_prepare_staging) and the same restore code path. The choice of destination only affects the write and the retrieval; it does not change what a restore does or how it consumes the archive." + }, + "comparison": { + "heading": "Feature comparison", + "intro": "The following table lists the concrete differences between the three backends as they behave in ProxMenux today. All values describe observed behaviour of the backend itself, not editorial recommendations.", + "rows": [ + { + "feature": "Storage format", + "local": "Single .tar.zst file (or .tar.gz if zstd is absent).", + "pbs": "PBS backup (.pxar chunks in the datastore).", + "borg": "Borg archive inside a Borg repository (segment files)." + }, + { + "feature": "Compression", + "local": "zstd (default level) via tar --zstd. gzip fallback.", + "pbs": "PBS-managed. The client streams uncompressed; the server chunks + compresses.", + "borg": "Borg's built-in (lz4 default). Configurable at repo init." + }, + { + "feature": "Deduplication", + "local": "None. Each backup is a full independent archive.", + "pbs": "Full chunk-level deduplication across all backups in the datastore.", + "borg": "Full chunk-level deduplication across all archives in the repository." + }, + { + "feature": "Encryption at rest", + "local": "None (relies on filesystem-level protection).", + "pbs": "Optional client-side keyfile encryption. Recovery blob uploaded as separate backup group when enabled.", + "borg": "Optional repokey encryption (key stored in the repo, unlocked by a passphrase)." + }, + { + "feature": "Retention / pruning", + "local": "Applied per scheduled job via KEEP_LAST. Old archives (and their sidecars + runner logs) are deleted symmetrically. Interactive backups do not prune.", + "pbs": "Applied per scheduled job via proxmox-backup-client prune --keep-last / --keep-daily / --keep-weekly. Interactive backups do not prune.", + "borg": "Applied per scheduled job via borg prune --keep-last / --keep-daily / --keep-weekly. Interactive backups do not prune." + }, + { + "feature": "Network", + "local": "None. Writes to a local mount point (typically an internal disk or a USB drive).", + "pbs": "TCP to the PBS server (default port 8007). Requires PBS credentials + fingerprint.", + "borg": "Local filesystem path or SSH tunnel to a remote Borg host." + }, + { + "feature": "Dependencies", + "local": "tar, zstd (present on Proxmox by default).", + "pbs": "proxmox-backup-client package (bundled with Proxmox VE 8+).", + "borg": "borg binary. Auto-provisioned by ProxMenux from the Monitor AppImage bundle when missing." + }, + { + "feature": "Restore access", + "local": "Any host with tar+zstd can extract the archive. No PBS/Borg needed.", + "pbs": "Requires proxmox-backup-client + PBS credentials + the keyfile (if encrypted).", + "borg": "Requires borg + the repository path + the passphrase (if encrypted)." + } + ], + "captionCode": "feature", + "captionLocal": "Local", + "captionPbs": "Proxmox Backup Server (recommended)", + "captionBorg": "Borg" + }, + "sameArchive": { + "heading": "The archive layout does not change with the destination", + "body": "Inside any of the three destinations, the same rootfs/ + metadata/ + manifest.json layout described in How it works is present. A .tar.zst extracted from a local archive, a .pxar restored from PBS, and a Borg archive extracted with borg extract all yield an identical directory tree. The restore code path (_rs_check_layout, _rs_apply, _rs_prepare_pending_restore) reads the same three payloads without knowing which destination they came from." + }, + "extractStandalone": { + "heading": "Extracting a backup outside of ProxMenux", + "intro": "Any of the three archive formats can be read with standard tools without ProxMenux installed on the reading host. The commands below produce the same directory tree that a restore consumes internally.", + "localCmd": "# From a local .tar.zst archive:\ntar --zstd -xf hostcfg-HOST-TIMESTAMP.tar.zst\n\n# From a .tar.gz fallback:\ntar -xzf hostcfg-HOST-TIMESTAMP.tar.gz", + "pbsCmd": "# From a PBS backup (requires proxmox-backup-client):\nproxmox-backup-client restore \\\n --repository USER@REALM@HOST:DATASTORE \\\n host/hostcfg-HOST/BACKUP-TIME \\\n hostcfg.pxar /tmp/hostcfg\n\n# Add --keyfile KEY-PATH when the backup is encrypted.", + "borgCmd": "# From a Borg archive (requires borg binary + passphrase for encrypted repos):\nborg extract REPO-PATH::ARCHIVE-NAME\n\n# On a remote SSH-served repo:\nborg extract ssh://USER@HOST:PORT/REPO-PATH::ARCHIVE-NAME", + "note": "The extracted tree can then be inspected manually or fed to a manual restore. See the individual destination pages for the exact retrieval flow ProxMenux uses under the hood." + }, + "whereNext": { + "heading": "Per-destination detail", + "intro": "Each destination has its own page covering the configuration flow, the on-disk format and the retrieval command used by the restore.", + "items": [ + { + "label": "Local archive", + "href": "/docs/backup-restore/destinations/local", + "tail": " — pre-configuring a local target, USB drive mounting, the safety check against writing the archive into itself, the sidecar JSON that lets the Monitor identify the file." + }, + { + "label": "Proxmox Backup Server (recommended)", + "href": "/docs/backup-restore/destinations/pbs", + "tail": " — datastore selection, credentials and fingerprint, encryption keyfile lifecycle, recovery passphrase, uploading the recovery blob to PBS for fresh-install recovery." + }, + { + "label": "Borg", + "href": "/docs/backup-restore/destinations/borg", + "tail": " — local vs. SSH-served repositories, the Borg binary sourcing (system / cache / Monitor AppImage bundle / GitHub download), repository initialization, passphrase handling, SSH key setup for remote repos." + } + ] + } +} diff --git a/web/messages/en/docs/backup-restore/destinations/local.json b/web/messages/en/docs/backup-restore/destinations/local.json new file mode 100644 index 00000000..b9c88bad --- /dev/null +++ b/web/messages/en/docs/backup-restore/destinations/local.json @@ -0,0 +1,63 @@ +{ + "meta": { + "title": "Local archive destination — tar.zst on a filesystem or USB drive | ProxMenux", + "description": "The local backup destination writes a single .tar.zst archive to any writeable directory: an internal disk, a Proxmox mount point, an NFS share, or a USB drive. Documents the configuration flow, the USB detection and mounting logic, the safety check against writing the archive into a path being backed up, and the sidecar JSON that identifies the file.", + "ogTitle": "ProxMenux Backup — Local archive destination", + "ogDescription": "How ProxMenux writes local backups as .tar.zst archives and how to configure the destination directory or USB drive.", + "twitterTitle": "Local backup destination | ProxMenux", + "twitterDescription": "How ProxMenux writes local backups as .tar.zst archives on a filesystem or USB drive." + }, + "header": { + "title": "Local archive", + "description": "The local destination writes a single compressed tar archive to any writeable directory on the host: an internal disk, an NFS or SMB mount, or a USB drive.", + "section": "Backup & Restore" + }, + "intro": { + "title": "One file, self-contained", + "body": "A local backup produces a single hostcfg-HOST-TIMESTAMP.tar.zst file (or .tar.gz when zstd is absent). The file contains the entire archive tree — manifest.json, metadata/ and rootfs/ — and can be restored on any Proxmox host with the ProxMenux restore flow, or extracted manually with tar --zstd -xf on any Linux system. No server, no repository init, no external dependency. This is the destination with the shortest recovery path when neither PBS nor Borg is available." + }, + "targetConfig": { + "heading": "Configuring the target directory", + "intro": "The local destination is a single persisted target directory — not a list. ProxMenux stores the operator's choice at /usr/local/share/proxmenux/local-target.conf and reads it on every backup. When no target has been configured, the default HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump is used (the same directory Proxmox uses for its own vzdump outputs). The target is configured from Configure backup destinations → Local destinations:", + "options": [ + "Use default (/var/lib/vz/dump). The Proxmox local storage. Present on every Proxmox install; the archive sits next to vzdump outputs and is picked up by the Monitor's Backups tab automatically.", + "Enter a custom path. Any absolute filesystem path can be used: an NFS mount, an SMB share mounted via fstab, a dedicated ZFS dataset, a second internal disk. ProxMenux validates that the path exists and is a directory before persisting it.", + "Pick a USB drive. Opens the USB submenu (below), which detects removable devices and offers to mount or format one." + ] + }, + "usbFlow": { + "heading": "USB drive detection and mounting", + "intro": "The USB submenu lists partitions on removable devices reported by lsblk. Each partition is presented with its size, filesystem label and current state. The state determines the action ProxMenux offers.", + "statesTitle": "The three device states", + "stateRows": [ + { "state": "mounted", "shown": "Size · label · [fstype] · → /mount/point", "action": "The partition is already mounted somewhere. Selecting it persists the current mount point as the local target. No mounting is performed." }, + { "state": "unmounted", "shown": "Size · label · [fstype] · (not mounted — will be mounted)", "action": "A filesystem is present but not mounted. On confirmation, ProxMenux runs hb_mount_usb_partition: creates /mnt/backup-LABEL (or a UUID-based path if there is no label), mounts the partition and persists the mount point as the local target." }, + { "state": "empty", "shown": "Size · raw USB disk — no filesystem (will be FORMATTED)", "action": "The device has no filesystem. A destructive path — protected by two confirmations. First a Yes/No dialog explains that the operation will erase the disk. Second, an inputbox requires the operator to type the exact device path (e.g. /dev/sdb) before ProxMenux creates a fresh GPT + ext4 partition and mounts it." } + ], + "notMountedFallback": "When no USB device is detected, the submenu falls back to a plain inputbox. The operator can enter an arbitrary mount point path; if the path is not a registered mount point, a confirmation dialog warns before proceeding." + }, + "safetyCheck": { + "heading": "Safety check — destination inside a backup path", + "body": "Before writing the archive, _bk_local verifies that the destination directory is not a subpath of any directory being backed up. A common footgun would be adding /root to the profile and picking /root/backups as the destination — the archive would then include itself, either producing a corrupt archive or growing without bound until the disk fills. The check resolves both paths with readlink -m, compares them and, on conflict, aborts the backup with a dialog that names the conflicting path and lists three ways to resolve it: choose a destination outside the conflicting path, remove the custom entry that contains the destination, or use Custom mode to uncheck the conflicting path for this run." + }, + "archiveFormat": { + "heading": "Archive format and compression", + "intro": "The output filename embeds the source hostname and the backup timestamp so that a directory holding several archives sorts chronologically and each file is self-identifying.", + "namePattern": "hostcfg-HOSTNAME-YYYYMMDD_HHMMSS.tar.zst", + "compressionTitle": "Compression", + "compressionBody": "The primary path uses tar --zstd -cf — a single-command pipeline that compresses at zstd's default level. When zstd is not present on the source (rare on Proxmox but possible on minimal installs), ProxMenux falls back to gzip. In the fallback path, if pv is available, a progress bar is added to the pipeline so the operator sees the archive size grow in real time; without pv, plain tar -czf is used silently.", + "sourceTitle": "What goes into the archive", + "sourceBody": "The tar command is invoked with -C \"$staging_root\" ., which archives the full staging root: rootfs/, metadata/ and manifest.json. All three payloads land side by side at the top of the tarball. Extracting the archive produces the exact same tree that the restore code consumes." + }, + "sidecar": { + "heading": "The sidecar JSON", + "intro": "Every successful local backup produces a companion file: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, written next to the archive by hb_write_archive_sidecar. This small JSON file lets the ProxMenux Monitor identify the archive as a ProxMenux host backup even if it is later moved, renamed or archived elsewhere.", + "contentTitle": "Sidecar contents", + "contentBody": "The sidecar stores the schema version, whether the backup came from an interactive run or a scheduled job (kind), the job ID for scheduled runs, the profile mode used (default or custom), the source hostname, the original archive basename, an ISO-8601 creation timestamp and the archive size in bytes.", + "whyBody": "The Monitor's Backups tab scans configured local directories for *.proxmenux.json sidecars — not for *.tar.zst files — because a .tar.zst without a sidecar might not be a ProxMenux backup at all. The scan is fast (JSON files are tiny) and the pairing is stable across renames of the archive as long as the sidecar is renamed to match." + }, + "restoreAccess": { + "heading": "Restoring from a local archive", + "body": "The ProxMenux restore flow discovers local archives by scanning the configured local target for sidecars and displaying them in the Archives list. Selecting one triggers _rs_check_layout, which extracts the tarball into a staging directory and confirms the three-payload layout before proceeding. For manual extraction outside of ProxMenux, tar --zstd -xf hostcfg-HOSTNAME-TIMESTAMP.tar.zst -C /tmp/hostcfg yields the same tree the restore code consumes." + } +} diff --git a/web/messages/en/docs/backup-restore/destinations/pbs.json b/web/messages/en/docs/backup-restore/destinations/pbs.json new file mode 100644 index 00000000..53097c68 --- /dev/null +++ b/web/messages/en/docs/backup-restore/destinations/pbs.json @@ -0,0 +1,102 @@ +{ + "meta": { + "title": "Proxmox Backup Server destination — repository, encryption, recovery | ProxMenux", + "description": "The PBS destination writes ProxMenux host backups as PBS backups. Documents repository auto-discovery from /etc/pve/storage.cfg, manual PBS configuration, the .pxar upload command, the client-side keyfile encryption model, the recovery passphrase escrow blob, and the fresh-install keyfile recovery from PBS.", + "ogTitle": "ProxMenux Backup — Proxmox Backup Server destination", + "ogDescription": "How ProxMenux writes host backups to Proxmox Backup Server with client-side keyfile encryption and recovery escrow.", + "twitterTitle": "PBS backup destination | ProxMenux", + "twitterDescription": "Proxmox Backup Server destination with keyfile encryption and recovery passphrase escrow." + }, + "header": { + "title": "Proxmox Backup Server", + "description": "The PBS destination uploads the staging root as a single .pxar backup to a Proxmox Backup Server datastore, with optional client-side keyfile encryption and an automatic recovery passphrase escrow for fresh-install disaster recovery.", + "section": "Backup & Restore" + }, + "recommendedBadge": "Recommended destination", + "aboutPbs": { + "heading": "What Proxmox Backup Server is", + "body": "Proxmox Backup Server (PBS) is Proxmox's own backup server product, developed and maintained by the same team that authors Proxmox VE. It is a dedicated backup server designed to receive backups from Proxmox VE hosts (VMs, LXCs and — via proxmox-backup-client — arbitrary host directories) with chunk-based deduplication, client-side encryption, and retention policies applied server-side. ProxMenux uses PBS as one of the three destinations for host backups; every PBS-specific mechanism described on this page (backup groups, .pxar archives, --backup-id, keyfile encryption) is standard PBS behaviour." + }, + "intro": { + "title": "One backup per run, chunk-level dedup", + "body": "A PBS backup produces a single backup entry in the datastore, grouped under the backup ID host/hostcfg-HOSTNAME/BACKUP-TIME. The payload is a .pxar archive containing the same three-block layout described in How it works. PBS deduplicates at the chunk level across all backups in the datastore, so subsequent backups of the same host transfer and store only the chunks that changed. Retention is applied by ProxMenux itself for scheduled jobs — run_scheduled_backup.sh runs proxmox-backup-client prune with --keep-last / --keep-daily / --keep-weekly after each successful run using the values configured on the job." + }, + "repoSelection": { + "heading": "Repository selection", + "intro": "ProxMenux discovers PBS repositories from two sources on every backup. The operator picks one from a unified menu; the choice determines HB_PBS_REPOSITORY, HB_PBS_SECRET and HB_PBS_FINGERPRINT for the run.", + "sourceRows": [ + { + "source": "Proxmox storage.cfg (auto-discovered)", + "path": "/etc/pve/storage.cfg + /etc/pve/priv/storage/NAME.pw", + "content": "Any pbs: stanza in Proxmox's own storage config is picked up automatically. Server, datastore, username and fingerprint come from the stanza. The password is read from Proxmox's own credentials directory. No re-entry needed on the ProxMenux side — the repository is available as soon as it is configured in Proxmox." + }, + { + "source": "ProxMenux manual config", + "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt", + "content": "Added from Configure backup destinations → PBS destinations → Add PBS. Prompts for a name, username (root@pam or user@pbs!token), host or IP, datastore and password. The password re-prompts on empty input — an empty save would otherwise persist silently and every subsequent backup would fail with an opaque authentication error. This path is used when the target PBS is not registered as Proxmox storage." + } + ], + "menuTitle": "Selection menu", + "menuBody": "Both sources are shown in a single menu, each row tagged with its origin ([proxmox] or [manual]). Entries whose password could not be resolved are tagged with a ⚠ no password warning — selecting one triggers a password re-entry before the backup starts. The fingerprint is passed to proxmox-backup-client via the PBS_FINGERPRINT environment variable; when absent, the client asks the operator to accept the server certificate interactively on the first backup." + }, + "backupCommand": { + "heading": "The backup command", + "intro": "The upload is a single invocation of proxmox-backup-client backup. ProxMenux runs it inside an env wrapper so credentials never appear in the process argument list.", + "cmd": "env \\\n PBS_PASSWORD=\"$HB_PBS_SECRET\" \\\n PBS_ENCRYPTION_PASSWORD=\"$HB_PBS_ENC_PASS\" \\\n PBS_FINGERPRINT=\"$HB_PBS_FINGERPRINT\" \\\n proxmox-backup-client backup \\\n hostcfg.pxar:$staging_root \\\n --repository USER@REALM@HOST:DATASTORE \\\n --backup-type host \\\n --backup-id hostcfg-HOSTNAME \\\n --backup-time BACKUP-EPOCH \\\n [--keyfile /usr/local/share/proxmenux/pbs-key.conf]", + "backupIdTitle": "Backup ID naming", + "backupIdBody": "The default backup ID is hostcfg-HOSTNAME. The operator is asked to confirm or edit it before the upload; any characters outside [A-Za-z0-9_-] are stripped and trailing dashes are trimmed. Reusing the same ID across runs is intentional — PBS treats the ID as a group, and every subsequent backup appears as a new backup inside that group, sharing dedup with prior runs.", + "pxarTitle": "Why the source is the full staging root", + "pxarBody": "The .pxar source is the entire staging_rootrootfs/, metadata/ and manifest.json together. Earlier versions passed $staging_root/rootfs as the source; that left metadata/ out of the archive and the restore's compatibility check had nothing to read, degrading to cross-host warnings even on same-host restores. Old backups created with the rootfs-only source still restore correctly via _rs_check_layout's case-3 branch, which wraps a flat etc/var/root/usr tree back into a rootfs/ hierarchy." + }, + "encryption": { + "heading": "Client-side encryption", + "intro": "PBS client-side keyfile encryption encrypts chunks on the source host before upload. ProxMenux enables the feature with one added constraint: a recovery passphrase is mandatory when encryption is enabled. The passphrase does not protect the local keyfile; it protects the escrow copy of the keyfile that ProxMenux uploads to PBS for disaster recovery.", + "keyfileTitle": "Keyfile", + "keyfileBody": "On first use, proxmox-backup-client key create --kdf none generates the keyfile at /usr/local/share/proxmenux/pbs-key.conf (chmod 600). Subsequent backups reuse it after a single confirmation dialog. If key creation fails, the backup is cancelled and the tool's error output is shown in a dialog.", + "recoveryTitle": "Recovery passphrase and escrow blob", + "recoveryBody": "After the keyfile is created, ProxMenux prompts twice for a recovery passphrase (with match validation) and runs openssl to produce pbs-key.recovery.enc — the keyfile encrypted with the passphrase. A copy is written to /root/pbs-key.recovery-HOSTNAME-YYYYMMDD.enc for offsite storage. Cancelling the passphrase dialog wipes the freshly-created keyfile.", + "blobUploadTitle": "Paired backup group on PBS", + "blobUploadBody1": "After a PBS backup that used the keyfile, the escrow blob is uploaded as a second backup group: host/hostcfg-HOSTNAME-keyrecovery/BACKUP-TIME. The shared hostcfg-HOSTNAME prefix places both groups adjacent in the PBS UI; the -keyrecovery suffix labels the relationship. The upload runs without --keyfile (the blob is already passphrase-protected by openssl) and only when the current backup used the keyfile.", + "blobUploadConstraintTitle": "Why two groups", + "blobUploadConstraintBody": "--keyfile is a per-invocation flag in proxmox-backup-client backup: all archives in a single invocation are encrypted with the keyfile or none are. hostcfg.pxar requires encryption; keyrecovery.conf cannot be encrypted with the same keyfile (fresh-install recovery would then require the keyfile it is meant to recover). Two invocations, two backup IDs.", + "blobUploadImageAlt": "PBS UI showing the hostcfg-HOSTNAME and hostcfg-HOSTNAME-keyrecovery backup groups adjacent in the datastore listing.", + "blobUploadImageCaption": "PBS UI — the paired backup groups. The main group holds the host backups; the -keyrecovery group holds the escrow blob.", + "recoverTitle": "Fresh-install recovery", + "recoverBody": "On a host without a local keyfile, the restore flow calls hb_pbs_try_keyfile_recovery. The function lists keyrecovery groups on the configured PBS, downloads the newest and prompts for the passphrase. On success, pbs-key.conf is written to the ProxMenux state directory and the encrypted backup can be restored. Without both the keyfile and the passphrase, the encrypted backup is not recoverable." + }, + "restoreAccess": { + "heading": "Retrieval on the restore side", + "body": "The restore flow discovers ProxMenux host backups on PBS by listing backup groups under the configured repository and filtering by backup ID pattern. The Monitor's Backups tab renders the same list. Selecting a backup triggers proxmox-backup-client restore with the same repository + password + fingerprint (and --keyfile when the backup was encrypted), extracting the .pxar into a staging directory that _rs_check_layout then feeds to the standard restore pipeline. For manual retrieval outside ProxMenux, the same command extracts the archive to any path — the resulting tree can be inspected or fed to a hand-driven restore." + }, + "references": { + "heading": "References", + "intro": "Official Proxmox Backup Server documentation for the components ProxMenux relies on.", + "items": [ + { + "label": "Proxmox Backup Server documentation", + "href": "https://pbs.proxmox.com/docs/", + "tail": " — main entry point covering installation, administration, storage, users and roles." + }, + { + "label": "proxmox-backup-client", + "href": "https://pbs.proxmox.com/docs/backup-client.html", + "tail": " — the command-line tool ProxMenux invokes for every backup and restore. Covers backup IDs, backup types, archives, repository syntax and environment variables." + }, + { + "label": "Client-side encryption", + "href": "https://pbs.proxmox.com/docs/backup-client.html#encryption", + "tail": " — keyfile creation, --kdf modes, the encryption model that ProxMenux extends with a passphrase escrow." + }, + { + "label": "Datastore management", + "href": "https://pbs.proxmox.com/docs/storage.html", + "tail": " — creating and managing the datastores that receive host backups, including chunk-store layout and permissions." + }, + { + "label": "Pruning and garbage collection", + "href": "https://pbs.proxmox.com/docs/maintenance.html#pruning", + "tail": " — the retention model behind --keep-last / --keep-daily / --keep-weekly that ProxMenux applies per scheduled job, plus how PBS reclaims chunks after prune." + } + ] + } +} diff --git a/web/messages/en/docs/backup-restore/how-it-works.json b/web/messages/en/docs/backup-restore/how-it-works.json new file mode 100644 index 00000000..1064896e --- /dev/null +++ b/web/messages/en/docs/backup-restore/how-it-works.json @@ -0,0 +1,199 @@ +{ + "meta": { + "title": "How ProxMenux Backup works internally — rootfs, manifest, applications", + "description": "Detailed breakdown of what a ProxMenux backup contains: the rootfs produced by rsync of the default path profile, the structured manifest built by six independent collectors, and the application inventory that drives the automatic reinstall of packages and components after a restore.", + "ogTitle": "How ProxMenux Backup works internally", + "ogDescription": "The three payloads of a ProxMenux backup explained: rootfs, manifest and applications.", + "twitterTitle": "How ProxMenux Backup works | ProxMenux", + "twitterDescription": "The three payloads of a ProxMenux backup and how the restore reproduces the source host from them." + }, + "header": { + "title": "How it works", + "description": "The internal breakdown of a ProxMenux backup — filesystem, manifest and application inventory — and how the restore consumes all three to reproduce the source host on a target that may not share the same kernel.", + "section": "Backup & Restore" + }, + "intro": { + "title": "One archive, three payloads", + "body": "Every backup produces a directory layout with three well-defined payloads under a single staging root. The archive uploaded to the destination (local .tar.zst, PBS backup or Borg archive) contains this exact layout. The restore reads the three payloads independently, in a specific order that guarantees correctness: rootfs is copied first to lay down configuration, the manifest is consulted to detect drift and decide what to skip, and the application inventory drives the post-boot reinstall pass. No cross-payload dependencies — each one can be inspected or extracted independently." + }, + "layout": { + "heading": "Archive layout", + "intro": "Every archive follows the same tree layout regardless of destination. The metadata/ subdirectory holds the structured payloads; the rootfs/ subdirectory holds the filesystem copy.", + "treeCaption": "The staging directory laid out during a backup. Every destination receives the same tree (adapted to its native format: tar for local, PBS chunks for PBS, borg segments for Borg).", + "tree": "backup-[timestamp]/\n├── manifest.json # structured host state\n├── metadata/\n│ ├── packages.manual.list # apt-mark showmanual\n│ ├── run_info.env # backup run identity + kernel version\n│ ├── paths_archived.txt # exact list of paths that reached rootfs/\n│ └── missing_paths.txt # paths from the profile absent on source\n└── rootfs/\n ├── etc/ # /etc/pve, /etc/network, /etc/ssh, …\n ├── root/ # /root (with volatile subdirs excluded)\n ├── usr/local/ # /usr/local/bin, /usr/local/share/proxmenux, …\n └── var/ # /var/lib/pve-cluster, /var/spool/cron/…" + }, + "rootfs": { + "heading": "The rootfs payload", + "intro": "The rootfs/ tree is a plain filesystem copy produced by rsync from the source host. It contains a curated default profile of paths that matter for a Proxmox restore, plus any custom paths added by the operator to the backup job or interactive session. The set is deliberately narrow: only paths that either hold configuration or hold state that Proxmox cannot regenerate on its own.", + "defaultProfileTitle": "The default profile", + "defaultProfileBody": "The default profile is defined by hb_default_profile_paths in lib_host_backup_common.sh. It covers eight categories that together describe a working Proxmox host:", + "categoriesTitle": "Path categories", + "categoryRows": [ + { + "category": "PVE core", + "paths": "/etc/pve, /var/lib/pve-cluster, /etc/vzdump.conf", + "why": "Cluster filesystem contents, cluster live data, vzdump defaults." + }, + { + "category": "Host identity & network", + "paths": "/etc/hostname, /etc/hosts, /etc/timezone, /etc/resolv.conf, /etc/network", + "why": "Everything the host needs to come up on the network with the same identity." + }, + { + "category": "Access & auth", + "paths": "/etc/ssh, /etc/sudoers, /etc/sudoers.d, /etc/pam.d, /etc/security", + "why": "SSH keys, sudo rules and PAM configuration. Losing these locks the operator out of the restored host." + }, + { + "category": "Kernel & boot", + "paths": "/etc/default/grub, /etc/kernel, /etc/modules, /etc/modules-load.d, /etc/modprobe.d, /etc/sysctl.conf, /etc/sysctl.d, /etc/udev/rules.d, /etc/fstab, /etc/iscsi, /etc/multipath", + "why": "IOMMU tokens, module blacklists, VFIO device IDs, mount table, storage stack config." + }, + { + "category": "Shell & locale", + "paths": "/etc/environment, /etc/bash.bashrc, /etc/inputrc, /etc/profile, /etc/profile.d, /etc/locale.gen, /etc/locale.conf", + "why": "System-wide shell setup, environment variables, locale generation configuration." + }, + { + "category": "Packaging & cron", + "paths": "/etc/apt, /etc/cron.d, /etc/cron.{daily,hourly,weekly,monthly}, /etc/cron.allow, /etc/cron.deny, /var/spool/cron/crontabs", + "why": "APT sources for consistent package resolution, scheduled tasks defined by the operator." + }, + { + "category": "ProxMenux state & tools", + "paths": "/etc/proxmenux, /etc/systemd/system, /etc/log2ram.conf, /etc/logrotate.conf, /etc/logrotate.d, /etc/lm-sensors, /etc/sensors3.conf, /etc/fail2ban, /etc/snmp, /etc/postfix, /etc/wireguard, /etc/openvpn, /etc/grafana, /etc/influxdb, /etc/prometheus, /etc/telegraf, /etc/zabbix", + "why": "Optional but common Proxmox tooling. Missing paths are noted in metadata/missing_paths.txt without stopping the backup." + }, + { + "category": "ProxMenux binaries & root", + "paths": "/usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux, /root (volatile subdirs excluded)", + "why": "ProxMenux-installed binaries and per-user configuration under /root. Volatile paths (.bash_history, .cache/, tmp/, .local/share/Trash/) are excluded from the copy." + }, + { + "category": "ZFS state (conditional)", + "paths": "/etc/zfs", + "why": "Only included when the source host runs ZFS. Contains zpool.cache and hostid." + } + ], + "customTitle": "Extending the profile with custom paths", + "customBody": "Beyond the default profile, ProxMenux offers two ways to include additional paths in a backup. They compose without conflict and both apply to interactive backups and scheduled jobs.", + "customExtrasTitle": "1. Persistent extras (per-host file)", + "customExtrasBody": "A text file at /usr/local/share/proxmenux/backup-extra-paths.txt holds a list of absolute paths the operator has marked as \"always include\" on this host. When a backup runs in Default mode, ProxMenux appends these paths to the default profile automatically without asking. The file is edited from the interface — no manual editing needed — and persists across reboots and updates. One absolute path per line; # comments are allowed.", + "customModeTitle": "2. Custom mode (per-run)", + "customModeBody": "Launching a backup in Custom mode replaces the automatic application of the default profile with a checklist showing every path: the default-profile entries and the persistent extras (prefixed with [+] and pre-checked). The operator ticks or unticks entries for that specific run, and can also press Add custom path to enter a new path — which is then persisted to the extras file for future backups.", + "customMissingTitle": "Paths absent from the source", + "customMissingBody": "Any path from the profile (default or added) that does not exist on the source host is recorded in metadata/missing_paths.txt inside the archive. The backup neither fails nor stops — the operator sees a summary of archived paths and missing paths at the end. In practice this happens for optional tooling paths like /etc/wireguard or /etc/prometheus when those tools are not installed." + }, + "manifest": { + "heading": "The manifest payload", + "intro": "manifest.json is a structured JSON document that describes the source host at the moment of the backup. It is produced by six independent collectors orchestrated by build_manifest.sh. Each collector is read-only, produces a well-defined JSON fragment, and falls back to a safe empty default if it fails — the manifest is still usable if one section is incomplete.", + "orchestratorCaption": "The six collectors compose the manifest. Each collector runs in its own subprocess; a failure in one falls back to a documented safe default and warns but does not abort the backup.", + "collectorRows": [ + { + "collector": "collect_source_host.sh", + "produces": "source_host", + "content": "Hostname, PVE version (pveversion), PBS version if the host runs the backup-server role, kernel (uname -r), boot mode (efi/bios), root filesystem type, CPU model + architecture, memory in KB." + }, + { + "collector": "collect_hardware.sh", + "produces": "hardware_inventory", + "content": "GPUs (with vendor and ProxMenux installer mapping), TPUs (Coral USB + M.2 with lsusb/lspci detection), NICs (with MAC, PCI slot and bridge membership), wireless devices. GPU entries carry a passthrough_eligible heuristic." + }, + { + "collector": "collect_storage.sh", + "produces": "storage_inventory", + "content": "ZFS pools (with pool type + member disks resolved to /dev/disk/by-id/* for portability), LVM volume groups + thin pools, physical disks with SMART capability, PVE storage.cfg entries, external mounts." + }, + { + "collector": "collect_kernel.sh", + "produces": "kernel_params", + "content": "Operator-authored tokens from /proc/cmdline (stripped of boilerplate like BOOT_IMAGE=, root=, ro/rw, quiet, splash), modules loaded at boot from /etc/modules, and paths of /etc/modprobe.d/*.conf files with actual directives (options, blacklist, install, alias, softdep)." + }, + { + "collector": "collect_proxmenux_state.sh", + "produces": "proxmenux_installed_components", + "content": "Reads /usr/local/share/proxmenux/managed_installs.json (registry of everything ProxMenux has installed) and installed_tools.json. Each entry keeps the installer path (menu_script) so the restore can trigger the exact same install flow." + }, + { + "collector": "collect_guests.sh", + "produces": "vms_lxcs_at_backup", + "content": "Enumerates VMs (qm list) and LXCs (pct list) present at backup time — VMID, name, current status. Only the inventory: the actual guest data is the responsibility of vzdump / PBS." + } + ], + "schemaTitle": "Schema validation", + "schemaBody": "The manifest validates against scripts/backup_restore/schema/manifest.schema.json. Running build_manifest.sh --validate triggers a Python-side JSON Schema validation (requires python3 + jsonschema). If the module is not present, the check is skipped silently — validation is primarily a developer aid, not a runtime dependency." + }, + "applications": { + "heading": "The application inventory", + "intro": "Two files under metadata/ catalogue everything installed on the source that is not part of the base Proxmox VE package set. The restore uses them to reproduce the exact set of user-installed software on the target, using either APT or ProxMenux's own installers depending on how the software was originally installed.", + "packagesTitle": "packages.manual.list", + "packagesBody": "A plain-text list produced by apt-mark showmanual: every APT package that was explicitly installed on the source host, sorted alphabetically. This excludes packages installed as dependencies of the base Proxmox VE ISO (they are re-pulled automatically by the target's own APT). Read by _rs_run_complete_extras during restore, filtered through a three-pass cascade-safe filter (dpkg -s for already-installed, sibling-major detection for library packages, apt-get install --simulate for cascade-remove risk), then installed with apt-get install -y.", + "componentsTitle": "components_status.json (part of the rootfs)", + "componentsBody": "A JSON registry under /usr/local/share/proxmenux/ that records every component ProxMenux has installed with its exact state: version, ProxMenux-specific flags (for NVIDIA: patched boolean; for Coral: DKMS version). This file lives inside the rootfs — not in metadata/ — because it is read after the rootfs has been copied to the target. The post-boot dispatcher (apply_cluster_postboot.sh) iterates over its entries and runs each component's --auto-reinstall hook, which reads the recorded state and reproduces the install against the target's current kernel.", + "componentInstallersTitle": "Component installers", + "componentInstallersBody": "Four ProxMenux installers currently expose a --auto-reinstall entry point:", + "installerRows": [ + { + "component": "nvidia_driver", + "installer": "gpu_tpu/nvidia_installer.sh", + "action": "Reads version + patched. Downloads the exact same NVIDIA runfile, builds DKMS modules against the target's kernel, re-applies the ProxMenux patch if the source had it." + }, + { + "component": "coral_driver", + "installer": "gpu_tpu/install_coral.sh", + "action": "Reads the Coral driver version. Compiles the DKMS module against the target's kernel." + }, + { + "component": "amdgpu_top", + "installer": "gpu_tpu/amd_gpu_tools.sh", + "action": "Reads the recorded version. Re-downloads the exact .deb from the GitHub release." + }, + { + "component": "intel_gpu_tools", + "installer": "gpu_tpu/intel_gpu_tools.sh", + "action": "APT-installs the package. Idempotent if already present from packages.manual.list." + } + ] + }, + "restoreFlow": { + "heading": "How the restore consumes the three payloads", + "intro": "The restore is a five-stage pipeline. Each stage reads a specific subset of the archive and updates the target host. No stage requires the source host to be reachable — the archive is fully self-contained.", + "stagesCaption": "Stage 1 uses the manifest to decide what to touch. Stage 2 copies rootfs paths that are safe to apply on a running system. Stage 3 stages the risky paths for the next boot. Stage 4 handles packages. Stage 5 runs after the reboot and reinstalls components against the target's kernel.", + "stageRows": [ + { + "stage": "1", + "name": "Compatibility check", + "reads": "manifest.json", + "action": "Runs hb_compat_check. Compares source vs. target hardware (NICs, storage IDs), PVE version, and major kernel version. Sets HB_COMPAT_KERNEL_DIRECTION (same, bk_newer or bk_older) and populates RS_SKIP_PATHS with hardware-drift and cross-kernel exclusions." + }, + { + "stage": "2", + "name": "Hot apply", + "reads": "rootfs/ (safe paths only)", + "action": "_rs_apply … hot copies hb_classify_path=hot entries directly to the live target. Anything under /etc/pve, /etc/network or paths classified as reboot/dangerous is deferred." + }, + { + "stage": "3", + "name": "Pending prepare", + "reads": "rootfs/ (reboot + dangerous paths)", + "action": "_rs_prepare_pending_restore stages risky paths under /var/lib/proxmenux/pending-restore/, writes plan.env, apply-on-boot.list and rs-skip-paths.txt, and enables proxmenux-restore-onboot.service to fire on the next boot." + }, + { + "stage": "4", + "name": "Package install", + "reads": "metadata/packages.manual.list", + "action": "_rs_run_complete_extras runs the cascade-safe filter and calls apt-get install -y with the surviving package list. Full output goes to /var/log/proxmenux/restore-apt-*.log." + }, + { + "stage": "5", + "name": "Post-boot", + "reads": "rootfs (already applied) + components_status.json", + "action": "After reboot, apply_pending_restore.sh plays back the deferred paths and apply_cluster_postboot.sh runs update-initramfs, update-grub (or proxmox-boot-tool refresh) and iterates over components_status.json firing each component's --auto-reinstall hook." + } + ] + }, + "whyItWorks": { + "heading": "Why the three-payload split is the right one", + "body": "The split is not a filesystem decision — it is a lifecycle decision. Filesystem content moves with rsync: fast, transparent, atomic per file. Configuration state that a restore has to interpret before touching the target moves as structured JSON: readable independently, versionable through a schema, machine-diffable against the target's own state. Software that has to be re-installed against the target's environment moves as an inventory: names and versions only, letting the target's package manager and ProxMenux's own installers decide the actual binaries. Each payload optimises for what it needs to do, and the three combine into a restore that is atomic in intent but fault-tolerant in practice: a corrupt manifest still leaves the rootfs restorable, a missing package still leaves the components installable, a component installer failing on one entry does not stop the next." + } +} diff --git a/web/messages/en/docs/backup-restore/index.json b/web/messages/en/docs/backup-restore/index.json new file mode 100644 index 00000000..2dcf3de6 --- /dev/null +++ b/web/messages/en/docs/backup-restore/index.json @@ -0,0 +1,87 @@ +{ + "meta": { + "title": "ProxMenux Backup & Restore — Overview | Full host backup and restore for Proxmox VE", + "description": "ProxMenux Backup & Restore captures the complete state of a Proxmox host — filesystem, structured configuration manifest, and installed packages/components — and reproduces it on the same or a different host. The backup and the restore are self-contained: no external dependencies, and cross-kernel restores are supported through a direction-aware safe-subset filter and kernel-agnostic hydration.", + "ogTitle": "ProxMenux Backup & Restore — Overview", + "ogDescription": "Full host backup and restore for Proxmox VE with structured manifest, package list and component reinstallers.", + "twitterTitle": "ProxMenux Backup & Restore | ProxMenux", + "twitterDescription": "Full host backup and restore for Proxmox VE with structured manifest, package list and component reinstallers." + }, + "header": { + "title": "Backup & Restore", + "description": "Full-host backup and restore for Proxmox VE. Captures filesystem, configuration and installed components in a single archive, and reproduces the host on the same or a different Proxmox install with no external dependencies.", + "section": "Backup & Restore" + }, + "intro": { + "title": "What it is, in one paragraph", + "body": "A ProxMenux backup captures the complete state of a Proxmox host: the filesystem (relevant directories under /etc, /root, /var/lib/pve-cluster, plus optional custom paths), a structured manifest (JSON with the detected hardware, kernel parameters, network layout, ZFS state, users and cron entries), and an application inventory (all packages marked as manually installed by APT, plus the list of components installed by ProxMenux with their exact versions). Any of the three supported destinations — local archive, Proxmox Backup Server (recommended) or Borg — receives the same self-contained payload, and any of them can hydrate the target host without depending on the source being reachable at restore time." + }, + "whatItIsNot": { + "heading": "What it is not", + "intro": "The section covers host-level backup and restore: the Proxmox installation itself, not the workloads running on top of it.", + "items": [ + "It is not a VM/CT backup tool. Guest disks and RAM state are not captured by this feature; that is what vzdump is for. The guest configuration files (/etc/pve/nodes/<node>/qemu-server/*.conf and lxc/*.conf) are captured, so after a restore the guest inventory reappears and disks can be re-attached from an existing PBS/local backup.", + "It is not a cluster-wide operation. Each node backs up itself. Cluster membership is captured as part of /etc/pve so a restored node can be re-added to its cluster, but restoring a full cluster requires per-node coordination.", + "It is not a full disk image. Kernel binaries, the initramfs and the boot partition are not captured. On a restore, ProxMenux relies on the target host's own boot artifacts (regenerated automatically by update-initramfs and the bootloader tool) and installs matching drivers against the target's running kernel." + ] + }, + "threePillars": { + "heading": "The three pillars of a backup", + "intro": "A ProxMenux archive is structured around three self-contained payloads. The restore uses all three together to reproduce the source host on a target that may not even have the same kernel installed.", + "diagramCaption": "Every backup ships the same three payloads regardless of destination. The restore consumes all three: rootfs to lay down files, manifest to detect drift and cross-kernel differences, and application inventory to re-install packages and components against the target's own kernel.", + "pillar1Label": "Filesystem", + "pillar1Detail": "rootfs/\n(rsync of\n/etc, /root,\n/var/lib/pve-cluster,\n+ optional paths)", + "pillar2Label": "Manifest", + "pillar2Detail": "manifest.json\n(hardware, kernel\nparams, network,\nZFS, users, cron,\nZFS pools, storage)", + "pillar3Label": "Applications", + "pillar3Detail": "packages.manual.list\n+ components_status.json\n(APT manual + ProxMenux\ninstallers with versions)" + }, + "restoreIsUniversal": { + "heading": "The restore reproduces the source host, not the archive", + "body": "Restoring a ProxMenux backup does not just extract the filesystem. The restore flow reads the manifest to detect drift between the source and the target (hardware differences, NIC renames, kernel version, ZFS pool identity), replays the filesystem, then triggers the correct component installer for every service that was installed on the source (NVIDIA driver, Coral TPU, AMD GPU tools, Intel GPU tools). Each installer runs against the target's current kernel, so the restored host does not depend on the source's kernel being present. When the target's kernel is newer than the backup's, a kernel-agnostic hydration pass merges the operator's own tuning (IOMMU tokens, VFIO device IDs, custom quirks, GRUB keys) into the target's fresh boot configuration without copying kernel-tied files verbatim." + }, + "twoInterfaces": { + "heading": "Two interfaces, one backend", + "intro": "Every step of the backup and restore workflow is available from two entry points. Both call the same shell library, produce identical archives, and read the same job registry.", + "cliLabel": "ProxMenux Scripts (TUI)", + "cliDetail": "menu → Utilities →\nHost Backup / Restore\n\nDialog-based flow,\nSSH-friendly, scriptable\nfor unattended use.", + "webLabel": "ProxMenux Monitor (Web UI)", + "webDetail": "Backups tab in the\nMonitor's Web interface.\n\nOne-click restore, live\nlog tail, integrated with\nnotifications and the\nrollback delta viewer." + }, + "whereNext": { + "heading": "Where to go from here", + "intro": "Each subsection below covers one aspect of the workflow in depth. Start with How it works if the goal is to understand what the archive contains and why. Jump to Destinations to configure the target for the copy. Read Restoring and Cross-kernel restore for the recovery side.", + "items": [ + { + "label": "How it works", + "href": "/docs/backup-restore/how-it-works", + "tail": " — the three payloads (rootfs, manifest, applications) in detail, with the collectors that produce them and the format of each file." + }, + { + "label": "Destinations", + "href": "/docs/backup-restore/destinations", + "tail": " — comparison of local, Proxmox Backup Server (recommended) and Borg, and how to configure each one." + }, + { + "label": "Creating backups", + "href": "/docs/backup-restore/creating-backups", + "tail": " — one-off backups, the default path profile, adding custom paths, encryption for PBS." + }, + { + "label": "Scheduled jobs", + "href": "/docs/backup-restore/scheduled-jobs", + "tail": " — new jobs vs. attaching to an existing PVE vzdump job, scheduling formats, the job detail modal." + }, + { + "label": "Restoring", + "href": "/docs/backup-restore/restoring", + "tail": " — the three actions on an archive (view, download, restore), Complete vs. Custom restore, the post-boot dispatcher and why the last ten minutes matter." + }, + { + "label": "Cross-kernel restore", + "href": "/docs/backup-restore/cross-kernel", + "tail": " — direction-aware behaviour when the target kernel differs from the backup's, the safe-subset filter and the four hydration phases." + } + ] + } +} diff --git a/web/messages/en/docs/backup-restore/restoring.json b/web/messages/en/docs/backup-restore/restoring.json new file mode 100644 index 00000000..f3630b3a --- /dev/null +++ b/web/messages/en/docs/backup-restore/restoring.json @@ -0,0 +1,124 @@ +{ + "meta": { + "title": "Restoring a backup — full and custom flows | ProxMenux", + "description": "The complete restore workflow. Documents the three actions available on any backup (view, download, restore), the compatibility check and its outcomes, the Full and Custom restore modes, the path classification that decides what applies live and what waits for the next boot, the on-boot dispatcher, and the post-boot component reinstall pass.", + "ogTitle": "ProxMenux Backup — restoring", + "ogDescription": "Full and custom restore flows with the compatibility check, path classification, on-boot dispatcher and post-boot reinstall.", + "twitterTitle": "Restoring a backup | ProxMenux", + "twitterDescription": "How a ProxMenux host backup is turned back into a working Proxmox host." + }, + "header": { + "title": "Restoring a backup", + "description": "The full restore workflow: from picking an archive to a working host. Documents the compatibility check, the two restore modes, the on-boot dispatcher, the post-boot reinstall pass, and the mechanisms that make cross-host and cross-kernel restores predictable.", + "section": "Backup & Restore" + }, + "intro": { + "title": "The restore reproduces the source, not just the files", + "body": "Restoring a ProxMenux backup is not an extraction. Files are only the first step: after the filesystem is in place, the restore reads the manifest to detect differences between source and target, filters paths that would break the target's boot, hydrates operator-authored tuning that cannot be copied verbatim, and — after the mandatory reboot — reinstalls every component the source had (NVIDIA driver, Coral TPU, GPU tools) against the target's own kernel. What the operator picks from the menu is a single archive; what actually happens involves the compatibility check, the path classification, the on-boot dispatcher and the post-boot pass working together to produce a host that behaves like the source." + }, + "threeActions": { + "heading": "Three actions on a backup", + "intro": "Selecting a backup from the list opens a menu with three actions.", + "actionRows": [ + { "action": "View", "detail": "Opens a read-only view of the archive: manifest.json contents (source hostname, PVE version, kernel, hardware, installed components), the list of paths inside rootfs/, and a diff of what would change on the current host if the backup were applied." }, + { "action": "Download", "detail": "Exports the archive as a portable .tar.zst file (tar compressed with zstd). It can be extracted on any Linux system with tar --zstd -xf FILE.tar.zst, or on macOS/Windows with any tool that supports zstd (7-Zip, PeaZip, Keka…). The extracted tree contains manifest.json, metadata/ and rootfs/ — exactly the same layout the restore consumes. Useful for offline inspection or for restoring on a host without access to the original destination (PBS, Borg)." }, + { "action": "Restore", "detail": "The write path. Extracts the archive into a staging directory, runs the compatibility check, and presents the mode picker (Full or Custom)." } + ] + }, + "compatibilityCheck": { + "heading": "The compatibility check", + "intro": "Before any file is written, hb_compat_check compares the state described in the manifest against the target host. The check runs read-only and produces four independent outputs that drive the rest of the restore.", + "outputRows": [ + { "output": "Direction flag", "detail": "HB_COMPAT_KERNEL_DIRECTION — one of same, bk_newer or bk_older. Compares the backup's major kernel version against the target's. Drives the cross-kernel safe-subset filter (only fires on bk_older) and the hydration pass documented on the cross-kernel page." }, + { "output": "Skip-paths list", "detail": "RS_SKIP_PATHS — every path the restore must NOT apply. Populated by two mechanisms: hardware drift (missing NIC, missing storage ID, foreign ZFS pool) and — when the direction is bk_older — the cross-kernel unsafe-paths list." }, + { "output": "NIC remap plan", "detail": "When a NIC on the target has the same MAC as one on the source but a different name (typical after a motherboard swap), the compatibility check registers a rename plan (HB_NIC_REMAP) that will rewrite /etc/network/interfaces during the restore." }, + { "output": "Rollback plan", "detail": "Computed by compute_rollback_plan.sh. Lists VMs, LXCs and components present on the target but not in the backup. The operator opts in during the confirmation dialog to have these removed as part of the restore, so the target ends up matching the backup exactly." } + ], + "reportBody": "The check also emits a structured report (HB_COMPAT_RESULTS) categorised as PASS / INFO / WARN / FAIL. WARN and FAIL entries surface in the pre-restore panel; the restore refuses to proceed only when a FAIL is present that the operator cannot resolve by clicking Continue." + }, + "twoModes": { + "heading": "Full restore vs Custom restore", + "intro": "Once the compatibility check is complete, the restore mode menu appears. The choice determines what is applied, not how — both modes share the same underlying pipeline.", + "modeRows": [ + { "mode": "Full restore", "detail": "Applies every path in the archive that survives the drift and cross-kernel filters. Also runs the package install and the component auto-reinstall pass. This is the default and the recommended choice — the goal is to reproduce the source, not pick pieces." }, + { "mode": "Custom restore", "detail": "Opens a checklist showing every path the archive carries. The operator ticks a subset. Paths blocked by the cross-kernel filter appear greyed out and cannot be selected. Package install and component auto-reinstall are skipped by default in Custom mode — the operator is signalling that they want partial application, not a full reproduction." } + ] + }, + "pathClassification": { + "heading": "How paths are classified", + "intro": "Every path selected for the restore is classified by hb_classify_path into one of three categories. The category determines when the path is applied to the system and why.", + "rows": [ + { "class": "hot", "detail": "Paths applied immediately to the running system. The service that consumes them picks up the change on its own or at the next reload — no reboot required. These are the bulk of a backup: /etc/ssh, /etc/apt, /etc/cron.*, /root, /usr/local/bin, general service configuration files, etc." }, + { "class": "reboot", "detail": "Paths applied immediately as well, but whose actual effect only kicks in on the next boot: the kernel only reads /etc/default/grub when the bootloader starts, /etc/fstab when filesystems are mounted at boot, /etc/modules when modules are loaded, etc. The file is in place the moment it is applied but the system has to reboot to consume it. Examples: /etc/default/grub, /etc/kernel, /etc/modules, /etc/fstab, /etc/zfs, /etc/initramfs-tools." }, + { "class": "dangerous", "detail": "Paths that are NOT applied on the running system — a live write could corrupt state or drop the active connection. These are staged in the pending set and written by the post-boot dispatcher after the reboot, when the cluster is up but the system is not yet fully in use. Examples: /etc/pve (pmxcfs is a live FUSE mount; writing directly to it bypasses it), /var/lib/pve-cluster (live cluster data), /etc/network (could reconfigure the very interface the user is connected on over SSH and drop the session)." } + ] + }, + "fullFlow": { + "heading": "The full restore workflow", + "intro": "The complete pipeline from archive selection to a working restored host. Each stage feeds the next; the state written by earlier stages is consumed by later ones.", + "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│ Archive → staging_root/ │\n│ manifest.json metadata/ rootfs/ │\n└──────────────────────────────┬──────────────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 1. hb_compat_check │\n │ · hardware drift → RS_SKIP_PATHS │\n │ · kernel direction → same / bk_newer / bk_older │\n │ · NIC remap plan │\n │ · hydration plan (bk_older only) │\n │ · rollback plan │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 2. Confirmation dialog │\n │ Shows: hot count, pending count, drift, hydration, │\n │ NIC remap, cross-kernel note, reinstalls preview │\n └──────────────────────────────┬──────────────────────────┘\n │ (accepted)\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 3. Apply hot paths → LIVE │\n │ _rs_apply rsyncs each hot path │\n │ from staging_root/rootfs to / │\n │ (skips paths in RS_SKIP_PATHS) │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 4. _rs_prepare_pending_restore │\n │ /var/lib/proxmenux/pending-restore/ │\n │ ├── apply-on-boot.list │\n │ ├── plan.env │\n │ ├── rs-skip-paths.txt │\n │ └── rootfs/ (deferred paths) │\n │ Enable proxmenux-restore-onboot.service │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 5. _rs_run_complete_extras │\n │ packages.manual.list │\n │ → cascade-safe filter │\n │ → apt-get install │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 6. REBOOT │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 7. apply_pending_restore.sh (on early boot) │\n │ Reads plan.env │\n │ Applies apply-on-boot.list (filtered) │\n │ Installs the postboot systemd unit │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 8. apply_cluster_postboot.sh (~10 minutes) │\n │ Runs after pve-cluster is up │\n │ · Applies /etc/pve entries to pmxcfs │\n │ · update-initramfs -u -k all │\n │ · update-grub / proxmox-boot-tool refresh │\n │ · component --auto-reinstall (nvidia, coral, ...) │\n │ · Boot sanity check │\n │ · Notification: \"Host restore finished\" │\n └─────────────────────────────────────────────────────────┘" + }, + "pendingMachinery": { + "heading": "The pending-restore machinery", + "intro": "Paths classified as reboot or dangerous, and hydration writes, are staged for the next boot instead of being applied live. This is done through a small, self-contained set of files under /var/lib/proxmenux/pending-restore/:", + "rows": [ + { "file": "apply-on-boot.list", "content": "One relative path per line — the exact set of paths apply_pending_restore.sh will apply from the staged rootfs. Reading this file tells the operator exactly what will change on the next boot." }, + { "file": "plan.env", "content": "Environment variables sourced by the on-boot script: restore ID, compatibility flags (HB_COMPAT_CROSS_VERSION, HB_COMPAT_KERNEL_DIRECTION, HB_HYDRATION_APPLIED), rollback opt-in flag, cluster options." }, + { "file": "rs-skip-paths.txt", "content": "The final RS_SKIP_PATHS list, persisted so apply_pending_restore.sh applies the same exclusions after reboot that were computed during the interactive step. Prevents drift-affected or cross-kernel-unsafe paths from being restored at boot time." }, + { "file": "rootfs/", "content": "The staged files themselves — the exact bytes that will be laid down. Kept on the same filesystem as / so the on-boot rsync is fast and does not depend on external storage still being reachable at boot." } + ], + "unitBody": "The proxmenux-restore-onboot.service systemd unit is enabled at the end of the interactive step. It is a one-shot service that fires early on the next boot, calls apply_pending_restore.sh, then disables itself. The unit is gated by ConditionPathExists=/var/lib/proxmenux/pending-restore/state, so on any boot without a pending restore it is a no-op." + }, + "postbootDispatcher": { + "heading": "The post-boot dispatcher — apply_cluster_postboot.sh", + "intro": "Where the interactive step ends and where the actual host reproduction happens. apply_cluster_postboot.sh is installed as a second one-shot systemd unit (proxmenux-apply-cluster-postboot.service) whose After=/Wants= targets ensure it runs only after pve-cluster and network-online are up. This is where the host-visible restore actions land.", + "tasks": [ + { "task": "Apply /etc/pve", "detail": "/etc/pve is a live pmxcfs FUSE mount — it cannot be written to on early boot. The dispatcher copies files from the pending rootfs to the live /etc/pve once the cluster filesystem is up, one file at a time, without restarting pve-cluster." }, + { "task": "Rebuild initramfs and bootloader", "detail": "Runs update-initramfs -u -k all across every installed kernel, then either update-grub (GRUB installs) or proxmox-boot-tool refresh (systemd-boot / ZFS installs). Skipped if the interactive step determined nothing changed in the paths that affect these tools." }, + { "task": "Component auto-reinstall", "detail": "Reads the restored components_status.json and iterates over the registered installers (nvidia_driver, coral_driver, amdgpu_top, intel_gpu_tools). Each installer runs in --auto-reinstall mode, reads its previously-recorded version from the state file, and rebuilds against the target's current kernel. The interactive flow does not do this — reinstalling drivers against a kernel that has not yet booted is meaningless." }, + { "task": "Boot sanity check", "detail": "Before firing the completion notification, verifies that proxmox-boot-tool status shows a configured ESP, every /boot/vmlinuz-* has a matching /lib/modules/<ver> directory, and /vmlinuz resolves. Any inconsistency is surfaced in the notification instead of being hidden." }, + { "task": "Completion notification", "detail": "Sends the Host restore finished event through hb_notify_lifecycle. Includes total duration, applied path count, sanity-check warnings if any, and a link to the log at /var/log/proxmenux/proxmenux-cluster-postboot-*.log. If notifications are not configured, the event is silent — the log still records everything." } + ] + }, + "postbootExample": { + "heading": "What the post-boot looks like on the console", + "body": "On the host's physical console, a successful dispatcher run ends with a [ OK ] Finished proxmenux-apply-cluster-postboot.service line. When that line appears — usually alongside the OKs for multi-user.target and graphical.target — the restore is completely finished: components reinstalled, boot artifacts regenerated, cluster reconciled.", + "imageAlt": "Proxmox physical console showing '[ OK ] Finished proxmenux-apply-cluster-postboot.service - ProxMenux Apply Cluster Configs (post-boot).' followed by the OK for multi-user.target and graphical.target, with the host login prompt above.", + "imageCaption": "Physical console after a successful post-boot. The 'Finished proxmenux-apply-cluster-postboot.service' line is the signal that the restore flow is fully complete — the same event the 'Host restore finished' notification surfaces in the Monitor." + }, + "tenMinutes": { + "heading": "Why the last ten minutes matter", + "intro": "The reboot itself takes seconds, but the post-boot dispatcher spends around ten minutes finishing the restore. During this window the host is reachable and login works, but some services (mainly GPU-driver-dependent ones) are not yet available. Time breakdown for a typical Proxmox host with NVIDIA + Coral installed:", + "rows": [ + { "stage": "Boot + pve-cluster ready", "time": "~30 s", "detail": "Standard Proxmox boot. SSH, web UI and login are available at the end of this stage." }, + { "stage": "Apply /etc/pve to pmxcfs", "time": "~10 s", "detail": "Fast — small config files copied one by one to the live cluster filesystem." }, + { "stage": "Rebuild initramfs across kernels", "time": "3–5 min", "detail": "update-initramfs -u -k all rebuilds one initramfs image per installed kernel. Bulk of the wait." }, + { "stage": "Rebuild bootloader config", "time": "10–30 s", "detail": "update-grub or proxmox-boot-tool refresh. Fast even on ZFS." }, + { "stage": "NVIDIA driver reinstall (if applicable)", "time": "5–10 min", "detail": "Downloads the recorded driver version, compiles the DKMS modules against the current kernel, applies the ProxMenux patch if the source had it. Longest single task." }, + { "stage": "Coral / other component reinstalls", "time": "3–5 min", "detail": "DKMS compiles for Coral, apt install for intel-gpu-tools, .deb download + install for amdgpu_top." }, + { "stage": "Sanity check + notification", "time": "~2 s", "detail": "Cheap. The operator learns the run completed the moment the notification fires." } + ], + "outroBody": "The window matters because the operator can log in during this time and see missing tools (nvidia-smi not found, coral not detected). This is expected — the reinstall is still running in the background. The completion notification signals when everything is ready." + }, + "destructiveRollback": { + "heading": "The optional destructive rollback", + "body": "A restore is additive by default: if the target has VMs, LXCs or components that are not in the backup, they survive. This preserves work but can leave stale hostpci entries, orphan components or VMs the operator no longer wants. When drift is detected between target and backup, the confirmation dialog offers a second yes/no: Execute destructive rollback?. Accepting it triggers _rs_execute_rollback, which deletes the VMs, LXCs and components that exist on the target but not in the backup. The rollback runs BEFORE the reboot so that the pending restore machinery sees a clean state. This is opt-in — the default is No, and the operator sees exactly what would be removed listed by name (VMID and CTID) before deciding." + }, + "logs": { + "heading": "Where the logs live", + "rows": [ + { "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log", "detail": "Written by the interactive step. Contains: compatibility results, drift filter, hydration plan, apply hot output, prepare pending output, package install." }, + { "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log", "detail": "Written by apply_pending_restore.sh on early boot. Contains: sourced plan.env, apply-on-boot iteration, skip-paths filtering." }, + { "log": "/var/log/proxmenux/proxmenux-cluster-postboot-YYYYMMDD_HHMMSS.log", "detail": "Written by apply_cluster_postboot.sh. Contains: pve-cluster apply, initramfs and bootloader output, per-component installer output, sanity check, notification payload." }, + { "log": "/var/log/proxmenux/component--YYYYMMDD_HHMMSS.log", "detail": "One log per component installer (nvidia, coral, etc.) that ran in the post-boot pass. Useful when a specific component's reinstall failed and the operator needs the tool's own output." } + ] + }, + "whereNext": { + "heading": "Where to go next", + "items": [ + { "label": "Cross-kernel restore", "href": "/docs/backup-restore/cross-kernel", "tail": " — the direction-aware safe-subset filter and the hydration pass invoked when the target kernel differs from the backup's." }, + { "label": "How it works", "href": "/docs/backup-restore/how-it-works", "tail": " — the archive layout, manifest and application inventory that the restore consumes." }, + { "label": "Scheduled jobs", "href": "/docs/backup-restore/scheduled-jobs", "tail": " — the unattended flow that produces the archives this page consumes." } + ] + } +} diff --git a/web/messages/en/docs/backup-restore/scheduled-jobs.json b/web/messages/en/docs/backup-restore/scheduled-jobs.json new file mode 100644 index 00000000..eb8b9ed0 --- /dev/null +++ b/web/messages/en/docs/backup-restore/scheduled-jobs.json @@ -0,0 +1,87 @@ +{ + "meta": { + "title": "Scheduled jobs — timers, attach mode, retention | ProxMenux", + "description": "Scheduled ProxMenux host backup jobs. Two creation modes (own systemd timer or attach to an existing PVE vzdump job), retention values applied per job via proxmox-backup-client/borg/local prune, storage layout under /var/lib/proxmenux/backup-jobs, and the runner script that produces archives identical to the interactive flow.", + "ogTitle": "ProxMenux Backup — scheduled jobs", + "ogDescription": "Unattended scheduled host backups with attach mode, retention, and the same three destinations as the interactive flow.", + "twitterTitle": "Scheduled jobs | ProxMenux", + "twitterDescription": "Unattended scheduled host backup jobs with attach mode and retention." + }, + "header": { + "title": "Scheduled jobs", + "description": "Unattended host backup jobs. Two creation models: a new independent job with its own schedule, or a job attached to an existing PVE vzdump task that inherits that task's schedule and retention. Both produce archives identical to the interactive flow.", + "section": "Backup & Restore" + }, + "intro": { + "title": "The two scheduled-job models", + "body": "A scheduled job can be created following one of two models:", + "modelsList": [ + "New independent job — ProxMenux defines the job schedule itself with its own systemd timer, independent of any other task on the host. Compatible with all three destinations: Local, PBS and Borg.", + "Attach to an existing PVE vzdump task — the job has no schedule of its own; it runs automatically whenever the parent PVE vzdump task that already backs up the VMs and LXCs on this host fires. Inherits the schedule and retention from the parent task. Compatible with Local and PBS (Borg is not supported because PVE has no native scheduler for Borg)." + ] + }, + "attachBadge": { + "title": "Attach mode — recommended when a PVE vzdump task already exists", + "body": "When a PVE vzdump backup task is already configured for the VMs and LXCs on this node, attaching the host backup to that task guarantees the host configuration is captured in the same window as the guests. On restore, the guest configs come from the host backup (they live under /etc/pve), and the guest disks come from the vzdump backup taken alongside — both sets are consistent with each other, so a full-node recovery can reproduce the host and re-attach every guest without version drift." + }, + "modes": { + "heading": "The two modes", + "rows": [ + { "mode": "New scheduled job", "backends": "Local, PBS, Borg", "schedule": "Own OnCalendar expression (systemd calendar syntax — e.g. daily, Mon..Fri 03:00).", "retention": "Prompted separately: keep-last, keep-hourly, keep-daily, keep-weekly, keep-monthly, keep-yearly. Applied by the runner after each successful backup." }, + { "mode": "Attach to a PVE vzdump job", "backends": "Local, PBS (Borg has no PVE-side scheduler)", "schedule": "Inherited from the parent PVE job. No systemd timer is installed on the ProxMenux side.", "retention": "Inherited from the parent's prune-backups configuration (mapped one-to-one to the runner's KEEP_* variables via hb_pve_prune_to_keep_env)." } + ] + }, + "attachDetail": { + "heading": "How attach mode works", + "intro": "PVE writes vzdump tasks to /etc/pve/jobs.cfg — one stanza per task, each pointing at a storage where the VM and LXC dumps land. Attach mode requires that storage to be a backend ProxMenux understands (Local or PBS); when the task fires, ProxMenux runs alongside it.", + "steps": [ + { "step": "1", "detail": "During job creation, ProxMenux lists compatible parent PVE tasks via hb_pve_list_vzdump_jobs_for_backend. The operator picks one." }, + { "step": "2", "detail": "The job's .env is written with PVE_PARENT_JOB, PVE_STORAGE and the inherited KEEP_* values; no systemd timer is created." }, + { "step": "3", "detail": "hb_install_vzdump_hook registers a script-hook in /etc/vzdump.conf. When PVE runs any vzdump task, the hook script fires; if the $STOREID passed to it matches an attached job's PVE_STORAGE, the runner is invoked for that job." }, + { "step": "4", "detail": "The archive lands in the same storage the vzdump dumps just wrote to: path/dump/ for Local, or the PBS repository configured on the PVE storage entry." } + ], + "outroBody": "Attach mode has one operational implication: disabling or deleting the parent PVE task also disables the host backup — there is no timer of its own to fall back on. The job entry stays on disk so it can be re-attached later." + }, + "storageLayout": { + "heading": "Files that make up a job", + "intro": "Every scheduled job is fully described by three or four files on disk. Reading them gives the full configuration of the job without relying on the interface.", + "rows": [ + { "path": "/var/lib/proxmenux/backup-jobs/JOB_ID.env", "content": "Backend, backup ID or destination, schedule (New mode) or PVE parent (Attach mode), enabled flag, retention KEEP_* values, credentials pointers." }, + { "path": "/var/lib/proxmenux/backup-jobs/JOB_ID.paths", "content": "One absolute path per line — the frozen selection resolved from the profile at job-creation time. Editing the file re-runs the job with the new selection on the next timer fire." }, + { "path": "/etc/systemd/system/proxmenux-backup-JOB_ID.timer + .service", "content": "New mode only. The service invokes run_scheduled_backup.sh JOB_ID. The timer schedules it with Persistent=true (missed fires run at next boot) and a small RandomizedDelaySec=120 to spread load when multiple jobs share the same OnCalendar expression." }, + { "path": "/etc/vzdump.conf script-hook", "content": "Attach mode only. Installed once by hb_install_vzdump_hook; matches every attached job's PVE_STORAGE against the $STOREID PVE passes to the hook script." } + ] + }, + "runner": { + "heading": "What a job does when it fires", + "intro": "Every job — whether new-model or attached — runs the same three-step sequence:", + "steps": [ + "Prepare the backup. Reads the job configuration (destination, profile, paths) and assembles the archive tree exactly the same way the interactive flow does. The result is the same archive a manual backup with those settings would produce.", + "Write to the destination. Uploads or writes the archive to the configured destination — a local .tar.zst file, a PBS backup, or a Borg archive — using exactly the same tools and credentials a manual backup to the same destination would use.", + "Apply retention. Removes old backups from the destination, keeping the keep-last / keep-daily / keep-weekly counts configured on the job. Pruning is performed by the destination itself: PBS applies its own retention, Borg runs borg prune, and for Local ProxMenux deletes the files that fall outside the keep-last window." + ] + }, + "management": { + "heading": "Managing jobs", + "intro": "The scheduler menu (Scripts TUI) and the Monitor Backups tab expose the same actions on any job.", + "rows": [ + { "action": "Run now", "detail": "Invokes the runner immediately, bypassing the timer / vzdump hook. Useful to verify a job configuration without waiting for the next scheduled fire." }, + { "action": "Enable / Disable", "detail": "New mode: systemctl enable/disable --now the timer. Attach mode: flips the ENABLED flag in the job env — the hook script honours it on the next PVE fire." }, + { "action": "Edit", "detail": "Reopens the destination, schedule, profile and retention prompts and rewrites the job files. Preserves the job ID." }, + { "action": "Delete", "detail": "Removes the job env, paths file, systemd timer + service (New mode), and disables the hook binding (Attach mode). Does not touch archives already on the destination." }, + { "action": "View log", "detail": "Streams /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log — one log file per run. The runner also emits a compact status line to journald under the systemd service." } + ] + }, + "notifications": { + "heading": "Notifications", + "body": "Every scheduled run fires the same hb_notify_lifecycle events as the interactive flow (start, complete, fail). If notification channels are configured in the Monitor, unattended jobs surface their results the same way manual backups do — an operator does not need to check the log to know whether a job succeeded." + }, + "whereNext": { + "heading": "Where to go next", + "items": [ + { "label": "Creating backups", "href": "/docs/backup-restore/creating-backups", "tail": " — the interactive flow that shares the backend with the scheduler." }, + { "label": "Destinations", "href": "/docs/backup-restore/destinations", "tail": " — per-backend configuration used by both the interactive flow and the scheduler." }, + { "label": "Restoring", "href": "/docs/backup-restore/restoring", "tail": " — the flow that consumes what jobs produce." } + ] + } +} diff --git a/web/messages/en/docs/hardware/coral-tpu-lxc.json b/web/messages/en/docs/hardware/coral-tpu-lxc.json index f834752c..cb97aeb7 100644 --- a/web/messages/en/docs/hardware/coral-tpu-lxc.json +++ b/web/messages/en/docs/hardware/coral-tpu-lxc.json @@ -20,7 +20,7 @@ "title": "Before you start", "drivers": "Coral drivers already installed on the host. This script does not install them; it only configures passthrough to the container. Run Install Coral TPU on the Host first if you haven't.", "driversCheck": "ls /dev/apex_* 2>/dev/null ; lsusb | grep -E '1a6e:089a|18d1:9302'", - "container": "An existing LXC container, ideally running a Debian / Ubuntu-based distro. The inside-container install uses apt-get; Alpine / Arch containers are not currently supported by this script.", + "container": "An existing LXC container, ideally running a Debian / Ubuntu-based distro — the in-container runtime install uses apt-get. Non-Debian containers (Alpine, Arch, RHEL, SUSE…) are still supported in passthrough-only mode: the script detects the distro and offers a prompt to skip the libedgetpu APT install while still writing the device passthrough config — useful for app containers that bundle the runtime themselves (e.g. the Frigate Docker image).", "downtime": "Be OK with a brief downtime of the container. The script stops it to apply config changes, then starts it back up to install drivers inside. No host reboot needed." }, "hostPrep": { @@ -70,8 +70,8 @@ ], "noIgpuTitle": "Why no iGPU drivers here?", "noIgpuBody": "Earlier versions of this script also installed Intel va-driver-all, intel-opencl-icd and friends so the same container could do Quick Sync video decode alongside Coral inference. That doubled-up responsibility caused confusing failures when the user only wanted Coral. The iGPU side is now the exclusive job of Add GPU to LXC — run it first if you also want hardware video decode in the container.", - "debianTitle": "Debian / Ubuntu containers only", - "debianBody": "The in-container install uses apt-get directly. Alpine, Arch or RHEL-based containers are not currently supported — the install step will fail and leave the LXC with the passthrough config but no drivers inside. For those distros, install the Coral runtime manually following Google's official guide after the LXC config step." + "debianTitle": "Non-Debian containers — passthrough-only mode", + "debianBody": "The in-container runtime install uses apt-get, which only ships on Debian/Ubuntu-family distros. On Alpine, Arch, RHEL or SUSE containers the script detects the distro via /etc/os-release and shows a confirmation prompt: continue in passthrough-only mode (writes the device config to /etc/pve/lxc/<ctid>.conf and skips the libedgetpu APT install) or abort. Passthrough-only is the right choice if the app container that will actually use Coral already bundles the runtime — the canonical example is the Frigate Docker image. Otherwise, follow Google's official guide to install libedgetpu manually after the script has written the LXC config." }, "summary": { "title": "Summary", @@ -95,8 +95,8 @@ "apexBody": "Host apex module isn't loaded. On the host: lsmod | grep apex — if empty, run modprobe apex, or reboot if you just installed Coral drivers. Once the host has /dev/apex_0, restart the container: pct stop <ctid> && pct start <ctid>.", "replugTitle": "USB Coral disappears after replug in a different port", "replugBody": "This is exactly why the script mounts /dev/bus/usb instead of the /dev/coral symlink. If you're hitting this, check your LXC config has lxc.mount.entry: /dev/bus/usb dev/bus/usb ... and not a reference to /dev/coral directly. Old configs from earlier script versions may need updating — re-run the script on the same container and the config gets refreshed.", - "alpineTitle": "In-container install fails on an Alpine container", - "alpineBody": "The script uses apt-get, which Alpine doesn't have. The LXC passthrough config is still valid — just install the Coral runtime manually with apk add following Google's guide for Alpine, or use a Debian-based container if you don't need the smaller footprint.", + "alpineTitle": "Alpine / Arch / RHEL / SUSE container — runtime not installed", + "alpineBody": "If you chose passthrough-only mode when the script prompted, the LXC config is written and the Coral device is visible inside the container, but the libedgetpu runtime is not installed. That's by design: Google's APT repo only ships for Debian/Ubuntu. Install the runtime manually with your distro's package manager (Alpine: apk add; Arch: AUR; RHEL/SUSE: build from source) following Google's official guide, or run an app container that bundles the runtime — the Frigate Docker image is the canonical example: just expose the device with --device /dev/apex_0:/dev/apex_0 (M.2) or the USB bind mount the script already wrote (USB).", "frigateTitle": "Frigate says 'Coral EdgeTPU detected but not available'", "frigateBody": "Almost always a permissions issue inside the container. Frigate runs as root by default; check the root user is in the plugdev group inside the container (for USB), and that the process can read /dev/apex_0 (for M.2). ls -l /dev/apex_0 from inside the container should show group apex — if not, add the GID alignment to /etc/group or switch the container to privileged mode.", "logsTitle": "Check both host and container logs", diff --git a/web/messages/en/docs/monitor/dashboard/network.json b/web/messages/en/docs/monitor/dashboard/network.json index 6c517689..887a3b7f 100644 --- a/web/messages/en/docs/monitor/dashboard/network.json +++ b/web/messages/en/docs/monitor/dashboard/network.json @@ -35,6 +35,32 @@ } ] }, + "flow": { + "heading": "Network Flow diagram", + "intro": "Between the top row and the three group cards, the tab renders a live topology view called Network Flow. It draws every path a packet can take through the host — physical NICs at one end, bridges in the middle, VMs and containers at the other — with animated pulses that show real-time rx/tx traffic on each link.", + "imageAlt": "Network Flow diagram — NICs on the left, host and bridges in the middle, VMs and LXCs on the right, with animated pulses on each connection", + "imageCaption": "Network Flow — colour-coded nodes plus animated comets whose direction and thickness reflect live traffic.", + "elementsTitle": "What the diagram shows", + "elementsIntro": "Every node is one entity from the same inventory used by the group cards below, but arranged as a topology so relationships between them are immediate:", + "elements": [ + "NICs (amber) — every physical interface with link up. Interfaces reported as down are drawn faded.", + "Host (amber) — the Proxmox host itself, sitting between the NICs and the bridges. Rendered as a fixed anchor; not clickable.", + "Bridges (cyan) — Linux and OVS bridges. Only bridges that carry at least one active guest are drawn; unused bridges are hidden to keep the diagram focused on what actually moves traffic.", + "LXCs (cyan) — running containers attached to a bridge.", + "VMs (purple) — running virtual machines attached to a bridge.", + "Offline guests (stopped VMs / containers) are hidden." + ], + "pulsesTitle": "What the animation encodes", + "pulsesBody": "The animated comets that travel along each edge represent live traffic:", + "pulses": [ + "Direction — a pulse flowing toward the NIC is tx from the guest; toward the guest is rx.", + "Stroke width scales with the guest's combined rx+tx rate. An idle guest still draws a faint animated line; a busy one gets a thick one.", + "Comet head glow — warm at ~1 MB/s, hot at ≥30 MB/s. Useful to spot at a glance which guest is dominating a NIC.", + "Click — every node except the host is clickable and opens the same per-interface drill-in modal documented below. Tapping the host is intentionally a no-op — there is no host-level modal in this view." + ], + "useTitle": "When it's useful", + "useBody": "The group cards below tell you what exists; the diagram tells you how it's connected and where the traffic is flowing right now. Patterns easy to spot at a glance: a bridge with no active guest attached, two heavy guests going through the same NIC (bottleneck candidate), or a single VM saturating a link while everything else is quiet." + }, "groups": { "heading": "Three interface groups", "intro": "Below the top row, three cards split the inventory by role. Each card has its own active-count badge in the header. Interface type is identified at a glance by a coloured badge on every row:", diff --git a/web/messages/en/docs/monitor/dashboard/system-overview.json b/web/messages/en/docs/monitor/dashboard/system-overview.json index dea9a13e..d7267b5e 100644 --- a/web/messages/en/docs/monitor/dashboard/system-overview.json +++ b/web/messages/en/docs/monitor/dashboard/system-overview.json @@ -53,6 +53,30 @@ "sparklineTitle": "The sparkline is meaningful", "sparklineBody": "The temperature card draws a 5-minute trace under the value, with the line and gradient colour following the same Warning/Critical pair documented above. It's the fastest way to see whether the host is in a thermal climb without opening the detail modal." }, + "processes": { + "heading": "Top processes by CPU / Memory", + "intro": "The CPU Usage and Memory cards on the Overview tab are clickable. Clicking either opens a sortable list of the top 25 processes — ordered by CPU usage when opened from the CPU card, by resident memory when opened from the Memory card.", + "listTitle": "The list dialog", + "listItems": [ + "Auto-refresh — the list updates every 5 seconds while the dialog is open and stops polling as soon as it closes.", + "Filter — the search box narrows the list by command, user or PID.", + "Inline bar — the primary metric column draws a small bar scaled to the highest value in the filtered list, so ranking stays visible even when no process is near 100 %.", + "Mobile layout — under 640 px the PID and User columns hide so Command, CPU % and Memory still fit on a phone screen without horizontal scroll." + ], + "captureListAlt": "Top processes by Memory modal — table with PID, USER, COMMAND, CPU %, Memory columns sorted by RSS", + "captureListCaption": "The Memory card opens the list sorted by RSS (indigo accent). The CPU card opens the same list sorted by CPU usage (blue accent).", + "detailTitle": "Per-process detail", + "detailIntro": "Clicking any row in the list opens a second dialog with the live picture of that one process, organised in four sections:", + "detailItems": [ + "Overview — state, parent process, thread count, open file descriptors, user and group.", + "Resources — CPU %, Memory %, Resident (RSS), Virtual size, Swap, I/O read and write totals.", + "Command — short name, full command line, executable path and working directory.", + "Lifetime — start timestamp and elapsed runtime." + ], + "detailRefresh": "The detail dialog refreshes every 3 seconds while open. If the process finishes mid-dialog, polling stops, an amber This process has finished banner appears, and the last captured snapshot stays on screen (dimmed) so you can still see what was happening just before it ended.", + "captureDetailAlt": "Process detail modal — Overview, Resources, Command and Lifetime sections for a single PID", + "captureDetailCaption": "Per-process detail dialog opened from a list row. The accent colour matches the card that opened it (blue for CPU, indigo for Memory)." + }, "middle": { "heading": "Middle: node metrics charts", "body1": "Below the top row sits the NodeMetricsCharts component — historical CPU, memory and disk-I/O graphs sourced from Proxmox's own RRD store via /api/node/metrics. A timeframe selector switches between 1 hour / 24 hours / 7 days / 30 days / 1 year; data resolution drops as the window grows so the chart stays smooth.", diff --git a/web/messages/en/docs/post-install/automated.json b/web/messages/en/docs/post-install/automated.json index adbdaa84..7b9c92c6 100644 --- a/web/messages/en/docs/post-install/automated.json +++ b/web/messages/en/docs/post-install/automated.json @@ -79,13 +79,13 @@ }, { "tool": "Log2RAM (SSD-aware, auto)", - "what": "Detects whether the root disk is SSD / NVMe and installs Log2RAM from upstream git. Sizes the ramdisk by host RAM (128M / 256M / 512M), schedules periodic sync and a 95 % threshold auto-sync. Adjusts journald limits to fit in the ramdisk.", + "what": "Reduces SSD/NVMe wear by moving /var/log to a tmpfs ramdisk with periodic disk sync. Installed from the upstream project when the root disk is SSD or NVMe. Sizes the ramdisk by host RAM (128M / 256M / 512M), schedules periodic sync and an auto-sync guard that vacuums at 80% and truncates hot logs at 92% before writing. Adjusts journald limits to fit in the ramdisk. Full details on Log2RAM.", "category": "Storage", "categorySlug": "storage" }, { "tool": "ZFS autotrim (SSD-only)", - "what": "Enables zpool autotrim=on on every ZFS pool whose vdevs are all SSD/NVMe with TRIM support (checks /sys/block//queue/rotational and discard_granularity). Pools backed by HDDs are skipped automatically. Only pools actually changed by ProxMenux are recorded for uninstall — pools you set autotrim on manually are left alone.", + "what": "Enables zpool autotrim=on on every ZFS pool whose vdevs are all SSD/NVMe with TRIM support (checks /sys/block/[dev]/queue/rotational and discard_granularity). Pools backed by HDDs are skipped automatically. Only pools actually changed by ProxMenux are recorded for uninstall — pools you set autotrim on manually are left alone.", "category": "Storage", "categorySlug": "storage" }, diff --git a/web/messages/en/docs/post-install/customizable.json b/web/messages/en/docs/post-install/customizable.json index a745a49e..bb58e900 100644 --- a/web/messages/en/docs/post-install/customizable.json +++ b/web/messages/en/docs/post-install/customizable.json @@ -25,43 +25,43 @@ "categories": [ { "name": "Basic Settings", - "description": "Repositories, system upgrade, timezone, locale, common utilities." + "description": "Clean the APT sources, run the official Proxmox upgrade and set timezone, locale and common utilities. The baseline every other group sits on top of." }, { "name": "System", - "description": "Journald, logrotate, kernel limits, memory tuning, kernel panic, fast reboots." + "description": "Tune the log subsystem and kernel limits for a host that carries many VMs and containers. Covers journald, logrotate, sysctl (memory, file limits), kernel panic behaviour and fast reboots." }, { "name": "Virtualization", - "description": "Guest agent auto-install, IOMMU/VFIO enablement for PCI passthrough." + "description": "Prepare the host for advanced virtualization. Auto-installs the guest agent on templates and enables IOMMU/VFIO for PCI passthrough of GPUs, controllers and TPUs." }, { "name": "Network", - "description": "APT over IPv4, network sysctl tuning, Open vSwitch, TCP BBR, persistent interface names." + "description": "Harden and tune the host's network stack. Forces APT over IPv4, applies sysctl hardening + TCP buffer tuning, offers Open vSwitch and BBR, and pins persistent interface names by MAC." }, { "name": "Storage", - "description": "ZFS ARC sizing, ZFS auto-snapshot, vzdump backup speed limits." + "description": "Set up Proxmox's common storage subsystems: ZFS ARC sizing, ZFS auto-snapshot, and vzdump speed limits to avoid saturating the disk during backups." }, { "name": "Security", - "description": "Disable portmapper/rpcbind to reduce the attack surface." + "description": "Reduce the attack surface exposed by default. Disables the RPC services (portmapper/rpcbind) that Proxmox does not need but leaves listening." }, { "name": "Customization", - "description": "Bashrc colors & aliases, MOTD banner, subscription-notice removal." + "description": "Change the host's visual and shell experience: colors and aliases in bashrc, MOTD banner and removal of the subscription notice in the web UI." }, { "name": "Monitoring", - "description": "OVH Real-Time Monitoring (only on detected OVH servers)." + "description": "Integrate the host with OVH Real-Time Monitoring. Only appears when ProxMenux detects an OVH server." }, { "name": "Performance", - "description": "Parallel gzip (pigz) for faster compression in backups and transfers." + "description": "Speed up compression operations (backups, transfers) by replacing gzip with its parallel counterpart pigz." }, { "name": "Optional", - "description": "AMD CPU fixes, Fastfetch, Figurine, Ceph repo, High Availability, Log2RAM." + "description": "Niche pieces not every host needs: AMD CPU fixes, Fastfetch banner, Figurine 3D hostname, Ceph repository, High Availability services and Log2RAM to reduce SSD wear." } ], "mixTip": { diff --git a/web/messages/en/docs/post-install/optional.json b/web/messages/en/docs/post-install/optional.json index 12fc2ce6..b2585266 100644 --- a/web/messages/en/docs/post-install/optional.json +++ b/web/messages/en/docs/post-install/optional.json @@ -136,6 +136,27 @@ "automates": "This adjustment automates the following process:", "outro": "After installation, you'll see your hostname displayed in 3D ASCII art each time you log in, making it immediately clear which Proxmox node you're working on." }, + "log2ram": { + "title": "Install Log2RAM (SSD/NVMe wear reduction)", + "intro": "Log2RAM mounts /var/log on a tmpfs ramdisk and periodically syncs the contents back to the underlying disk. On a hypervisor journald churn hits the root SSD every few seconds; moving the writes to RAM reduces wear and disk I/O without losing logs — the periodic sync flushes them to disk and a clean shutdown flushes them too.", + "upstreamLabel": "Upstream project:", + "upstreamUrl": "https://github.com/azlux/log2ram", + "upstreamLinkLabel": "azlux/log2ram on GitHub", + "doesLabel": "What ProxMenux does:", + "doesItems": [ + "Detects whether the root disk is SSD/NVMe by reading /sys/block/<dev>/queue/rotational. On a rotational disk the Automated flow asks first before installing.", + "Clones the upstream repository (azlux/log2ram) into /tmp/log2ram and runs its install.sh, then enables the log2ram systemd unit.", + "Sizes the ramdisk based on host RAM: ≤ 8 GB → 128M, ≤ 16 GB → 256M, > 16 GB → 512M. Writes the value to SIZE= in /etc/log2ram.conf.", + "Schedules a periodic disk sync via /etc/cron.d/log2ram: every 1h / 3h / 6h according to the same RAM tier.", + "Installs an auto-sync guard at /usr/local/bin/log2ram-check.sh, wired to /etc/cron.d/log2ram-auto-sync to run every 10 minutes. When /var/log reaches 80% of the ramdisk size the guard vacuums journald; at 92% it also truncates pveproxy access/error and pveam.log before syncing — the plain log2ram write command copies tmpfs to disk but does NOT shrink the tmpfs, so this guard prevents PVE from crashing with No space left on device when logs grow uncontrolled.", + "Adjusts systemd-journald limits (SystemMaxUse, RuntimeMaxUse) to fit within the ramdisk so a single burst cannot fill it.", + "Registers itself in installed_tools.json so it can be reverted from Uninstall Optimizations." + ], + "howUseLabel": "How to use:", + "howUseBody": "On the Automated flow Log2RAM applies unattended when an SSD/NVMe root is detected. On the Customizable flow the script prompts for size, sync interval and whether to enable the 90% auto-sync guard.", + "verifyLabel": "Verify and manage from the shell:", + "verifyCode": "# Service state and configured timers\nsystemctl status log2ram\nsystemctl list-timers log2ram*\n\n# Current ramdisk usage — /var/log IS the tmpfs itself\ndf -h /var/log\n\n# Force a sync now (tmpfs → disk); does NOT shrink the tmpfs\nlog2ram write\n\n# Trigger the auto-sync guard on demand (vacuum + sync when usage is high)\n/usr/local/bin/log2ram-check.sh\n\n# Current config: SIZE, mail settings, path\ncat /etc/log2ram.conf\n\n# ProxMenux-specific cron jobs\ncat /etc/cron.d/log2ram /etc/cron.d/log2ram-auto-sync\n\n# Recent Log2RAM activity\njournalctl -u log2ram -n 50 --no-pager" + }, "autoApplication": { "title": "Automatic Application", "body": "These optional features are applied only when specifically selected during the post-install process. Each feature can be individually chosen based on your specific needs and preferences." diff --git a/web/messages/en/docs/post-install/storage.json b/web/messages/en/docs/post-install/storage.json index 40e4416e..8ab79acb 100644 --- a/web/messages/en/docs/post-install/storage.json +++ b/web/messages/en/docs/post-install/storage.json @@ -9,7 +9,7 @@ }, "intro": { "title": "What this category covers", - "body": "Three storage-related optimizations: tune the ZFS ARC cache size to a sensible fraction of host RAM, install and schedule ZFS auto-snapshots, and remove throttles from vzdump so backups run at full speed. All three are independent — pick the ones that match your setup." + "body": "Three storage-related optimizations: tune the ZFS ARC cache size to a sensible fraction of host RAM, install and schedule ZFS auto-snapshots, and remove throttles from vzdump so backups run at full speed. All three are independent — pick the ones that match your setup. A fourth storage-adjacent optimization, Log2RAM, reduces SSD/NVMe wear by moving /var/log to a ramdisk — it lives on the Optional page because the ProxMenux Customizable menu groups it there." }, "notTrackedTitle": "None of these are in the Uninstall menu", "notTrackedBody": "Unlike most post-install optimizations, the three Storage options are not currently tracked in the Uninstall Optimizations flow. If you apply them and later want to revert, you'll have to do it by hand. The manual rollback commands are shown below each section.", @@ -148,6 +148,11 @@ "href": "/docs/post-install/uninstall", "tail": " — revert ARC / vzdump changes." }, + { + "label": "Log2RAM", + "href": "/docs/post-install/optional#log2ram", + "tail": " — reduces SSD/NVMe wear by ramdisk-backing /var/log; documented under Optional." + }, { "label": "Customizable Post-Install", "href": "/docs/post-install/customizable", diff --git a/web/messages/es/common.json b/web/messages/es/common.json index 2355908c..52410f72 100644 --- a/web/messages/es/common.json +++ b/web/messages/es/common.json @@ -168,7 +168,19 @@ "aboutContributors": "Contribuidores", "aboutContributing": "Contribuir", "aboutCodeOfConduct": "Código de conducta", - "externalRepositories": "Repositorios externos" + "externalRepositories": "Repositorios externos", + "backupRestore": "Backup & Restore", + "backupRestoreOverview": "Descripción general", + "backupRestoreHowItWorks": "Cómo funciona", + "backupRestoreDestinations": "Destinos", + "backupRestoreDestOverview": "Descripción general", + "backupRestoreDestLocal": "Archivo local", + "backupRestoreDestPbs": "Proxmox Backup Server", + "backupRestoreDestBorg": "Borg", + "backupRestoreCreating": "Crear copias", + "backupRestoreJobs": "Trabajos programados", + "backupRestoreRestoring": "Restauración", + "backupRestoreCrossKernel": "Restauración cross-kernel" } }, "hero": { @@ -213,4 +225,4 @@ "ogTail": "Suscríbete vía RSS o vuelve a comprobarlo para nuevas versiones." } } -} +} \ No newline at end of file diff --git a/web/messages/es/docs/backup-restore/creating-backups.json b/web/messages/es/docs/backup-restore/creating-backups.json new file mode 100644 index 00000000..8c744554 --- /dev/null +++ b/web/messages/es/docs/backup-restore/creating-backups.json @@ -0,0 +1,220 @@ +{ + "meta": { + "title": "Crear copias — flujo interactivo de copia | ProxMenux", + "description": "El flujo interactivo de copia en ProxMenux. Dos puntos de entrada (menú TUI de Scripts y UI Web del Monitor), tres destinos, dos modos de perfil, un paso común de staging y un diálogo de confirmación. Documenta la matriz de seis opciones, los perfiles Default y Custom, y qué ve el usuario entre seleccionar una copia y ver el archivo aterrizando en el destino.", + "ogTitle": "ProxMenux Backup — crear copias", + "ogDescription": "El flujo interactivo de copia con tres destinos, dos perfiles y un paso común de staging.", + "twitterTitle": "Crear copias | ProxMenux", + "twitterDescription": "Flujo interactivo de copia con tres destinos y dos modos de perfil." + }, + "header": { + "title": "Crear copias", + "description": "El flujo interactivo de copia: elegir un destino y un perfil, revisar el resumen de confirmación, y ver el archivo aterrizando. Dos puntos de entrada comparten el mismo backend y producen archivos idénticos.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Dos puntos de entrada, funcionalidad idéntica", + "body": "El TUI de Scripts y la UI Web del Monitor exponen exactamente la misma funcionalidad. Cada copia — manual o programada — pasa por la misma matriz de elección (tres destinos × dos perfiles) e invoca la misma función backend por celda (_bk_pbs, _bk_borg o _bk_local). Los archivos producidos desde uno u otro punto de entrada son indistinguibles. Cuál usar es una cuestión de preferencia: el TUI es amigable por SSH y admite scripting; el Monitor ofrece click-y-elegir y convive con las vistas de notificaciones y de tail del log." + }, + "entryPoints": { + "heading": "Los dos puntos de entrada", + "rows": [ + { + "entry": "ProxMenux Scripts (TUI)", + "path": "menu → Utilities → Host Config Backup", + "detail": "Flujo basado en diálogos, amigable por SSH. El menú principal presenta las seis opciones directamente. Usa backup_menu en backup_host.sh." + }, + { + "entry": "ProxMenux Monitor (UI Web)", + "path": "Pestaña Backups → Create backup", + "detail": "Flujo en estilo asistente. Las mismas seis opciones presentadas como formulario de dos pasos (destino → perfil). Se invocan las mismas funciones backend por la API Flask." + } + ] + }, + "modes": { + "heading": "Copias manuales vs programadas", + "body": "Las copias se pueden producir en dos modos: manuales (el flujo interactivo que documenta esta página — el usuario elige un destino y un perfil desde un menú y ve el archivo aterrizando) o programadas (un trabajo desatendido que se ejecuta en un timer estilo cron y aplica la retención configurada en el trabajo). Ambos modos soportan los mismos tres destinos y los mismos dos perfiles, y ambos están disponibles desde los dos puntos de entrada — el menú TUI de Scripts y la pestaña Backups del Monitor. Los trabajos programados usan las mismas funciones backend que el flujo manual a través de run_scheduled_backup.sh; los archivos producidos son indistinguibles.", + "seeAlso": "La página de trabajos programados cubre el flujo completo, incluyendo cómo crear un trabajo, adjuntarlo a un timer vzdump de PVE existente, y configurar los valores de retención.", + "monitorAlt": "Pestaña Backups del Monitor de ProxMenux mostrando el diálogo New scheduled backup con los campos de destino, perfil, horario y retención.", + "monitorCaption": "Copia programada — Monitor de ProxMenux. El mismo diálogo estilo asistente que crea una copia manual lleva los campos de horario y retención al final para la ruta desatendida." + }, + "matrix": { + "heading": "La matriz de seis opciones", + "intro": "La elección determina qué backend se ejecuta y qué estrategia de selección de rutas se aplica. Consulta las páginas específicas de cada destino para los detalles de configuración de cada celda.", + "rows": [ + { + "combo": "1", + "destination": "PBS", + "profile": "Default", + "action": "Sube el perfil por defecto más los extras persistentes a un repositorio PBS configurado." + }, + { + "combo": "2", + "destination": "Borg", + "profile": "Default", + "action": "Crea un archivo con el perfil por defecto más los extras persistentes en el repositorio Borg seleccionado." + }, + { + "combo": "3", + "destination": "Local", + "profile": "Default", + "action": "Escribe un archivo .tar.zst con el perfil por defecto más los extras persistentes en el destino local configurado." + }, + { + "combo": "4", + "destination": "PBS", + "profile": "Custom", + "action": "Abre el path picker antes de la subida a PBS; el usuario marca rutas y puede añadir nuevas." + }, + { + "combo": "5", + "destination": "Borg", + "profile": "Custom", + "action": "Abre el path picker antes de crear el archivo Borg." + }, + { + "combo": "6", + "destination": "Local", + "profile": "Custom", + "action": "Abre el path picker antes de escribir el .tar.zst local." + } + ] + }, + "profiles": { + "heading": "Perfil Default vs Custom", + "defaultTitle": "Perfil Default", + "defaultBody": "El perfil por defecto es la lista curada de hb_default_profile_paths (documentada en Cómo funciona bajo Categorías de rutas) más cada entrada del fichero de extras persistentes /usr/local/share/proxmenux/backup-extra-paths.txt. El usuario confirma el destino y las opciones de cifrado y la copia continúa sin más selección de rutas.", + "customTitle": "Perfil Custom", + "customBody": "El perfil Custom abre un checklist mostrando cada ruta del perfil por defecto (sin marcar) y cada extra persistente (premarcado, prefijado con [+]). El usuario marca el conjunto para esa ejecución y puede pulsar Add custom path para añadir una nueva ruta absoluta. Cualquier ruta añadida en línea se persiste en backup-extra-paths.txt para que futuras copias la recojan automáticamente sin volver a añadirla. Quitar la marca a un extra persistente lo desmarca para esa ejecución pero no lo elimina del fichero — la eliminación es una acción Manage custom paths separada, fuera del flujo de copia.", + "customPickerAlt": "Checklist del perfil Custom mostrando las rutas del perfil por defecto (sin marcar) y los extras persistentes (premarcados con prefijo [+]), más botones para añadir una ruta nueva o confirmar la selección.", + "customPickerCaption": "Perfil Custom — el path picker. Las rutas del perfil por defecto aparecen sin marcar; los extras persistentes aparecen premarcados con prefijo [+]. El usuario marca el conjunto para esa ejecución.", + "manageCustomAlt": "Menú Manage custom paths mostrando la lista de extras persistentes y opciones para añadirlos, eliminarlos o editarlos.", + "manageCustomCaption": "Manage custom paths — el punto de entrada donde se añaden o eliminan los extras persistentes. Cada ruta listada aquí se incluye automáticamente en las copias en modo Default sin necesidad de abrir el picker Custom." + }, + "commonPipeline": { + "heading": "Qué se ejecuta con independencia del destino", + "intro": "Después de resolver el perfil, cada backend ejecuta el mismo pipeline de staging antes de divergir a su propio camino de subida. hb_prepare_staging ensambla el árbol del archivo en /tmp/proxmenux-DESTINATION-stage.XXXXXX y pobla cada uno de los tres bloques.", + "steps": [ + { + "step": "1", + "name": "Ensamblado del rootfs", + "detail": "Ejecuta rsync -a por cada ruta seleccionada hacia staging_root/rootfs/. Excluye subrutas volátiles (historial de bash, cachés, papelera) de /root/. Las rutas ausentes en el origen se registran en metadata/missing_paths.txt sin detener la copia." + }, + { + "step": "2", + "name": "Generación del manifiesto", + "detail": "build_manifest.sh orquesta los seis colectores y escribe manifest.json en la raíz del staging. Si un colector falla, la sección afectada hace fallback a un default vacío documentado; el manifiesto sigue siendo válido." + }, + { + "step": "3", + "name": "Inventario de paquetes", + "detail": "apt-mark showmanual se captura tal cual en metadata/packages.manual.list. El estado de componentes ya está dentro del rootfs restaurado (components_status.json) porque /usr/local/share/proxmenux/ forma parte del perfil por defecto." + }, + { + "step": "4", + "name": "Info de la ejecución", + "detail": "metadata/run_info.env registra la identidad de la ejecución de copia — hostname, timestamp, versión del kernel — usada por el chequeo de compatibilidad de la restauración para determinar la dirección cross-kernel." + }, + { + "step": "5", + "name": "Notificación (start)", + "detail": "Se dispara hb_notify_lifecycle \"start\". Si las notificaciones están configuradas en el Monitor, se emite un evento usuario-facing Host backup started. Silencioso si no hay canales configurados." + } + ] + }, + "included": { + "heading": "Qué entra y qué se excluye", + "intro": "Cada ruta del perfil resuelto (default + extras persistentes + selección en modo Custom) se copia con rsync -aAXH --numeric-ids. Una lista de exclusiones compartida aplica a cada ruta, y dos directorios llevan exclusiones específicas adicionales.", + "globalTitle": "Exclusiones globales (aplican a cada ruta)", + "globalItems": [ + "images/ — dumps de imágenes.", + "dump/ — salidas de vzdump.", + "tmp/ — ficheros temporales.", + "*.log — ficheros de log." + ], + "rootTitle": "Exclusiones de /root/", + "rootBody": "/root/ forma parte del perfil por defecto para que los scripts y config del usuario entren en el archivo. Se descartan los subpaths volátiles:", + "rootItems": [ + ".bash_history", + ".cache/", + "tmp/", + ".local/share/Trash/" + ], + "proxmenuxTitle": "Exclusiones de /usr/local/share/proxmenux/", + "proxmenuxBody": "Este directorio contiene sólo estado de usuario — components_status.json, preferencias, caché post-install. El código que el destino ya tendrá de su propia instalación de ProxMenux se excluye para que una restauración no sobrescriba los binarios actuales del destino con versiones más antiguas:", + "proxmenuxItems": [ + "restore-pending/, scripts/, web/", + "monitor-app/, monitor-app.*/, AppImage/", + "images/, json/", + "utils.sh, helpers_cache.json", + "ProxMenux-Monitor.AppImage*, install_proxmenux*.sh" + ], + "notInProfileTitle": "Rutas fuera del perfil", + "notInProfileBody": "Todo lo que no esté listado en hb_default_profile_paths y no se haya añadido como ruta custom o extra persistente no forma parte de la copia. Ejemplos notables:", + "notInProfileItems": [ + "Discos de VMs y LXCs — los gestiona vzdump, no esta funcionalidad. Los ficheros de configuración de los invitados bajo /etc/pve/nodes/*/qemu-server/*.conf y lxc/*.conf sí se capturan (viven bajo /etc/pve) para que la restauración reproduzca el inventario; los discos se re-adjuntan desde una copia existente de vzdump/PBS.", + "/boot y /boot/efi — los binarios del kernel, el initramfs y la partición ESP UEFI los regenera el propio destino con update-initramfs, update-grub o proxmox-boot-tool refresh tras la restauración. El bootloader nunca se copia verbatim.", + "Filesystems runtime del kernel y sistema/proc, /sys, /dev y /run son pseudo-filesystems que produce el kernel y udev; no se persisten en ningún sitio.", + "Binarios de paquetes bajo /usr/bin, /usr/lib, /lib, /sbin — los reinstala el APT del destino a partir de packages.manual.list.", + "/var/log/, /var/tmp/, /var/cache/ — estado runtime por host, no se restaura.", + "/home/USUARIO — no está en el perfil por defecto. Añadirlo como ruta custom cuando un sistema tenga directorios home de usuario que deban sobrevivir a una restauración." + ], + "customPathsTitle": "Cómo se tratan las rutas custom", + "customPathsBody": "Una ruta custom añadida en línea en modo Custom o persistida en backup-extra-paths.txt pasa por el mismo pipeline de rsync que las rutas del perfil por defecto. Se aplican las exclusiones globales. Si la ruta custom está bajo /root/ o /usr/local/share/proxmenux/, siguen aplicando las exclusiones específicas de arriba. Cada ruta archivada — default o custom — queda registrada en metadata/paths_archived.txt. Las rutas que no existen en el origen se registran en metadata/missing_paths.txt sin detener la copia." + }, + "archiveStructure": { + "heading": "Estructura del archive", + "intro": "El directorio de staging que produce cada backend sigue el mismo layout con independencia del destino. El tarball, el .pxar de PBS o el archivo Borg almacenan este árbol verbatim.", + "tree": "backup-[timestamp]/\n├── manifest.json # estado estructurado del host (kernel_params, hardware, storage, guests, components, source_host)\n├── metadata/\n│ ├── packages.manual.list # salida de apt-mark showmanual\n│ ├── run_info.env # hostname, timestamp, versión del kernel\n│ ├── paths_archived.txt # lista exacta de rutas que llegaron a rootfs/\n│ └── missing_paths.txt # rutas del perfil ausentes en el origen\n└── rootfs/\n ├── etc/ # /etc/pve, /etc/network, /etc/ssh, /etc/apt, ...\n ├── root/ # /root sin subpaths volátiles\n ├── usr/local/ # /usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux (sólo estado)\n └── var/ # /var/lib/pve-cluster, /var/spool/cron/crontabs" + }, + "confirmation": { + "heading": "Resumen de confirmación", + "body": "Antes de que el backend escriba nada en el destino, ProxMenux muestra un diálogo resumen con el destino, el backup ID o nombre del archivo, el estado del cifrado y la lista de rutas que se copian. Cancelar aquí aborta la copia limpiamente — el directorio de staging se elimina por el hook trap definido en la función backend y ningún dato parcial llega al destino." + }, + "writing": { + "heading": "Escritura en el destino", + "intro": "Una vez el usuario confirma, cada backend ejecuta su propio paso de escritura. La mecánica está cubierta en las páginas de cada destino; la superficie compartida son el log, el sidecar y la notificación de finalización.", + "rows": [ + { + "topic": "Fichero de log", + "detail": "Cada backend escribe su salida completa en /tmp/proxmenux-DESTINATION-backup-YYYYMMDD_HHMMSS.log y, en caso de fallo, ofrece abrirlo en un diálogo scrollable. La ruta al log se imprime en el resumen de finalización sólo cuando el fichero tiene contenido." + }, + { + "topic": "Sidecar (sólo local)", + "detail": "hb_write_archive_sidecar deposita un *.proxmenux.json junto al archivo local para que el Monitor lo identifique como copia de host de ProxMenux incluso tras movimientos o renombrados." + }, + { + "topic": "Notificación (complete/fail)", + "detail": "Se dispara hb_notify_lifecycle \"complete\" o \"fail\" con duración, tamaño del archivo y — en fallos — la última línea del log que parezca un error." + } + ] + }, + "finishedScreens": { + "heading": "Cómo se ve una copia finalizada", + "intro": "El mismo evento de finalización lo exponen los dos puntos de entrada. El TUI escribe un bloque resumen en el terminal; la pestaña Backups del Monitor muestra la ejecución en la lista de archivos con badges de tamaño, duración y estado.", + "scriptsAlt": "TUI de ProxMenux Scripts mostrando una copia de host finalizada — destino, backup ID, ruta del snapshot, tamaño de datos, duración y estado de cifrado.", + "scriptsCaption": "Copia finalizada — ProxMenux Scripts (TUI). El bloque de finalización imprime el destino, backup ID, nombre del snapshot o archivo resultante, tamaño de datos, duración y estado de cifrado.", + "monitorAlt": "Pestaña Backups del Monitor de ProxMenux mostrando una entrada de copia de host finalizada con tamaño, duración, badge del método y indicador de cifrado.", + "monitorCaption": "Copia finalizada — pestaña Backups del Monitor de ProxMenux. La nueva copia aparece en la lista de archivos con el badge del método del destino, tamaño, duración y — cuando aplica — el indicador de cifrado." + }, + "whereNext": { + "heading": "A dónde seguir", + "items": [ + { + "label": "Destinos", + "href": "/docs/backup-restore/destinations", + "tail": " — detalles de configuración para Local, PBS y Borg." + }, + { + "label": "Trabajos programados", + "href": "/docs/backup-restore/scheduled-jobs", + "tail": " — ejecutar la misma copia desatendida en un horario en lugar de interactivamente." + }, + { + "label": "Restauración", + "href": "/docs/backup-restore/restoring", + "tail": " — el flujo que consume lo que esta página produce." + } + ] + } +} diff --git a/web/messages/es/docs/backup-restore/cross-kernel.json b/web/messages/es/docs/backup-restore/cross-kernel.json new file mode 100644 index 00000000..407a7f53 --- /dev/null +++ b/web/messages/es/docs/backup-restore/cross-kernel.json @@ -0,0 +1,94 @@ +{ + "meta": { + "title": "Restauración cross-kernel — detección de dirección, filtro de subconjunto seguro, hidratación | ProxMenux", + "description": "Cómo restaura ProxMenux una copia de host sobre un destino con un kernel de versión mayor distinta. Documenta cómo se detecta la dirección del salto, el filtro de subconjunto seguro que salta rutas críticas del arranque cuando el destino corre un kernel más reciente que la copia, y la hidratación independiente del kernel de cuatro fases que reaplica la configuración del usuario como IOMMU, IDs VFIO y tokens custom del cmdline sin copiar verbatim los ficheros ligados al kernel.", + "ogTitle": "ProxMenux Backup — restauración cross-kernel e hidratación", + "ogDescription": "Restauración cross-kernel direccional con filtro de subconjunto seguro e hidratación independiente del kernel.", + "twitterTitle": "Restauración cross-kernel | ProxMenux", + "twitterDescription": "Cómo maneja ProxMenux una restauración cuando el kernel del destino difiere del de la copia." + }, + "header": { + "title": "Restauración cross-kernel", + "description": "Cómo maneja ProxMenux una restauración cuando el kernel del host de destino es distinto al kernel que había en el momento de la copia — especialmente cuando el destino ejecuta un kernel más reciente. Documenta cómo se detecta la diferencia, cómo se filtran las rutas críticas del arranque que podrían romper el destino, y cómo se reaplica la configuración propia del usuario sin copiar verbatim los ficheros ligados al kernel.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Cada salto de kernel se maneja de forma distinta", + "body": "Cuando la restauración detecta que la copia y el destino tienen versiones mayores distintas de kernel, el flujo se ramifica en función de la dirección del salto — si la copia es más antigua o más reciente que el destino — porque los dos casos tienen modos de fallo opuestos. Las copias más recientes que el destino se restauran limpiamente tal cual (verificado empíricamente en múltiples pruebas de banco). Las copias más antiguas que el destino necesitan un filtro para evitar romper el arranque del destino con configuración escrita para un kernel que desde entonces ha cambiado." + }, + "directionCheck": { + "heading": "La comprobación de dirección", + "intro": "Durante la comprobación de compatibilidad, hb_compat_check compara el kernel registrado en el manifiesto de la copia contra el kernel actual del destino (uname -r) y clasifica la restauración en uno de tres casos, guardado en la variable interna HB_COMPAT_KERNEL_DIRECTION:", + "rows": [ + { "direction": "same", "condition": "La versión mayor del kernel coincide entre la copia y el destino.", "behavior": "El flujo de restauración completo corre sin cambios. Sin filtro adicional, sin hidratación, sin aviso especial en la interfaz." }, + { "direction": "bk_newer", "condition": "El kernel de la copia es más RECIENTE que el del destino (por ejemplo: la copia se hizo con kernel 7.0 y el destino corre kernel 6.17).", "behavior": "El flujo de restauración completo corre sin cambios, exactamente igual que en same. No se aplica ningún filtro. Verificado empíricamente: los controladores se reinstalan contra el kernel del destino, la configuración IOMMU se aplica limpia, las VMs con passthrough GPU arrancan sin problemas." }, + { "direction": "bk_older", "condition": "El kernel de la copia es más ANTIGUO que el del destino (por ejemplo: la copia se hizo con kernel 6.17 y el destino corre kernel 7.0).", "behavior": "Se activan el filtro de subconjunto seguro y la hidratación de cuatro fases descritos más abajo. La restauración procede — el destino reproduce el origen, pero los ficheros críticos del arranque no se copian verbatim." } + ] + }, + "whyBkNewerIsSafe": { + "heading": "Por qué una copia con kernel más reciente que el destino se restaura sin cambios", + "body": "Cuando la restauración corre contra un destino con un kernel más antiguo que el registrado en la copia, todos los mecanismos de los que depende la restauración son independientes de la versión del kernel. Los instaladores de controladores de GPU y otros dispositivos PCI detectan el kernel en ejecución con uname -r y compilan DKMS contra lo que tenga el destino. La instalación de paquetes por APT trae binarios construidos para la distribución del destino. Los tokens IOMMU del cmdline como intel_iommu=on son estables entre versiones mayores de kernel. La copia lleva rutas escritas bajo un kernel más reciente, pero esas rutas (blacklists de módulos, defaults de GRUB, configuración de initramfs) siguen siendo sintaxis válida en el más antiguo — los kernels ignoran los tokens que no reconocen en lugar de fallar. Este caso es equivalente en la práctica a una restauración con el mismo kernel, y ProxMenux lo trata como tal." + }, + "safeSubsetFilter": { + "heading": "El filtro de subconjunto seguro (sólo cuando el kernel del destino es más reciente)", + "intro": "Cuando el kernel del destino es más reciente que el de la copia, la comprobación de compatibilidad añade 16 rutas críticas del arranque de hb_unsafe_paths_cross_version a RS_SKIP_PATHS. Estas rutas se excluyen de la restauración porque escribir una versión suya de un kernel más antiguo sobre un destino que corre uno más reciente ha causado kernel panics en pruebas de banco. Las rutas cubren cuatro categorías:", + "categoryRows": [ + { "category": "Bootloader", "paths": "/etc/default/grub, /etc/kernel", "reason": "Defaults de GRUB atados al orden previo de kernels, y estado de proxmox-boot-tool (cmdline, UUIDs de ESP, hooks) que referencia rutas e identificadores de la instalación más antigua." }, + { "category": "Módulos del kernel y artefactos de arranque", "paths": "/etc/modules-load.d, /etc/modprobe.d, /etc/initramfs-tools", "reason": "Listas de autocarga que pueden referenciar módulos renombrados entre majors del kernel, opciones de módulo que pueden no aplicar, hooks de initramfs escritos para el kernel más antiguo." }, + { "category": "Stack de almacenamiento e identidad de filesystem", "paths": "/etc/fstab, /etc/multipath, /etc/iscsi, /etc/udev/rules.d, /etc/zfs", "reason": "UUIDs que pueden no existir en esta instalación, drivers de multipath que cambian entre kernels, parámetros iSCSI que evolucionan, reglas udev que pueden atarse a subsistemas inexistentes, estado ZFS (zpool.cache + hostid) que puede bloquear el pool como si no perteneciera al host." }, + { "category": "Fuentes APT", "paths": "/etc/apt", "reason": "Las suites de fuentes APT pueden disparar un downgrade de paquetes críticos en la próxima actualización." }, + { "category": "systemd", "paths": "/etc/systemd/system, /etc/systemd/journald.conf, /etc/systemd/logind.conf, /etc/systemd/system.conf, /etc/systemd/user.conf", "reason": "Overrides de unit y .wants ligados al major de systemd más antiguo; claves de configuración que pueden no parsearse en un systemd más reciente." } + ], + "outroBody": "El filtro corre ANTES del diálogo de confirmación para que el usuario vea la lista exacta de rutas que se saltarán, categorizadas por motivo. La restauración procede igualmente — todo lo demás (VMs, LXCs, red, /etc/pve, usuarios, cron, estado de ProxMenux, paquetes, drivers, /root) se restaura con normalidad." + }, + "hydration": { + "heading": "Hidratación independiente del kernel", + "intro": "El filtro de subconjunto seguro por sí solo dejaría al destino sin la configuración que el usuario había puesto dentro de esos ficheros críticos del arranque: cmdline IOMMU para passthrough GPU, IDs de dispositivo VFIO, GRUB_TIMEOUT custom, blacklists de nvidia. La pasada de hidratación reaplica esas piezas de forma independiente de la versión del kernel. Corren cuatro fases cuando el kernel del destino es más reciente que el de la copia, cada una aditiva (nunca sobrescribe un valor que el destino ya lleva) e idempotente (correr dos veces es un no-op).", + "phaseRows": [ + { "phase": "1a — Ruta GRUB", "detail": "Para hosts que usan GRUB (instalaciones ext4/lvm). _rs_hyd_grub mergea cada token de manifest.kernel_params.cmdline_extra de la copia en el GRUB_CMDLINE_LINUX_DEFAULT vivo del destino, saltando los tokens cuya clave el destino ya lleva. Después mergea claves whitelisted GRUB_* (GRUB_TIMEOUT, GRUB_TIMEOUT_STYLE, GRUB_DEFAULT, GRUB_TERMINAL, GRUB_DISABLE_OS_PROBER, GRUB_SERIAL_COMMAND, GRUB_GFXMODE, GRUB_GFXPAYLOAD_LINUX) del /etc/default/grub de la copia si difieren de las del destino." }, + { "phase": "1b — Ruta systemd-boot / ZFS", "detail": "Para hosts que usan systemd-boot (típicamente ZFS-on-root). _rs_hyd_kernel_cmdline mergea los tokens del usuario de cmdline_extra en el /etc/kernel/cmdline del destino, manteniendo intacto el boilerplate propio del destino: root=, boot= y rootflags=." }, + { "phase": "2 — Merge en /etc/modules", "detail": "_rs_hyd_modules añade los módulos de manifest.kernel_params.modules_loaded_at_boot que estén en la whitelist (vfio, vfio_pci, vfio_iommu_type1, vfio_virqfd, kvm, kvm_intel, kvm_amd, nvidia, nvidia_drm, nvidia_modeset, nvidia_uvm, i915, xe) Y que aún no estén presentes en /etc/modules del destino." }, + { "phase": "3 — Copia de ficheros whitelisted", "detail": "_rs_hyd_files copia ficheros escritos por el usuario del staging rootfs al destino en vivo cuando el contenido difiere. La whitelist cubre ficheros VFIO/nvidia/blacklist bajo /etc/modprobe.d, /etc/modules-load.d, y la regla VFIO bind + reglas udev de nvidia de ProxMenux bajo /etc/udev/rules.d. Los ficheros propiedad de la distro (pve-blacklist.conf, mdadm.conf, nvme.conf) se excluyen intencionadamente — sus contenidos evolucionan entre releases." }, + { "phase": "4 — Forzar reflows post-arranque", "detail": "Las cuatro fases escriben directamente en el destino vivo FUERA del pipeline normal de restauración. Para que los tokens/módulos/ficheros mergeados tengan efecto en el siguiente arranque, HB_HYDRATION_APPLIED=1 se propaga a través de plan.env a apply_pending_restore.sh, que fuerza NEEDS_INITRAMFS=1 y NEEDS_GRUB=1 con independencia de lo que hubiera en la lista de apply. El dispatcher post-arranque después regenera el initramfs y refresca el bootloader." } + ] + }, + "planCommit": { + "heading": "Plan vs commit — el usuario ve un preview antes", + "body": "La hidratación corre en dos modos. Antes del diálogo de confirmación, ProxMenux ejecuta _rs_apply_bk_older_hydration en modo plan: calcula exactamente lo que se mergearía, rellena RS_HYDRATION_SUMMARY con un bloque verde que lista cada acción, y retorna sin escribir nada. El diálogo de confirmación muestra ese bloque verde junto a la lista ámbar de rutas saltadas por el subconjunto seguro, para que el usuario vea POR ADELANTADO qué se reaplicará automáticamente. Tras la confirmación del usuario, ProxMenux vuelve a ejecutar el mismo helper en modo commit — mismas fases, misma lógica, pero esta vez cada fase escribe en el destino vivo. Cancelar el diálogo de confirmación deja el destino intacto." + }, + "flowDiagram": { + "heading": "El flujo cuando el kernel del destino es más reciente que el de la copia", + "intro": "Los pasos específicos que se añaden cuando el kernel del destino es más reciente se insertan dentro del flujo normal de restauración. Todo lo demás — hot apply, prepare pending, instalación de paquetes, dispatcher post-arranque — corre idéntico a una restauración con el mismo kernel.", + "diagram": " ┌────────────────────────────────────────────────────────────┐\n │ hb_compat_check │\n │ HB_COMPAT_KERNEL_DIRECTION = bk_older │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Añade 16 rutas críticas del arranque a RS_SKIP_PATHS │\n │ /etc/default/grub, /etc/kernel, /etc/modules-load.d, ... │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ _rs_apply_bk_older_hydration \"plan\" │\n │ Calcula qué se mergearía │\n │ Rellena RS_HYDRATION_SUMMARY (bloque verde) │\n │ Sin escrituras │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ Diálogo de confirmación │\n │ Ámbar: rutas saltadas por el filtro de subconjunto seguro │\n │ Verde: tokens/ficheros reaplicados por la hidratación │\n │ El usuario acepta o cancela │\n └───────────────────────────┬────────────────────────────────┘\n │ (aceptado)\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ _rs_apply_bk_older_hydration \"commit\" │\n │ Fase 1a/1b: mergea tokens del cmdline + claves GRUB │\n │ Fase 2: añade módulos a /etc/modules │\n │ Fase 3: copia ficheros whitelisted vfio/nvidia │\n │ Fija HB_HYDRATION_APPLIED=1 │\n └───────────────────────────┬────────────────────────────────┘\n │\n ▼\n ┌────────────────────────────────────────────────────────────┐\n │ El resto de la restauración corre normalmente │\n │ _rs_apply hot (salta RS_SKIP_PATHS) │\n │ _rs_prepare_pending_restore (escribe plan.env con │\n │ HB_HYDRATION_APPLIED=1) │\n │ packages.manual.list install │\n │ Reinicio │\n │ apply_pending_restore.sh (fuerza NEEDS_INITRAMFS=1, │\n │ NEEDS_GRUB=1 por el flag de hidratación) │\n │ apply_cluster_postboot.sh │\n │ update-initramfs -u -k all │\n │ update-grub / proxmox-boot-tool refresh │\n │ component --auto-reinstall │\n └────────────────────────────────────────────────────────────┘" + }, + "concreteExamples": { + "heading": "Ejemplos concretos", + "intro": "La pasada de hidratación no es abstracta — produce resultados observables y correctos en escenarios habituales. Dos ejemplos que la hidratación resuelve automáticamente:", + "rows": [ + { "scenario": "GPU passthrough (VFIO)", "detail": "El origen tenía intel_iommu=on iommu=pt en el cmdline, vfio/vfio_pci/vfio_iommu_type1 en /etc/modules, un /etc/modprobe.d/vfio.conf con options vfio-pci ids=10de:2216, y un /etc/modprobe.d/blacklist-nvidia.conf. La hidratación mergea los tokens del cmdline en el GRUB o kernel cmdline del destino, añade los módulos vfio a /etc/modules, y copia los dos ficheros modprobe escritos por el usuario. En el siguiente arranque, IOMMU está activo, los módulos VFIO cargan, la GPU queda ligada a vfio-pci y la VM arranca con el passthrough funcionando." }, + { "scenario": "Defaults custom de GRUB", "detail": "El origen tenía GRUB_TIMEOUT=1 y GRUB_DISABLE_OS_PROBER=true. La hidratación lee ambas claves del /etc/default/grub de la copia, ve que difieren de los defaults de la instalación fresca, y reescribe esas dos líneas en el fichero del destino (dejando todo lo demás, incluido GRUB_DISTRIBUTOR, intacto)." } + ] + }, + "callout": { + "warningTitle": "Qué no reaplica la hidratación cuando el kernel del destino es más reciente", + "warningBody": "La hidratación reaplica sólo la configuración que ProxMenux sabe que es segura entre versiones del kernel: tokens IOMMU, IDs VFIO, claves whitelisted de GRUB y módulos de una lista fija (vfio*, nvidia*, i915, xe, kvm*). Todo lo que quede fuera de ese conjunto — hooks custom de initramfs bajo /etc/initramfs-tools/hooks/, ficheros no whitelisted bajo /etc/modprobe.d/, overrides de unit de systemd escritos por el usuario — permanece excluido. El motivo es concreto: esos ficheros pueden invocar interfaces internas del kernel (APIs de módulos, layout de /sys, hooks de udev) que cambian entre versiones mayores, y aplicarlos verbatim sobre el kernel más reciente puede impedir que el destino arranque. Cuando se necesita reproducción exacta de la cadena de arranque, la restauración debe correr sobre un host con la misma versión mayor de kernel que la copia." + }, + "codeReference": { + "heading": "Dónde viven los mecanismos", + "intro": "Para desarrolladores que quieran trazar o extender el comportamiento cross-kernel:", + "rows": [ + { "component": "Detección de dirección", "location": "hb_compat_check en lib_host_backup_common.sh. Fija HB_COMPAT_KERNEL_DIRECTION." }, + { "component": "Lista de rutas del subconjunto seguro", "location": "hb_unsafe_paths_cross_version en lib_host_backup_common.sh. Emite líneas path\\tmotivo usadas tanto por el filtro CLI como por el endpoint Web /api/host-backups/restore/prepare." }, + { "component": "Fases de hidratación", "location": "_rs_hyd_grub, _rs_hyd_kernel_cmdline, _rs_hyd_modules, _rs_hyd_files en backup_host.sh. Orquestados por _rs_apply_bk_older_hydration." }, + { "component": "Forzado de reflow post-arranque", "location": "apply_pending_restore.sh lee HB_HYDRATION_APPLIED de plan.env y fuerza NEEDS_INITRAMFS=1/NEEDS_GRUB=1." }, + { "component": "Preview Web", "location": "/api/host-backups/restore/prepare en flask_server.py. Fuentea la librería helper, ejecuta _rs_apply_bk_older_hydration en modo plan, devuelve las acciones al modal Web." } + ] + }, + "whereNext": { + "heading": "A dónde seguir", + "items": [ + { "label": "Restaurar", "href": "/docs/backup-restore/restoring", "tail": " — el pipeline completo de restauración en el que se enchufan los mecanismos de esta página." }, + { "label": "Cómo funciona", "href": "/docs/backup-restore/how-it-works", "tail": " — el manifiesto y los colectores que producen el bloque kernel_params del que lee la hidratación." } + ] + } +} diff --git a/web/messages/es/docs/backup-restore/destinations/borg.json b/web/messages/es/docs/backup-restore/destinations/borg.json new file mode 100644 index 00000000..860788e8 --- /dev/null +++ b/web/messages/es/docs/backup-restore/destinations/borg.json @@ -0,0 +1,234 @@ +{ + "meta": { + "title": "Destino Borg — tipos de repositorio, autenticación SSH, cifrado | ProxMenux", + "description": "El destino Borg escribe las copias de host de ProxMenux en un repositorio Borg — local, en un disco externo montado, o en un servidor remoto accedido por SSH. Cubre la cadena de resolución del binario borg, las cuatro estrategias de clave SSH, el cifrado repokey, la configuración de destinos guardados y la retención por trabajo programado.", + "ogTitle": "ProxMenux Backup — destino Borg", + "ogDescription": "Cómo escribe ProxMenux las copias de host en repositorios Borg, con estrategias de autenticación SSH y cifrado repokey.", + "twitterTitle": "Destino Borg de copia | ProxMenux", + "twitterDescription": "Repositorios Borg local, USB y servidos por SSH para copias del host." + }, + "header": { + "title": "Borg", + "description": "El destino Borg escribe las copias del host en un repositorio Borg. Se soportan tres tipos de repositorio: ruta local de sistema de ficheros, disco externo montado o servidor remoto vía SSH. Deduplicación a nivel de chunk entre todos los archivos del repositorio y cifrado opcional repokey.", + "section": "Backup & Restore" + }, + "aboutBorg": { + "heading": "Qué es Borg", + "body": "Borg (también escrito BorgBackup) es una herramienta de copia de seguridad open-source con deduplicación, mantenida por la comunidad Borg Backup. Almacena cada archivo como un conjunto de chunks de longitud variable dentro de un repositorio; los chunks se comparten entre archivos, por lo que volver a copiar datos que no han cambiado tiene un coste de almacenamiento prácticamente nulo. Borg no está atado a Proxmox — es una herramienta de copia de uso general utilizada en muchos entornos. ProxMenux la usa como uno de los tres destinos para copias del host; cada mecanismo específico de Borg descrito en esta página (tipos de repositorio, borg-serve por SSH, cifrado repokey, prune) es comportamiento estándar de Borg." + }, + "intro": { + "title": "Deduplicación por chunks con repositorios locales o servidos por SSH", + "body": "Un repositorio Borg almacena chunks que se comparten entre cada archivo que contiene, por lo que volver a copiar un fichero que no ha cambiado no transfiere ni almacena nada nuevo. ProxMenux invoca borg create contra el repositorio en el momento de la copia y borg extract en la restauración. El repositorio puede vivir en el mismo sistema de ficheros que el origen, en un disco externo montado, o en un host remoto accedido por SSH." + }, + "binarySourcing": { + "heading": "El binario borg", + "intro": "Borg no es una dependencia base del instalador de ProxMenux. Cuando se ejecuta una copia, hb_ensure_borg resuelve el binario desde cuatro fuentes por orden y devuelve la primera que funciona:", + "rows": [ + { + "priority": "1", + "source": "borg del sistema", + "detail": "Si el host ya tiene borg instalado vía APT (which borg resuelve), se usa ese binario." + }, + { + "priority": "2", + "source": "Caché del state-dir", + "detail": "/usr/local/share/proxmenux/borg — conservado de una descarga previa desde GitHub en este host." + }, + { + "priority": "3", + "source": "Bundle del Monitor AppImage", + "detail": "/usr/local/share/proxmenux/monitor-app/usr/bin/borg — el AppImage incluye un binario borg-linux64 firmado. Este es el camino offline-safe: un host sin internet sigue teniendo un borg operativo." + }, + { + "priority": "4", + "source": "Descarga desde GitHub", + "detail": "wget contra la URL fijada de borg-linux64 bajo github.com/borgbackup/borg/releases/, verificado contra un SHA-256 constante en lib_host_backup_common.sh (HB_BORG_LINUX64_SHA256). Si el checksum no coincide, el binario se descarta y la copia se aborta. El fichero descargado se cachea en el state-dir para que las copias posteriores usen el camino 2." + } + ] + }, + "repoTypes": { + "heading": "Tipos de repositorio", + "intro": "El tipo de repositorio se elige al añadir un destino. Cada tipo se resuelve a una URL de repositorio que Borg entiende.", + "rows": [ + { + "type": "remote", + "url": "ssh://USER@HOST/RPATH", + "detail": "El repositorio vive en un servidor remoto que ejecuta borg serve. Requiere una conexión SSH a ese servidor." + }, + { + "type": "usb", + "url": "/mnt/MOUNTPOINT/borgbackup", + "detail": "El repositorio vive en un disco externo montado (típicamente USB). El punto de montaje se resuelve vía hb_prompt_mounted_path, que detecta, monta o formatea particiones USB según sea necesario." + }, + { + "type": "local", + "url": "/backup/borgbackup (cualquier ruta absoluta)", + "detail": "El repositorio vive en un directorio local. Sólo tiene sentido cuando el directorio está en un disco físico distinto — un repositorio en el mismo disco que el origen protege contra error humano pero no contra fallo de disco." + } + ] + }, + "serverSetup": { + "heading": "Preparar el servidor Borg (lado servidor)", + "intro": "El tipo de repositorio remote espera un servidor Borg operativo accesible por SSH. ProxMenux no bootstrapea el servidor por sí mismo — sólo autoriza una clave contra una cuenta existente en él. Esta sección documenta lo que necesita el servidor antes de que ProxMenux pueda conectarse.", + "hostChoicesTitle": "Dónde puede vivir el servidor", + "hostChoicesBody": "Cualquier host Linux con acceso SSH cumple. Setups habituales:", + "hostChoicesItems": [ + "Un NAS dedicado o caja de backup (Debian, Ubuntu, TrueNAS SCALE con shell).", + "Un contenedor LXC dentro de un nodo Proxmox. Huella pequeña, aislado del host que ejecuta las copias.", + "Otro host Proxmox en la misma LAN, o una VM en cualquier lugar alcanzable por SSH." + ], + "lxcWarningTitle": "LXC como servidor Borg", + "lxcWarningBody": "Si el servidor Borg se ejecuta dentro de un LXC, el contenedor debe tener una cuenta de usuario con contraseña.", + "requirementsTitle": "Requisitos en el servidor", + "requirementsRows": [ + { + "requirement": "binario borg en /usr/bin/borg", + "detail": "La línea command=\"/usr/bin/borg serve ...\" que ProxMenux escribe en authorized_keys hardcodea esa ruta. Instalar vía APT (apt install borgbackup) lo deja ahí. Un binario independiente debe symlinkarse a /usr/bin/borg." + }, + { + "requirement": "Una cuenta de usuario dedicada (típicamente borg)", + "detail": "Es dueña del directorio del repositorio y recibe las conexiones SSH entrantes. No necesita sudo ni acceso a shell — la línea de authorized_keys desactiva las shells interactivas de todos modos." + }, + { + "requirement": "Un directorio de repositorio escribible", + "detail": "La ruta que el usuario introduce en ProxMenux (por ejemplo /backup/borgbackup) debe existir en el servidor y ser propiedad del usuario borg." + }, + { + "requirement": "Demonio SSH aceptando al usuario borg", + "detail": "PubkeyAuthentication yes (por defecto). La autenticación por contraseña sólo hace falta para el flujo puntual generate-auto — una vez instalada la clave, el servidor puede desactivar la autenticación por contraseña por completo." + } + ], + "minimalSetupTitle": "Setup mínimo del servidor", + "minimalSetupBody": "En un servidor Borg Debian o Ubuntu, un baseline funcional son cuatro comandos como root:", + "minimalSetupCmd": "apt install borgbackup\nuseradd -m -d /home/borg -s /bin/bash borg\nmkdir -p /backup/borgbackup\nchown borg:borg /backup/borgbackup", + "minimalSetupNote": "Tras esto, el modo generate-auto de ProxMenux puede conectarse usando la contraseña del usuario borg una vez, instalar su propia clave SSH, y cada copia posterior usa esa clave. No hace falta más configuración en el servidor — la clave se restringe a sí misma a borg serve sobre esa ruta." + }, + "sshAuth": { + "heading": "Autenticación SSH (lado cliente)", + "intro": "Los repositorios Borg remotos se acceden vía SSH. La conexión va desde el host ProxMenux a una cuenta de usuario en el servidor Borg que ejecuta borg serve. Ese usuario se llama típicamente borg, NO el usuario admin/root del servidor — la línea de comando de borg-serve bloquea la conexión a esa ruta concreta del repositorio (ver las estrategias de clave más abajo).", + "strategiesTitle": "Las cuatro estrategias de clave", + "strategiesIntro": "Al añadir un destino remoto, ProxMenux pregunta por el usuario SSH, host y ruta remota, y a continuación pregunta cómo autenticar. Cuatro modos:", + "strategyRows": [ + { + "mode": "generate-auto", + "label": "Recomendado", + "detail": "ProxMenux genera una nueva keypair ed25519 en ~/.ssh/borg_proxmenux_HOST_ed25519, y a continuación usa sshpass para hacer login UNA VEZ en el servidor con la contraseña de admin y añadir la clave pública a ~borg/.ssh/authorized_keys. La contraseña de admin se usa sólo para esa única llamada — nunca se almacena." + }, + { + "mode": "generate-manual", + "label": "Sin contraseña admin en este host", + "detail": "ProxMenux genera la keypair como arriba pero muestra la línea completa de authorized_keys para que el usuario la pegue manualmente en el servidor. La contraseña de admin nunca sale del host ProxMenux porque nunca se pregunta." + }, + { + "mode": "generate-pct", + "label": "El servidor Borg es un LXC de PVE", + "detail": "El servidor Borg se ejecuta dentro de un LXC en un nodo PVE. ProxMenux autoriza la clave vía pct exec desde el host PVE — root en el host PVE escribe en el ~borg/.ssh/authorized_keys del LXC sin necesidad de hacer SSH al LXC." + }, + { + "mode": "existing", + "label": "Usar una clave existente", + "detail": "ProxMenux escanea /root/.ssh/ y $HOME/.ssh/ buscando claves privadas ed25519/RSA parseables y las lista. El usuario elige una o navega manualmente a una ruta no estándar." + }, + { + "mode": "none", + "label": "Configuración SSH por defecto", + "detail": "Sin clave personalizada — Borg usa la configuración SSH por defecto del host (típicamente ~/.ssh/id_rsa o un agente SSH)." + } + ], + "restrictTitle": "La línea authorized_keys", + "restrictBody": "Para cada clave generada, la línea de authorized_keys que ProxMenux escribe en el servidor bloquea la clave a una única invocación de borg-serve contra la ruta configurada del repositorio:", + "restrictLine": "command=\"/usr/bin/borg serve --restrict-to-path RPATH\",restrict PUBKEY", + "restrictNote": "El command= fuerza que cada sesión SSH que use esta clave ejecute sólo ese comando borg-serve; restrict desactiva port forwarding, agent forwarding, X11 forwarding y asignación de PTY. La clave no se puede usar para abrir una shell interactiva en el servidor ni para acceder a ninguna otra ruta de repositorio, aunque la cuenta tenga privilegios más amplios." + }, + "savedTargets": { + "heading": "Destinos guardados", + "intro": "Un destino guardado persiste la configuración del repositorio bajo un nombre amigable para no tener que reintroducir los detalles. Layout de almacenamiento en el directorio de estado de ProxMenux:", + "rows": [ + { + "file": "borg-targets.txt", + "content": "Una línea por destino: NAME|REPO|SSH_KEY_PATH|ENCRYPT_MODE. La lee hb_collect_borg_configs para poblar el menú de selección de destino." + }, + { + "file": "borg-pass-NAME.txt", + "content": "Passphrase del destino con el NAME dado arriba (chmod 600). Sólo está presente si el usuario eligió cifrado repokey." + } + ], + "outro": "Guardar es opcional — el usuario puede rechazar el prompt de guardado para una copia puntual que no deje credenciales en el host." + }, + "encryption": { + "heading": "Cifrado", + "body": "Los repositorios se inicializan con un modo de cifrado vía hb_borg_init_if_needed: borg repo-create -e MODE en versiones modernas, o el legacy borg init --encryption=MODE. ProxMenux expone dos modos: repokey (por defecto) y none. En modo repokey, Borg almacena la clave de cifrado dentro del propio repositorio; el acceso requiere una passphrase, que ProxMenux pregunta dos veces con validación de coincidencia y guarda en borg-pass-NAME.txt. Un diálogo de reconocimiento obligatorio se muestra tras guardar la passphrase: la passphrase es la única manera de acceder a los archivos cifrados, y perderla hace irrecuperable todo archivo del repositorio." + }, + "runtimeEnv": { + "heading": "Entorno de ejecución", + "intro": "ProxMenux exporta las siguientes variables de entorno antes de invocar borg. Configuran la conexión y desbloquean el repositorio sin embeber secretos en la línea de comandos.", + "rows": [ + { + "var": "BORG_RSH", + "value": "ssh -i SSH_KEY_PATH -o StrictHostKeyChecking=accept-new", + "purpose": "El comando de shell remoto que Borg usa para repositorios servidos por SSH. Se fija sólo cuando se seleccionó una clave personalizada; en caso contrario queda sin definir para que Borg use la configuración SSH por defecto." + }, + { + "var": "BORG_PASSPHRASE", + "value": "(passphrase de borg-pass-NAME.txt)", + "purpose": "Desbloquea la repokey. Se fija sólo cuando el destino usa cifrado repokey. Nunca aparece en la lista de argumentos del proceso." + }, + { + "var": "BORG_ENCRYPT_MODE", + "value": "repokey | none", + "purpose": "Lo usa hb_borg_init_if_needed al inicializar un repositorio que aún no existe. borg create lo ignora en repositorios existentes." + }, + { + "var": "BORG_RELOCATED_REPO_ACCESS_IS_OK", + "value": "yes", + "purpose": "Suprime el prompt interactivo que Borg lanza cuando la URL del repositorio difiere de la URL desde la que se alcanzó originalmente (habitual tras un renombrado del punto de montaje o un cambio de dirección del host SSH)." + }, + { + "var": "BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK", + "value": "yes", + "purpose": "Suprime el prompt interactivo que Borg lanza la primera vez que se accede a un repositorio sin cifrar desde un cliente nuevo." + } + ] + }, + "archiveFormat": { + "heading": "Nombrado de archivos y retención", + "intro": "Cada copia crea un archivo nuevo dentro del repositorio.", + "namePattern": "hostcfg-HOSTNAME-YYYYMMDD_HHMMSS", + "retentionBody": "Para trabajos programados, run_scheduled_backup.sh ejecuta borg prune contra el repositorio tras cada copia exitosa con los valores de retención configurados en el trabajo (--keep-last, --keep-daily, --keep-weekly). Las copias interactivas no aplican poda." + }, + "restoreAccess": { + "heading": "Recuperación del lado de la restauración", + "body": "El flujo de restauración lista los archivos con borg list REPO y extrae el seleccionado con borg extract REPO::ARCHIVE-NAME a un directorio de staging que _rs_check_layout alimenta al pipeline estándar de restauración. Para recuperación manual fuera de ProxMenux, los mismos comandos funcionan; el árbol extraído es el layout estándar de tres bloques descrito en Cómo funciona." + }, + "references": { + "heading": "Referencias", + "intro": "Documentación oficial de Borg para los componentes en los que se apoya ProxMenux.", + "items": [ + { + "label": "Documentación de Borg", + "href": "https://borgbackup.readthedocs.io/", + "tail": " — punto de entrada principal, cubre conceptos, despliegue, quickstart y todos los comandos." + }, + { + "label": "borg create", + "href": "https://borgbackup.readthedocs.io/en/stable/usage/create.html", + "tail": " — el comando que ProxMenux invoca en cada copia, con todas las flags soportadas." + }, + { + "label": "Cifrado del repositorio", + "href": "https://borgbackup.readthedocs.io/en/stable/usage/init.html#encryption-mode", + "tail": " — los modos de cifrado que Borg soporta, incluido repokey que ProxMenux usa por defecto." + }, + { + "label": "borg serve y despliegue SSH", + "href": "https://borgbackup.readthedocs.io/en/stable/deployment/central-backup-server.html", + "tail": " — cómo montar un servidor Borg central accedido por SSH, incluyendo el comando borg-serve y la flag restrict-to-path que ProxMenux escribe en authorized_keys." + }, + { + "label": "borg prune", + "href": "https://borgbackup.readthedocs.io/en/stable/usage/prune.html", + "tail": " — el modelo de retención detrás de --keep-last / --keep-daily / --keep-weekly que ProxMenux aplica por trabajo programado." + } + ] + } +} diff --git a/web/messages/es/docs/backup-restore/destinations/index.json b/web/messages/es/docs/backup-restore/destinations/index.json new file mode 100644 index 00000000..9348b86d --- /dev/null +++ b/web/messages/es/docs/backup-restore/destinations/index.json @@ -0,0 +1,110 @@ +{ + "meta": { + "title": "Destinos de copia — Local, Proxmox Backup Server, Borg | ProxMenux", + "description": "Tres backends de almacenamiento reciben el mismo contenido de una copia de ProxMenux: un archivo local .tar.zst en cualquier sistema de ficheros escribible, un backup en un datastore de Proxmox Backup Server o un archivo en un repositorio local o remoto de Borg. Cada backend tiene sus propias características de compresión, deduplicación, cifrado y red.", + "ogTitle": "Destinos de ProxMenux Backup", + "ogDescription": "Local, Proxmox Backup Server y Borg — tres backends de almacenamiento para el mismo contenido de copia del host.", + "twitterTitle": "Destinos de ProxMenux Backup | ProxMenux", + "twitterDescription": "Archivo local, Proxmox Backup Server y Borg como backends para copias del host." + }, + "header": { + "title": "Destinos", + "description": "Los tres backends de almacenamiento soportados por ProxMenux Backup: archivo local, Proxmox Backup Server (recomendado) y Borg. Cada uno recibe el mismo archivo de tres bloques; se diferencian en formato de almacenamiento, deduplicación, modelo de cifrado y requisitos de red.", + "section": "Backup & Restore" + }, + "intro": { + "title": "El mismo contenido, tres backends", + "body": "Cada copia produce los mismos tres bloques (rootfs, manifiesto, aplicaciones). Lo que cambia entre destinos es cómo se almacenan esos bloques y cómo el usuario los recupera para una restauración. Cada backend está implementado como una función independiente en backup_host.sh_bk_local, _bk_pbs, _bk_borg — pero los tres comparten el mismo paso de staging (hb_prepare_staging) y el mismo camino de restauración. La elección del destino sólo afecta a la escritura y la recuperación; no cambia lo que hace una restauración ni cómo consume el archivo." + }, + "comparison": { + "heading": "Comparativa de características", + "intro": "La siguiente tabla lista las diferencias concretas entre los tres backends tal como se comportan hoy en ProxMenux. Los valores describen el comportamiento observado del backend en sí, no recomendaciones editoriales.", + "rows": [ + { + "feature": "Formato de almacenamiento", + "local": "Un único fichero .tar.zst (o .tar.gz si zstd no está disponible).", + "pbs": "Backup PBS (chunks .pxar en el datastore).", + "borg": "Archivo Borg dentro de un repositorio Borg (ficheros de segmento)." + }, + { + "feature": "Compresión", + "local": "zstd (nivel por defecto) mediante tar --zstd. Fallback a gzip.", + "pbs": "Gestionada por PBS. El cliente envía sin comprimir; el servidor chunkiza + comprime.", + "borg": "La integrada de Borg (lz4 por defecto). Configurable al inicializar el repo." + }, + { + "feature": "Deduplicación", + "local": "Ninguna. Cada copia es un archivo completo independiente.", + "pbs": "Deduplicación completa a nivel de chunk entre todas las backups del datastore.", + "borg": "Deduplicación completa a nivel de chunk entre todos los archivos del repositorio." + }, + { + "feature": "Cifrado en reposo", + "local": "Ninguno (depende de la protección a nivel de filesystem).", + "pbs": "Cifrado opcional del lado del cliente mediante keyfile. Si se activa, el blob de recuperación se sube como grupo de backups aparte.", + "borg": "Cifrado opcional repokey (la clave se guarda en el repo, se desbloquea con una passphrase)." + }, + { + "feature": "Retención / poda", + "local": "Aplicada por trabajo programado vía KEEP_LAST. Los archivos antiguos (y sus sidecars + logs del runner) se eliminan de forma simétrica. Las copias interactivas no aplican poda.", + "pbs": "Aplicada por trabajo programado vía proxmox-backup-client prune --keep-last / --keep-daily / --keep-weekly. Las copias interactivas no aplican poda.", + "borg": "Aplicada por trabajo programado vía borg prune --keep-last / --keep-daily / --keep-weekly. Las copias interactivas no aplican poda." + }, + { + "feature": "Red", + "local": "Ninguna. Escribe en un punto de montaje local (típicamente un disco interno o una unidad USB).", + "pbs": "TCP contra el servidor PBS (puerto por defecto 8007). Requiere credenciales PBS + fingerprint.", + "borg": "Ruta local de sistema de ficheros o túnel SSH a un host Borg remoto." + }, + { + "feature": "Dependencias", + "local": "tar, zstd (presentes en Proxmox por defecto).", + "pbs": "Paquete proxmox-backup-client (incluido en Proxmox VE 8+).", + "borg": "Binario borg. ProxMenux lo provisiona automáticamente desde el bundle del Monitor AppImage cuando no está presente." + }, + { + "feature": "Acceso a la restauración", + "local": "Cualquier host con tar+zstd puede extraer el archivo. No se necesita PBS ni Borg.", + "pbs": "Requiere proxmox-backup-client + credenciales PBS + el keyfile (si está cifrado).", + "borg": "Requiere borg + la ruta del repositorio + la passphrase (si está cifrado)." + } + ], + "captionCode": "Característica", + "captionLocal": "Local", + "captionPbs": "Proxmox Backup Server (recomendado)", + "captionBorg": "Borg" + }, + "sameArchive": { + "heading": "El layout del archivo no cambia con el destino", + "body": "Dentro de cualquiera de los tres destinos está presente el mismo layout rootfs/ + metadata/ + manifest.json descrito en Cómo funciona. Un .tar.zst extraído de un archivo local, un .pxar restaurado desde PBS y un archivo Borg extraído con borg extract producen todos un árbol de directorios idéntico. El camino de código de la restauración (_rs_check_layout, _rs_apply, _rs_prepare_pending_restore) lee los mismos tres bloques sin saber de qué destino vienen." + }, + "extractStandalone": { + "heading": "Extraer una copia fuera de ProxMenux", + "intro": "Cualquiera de los tres formatos de archivo se puede leer con herramientas estándar sin tener ProxMenux instalado en el host de lectura. Los comandos siguientes producen el mismo árbol de directorios que consume internamente una restauración.", + "localCmd": "# Desde un archivo local .tar.zst:\ntar --zstd -xf hostcfg-HOST-TIMESTAMP.tar.zst\n\n# Desde el fallback .tar.gz:\ntar -xzf hostcfg-HOST-TIMESTAMP.tar.gz", + "pbsCmd": "# Desde un backup PBS (requiere proxmox-backup-client):\nproxmox-backup-client restore \\\n --repository USER@REALM@HOST:DATASTORE \\\n host/hostcfg-HOST/BACKUP-TIME \\\n hostcfg.pxar /tmp/hostcfg\n\n# Añadir --keyfile KEY-PATH cuando el backup está cifrado.", + "borgCmd": "# Desde un archivo Borg (requiere el binario borg + passphrase si hay cifrado):\nborg extract REPO-PATH::ARCHIVE-NAME\n\n# Sobre un repo servido por SSH:\nborg extract ssh://USER@HOST:PORT/REPO-PATH::ARCHIVE-NAME", + "note": "El árbol extraído puede inspeccionarse manualmente o alimentar una restauración manual. Consulta las páginas de cada destino para conocer el flujo exacto de recuperación que ProxMenux utiliza por dentro." + }, + "whereNext": { + "heading": "Detalle por destino", + "intro": "Cada destino tiene su propia página con el flujo de configuración, el formato en disco y el comando de recuperación que utiliza la restauración.", + "items": [ + { + "label": "Archivo local", + "href": "/docs/backup-restore/destinations/local", + "tail": " — preconfiguración del destino local, montaje de unidades USB, el chequeo de seguridad contra escribir el archivo dentro de sí mismo, el sidecar JSON que permite al Monitor identificar el fichero." + }, + { + "label": "Proxmox Backup Server (recomendado)", + "href": "/docs/backup-restore/destinations/pbs", + "tail": " — selección de datastore, credenciales y fingerprint, ciclo de vida del keyfile de cifrado, passphrase de recuperación, subida del blob de recuperación a PBS para recuperación tras reinstalación." + }, + { + "label": "Borg", + "href": "/docs/backup-restore/destinations/borg", + "tail": " — repositorios locales vs. servidos por SSH, cómo se obtiene el binario Borg (sistema / caché / bundle del Monitor AppImage / descarga de GitHub), inicialización del repositorio, gestión de la passphrase, setup de claves SSH para repos remotos." + } + ] + } +} diff --git a/web/messages/es/docs/backup-restore/destinations/local.json b/web/messages/es/docs/backup-restore/destinations/local.json new file mode 100644 index 00000000..e99f5190 --- /dev/null +++ b/web/messages/es/docs/backup-restore/destinations/local.json @@ -0,0 +1,75 @@ +{ + "meta": { + "title": "Destino de archivo local — tar.zst en filesystem o unidad USB | ProxMenux", + "description": "El destino local de la copia escribe un único archivo .tar.zst en cualquier directorio escribible: un disco interno, un punto de montaje de Proxmox, un share NFS o una unidad USB. Documenta el flujo de configuración, la lógica de detección y montaje USB, el chequeo de seguridad contra escribir el archivo dentro de una ruta que se está copiando, y el sidecar JSON que identifica el fichero.", + "ogTitle": "ProxMenux Backup — Destino local", + "ogDescription": "Cómo escribe ProxMenux las copias locales como archivos .tar.zst y cómo configurar el directorio de destino o la unidad USB.", + "twitterTitle": "Destino local de copia | ProxMenux", + "twitterDescription": "Cómo escribe ProxMenux las copias locales como archivos .tar.zst en filesystem o USB." + }, + "header": { + "title": "Archivo local", + "description": "El destino local escribe un único archivo tar comprimido en cualquier directorio escribible del host: un disco interno, un montaje NFS o SMB, o una unidad USB.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Un solo fichero, autocontenido", + "body": "Una copia local produce un único fichero hostcfg-HOST-TIMESTAMP.tar.zst (o .tar.gz cuando zstd no está presente). El fichero contiene el árbol completo del archivo — manifest.json, metadata/ y rootfs/ — y puede restaurarse en cualquier host Proxmox con el flujo de restauración de ProxMenux, o extraerse a mano con tar --zstd -xf en cualquier sistema Linux. Sin servidor, sin inicialización de repositorio, sin dependencia externa. Es el destino con el camino de recuperación más corto cuando ni PBS ni Borg están disponibles." + }, + "targetConfig": { + "heading": "Configurar el directorio de destino", + "intro": "El destino local es un único directorio de destino persistido — no una lista. ProxMenux guarda la elección del usuario en /usr/local/share/proxmenux/local-target.conf y la lee en cada copia. Cuando no hay ningún destino configurado, se usa el valor por defecto HB_LOCAL_TARGET_DEFAULT = /var/lib/vz/dump (el mismo directorio que Proxmox utiliza para las salidas de vzdump). El destino se configura desde Configure backup destinations → Local destinations:", + "options": [ + "Usar el valor por defecto (/var/lib/vz/dump). El almacenamiento local de Proxmox. Está presente en cualquier instalación de Proxmox; el archivo queda junto a las salidas de vzdump y lo detecta automáticamente la pestaña Backups del Monitor.", + "Introducir una ruta personalizada. Cualquier ruta absoluta del sistema de ficheros vale: un montaje NFS, un share SMB montado por fstab, un dataset ZFS dedicado, un segundo disco interno. ProxMenux valida que la ruta existe y es un directorio antes de persistirla.", + "Elegir una unidad USB. Abre el submenú USB (más abajo), que detecta los dispositivos extraíbles y ofrece montar o formatear uno." + ] + }, + "usbFlow": { + "heading": "Detección y montaje de la unidad USB", + "intro": "El submenú USB lista las particiones de los dispositivos extraíbles reportados por lsblk. Cada partición se muestra con su tamaño, etiqueta del filesystem y estado actual. El estado determina la acción que ProxMenux ofrece.", + "statesTitle": "Los tres estados del dispositivo", + "stateRows": [ + { + "state": "mounted", + "shown": "Tamaño · label · [fstype] · → /punto/de/montaje", + "action": "La partición ya está montada en algún sitio. Seleccionarla persiste ese punto de montaje como destino local. No se realiza ningún montaje." + }, + { + "state": "unmounted", + "shown": "Tamaño · label · [fstype] · (no montada — se montará)", + "action": "Hay un filesystem pero no está montado. Al confirmar, ProxMenux ejecuta hb_mount_usb_partition: crea /mnt/backup-LABEL (o una ruta basada en UUID si no hay label), monta la partición y persiste el punto de montaje como destino local." + }, + { + "state": "empty", + "shown": "Tamaño · disco USB en crudo — sin filesystem (se FORMATEARÁ)", + "action": "El dispositivo no tiene filesystem. Camino destructivo — protegido por dos confirmaciones. Primero un diálogo Yes/No explica que la operación borrará el disco. Después una caja de entrada obliga al usuario a escribir la ruta exacta del dispositivo (por ejemplo /dev/sdb) antes de que ProxMenux cree una partición GPT + ext4 fresca y la monte." + } + ], + "notMountedFallback": "Cuando no se detecta ningún dispositivo USB, el submenú cae en un inputbox simple. El usuario puede introducir una ruta de punto de montaje arbitraria; si la ruta no es un punto de montaje registrado, un diálogo de confirmación advierte antes de continuar." + }, + "safetyCheck": { + "heading": "Chequeo de seguridad — destino dentro de una ruta copiada", + "body": "Antes de escribir el archivo, _bk_local verifica que el directorio de destino no sea un subcamino de ninguno de los directorios que se están copiando. Un footgun habitual sería añadir /root al perfil y elegir /root/backups como destino — el archivo se incluiría a sí mismo, produciendo o bien un archivo corrupto o bien crecimiento sin límite hasta llenar el disco. El chequeo resuelve ambos caminos con readlink -m, los compara y, si hay conflicto, aborta la copia con un diálogo que nombra la ruta en conflicto y lista tres formas de resolverlo: elegir un destino fuera de la ruta en conflicto, eliminar la entrada personalizada que contiene el destino, o usar modo Custom para desmarcar la ruta en conflicto en esa ejecución." + }, + "archiveFormat": { + "heading": "Formato de archivo y compresión", + "intro": "El nombre del fichero de salida incorpora el hostname del origen y el timestamp de la copia para que un directorio con varios archivos se ordene cronológicamente y cada fichero se identifique por sí mismo.", + "namePattern": "hostcfg-HOSTNAME-YYYYMMDD_HHMMSS.tar.zst", + "compressionTitle": "Compresión", + "compressionBody": "El camino principal usa tar --zstd -cf — un pipeline de un solo comando que comprime al nivel por defecto de zstd. Cuando zstd no está presente en el origen (raro en Proxmox pero posible en instalaciones minimalistas), ProxMenux cae en gzip. En el camino de fallback, si pv está disponible, se añade una barra de progreso al pipeline para que el usuario vea crecer el tamaño del archivo en tiempo real; sin pv, se usa tar -czf plano en silencio.", + "sourceTitle": "Qué entra en el archivo", + "sourceBody": "El comando tar se invoca con -C \"$staging_root\" ., lo que archiva la raíz de staging completa: rootfs/, metadata/ y manifest.json. Los tres bloques quedan uno al lado del otro en el nivel superior del tarball. Extraer el archivo produce exactamente el mismo árbol que consume el código de restauración." + }, + "sidecar": { + "heading": "El sidecar JSON", + "intro": "Cada copia local con éxito produce un fichero compañero: HOSTNAME-TIMESTAMP.tar.zst.proxmenux.json, escrito junto al archivo por hb_write_archive_sidecar. Este pequeño fichero JSON permite al Monitor de ProxMenux identificar el archivo como una copia de host de ProxMenux incluso si posteriormente se mueve, renombra o archiva en otro lugar.", + "contentTitle": "Contenido del sidecar", + "contentBody": "El sidecar almacena la versión de schema, si la copia viene de una ejecución interactiva o de un trabajo programado (kind), el ID de trabajo para las ejecuciones programadas, el modo de perfil usado (default o custom), el hostname del origen, el basename original del archivo, un timestamp de creación en ISO-8601 y el tamaño del archivo en bytes.", + "whyBody": "La pestaña Backups del Monitor escanea los directorios locales configurados buscando sidecars *.proxmenux.json — no ficheros *.tar.zst — porque un .tar.zst sin sidecar podría no ser una copia de ProxMenux en absoluto. El escaneo es rápido (los JSON son diminutos) y el emparejamiento es estable frente a renombrados del archivo mientras el sidecar se renombre en paralelo." + }, + "restoreAccess": { + "heading": "Restaurar desde un archivo local", + "body": "El flujo de restauración de ProxMenux descubre los archivos locales escaneando el destino local configurado buscando sidecars y mostrándolos en la lista de copias disponibles. Seleccionar uno dispara _rs_check_layout, que extrae el tarball en un directorio de staging y confirma el layout de tres bloques antes de continuar. Para una extracción manual fuera de ProxMenux, tar --zstd -xf hostcfg-HOSTNAME-TIMESTAMP.tar.zst -C /tmp/hostcfg produce el mismo árbol que consume el código de restauración." + } +} diff --git a/web/messages/es/docs/backup-restore/destinations/pbs.json b/web/messages/es/docs/backup-restore/destinations/pbs.json new file mode 100644 index 00000000..8f95c2f6 --- /dev/null +++ b/web/messages/es/docs/backup-restore/destinations/pbs.json @@ -0,0 +1,102 @@ +{ + "meta": { + "title": "Destino Proxmox Backup Server — repositorio, cifrado, recuperación | ProxMenux", + "description": "El destino PBS escribe las copias de host de ProxMenux como backups PBS. Documenta la auto-detección del repositorio desde /etc/pve/storage.cfg, la configuración manual de PBS, el comando de subida .pxar, el modelo de cifrado por keyfile del lado del cliente, el blob de escrow con passphrase de recuperación, y la recuperación del keyfile desde PBS en instalaciones frescas.", + "ogTitle": "ProxMenux Backup — destino Proxmox Backup Server", + "ogDescription": "Cómo escribe ProxMenux las copias de host en Proxmox Backup Server con cifrado por keyfile del lado del cliente y escrow de recuperación.", + "twitterTitle": "Destino PBS de copia | ProxMenux", + "twitterDescription": "Destino Proxmox Backup Server con cifrado por keyfile y escrow de passphrase de recuperación." + }, + "header": { + "title": "Proxmox Backup Server", + "description": "El destino PBS sube la raíz de staging como un único backup .pxar a un datastore de Proxmox Backup Server, con cifrado opcional por keyfile del lado del cliente y escrow automático de una passphrase de recuperación para recuperación tras reinstalación.", + "section": "Backup & Restore" + }, + "recommendedBadge": "Destino recomendado", + "aboutPbs": { + "heading": "Qué es Proxmox Backup Server", + "body": "Proxmox Backup Server (PBS) es el producto de servidor de copias propio de Proxmox, desarrollado y mantenido por el mismo equipo que autora Proxmox VE. Es un servidor dedicado diseñado para recibir copias desde hosts Proxmox VE (VMs, LXCs y — vía proxmox-backup-client — directorios arbitrarios del host) con deduplicación a nivel de chunk, cifrado del lado del cliente y políticas de retención aplicadas del lado del servidor. ProxMenux usa PBS como uno de los tres destinos para copias del host; cada mecanismo específico de PBS descrito en esta página (grupos de copia, archivos .pxar, --backup-id, cifrado por keyfile) es comportamiento estándar de PBS." + }, + "intro": { + "title": "Un backup por ejecución, deduplicación a nivel de chunk", + "body": "Una copia PBS produce una única entrada en el datastore, agrupada bajo el backup ID host/hostcfg-HOSTNAME/BACKUP-TIME. El contenido es un archivo .pxar con el mismo layout de tres bloques descrito en Cómo funciona. PBS deduplica a nivel de chunk en todas las copias del datastore, por lo que las copias siguientes del mismo host transfieren y almacenan sólo los chunks que hayan cambiado. La retención la aplica el propio ProxMenux para los trabajos programados — run_scheduled_backup.sh ejecuta proxmox-backup-client prune con --keep-last / --keep-daily / --keep-weekly tras cada ejecución exitosa usando los valores configurados en el trabajo." + }, + "repoSelection": { + "heading": "Selección del repositorio", + "intro": "ProxMenux descubre repositorios PBS desde dos fuentes en cada copia. El usuario elige uno desde un menú unificado; la elección determina HB_PBS_REPOSITORY, HB_PBS_SECRET y HB_PBS_FINGERPRINT para esa ejecución.", + "sourceRows": [ + { + "source": "storage.cfg de Proxmox (auto-descubierto)", + "path": "/etc/pve/storage.cfg + /etc/pve/priv/storage/NAME.pw", + "content": "Cualquier entrada pbs: del propio storage de Proxmox se recoge automáticamente. Servidor, datastore, usuario y fingerprint vienen de la entrada. La contraseña se lee del directorio de credenciales propio de Proxmox. Sin re-introducción por el lado de ProxMenux — el repositorio queda disponible tan pronto como se configura en Proxmox." + }, + { + "source": "Configuración manual de ProxMenux", + "path": "/usr/local/share/proxmenux/pbs-manual-configs.txt + pbs-pass-NAME.txt + pbs-fingerprint-NAME.txt", + "content": "Se añade desde Configure backup destinations → PBS destinations → Add PBS. Pregunta por un nombre, usuario (root@pam o user@pbs!token), host o IP, datastore y contraseña. La contraseña se re-pregunta ante entrada vacía — un guardado vacío persistiría silenciosamente y cada copia posterior fallaría con un error de autenticación opaco. Este camino se usa cuando el PBS objetivo no está registrado como storage de Proxmox." + } + ], + "menuTitle": "Menú de selección", + "menuBody": "Ambas fuentes se muestran en un único menú, cada fila etiquetada con su origen ([proxmox] o [manual]). Las entradas cuya contraseña no se pudo resolver se marcan con un aviso ⚠ no password — seleccionarlas dispara una re-introducción de contraseña antes de iniciar la copia. El fingerprint se pasa a proxmox-backup-client vía la variable de entorno PBS_FINGERPRINT; cuando no está presente, el cliente pide al usuario aceptar el certificado del servidor de forma interactiva en la primera copia." + }, + "backupCommand": { + "heading": "El comando de copia", + "intro": "La subida es una única invocación de proxmox-backup-client backup. ProxMenux la ejecuta dentro de un envoltorio env para que las credenciales nunca aparezcan en la lista de argumentos del proceso.", + "cmd": "env \\\n PBS_PASSWORD=\"$HB_PBS_SECRET\" \\\n PBS_ENCRYPTION_PASSWORD=\"$HB_PBS_ENC_PASS\" \\\n PBS_FINGERPRINT=\"$HB_PBS_FINGERPRINT\" \\\n proxmox-backup-client backup \\\n hostcfg.pxar:$staging_root \\\n --repository USER@REALM@HOST:DATASTORE \\\n --backup-type host \\\n --backup-id hostcfg-HOSTNAME \\\n --backup-time BACKUP-EPOCH \\\n [--keyfile /usr/local/share/proxmenux/pbs-key.conf]", + "backupIdTitle": "Nombrado del backup ID", + "backupIdBody": "El backup ID por defecto es hostcfg-HOSTNAME. Se pide al usuario que lo confirme o edite antes de la subida; cualquier carácter fuera de [A-Za-z0-9_-] se elimina y los guiones finales se recortan. Reutilizar el mismo ID entre ejecuciones es intencional — PBS trata el ID como un grupo, y cada copia posterior aparece como un nuevo backup dentro de ese grupo, compartiendo dedup con las ejecuciones previas.", + "pxarTitle": "Por qué el origen es la raíz de staging completa", + "pxarBody": "El origen del .pxar es la staging_root entera — rootfs/, metadata/ y manifest.json juntos. Versiones anteriores pasaban $staging_root/rootfs como origen; eso dejaba metadata/ fuera del archivo y el chequeo de compatibilidad de la restauración no tenía nada que leer, degradándose a avisos cross-host incluso en restauraciones al mismo host. Los backups antiguos creados con el origen rootfs-only siguen restaurándose correctamente gracias a la rama caso-3 de _rs_check_layout, que envuelve un árbol plano etc/var/root/usr de vuelta en una jerarquía rootfs/." + }, + "encryption": { + "heading": "Cifrado del lado del cliente", + "intro": "El cifrado por keyfile del lado del cliente de PBS cifra los chunks en el host de origen antes de la subida. ProxMenux habilita esta funcionalidad con una restricción añadida: la passphrase de recuperación es obligatoria cuando se activa el cifrado. La passphrase no protege el keyfile local; protege la copia de escrow del keyfile que ProxMenux sube a PBS para recuperación ante desastre.", + "keyfileTitle": "Keyfile", + "keyfileBody": "En el primer uso, proxmox-backup-client key create --kdf none genera el keyfile en /usr/local/share/proxmenux/pbs-key.conf (chmod 600). Las copias posteriores lo reutilizan tras un único diálogo de confirmación. Si la creación del keyfile falla, la copia se cancela y la salida de error de la herramienta se muestra en un diálogo.", + "recoveryTitle": "Passphrase de recuperación y blob de escrow", + "recoveryBody": "Tras crear el keyfile, ProxMenux pregunta dos veces por una passphrase de recuperación (con validación de coincidencia) y ejecuta openssl para producir pbs-key.recovery.enc — el keyfile cifrado con la passphrase. Se escribe una copia en /root/pbs-key.recovery-HOSTNAME-YYYYMMDD.enc para almacenamiento offsite. Cancelar el diálogo de la passphrase borra el keyfile recién creado.", + "blobUploadTitle": "Grupo emparejado en PBS", + "blobUploadBody1": "Tras una copia PBS que usó el keyfile, el blob de escrow se sube como un segundo grupo de copia: host/hostcfg-HOSTNAME-keyrecovery/BACKUP-TIME. El prefijo compartido hostcfg-HOSTNAME coloca ambos grupos adyacentes en la interfaz de PBS; el sufijo -keyrecovery etiqueta la relación. La subida se ejecuta sin --keyfile (el blob ya está protegido con passphrase por openssl) y sólo cuando la copia actual usó el keyfile.", + "blobUploadConstraintTitle": "Por qué dos grupos", + "blobUploadConstraintBody": "--keyfile es un flag por invocación en proxmox-backup-client backup: todos los archivos de una misma invocación se cifran con el keyfile o ninguno. hostcfg.pxar requiere cifrado; keyrecovery.conf no puede cifrarse con el mismo keyfile (la recuperación en instalación fresca requeriría el propio keyfile que pretende recuperar). Dos invocaciones, dos backup IDs.", + "blobUploadImageAlt": "Interfaz de PBS mostrando los grupos hostcfg-HOSTNAME y hostcfg-HOSTNAME-keyrecovery adyacentes en el listado del datastore.", + "blobUploadImageCaption": "Interfaz de PBS — los grupos de copia emparejados. El grupo principal contiene las copias del host; el grupo -keyrecovery contiene el blob de escrow.", + "recoverTitle": "Recuperación en instalación fresca", + "recoverBody": "En un host sin keyfile local, el flujo de restauración llama a hb_pbs_try_keyfile_recovery. La función lista los grupos keyrecovery del PBS configurado, descarga el más reciente y pregunta por la passphrase. En caso de éxito, pbs-key.conf se escribe en el directorio de estado de ProxMenux y la copia cifrada puede restaurarse. Sin el keyfile y la passphrase, la copia cifrada no es recuperable." + }, + "restoreAccess": { + "heading": "Recuperación del lado de la restauración", + "body": "El flujo de restauración descubre las copias de host de ProxMenux en PBS listando los grupos de backups bajo el repositorio configurado y filtrando por patrón de backup ID. La pestaña Backups del Monitor renderiza la misma lista. Seleccionar un backup dispara proxmox-backup-client restore con el mismo repositorio + contraseña + fingerprint (y --keyfile cuando el backup iba cifrado), extrayendo el .pxar a un directorio de staging que _rs_check_layout alimenta al pipeline estándar de restauración. Para recuperación manual fuera de ProxMenux, el mismo comando extrae el archivo a cualquier ruta — el árbol resultante puede inspeccionarse o alimentar una restauración a mano." + }, + "references": { + "heading": "Referencias", + "intro": "Documentación oficial de Proxmox Backup Server para los componentes en los que se apoya ProxMenux.", + "items": [ + { + "label": "Documentación de Proxmox Backup Server", + "href": "https://pbs.proxmox.com/docs/", + "tail": " — punto de entrada principal, cubre instalación, administración, almacenamiento, usuarios y roles." + }, + { + "label": "proxmox-backup-client", + "href": "https://pbs.proxmox.com/docs/backup-client.html", + "tail": " — la herramienta de línea de comandos que ProxMenux invoca en cada copia y restauración. Cubre backup IDs, tipos de backup, archivos, sintaxis del repositorio y variables de entorno." + }, + { + "label": "Cifrado del lado del cliente", + "href": "https://pbs.proxmox.com/docs/backup-client.html#encryption", + "tail": " — creación del keyfile, modos --kdf, el modelo de cifrado que ProxMenux extiende con un escrow de passphrase." + }, + { + "label": "Gestión de datastores", + "href": "https://pbs.proxmox.com/docs/storage.html", + "tail": " — creación y administración de los datastores que reciben las copias de host, incluyendo el layout del chunk-store y permisos." + }, + { + "label": "Poda y recolección de basura", + "href": "https://pbs.proxmox.com/docs/maintenance.html#pruning", + "tail": " — el modelo de retención detrás de --keep-last / --keep-daily / --keep-weekly que ProxMenux aplica por trabajo programado, más cómo PBS reclama chunks tras podar." + } + ] + } +} diff --git a/web/messages/es/docs/backup-restore/how-it-works.json b/web/messages/es/docs/backup-restore/how-it-works.json new file mode 100644 index 00000000..c46d33c2 --- /dev/null +++ b/web/messages/es/docs/backup-restore/how-it-works.json @@ -0,0 +1,199 @@ +{ + "meta": { + "title": "Cómo funciona ProxMenux Backup por dentro — rootfs, manifiesto, aplicaciones", + "description": "Desglose detallado del contenido de una copia de ProxMenux: el rootfs producido por rsync del perfil de rutas por defecto, el manifiesto estructurado construido por seis colectores independientes, y el inventario de aplicaciones que dirige la reinstalación automática de paquetes y componentes tras una restauración.", + "ogTitle": "Cómo funciona ProxMenux Backup por dentro", + "ogDescription": "Los tres bloques de una copia de ProxMenux explicados: rootfs, manifiesto y aplicaciones.", + "twitterTitle": "Cómo funciona ProxMenux Backup | ProxMenux", + "twitterDescription": "Los tres bloques de una copia de ProxMenux y cómo la restauración reproduce el host de origen a partir de ellos." + }, + "header": { + "title": "Cómo funciona", + "description": "Desglose interno de una copia de ProxMenux — sistema de ficheros, manifiesto e inventario de aplicaciones — y cómo la restauración consume los tres para reproducir el host de origen sobre un destino que puede no compartir el mismo kernel.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Un archivo, tres bloques", + "body": "Cada copia produce un layout de directorio con tres bloques bien definidos bajo una única raíz de staging. El archivo que se sube al destino (archivo local .tar.zst, backup PBS o archivo Borg) contiene ese layout exacto. La restauración lee los tres bloques de forma independiente, en un orden concreto que garantiza la corrección: primero se copia el rootfs para colocar la configuración, después se consulta el manifiesto para detectar drift y decidir qué omitir, y finalmente el inventario de aplicaciones dirige la pasada de reinstalación post-arranque. No hay dependencias entre bloques — cada uno puede inspeccionarse o extraerse de forma independiente." + }, + "layout": { + "heading": "Layout del archivo", + "intro": "Cada archivo respeta el mismo layout con independencia del destino. El subdirectorio metadata/ contiene los bloques estructurados; el subdirectorio rootfs/ contiene la copia del sistema de ficheros.", + "treeCaption": "El directorio de staging generado durante una copia. Todos los destinos reciben el mismo árbol (adaptado a su formato nativo: tar para local, chunks PBS para PBS, segmentos borg para Borg).", + "tree": "backup-[timestamp]/\n├── manifest.json # estado estructurado del host\n├── metadata/\n│ ├── packages.manual.list # apt-mark showmanual\n│ ├── run_info.env # identidad de la ejecución + versión del kernel\n│ ├── paths_archived.txt # lista exacta de rutas que llegaron a rootfs/\n│ └── missing_paths.txt # rutas del perfil ausentes en el origen\n└── rootfs/\n ├── etc/ # /etc/pve, /etc/network, /etc/ssh, …\n ├── root/ # /root (con subdirs volátiles excluidos)\n ├── usr/local/ # /usr/local/bin, /usr/local/share/proxmenux, …\n └── var/ # /var/lib/pve-cluster, /var/spool/cron/…" + }, + "rootfs": { + "heading": "El bloque rootfs", + "intro": "El árbol rootfs/ es una copia plana del sistema de ficheros producida por rsync desde el host de origen. Contiene un perfil por defecto curado de rutas que importan para una restauración de Proxmox, más cualquier ruta personalizada añadida por el usuario al trabajo de copia o a la sesión interactiva. El conjunto es intencionadamente estrecho: solamente rutas que contienen configuración o contienen estado que Proxmox no puede regenerar por sí solo.", + "defaultProfileTitle": "El perfil por defecto", + "defaultProfileBody": "El perfil por defecto está definido por hb_default_profile_paths en lib_host_backup_common.sh. Cubre ocho categorías que en conjunto describen un host Proxmox operativo:", + "categoriesTitle": "Categorías de rutas", + "categoryRows": [ + { + "category": "Núcleo PVE", + "paths": "/etc/pve, /var/lib/pve-cluster, /etc/vzdump.conf", + "why": "Contenido del filesystem del clúster, datos vivos del clúster, valores por defecto de vzdump." + }, + { + "category": "Identidad del host y red", + "paths": "/etc/hostname, /etc/hosts, /etc/timezone, /etc/resolv.conf, /etc/network", + "why": "Todo lo necesario para que el host arranque en red con la misma identidad." + }, + { + "category": "Acceso y autenticación", + "paths": "/etc/ssh, /etc/sudoers, /etc/sudoers.d, /etc/pam.d, /etc/security", + "why": "Claves SSH, reglas de sudo y configuración de PAM. Perderlas deja al usuario fuera del host restaurado." + }, + { + "category": "Kernel y arranque", + "paths": "/etc/default/grub, /etc/kernel, /etc/modules, /etc/modules-load.d, /etc/modprobe.d, /etc/sysctl.conf, /etc/sysctl.d, /etc/udev/rules.d, /etc/fstab, /etc/iscsi, /etc/multipath", + "why": "Tokens IOMMU, blacklists de módulos, IDs de dispositivos VFIO, tabla de montaje, configuración del stack de almacenamiento." + }, + { + "category": "Shell y locale", + "paths": "/etc/environment, /etc/bash.bashrc, /etc/inputrc, /etc/profile, /etc/profile.d, /etc/locale.gen, /etc/locale.conf", + "why": "Configuración de shell a nivel de sistema, variables de entorno, generación de locales." + }, + { + "category": "Paquetería y cron", + "paths": "/etc/apt, /etc/cron.d, /etc/cron.{daily,hourly,weekly,monthly}, /etc/cron.allow, /etc/cron.deny, /var/spool/cron/crontabs", + "why": "Fuentes APT para resolución consistente de paquetes, tareas programadas definidas por el usuario." + }, + { + "category": "Estado ProxMenux y herramientas", + "paths": "/etc/proxmenux, /etc/systemd/system, /etc/log2ram.conf, /etc/logrotate.conf, /etc/logrotate.d, /etc/lm-sensors, /etc/sensors3.conf, /etc/fail2ban, /etc/snmp, /etc/postfix, /etc/wireguard, /etc/openvpn, /etc/grafana, /etc/influxdb, /etc/prometheus, /etc/telegraf, /etc/zabbix", + "why": "Herramientas opcionales pero habituales de Proxmox. Las rutas ausentes se registran en metadata/missing_paths.txt sin detener la copia." + }, + { + "category": "Binarios ProxMenux y root", + "paths": "/usr/local/bin, /usr/local/sbin, /usr/local/share/proxmenux, /root (subdirs volátiles excluidos)", + "why": "Binarios instalados por ProxMenux y configuración por usuario bajo /root. Las rutas volátiles (.bash_history, .cache/, tmp/, .local/share/Trash/) quedan fuera de la copia." + }, + { + "category": "Estado ZFS (condicional)", + "paths": "/etc/zfs", + "why": "Sólo se incluye cuando el host de origen usa ZFS. Contiene zpool.cache y hostid." + } + ], + "customTitle": "Ampliar el perfil con rutas propias", + "customBody": "Además del perfil por defecto, ProxMenux ofrece dos maneras de incluir rutas adicionales en una copia. Se combinan sin conflicto y ambas se aplican tanto a copias interactivas como a trabajos programados.", + "customExtrasTitle": "1. Extras persistentes (fichero por-host)", + "customExtrasBody": "Un fichero de texto en /usr/local/share/proxmenux/backup-extra-paths.txt guarda una lista de rutas absolutas que el usuario ha marcado como \"incluir siempre\" en este host. Cuando una copia se ejecuta en modo Default, ProxMenux añade automáticamente estas rutas al perfil por defecto sin preguntar. El fichero se edita desde la interfaz — no hay que tocarlo a mano — y persiste entre reinicios y actualizaciones. Cada línea es una ruta absoluta; se admiten comentarios con #.", + "customModeTitle": "2. Modo Custom (por ejecución)", + "customModeBody": "Al lanzar una copia en modo Custom, en lugar de aplicar el perfil por defecto directamente, se muestra un checklist con todas las rutas: las del perfil por defecto y las de extras persistentes (estas últimas premarcadas con [+]). El usuario marca o desmarca lo que quiera para esa ejecución concreta, y puede además pulsar Add custom path para introducir una ruta nueva — que queda registrada en el fichero de extras persistentes para futuras copias.", + "customMissingTitle": "Rutas ausentes en el origen", + "customMissingBody": "Cualquier ruta del perfil (por defecto o añadida) que no exista en el host de origen se registra en metadata/missing_paths.txt dentro del archivo. La copia no falla ni interrumpe — el usuario ve el resumen de rutas archivadas y rutas ausentes al terminar. En la práctica esto ocurre con las rutas de herramientas opcionales como /etc/wireguard o /etc/prometheus cuando esas herramientas no están instaladas." + }, + "manifest": { + "heading": "El bloque manifiesto", + "intro": "manifest.json es un documento JSON estructurado que describe el host de origen en el momento de la copia. Se produce mediante seis colectores independientes orquestados por build_manifest.sh. Cada colector es de sólo lectura, produce un fragmento JSON bien definido y hace fallback a un valor vacío seguro si falla — el manifiesto sigue siendo utilizable aunque una sección quede incompleta.", + "orchestratorCaption": "Los seis colectores componen el manifiesto. Cada uno corre en su propio subproceso; un fallo en uno hace fallback al valor por defecto documentado y advierte, pero no aborta la copia.", + "collectorRows": [ + { + "collector": "collect_source_host.sh", + "produces": "source_host", + "content": "Hostname, versión de PVE (pveversion), versión de PBS si el host ejecuta el rol de backup-server, kernel (uname -r), modo de arranque (efi/bios), tipo de filesystem raíz, modelo y arquitectura de CPU, memoria en KB." + }, + { + "collector": "collect_hardware.sh", + "produces": "hardware_inventory", + "content": "GPUs (con fabricante y mapeo al instalador de ProxMenux), TPUs (Coral USB + M.2 detectados con lsusb/lspci), NICs (con MAC, slot PCI y pertenencia a bridges), dispositivos wireless. Las entradas de GPU llevan un heurístico passthrough_eligible." + }, + { + "collector": "collect_storage.sh", + "produces": "storage_inventory", + "content": "Pools ZFS (con tipo de pool y discos miembro resueltos a /dev/disk/by-id/* para portabilidad), grupos de volúmenes LVM + thin pools, discos físicos con capacidad SMART, entradas de storage.cfg de PVE, puntos de montaje externos." + }, + { + "collector": "collect_kernel.sh", + "produces": "kernel_params", + "content": "Tokens del usuario extraídos de /proc/cmdline (limpios de boilerplate como BOOT_IMAGE=, root=, ro/rw, quiet, splash), módulos cargados al arranque desde /etc/modules, y rutas de los ficheros /etc/modprobe.d/*.conf con directivas efectivas (options, blacklist, install, alias, softdep)." + }, + { + "collector": "collect_proxmenux_state.sh", + "produces": "proxmenux_installed_components", + "content": "Lee /usr/local/share/proxmenux/managed_installs.json (registro de todo lo que ProxMenux ha instalado) e installed_tools.json. Cada entrada conserva la ruta al instalador (menu_script) para que la restauración pueda disparar el mismo flujo de instalación." + }, + { + "collector": "collect_guests.sh", + "produces": "vms_lxcs_at_backup", + "content": "Enumera VMs (qm list) y LXCs (pct list) presentes en el momento de la copia — VMID, nombre, estado actual. Sólo el inventario: los datos reales del invitado son responsabilidad de vzdump / PBS." + } + ], + "schemaTitle": "Validación por esquema", + "schemaBody": "El manifiesto valida contra scripts/backup_restore/schema/manifest.schema.json. Ejecutar build_manifest.sh --validate dispara una validación JSON Schema por Python (requiere python3 + jsonschema). Si el módulo no está presente, la comprobación se omite silenciosamente — la validación es principalmente una ayuda de desarrollo, no una dependencia en tiempo de ejecución." + }, + "applications": { + "heading": "El inventario de aplicaciones", + "intro": "Dos ficheros bajo metadata/ catalogan todo lo instalado en el origen que no forma parte del conjunto de paquetes base de Proxmox VE. La restauración los utiliza para reproducir el conjunto exacto de software instalado por el usuario en el destino, usando APT o los instaladores propios de ProxMenux según cómo se instalara originalmente el software.", + "packagesTitle": "packages.manual.list", + "packagesBody": "Una lista de texto plano producida por apt-mark showmanual: todos los paquetes APT que fueron instalados explícitamente en el host de origen, ordenados alfabéticamente. Esto excluye los paquetes instalados como dependencias del ISO base de Proxmox VE (que APT del destino vuelve a traer automáticamente). Es leída por _rs_run_complete_extras durante la restauración, filtrada por un filtro cascade-safe de tres pases (dpkg -s para lo ya instalado, detección de sibling-major para librerías, apt-get install --simulate para riesgo de cascade-remove) y después instalada con apt-get install -y.", + "componentsTitle": "components_status.json (parte del rootfs)", + "componentsBody": "Un registro JSON bajo /usr/local/share/proxmenux/ que anota cada componente que ProxMenux ha instalado con su estado exacto: versión, flags específicas de ProxMenux (para NVIDIA: booleano patched; para Coral: versión del DKMS). Este fichero vive dentro del rootfs — no en metadata/ — porque se lee después de haber copiado el rootfs al destino. El dispatcher post-arranque (apply_cluster_postboot.sh) itera sobre sus entradas y ejecuta el hook --auto-reinstall de cada componente, que lee el estado registrado y reproduce la instalación contra el kernel actual del destino.", + "componentInstallersTitle": "Instaladores de componentes", + "componentInstallersBody": "Cuatro instaladores de ProxMenux exponen actualmente un punto de entrada --auto-reinstall:", + "installerRows": [ + { + "component": "nvidia_driver", + "installer": "gpu_tpu/nvidia_installer.sh", + "action": "Lee version + patched. Descarga el runfile exacto de NVIDIA, compila los módulos DKMS contra el kernel del destino, reaplica el parche de ProxMenux si el origen lo tenía." + }, + { + "component": "coral_driver", + "installer": "gpu_tpu/install_coral.sh", + "action": "Lee la versión del driver Coral. Compila el módulo DKMS contra el kernel del destino." + }, + { + "component": "amdgpu_top", + "installer": "gpu_tpu/amd_gpu_tools.sh", + "action": "Lee la versión registrada. Vuelve a descargar el .deb exacto desde la release de GitHub." + }, + { + "component": "intel_gpu_tools", + "installer": "gpu_tpu/intel_gpu_tools.sh", + "action": "Instala el paquete vía APT. Idempotente si ya está presente por packages.manual.list." + } + ] + }, + "restoreFlow": { + "heading": "Cómo consume la restauración los tres bloques", + "intro": "La restauración es un pipeline de cinco etapas. Cada etapa lee un subconjunto concreto del archivo y actualiza el host de destino. Ninguna etapa requiere que el host de origen esté accesible — el archivo es totalmente autocontenido.", + "stagesCaption": "La etapa 1 usa el manifiesto para decidir qué tocar. La etapa 2 copia las rutas del rootfs seguras de aplicar en un sistema en ejecución. La etapa 3 deja las rutas de riesgo preparadas para el siguiente arranque. La etapa 4 gestiona los paquetes. La etapa 5 corre tras el reinicio y reinstala los componentes contra el kernel del destino.", + "stageRows": [ + { + "stage": "1", + "name": "Comprobación de compatibilidad", + "reads": "manifest.json", + "action": "Ejecuta hb_compat_check. Compara hardware (NICs, IDs de almacenamiento), versión de PVE y versión mayor del kernel entre origen y destino. Fija HB_COMPAT_KERNEL_DIRECTION (same, bk_newer o bk_older) y rellena RS_SKIP_PATHS con las exclusiones por drift de hardware y por cross-kernel." + }, + { + "stage": "2", + "name": "Aplicación hot", + "reads": "rootfs/ (sólo rutas seguras)", + "action": "_rs_apply … hot copia las entradas cuyo hb_classify_path es hot directamente al destino en vivo. Todo lo bajo /etc/pve, /etc/network o clasificado como reboot/dangerous queda diferido." + }, + { + "stage": "3", + "name": "Preparación de pending", + "reads": "rootfs/ (rutas reboot + dangerous)", + "action": "_rs_prepare_pending_restore deja las rutas de riesgo preparadas bajo /var/lib/proxmenux/pending-restore/, escribe plan.env, apply-on-boot.list y rs-skip-paths.txt, y habilita proxmenux-restore-onboot.service para que dispare en el siguiente arranque." + }, + { + "stage": "4", + "name": "Instalación de paquetes", + "reads": "metadata/packages.manual.list", + "action": "_rs_run_complete_extras ejecuta el filtro cascade-safe y llama a apt-get install -y con la lista de paquetes superviviente. La salida completa va a /var/log/proxmenux/restore-apt-*.log." + }, + { + "stage": "5", + "name": "Post-arranque", + "reads": "rootfs (ya aplicado) + components_status.json", + "action": "Tras el reinicio, apply_pending_restore.sh reproduce las rutas diferidas y apply_cluster_postboot.sh ejecuta update-initramfs, update-grub (o proxmox-boot-tool refresh) e itera sobre components_status.json disparando el hook --auto-reinstall de cada componente." + } + ] + }, + "whyItWorks": { + "heading": "Por qué la separación en tres bloques es la correcta", + "body": "La separación no responde a una decisión de sistema de ficheros — responde a una decisión de ciclo de vida. El contenido del sistema de ficheros se mueve con rsync: rápido, transparente, atómico por fichero. El estado de configuración que la restauración tiene que interpretar antes de tocar el destino se mueve como JSON estructurado: legible de forma independiente, versionable mediante un esquema, diff-eable contra el estado propio del destino. El software que se tiene que reinstalar contra el entorno del destino se mueve como inventario: sólo nombres y versiones, dejando que el gestor de paquetes del destino y los instaladores propios de ProxMenux decidan los binarios reales. Cada bloque se optimiza para lo que tiene que hacer, y los tres combinan en una restauración que es atómica en la intención pero tolerante a fallos en la práctica: un manifiesto corrupto sigue dejando el rootfs restaurable, un paquete perdido sigue dejando los componentes instalables, un instalador que falla en una entrada no detiene la siguiente." + } +} diff --git a/web/messages/es/docs/backup-restore/index.json b/web/messages/es/docs/backup-restore/index.json new file mode 100644 index 00000000..9c2c9bd1 --- /dev/null +++ b/web/messages/es/docs/backup-restore/index.json @@ -0,0 +1,87 @@ +{ + "meta": { + "title": "ProxMenux Backup & Restore — Descripción general | Copia y restauración completa del host Proxmox VE", + "description": "ProxMenux Backup & Restore captura el estado completo de un host Proxmox — sistema de ficheros, manifiesto estructurado de configuración y paquetes/componentes instalados — y lo reproduce en el mismo host o en uno distinto. La copia y la restauración son autocontenidas: sin dependencias externas, y con soporte para restauraciones cross-kernel mediante un filtro direccional de subconjunto seguro y una hidratación kernel-agnóstica.", + "ogTitle": "ProxMenux Backup & Restore — Descripción general", + "ogDescription": "Copia y restauración completa del host Proxmox VE con manifiesto estructurado, lista de paquetes y reinstaladores de componentes.", + "twitterTitle": "ProxMenux Backup & Restore | ProxMenux", + "twitterDescription": "Copia y restauración completa del host Proxmox VE con manifiesto estructurado, lista de paquetes y reinstaladores de componentes." + }, + "header": { + "title": "Backup & Restore", + "description": "Copia y restauración a nivel de host para Proxmox VE. Captura el sistema de ficheros, la configuración y los componentes instalados en un único archivo y reproduce el host sobre la misma instalación de Proxmox o sobre una distinta, sin dependencias externas.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Qué es, en un párrafo", + "body": "Una copia de ProxMenux captura el estado completo de un host Proxmox: el sistema de ficheros (directorios relevantes bajo /etc, /root, /var/lib/pve-cluster, más rutas personalizadas opcionales), un manifiesto estructurado (JSON con el hardware detectado, los parámetros del kernel, la topología de red, el estado de ZFS, los usuarios y las entradas de cron) y un inventario de aplicaciones (todos los paquetes marcados como instalados manualmente por APT, más la lista de componentes instalados por ProxMenux con sus versiones exactas). Cualquiera de los tres destinos soportados — archivo local, Proxmox Backup Server (recomendado) o Borg — recibe el mismo contenido autocontenido, y cualquiera de ellos puede hidratar el host de destino sin depender de que el origen esté accesible en el momento de la restauración." + }, + "whatItIsNot": { + "heading": "Qué no es", + "intro": "La sección cubre copia y restauración a nivel de host: la instalación de Proxmox en sí, no las cargas de trabajo que se ejecutan sobre ella.", + "items": [ + "No es una herramienta de copia de VMs/CTs. Los discos de los invitados y su estado de RAM no se capturan en esta función; para eso está vzdump. Los ficheros de configuración de los invitados (/etc/pve/nodes/<node>/qemu-server/*.conf y lxc/*.conf) se capturan, de modo que tras la restauración el inventario de invitados reaparece y sus discos pueden re-adjuntarse desde una copia existente de PBS o local.", + "No es una operación a nivel de clúster. Cada nodo se copia a sí mismo. La pertenencia al clúster se captura como parte de /etc/pve para que un nodo restaurado pueda re-incorporarse al clúster, pero restaurar un clúster completo requiere coordinación por nodo.", + "No es una imagen completa del disco. Los binarios del kernel, el initramfs y la partición de arranque no se capturan. En la restauración, ProxMenux utiliza los artefactos de arranque propios del host de destino (regenerados automáticamente por update-initramfs y la herramienta de bootloader) e instala los controladores adecuados contra el kernel que el destino tenga en ejecución." + ] + }, + "threePillars": { + "heading": "Los tres pilares de una copia", + "intro": "Un archivo de ProxMenux se estructura en torno a tres cargas útiles autocontenidas. La restauración utiliza las tres en conjunto para reproducir el host de origen sobre un destino que puede no tener siquiera el mismo kernel instalado.", + "diagramCaption": "Cada copia contiene los mismos tres bloques con independencia del destino. La restauración los consume todos: rootfs para colocar los ficheros, manifiesto para detectar drift y diferencias cross-kernel, e inventario de aplicaciones para reinstalar paquetes y componentes contra el kernel del propio destino.", + "pillar1Label": "Sistema de ficheros", + "pillar1Detail": "rootfs/\n(rsync de\n/etc, /root,\n/var/lib/pve-cluster,\n+ rutas opcionales)", + "pillar2Label": "Manifiesto", + "pillar2Detail": "manifest.json\n(hardware, params\ndel kernel, red,\nZFS, usuarios, cron,\npools ZFS, storage)", + "pillar3Label": "Aplicaciones", + "pillar3Detail": "packages.manual.list\n+ components_status.json\n(APT manual + instaladores\nProxMenux con versiones)" + }, + "restoreIsUniversal": { + "heading": "La restauración reproduce el host de origen, no el archivo", + "body": "Restaurar una copia de ProxMenux no consiste solamente en extraer el sistema de ficheros. El flujo de restauración lee el manifiesto para detectar diferencias entre origen y destino (variaciones de hardware, renombrado de NICs, versión de kernel, identidad de la pool ZFS), reproduce el sistema de ficheros y lanza el instalador correspondiente a cada servicio que estuviera instalado en el origen (controlador NVIDIA, Coral TPU, herramientas AMD GPU, herramientas Intel GPU). Cada instalador se ejecuta contra el kernel actual del destino, por lo que el host restaurado no depende de que el kernel del origen esté presente. Cuando el kernel del destino es más reciente que el de la copia, una pasada de hidratación kernel-agnóstica funde la configuración propia del usuario (tokens IOMMU, IDs de dispositivos VFIO, quirks personalizadas, claves de GRUB) sobre la configuración de arranque fresca del destino, sin copiar verbatim los ficheros ligados al kernel." + }, + "twoInterfaces": { + "heading": "Dos interfaces, un único backend", + "intro": "Cada paso del flujo de copia y restauración está disponible desde dos puntos de entrada. Ambos invocan la misma librería de shell, producen archivos idénticos y leen el mismo registro de trabajos.", + "cliLabel": "ProxMenux Scripts (TUI)", + "cliDetail": "menu → Utilities →\nHost Backup / Restore\n\nFlujo basado en diálogos,\namigable por SSH, admite\nejecución desatendida\ndesde scripts.", + "webLabel": "ProxMenux Monitor (Web UI)", + "webDetail": "Pestaña Backups en la\ninterfaz Web del Monitor.\n\nRestauración con un clic,\nlog en vivo, integrado con\nlas notificaciones y con\nel visor de rollback." + }, + "whereNext": { + "heading": "A dónde seguir desde aquí", + "intro": "Cada subsección posterior desarrolla en detalle un aspecto del flujo. Se recomienda empezar por Cómo funciona para entender qué contiene el archivo y por qué. Consultar Destinos para configurar el destino de la copia. Leer Restauración y Restauración cross-kernel para el lado de recuperación.", + "items": [ + { + "label": "Cómo funciona", + "href": "/docs/backup-restore/how-it-works", + "tail": " — los tres bloques (rootfs, manifiesto, aplicaciones) en detalle, con los colectores que los generan y el formato de cada fichero." + }, + { + "label": "Destinos", + "href": "/docs/backup-restore/destinations", + "tail": " — comparativa entre archivo local, Proxmox Backup Server (recomendado) y Borg, y cómo configurar cada uno." + }, + { + "label": "Crear copias", + "href": "/docs/backup-restore/creating-backups", + "tail": " — copias puntuales, perfil de rutas por defecto, adición de rutas personalizadas, cifrado en PBS." + }, + { + "label": "Trabajos programados", + "href": "/docs/backup-restore/scheduled-jobs", + "tail": " — trabajos nuevos vs. adjuntarse a un trabajo vzdump existente en PVE, formatos de programación, el modal de detalle del trabajo." + }, + { + "label": "Restauración", + "href": "/docs/backup-restore/restoring", + "tail": " — las tres acciones sobre un archivo (ver, descargar, restaurar), Completa vs. Personalizada, el dispatcher post-arranque y por qué importan los últimos diez minutos." + }, + { + "label": "Restauración cross-kernel", + "href": "/docs/backup-restore/cross-kernel", + "tail": " — comportamiento direccional cuando el kernel del destino difiere del de la copia, el filtro de subconjunto seguro y las cuatro fases de hidratación." + } + ] + } +} diff --git a/web/messages/es/docs/backup-restore/restoring.json b/web/messages/es/docs/backup-restore/restoring.json new file mode 100644 index 00000000..f09e2924 --- /dev/null +++ b/web/messages/es/docs/backup-restore/restoring.json @@ -0,0 +1,239 @@ +{ + "meta": { + "title": "Restaurar una copia — flujos Completo y Personalizado | ProxMenux", + "description": "El flujo completo de restauración. Documenta las tres acciones disponibles sobre cualquier copia (ver, descargar, restaurar), la comprobación de compatibilidad y sus resultados, los modos Completo y Personalizado, la clasificación de rutas que decide qué se aplica en vivo y qué espera al siguiente arranque, el dispatcher de arranque y la pasada de reinstalación post-arranque de componentes.", + "ogTitle": "ProxMenux Backup — restaurar", + "ogDescription": "Flujos de restauración completo y personalizado con la comprobación de compatibilidad, la clasificación de rutas, el dispatcher post-arranque y la reinstalación de componentes.", + "twitterTitle": "Restaurar una copia | ProxMenux", + "twitterDescription": "Cómo se convierte una copia de host de ProxMenux en un host Proxmox operativo." + }, + "header": { + "title": "Restaurar una copia", + "description": "El flujo completo de restauración: desde elegir una copia hasta un host operativo. Documenta la comprobación de compatibilidad, los dos modos de restauración, el dispatcher post-arranque, la pasada de reinstalación de componentes, y los mecanismos que hacen predecibles las restauraciones cross-host y cross-kernel.", + "section": "Backup & Restore" + }, + "intro": { + "title": "La restauración reproduce el origen, no sólo los ficheros", + "body": "Restaurar una copia de ProxMenux no es una extracción. Los ficheros son sólo el primer paso: una vez colocado el sistema de ficheros, la restauración lee el manifiesto para detectar diferencias entre origen y destino, filtra rutas que romperían el arranque del destino, hidrata configuración propia del usuario que no puede copiarse verbatim, y — tras el reinicio obligatorio — reinstala cada componente que tuviera el origen (controlador NVIDIA, Coral TPU, herramientas GPU) contra el kernel actual del destino. Lo que el usuario elige del menú es una única copia; lo que realmente sucede involucra a la comprobación de compatibilidad, la clasificación de rutas, el dispatcher post-arranque y la pasada de reinstalación trabajando juntos para producir un host que se comporta como el origen." + }, + "threeActions": { + "heading": "Tres acciones sobre una copia", + "intro": "Seleccionar una copia de la lista abre un menú con tres acciones.", + "actionRows": [ + { + "action": "Ver", + "detail": "Abre una vista de sólo lectura del archivo: contenido de manifest.json (hostname de origen, versión de PVE, kernel, hardware, componentes instalados), la lista de rutas dentro de rootfs/, y un diff de qué cambiaría en el host actual si se aplicara la copia." + }, + { + "action": "Descargar", + "detail": "Exporta el archivo como un fichero portable .tar.zst (tar comprimido con zstd). Se puede extraer en cualquier sistema Linux con tar --zstd -xf FICHERO.tar.zst, o desde macOS/Windows con herramientas que soporten zstd (7-Zip, PeaZip, Keka…). El árbol extraído contiene manifest.json, metadata/ y rootfs/, exactamente el mismo layout que consume la restauración. Es útil para inspección offline o para restaurar en un host que no tiene acceso al destino original (PBS, Borg)." + }, + { + "action": "Restaurar", + "detail": "La ruta que escribe. Extrae el archivo en un directorio de staging, ejecuta la comprobación de compatibilidad y presenta el selector de modo (Completo o Personalizado)." + } + ] + }, + "compatibilityCheck": { + "heading": "La comprobación de compatibilidad", + "intro": "Antes de escribir ningún fichero, hb_compat_check compara el estado descrito en el manifiesto contra el host de destino. La comprobación se ejecuta en sólo lectura y produce cuatro salidas independientes que dirigen el resto de la restauración.", + "outputRows": [ + { + "output": "Flag de dirección", + "detail": "HB_COMPAT_KERNEL_DIRECTION — uno de same, bk_newer o bk_older. Compara la versión mayor del kernel de la copia contra la del destino. Dirige el filtro cross-kernel de subconjunto seguro (sólo dispara en bk_older) y la pasada de hidratación documentada en la página cross-kernel." + }, + { + "output": "Lista de rutas a saltar", + "detail": "RS_SKIP_PATHS — cada ruta que la restauración NO debe aplicar. Se pobla por dos mecanismos: drift de hardware (NIC ausente, ID de storage ausente, pool ZFS que no pertenece a este host) y — cuando la dirección es bk_older — la lista cross-kernel de rutas no seguras." + }, + { + "output": "Plan de remapeo de NICs", + "detail": "Cuando una NIC del destino tiene la misma MAC que una del origen pero con nombre distinto (típico tras cambiar la placa base), la comprobación registra un plan de renombrado (HB_NIC_REMAP) que reescribirá /etc/network/interfaces durante la restauración." + }, + { + "output": "Plan de rollback", + "detail": "Lo calcula compute_rollback_plan.sh. Lista las VMs, LXCs y componentes presentes en el destino pero no en la copia. El usuario puede optar durante el diálogo de confirmación por eliminarlos como parte de la restauración, de modo que el destino termine casando exactamente con la copia." + } + ], + "reportBody": "La comprobación también emite un informe estructurado (HB_COMPAT_RESULTS) categorizado como PASS / INFO / WARN / FAIL. Las entradas WARN y FAIL aparecen en el panel previo a la restauración; la restauración se niega a continuar sólo cuando hay un FAIL que el usuario no puede resolver pulsando Continuar." + }, + "twoModes": { + "heading": "Restauración Completa vs Personalizada", + "intro": "Una vez completada la comprobación de compatibilidad, aparece el menú de modo de restauración. La elección determina qué se aplica, no cómo — ambos modos comparten el mismo pipeline subyacente.", + "modeRows": [ + { + "mode": "Restauración Completa", + "detail": "Aplica cada ruta del archivo que sobreviva a los filtros de drift y cross-kernel. También ejecuta la instalación de paquetes y la pasada de reinstalación de componentes. Es la elección por defecto y la recomendada — el objetivo es reproducir el origen, no elegir trozos." + }, + { + "mode": "Restauración Personalizada", + "detail": "Abre un checklist mostrando cada ruta que lleva el archivo. El usuario marca un subconjunto. Las rutas bloqueadas por el filtro cross-kernel aparecen en gris y no pueden seleccionarse. La instalación de paquetes y la reinstalación de componentes se omiten por defecto en modo Personalizado — el usuario está señalando que desea una aplicación parcial, no una reproducción completa." + } + ] + }, + "pathClassification": { + "heading": "Cómo se clasifican las rutas", + "intro": "Cada ruta seleccionada para la restauración se clasifica por hb_classify_path en una de tres categorías. La categoría determina el momento en que la ruta se aplica al sistema y por qué.", + "rows": [ + { + "class": "hot", + "detail": "Rutas que se aplican de inmediato sobre el sistema en ejecución. El servicio que las consume detecta el cambio por sí mismo o al siguiente reload, sin necesidad de reiniciar. Son la mayoría del contenido de una copia: /etc/ssh, /etc/apt, /etc/cron.*, /root, /usr/local/bin, ficheros de configuración de servicios generales, etc." + }, + { + "class": "reboot", + "detail": "Rutas que se aplican de inmediato también, pero cuyo efecto real sólo se manifiesta en el siguiente arranque: el kernel sólo lee /etc/default/grub al iniciar el bootloader, /etc/fstab al montar los filesystems, /etc/modules al cargar módulos, etc. El fichero está en su sitio nada más aplicarse, pero el sistema tiene que reiniciar para consumirlo. Ejemplos: /etc/default/grub, /etc/kernel, /etc/modules, /etc/fstab, /etc/zfs, /etc/initramfs-tools." + }, + { + "class": "dangerous", + "detail": "Rutas que NO se aplican en el sistema en ejecución — la escritura viva podría corromper estado o cortar la conexión activa. Estas rutas se preparan en el conjunto pendiente y las escribe el dispatcher post-arranque después del reinicio, cuando el clúster está arriba pero antes de que el sistema esté completamente en uso. Ejemplos: /etc/pve (pmxcfs es un FUSE vivo; escribir directo en él lo saltea), /var/lib/pve-cluster (datos vivos del clúster), /etc/network (podría reconfigurar la misma interfaz por la que el usuario está conectado por SSH y cortar la sesión)." + } + ] + }, + "fullFlow": { + "heading": "El flujo completo de restauración", + "intro": "El pipeline completo desde la selección del archivo hasta un host restaurado y operativo. Cada etapa alimenta a la siguiente; el estado escrito por etapas anteriores lo consumen las posteriores.", + "diagram": "┌─────────────────────────────────────────────────────────────────┐\n│ Archivo → staging_root/ │\n│ manifest.json metadata/ rootfs/ │\n└──────────────────────────────┬──────────────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 1. hb_compat_check │\n │ · drift de hardware → RS_SKIP_PATHS │\n │ · dirección kernel → same / bk_newer / bk_older │\n │ · plan de remapeo NIC │\n │ · plan de hidratación (sólo bk_older) │\n │ · plan de rollback │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 2. Diálogo de confirmación │\n │ Muestra: rutas hot, rutas pending, drift, │\n │ hidratación, remapeo NIC, nota cross-kernel, │\n │ preview de reinstalaciones │\n └──────────────────────────────┬──────────────────────────┘\n │ (aceptado)\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 3. Aplicar rutas hot → EN VIVO │\n │ _rs_apply rsyncea cada ruta hot │\n │ desde staging_root/rootfs a / │\n │ (salta rutas en RS_SKIP_PATHS) │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 4. _rs_prepare_pending_restore │\n │ /var/lib/proxmenux/pending-restore/ │\n │ ├── apply-on-boot.list │\n │ ├── plan.env │\n │ ├── rs-skip-paths.txt │\n │ └── rootfs/ (rutas diferidas) │\n │ Habilita proxmenux-restore-onboot.service │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 5. _rs_run_complete_extras │\n │ packages.manual.list │\n │ → filtro cascade-safe │\n │ → apt-get install │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 6. REINICIO │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 7. apply_pending_restore.sh (arranque temprano) │\n │ Lee plan.env │\n │ Aplica apply-on-boot.list (filtrado) │\n │ Instala la unit systemd de postboot │\n └──────────────────────────────┬──────────────────────────┘\n │\n ▼\n ┌─────────────────────────────────────────────────────────┐\n │ 8. apply_cluster_postboot.sh (~10 minutos) │\n │ Corre cuando pve-cluster está arriba │\n │ · Aplica entradas de /etc/pve a pmxcfs │\n │ · update-initramfs -u -k all │\n │ · update-grub / proxmox-boot-tool refresh │\n │ · component --auto-reinstall (nvidia, coral, ...) │\n │ · Comprobación de sanidad de arranque │\n │ · Notificación: \"Host restore finished\" │\n └─────────────────────────────────────────────────────────┘" + }, + "pendingMachinery": { + "heading": "La maquinaria del pending-restore", + "intro": "Las rutas clasificadas como reboot o dangerous, y las escrituras de hidratación, se preparan para el siguiente arranque en lugar de aplicarse en vivo. Esto se hace mediante un pequeño conjunto autocontenido de ficheros bajo /var/lib/proxmenux/pending-restore/:", + "rows": [ + { + "file": "apply-on-boot.list", + "content": "Una ruta relativa por línea — el conjunto exacto de rutas que apply_pending_restore.sh aplicará desde el rootfs preparado. Consultar este fichero le dice al usuario exactamente qué cambiará en el siguiente arranque." + }, + { + "file": "plan.env", + "content": "Variables de entorno que sourcea el script de arranque: ID de la restauración, flags de compatibilidad (HB_COMPAT_CROSS_VERSION, HB_COMPAT_KERNEL_DIRECTION, HB_HYDRATION_APPLIED), flag de opt-in de rollback, opciones de clúster." + }, + { + "file": "rs-skip-paths.txt", + "content": "La lista final RS_SKIP_PATHS, persistida para que apply_pending_restore.sh aplique las mismas exclusiones tras el reinicio que las que se calcularon durante el paso interactivo. Evita que las rutas afectadas por drift o inseguras cross-kernel se restauren en el arranque." + }, + { + "file": "rootfs/", + "content": "Los ficheros preparados en sí — los bytes exactos que se colocarán. Se mantienen en el mismo sistema de ficheros que / para que el rsync de arranque sea rápido y no dependa de almacenamiento externo alcanzable en el arranque." + } + ], + "unitBody": "La unit systemd proxmenux-restore-onboot.service se habilita al final del paso interactivo. Es un servicio oneshot que dispara temprano en el siguiente arranque, invoca apply_pending_restore.sh, y después se autodeshabilita. La unit está protegida por la condición ConditionPathExists=/var/lib/proxmenux/pending-restore/state, por lo que en cualquier arranque sin restauración pendiente es un no-op." + }, + "postbootDispatcher": { + "heading": "El dispatcher post-arranque — apply_cluster_postboot.sh", + "intro": "Donde termina el paso interactivo y donde ocurre la reproducción real del host. apply_cluster_postboot.sh se instala como una segunda unit systemd oneshot (proxmenux-apply-cluster-postboot.service) cuyos targets After=/Wants= aseguran que corre sólo después de que pve-cluster y network-online estén arriba. Aquí es donde aterrizan las acciones visibles de la restauración.", + "tasks": [ + { + "task": "Aplicar /etc/pve", + "detail": "/etc/pve es un montaje FUSE de pmxcfs en vivo — no se puede escribir en él en el arranque temprano. El dispatcher copia ficheros desde el rootfs pendiente al /etc/pve vivo una vez el filesystem del clúster está arriba, fichero por fichero, sin reiniciar pve-cluster." + }, + { + "task": "Regenerar initramfs y bootloader", + "detail": "Ejecuta update-initramfs -u -k all en cada kernel instalado, después update-grub (instalaciones GRUB) o proxmox-boot-tool refresh (systemd-boot / ZFS). Se omite si el paso interactivo determinó que nada cambió en las rutas que afectan a estas herramientas." + }, + { + "task": "Reinstalación de componentes", + "detail": "Lee el components_status.json restaurado e itera sobre los instaladores registrados (nvidia_driver, coral_driver, amdgpu_top, intel_gpu_tools). Cada instalador corre en modo --auto-reinstall, lee su versión previamente registrada del state file, y recompila contra el kernel actual del destino. El flujo interactivo no hace esto — reinstalar drivers contra un kernel que aún no ha arrancado carece de sentido." + }, + { + "task": "Comprobación de sanidad del arranque", + "detail": "Antes de emitir la notificación de finalización, verifica que proxmox-boot-tool status muestra un ESP configurado, que cada /boot/vmlinuz-* tiene su directorio /lib/modules/<ver> correspondiente, y que /vmlinuz resuelve. Cualquier inconsistencia aparece en la notificación en lugar de quedar oculta." + }, + { + "task": "Notificación de finalización", + "detail": "Envía el evento Host restore finished a través de hb_notify_lifecycle. Incluye duración total, cuenta de rutas aplicadas, avisos de la comprobación de sanidad si los hay, y un enlace al log en /var/log/proxmenux/proxmenux-cluster-postboot-*.log. Si no hay notificaciones configuradas, el evento es silencioso — el log sigue registrando todo." + } + ] + }, + "postbootExample": { + "heading": "Cómo se ve el post-boot en la consola", + "body": "En la consola física del host la ejecución exitosa del dispatcher termina con una línea [ OK ] Finished proxmenux-apply-cluster-postboot.service. Cuando esa línea aparece — normalmente acompañada de los OK del multi-user.target y del graphical.target — la restauración ha terminado por completo: componentes reinstalados, boot artifacts regenerados, cluster reconciliado.", + "imageAlt": "Consola física de Proxmox mostrando el mensaje '[ OK ] Finished proxmenux-apply-cluster-postboot.service - ProxMenux Apply Cluster Configs (post-boot).' seguido del OK de multi-user.target y graphical.target, con el prompt de login del host encima.", + "imageCaption": "Consola física tras un post-boot exitoso. La línea 'Finished proxmenux-apply-cluster-postboot.service' es la señal de que el flujo de restauración ha terminado por completo — el mismo evento que la notificación 'Host restore finished' expone en el Monitor." + }, + "tenMinutes": { + "heading": "Por qué importan los últimos diez minutos", + "intro": "El reinicio en sí toma segundos, pero el dispatcher post-arranque tarda alrededor de diez minutos en terminar la restauración. Durante esta ventana el host es alcanzable y el login funciona, pero algunos servicios (principalmente los dependientes del driver GPU) todavía no están disponibles. Desglose de tiempos para un host Proxmox típico con NVIDIA + Coral instalados:", + "rows": [ + { + "stage": "Arranque + pve-cluster listo", + "time": "~30 s", + "detail": "Arranque estándar de Proxmox. SSH, UI web y login están disponibles al final de esta etapa." + }, + { + "stage": "Aplicar /etc/pve a pmxcfs", + "time": "~10 s", + "detail": "Rápido — ficheros pequeños de config copiados uno a uno al filesystem del clúster vivo." + }, + { + "stage": "Regenerar initramfs en cada kernel", + "time": "3–5 min", + "detail": "update-initramfs -u -k all reconstruye una imagen de initramfs por cada kernel instalado. El grueso de la espera." + }, + { + "stage": "Regenerar config del bootloader", + "time": "10–30 s", + "detail": "update-grub o proxmox-boot-tool refresh. Rápido incluso en ZFS." + }, + { + "stage": "Reinstalación del driver NVIDIA (si aplica)", + "time": "5–10 min", + "detail": "Descarga la versión registrada del driver, compila los módulos DKMS contra el kernel actual, aplica el parche de ProxMenux si el origen lo tenía. La tarea individual más larga." + }, + { + "stage": "Reinstalación de otros componentes", + "time": "3–5 min", + "detail": "Compilaciones DKMS de Coral, apt install para intel-gpu-tools, descarga + install del .deb para amdgpu_top." + }, + { + "stage": "Comprobación de sanidad + notificación", + "time": "~2 s", + "detail": "Barata. El usuario se entera de que la ejecución ha terminado en el momento en que dispara la notificación." + } + ], + "outroBody": "La ventana importa porque el usuario puede hacer login durante este tiempo y ver herramientas ausentes (nvidia-smi no encontrado, coral no detectado). Es esperado — la reinstalación sigue corriendo en background. La notificación de finalización señala cuando todo está listo." + }, + "destructiveRollback": { + "heading": "El rollback destructivo opcional", + "body": "Una restauración es aditiva por defecto: si el destino tiene VMs, LXCs o componentes que no están en la copia, sobreviven. Esto preserva el trabajo pero puede dejar entradas hostpci obsoletas, componentes huérfanos o VMs que el usuario ya no quiere. Cuando se detecta drift entre destino y copia, el diálogo de confirmación ofrece un segundo yes/no: ¿Ejecutar rollback destructivo?. Aceptar dispara _rs_execute_rollback, que elimina las VMs, LXCs y componentes que existen en el destino pero no en la copia. El rollback corre ANTES del reinicio para que la maquinaria de pending-restore vea un estado limpio. Es opt-in — el default es No, y el usuario ve exactamente qué se eliminaría listado por nombre (VMID y CTID) antes de decidir." + }, + "logs": { + "heading": "Dónde viven los logs", + "rows": [ + { + "log": "/var/log/proxmenux/restore-YYYYMMDD_HHMMSS.log", + "detail": "Lo escribe el paso interactivo. Contiene: resultados de compatibilidad, filtro de drift, plan de hidratación, salida de aplicación hot, salida de preparación pending, instalación de paquetes." + }, + { + "log": "/var/log/proxmenux/apply-pending-YYYYMMDD_HHMMSS.log", + "detail": "Lo escribe apply_pending_restore.sh en el arranque temprano. Contiene: plan.env sourceado, iteración de apply-on-boot, filtrado por skip-paths." + }, + { + "log": "/var/log/proxmenux/proxmenux-cluster-postboot-YYYYMMDD_HHMMSS.log", + "detail": "Lo escribe apply_cluster_postboot.sh. Contiene: aplicación de pve-cluster, salida de initramfs y bootloader, salida de cada instalador de componentes, comprobación de sanidad, payload de notificación." + }, + { + "log": "/var/log/proxmenux/component--YYYYMMDD_HHMMSS.log", + "detail": "Un log por instalador de componente (nvidia, coral, etc.) que corrió en la pasada post-arranque. Útil cuando la reinstalación de un componente concreto ha fallado y el usuario necesita la salida de la propia herramienta." + } + ] + }, + "whereNext": { + "heading": "A dónde seguir", + "items": [ + { + "label": "Restauración cross-kernel", + "href": "/docs/backup-restore/cross-kernel", + "tail": " — el filtro direccional de subconjunto seguro y la pasada de hidratación que se invocan cuando el kernel del destino difiere del de la copia." + }, + { + "label": "Cómo funciona", + "href": "/docs/backup-restore/how-it-works", + "tail": " — el layout del archivo, el manifiesto y el inventario de aplicaciones que consume la restauración." + }, + { + "label": "Trabajos programados", + "href": "/docs/backup-restore/scheduled-jobs", + "tail": " — el flujo desatendido que produce los archivos que consume esta página." + } + ] + } +} diff --git a/web/messages/es/docs/backup-restore/scheduled-jobs.json b/web/messages/es/docs/backup-restore/scheduled-jobs.json new file mode 100644 index 00000000..8f9ed947 --- /dev/null +++ b/web/messages/es/docs/backup-restore/scheduled-jobs.json @@ -0,0 +1,148 @@ +{ + "meta": { + "title": "Trabajos programados — timers, modo adjunto, retención | ProxMenux", + "description": "Trabajos de copia de host programados en ProxMenux. Dos modos de creación (timer systemd propio o adjuntarse a un trabajo vzdump de PVE existente), valores de retención aplicados por trabajo vía proxmox-backup-client/borg/local prune, layout de almacenamiento bajo /var/lib/proxmenux/backup-jobs, y el script runner que produce archivos idénticos al flujo interactivo.", + "ogTitle": "ProxMenux Backup — trabajos programados", + "ogDescription": "Copias de host desatendidas programadas con modo adjunto, retención, y los mismos tres destinos que el flujo interactivo.", + "twitterTitle": "Trabajos programados | ProxMenux", + "twitterDescription": "Trabajos de copia de host desatendidos con modo adjunto y retención." + }, + "header": { + "title": "Trabajos programados", + "description": "Trabajos de copia de host desatendidos. Dos modelos de creación: un trabajo nuevo e independiente con su propio horario, o un trabajo adjuntado a una tarea vzdump de PVE existente que hereda el horario y la retención de esa tarea. Ambos producen archivos idénticos al flujo interactivo.", + "section": "Backup & Restore" + }, + "intro": { + "title": "Los dos modelos de trabajo programado", + "body": "Un trabajo programado puede crearse siguiendo uno de dos modelos:", + "modelsList": [ + "Trabajo nuevo e independiente — el propio ProxMenux define el horario del trabajo con un timer systemd propio, independiente de cualquier otra tarea del host. Compatible con los tres destinos: Local, PBS y Borg.", + "Adjuntarse a una tarea vzdump de PVE existente — el trabajo no lleva horario propio; se ejecuta automáticamente cada vez que se dispara la tarea vzdump padre que ya copia las VMs y los LXCs del host. Hereda el horario y la retención de la tarea padre. Compatible con Local y PBS (Borg no está soportado porque no existe scheduler nativo de PVE para Borg)." + ] + }, + "attachBadge": { + "title": "Modo adjunto — recomendado cuando ya existe una tarea vzdump de PVE", + "body": "Cuando ya hay una tarea de copia vzdump de PVE configurada para las VMs y los LXCs de este nodo, adjuntar la copia de host a esa tarea garantiza que la configuración del host se captura en la misma ventana que los invitados. En la restauración, las configuraciones de los invitados vienen de la copia de host (viven bajo /etc/pve), y los discos de los invitados vienen de la copia vzdump tomada al mismo tiempo — ambos conjuntos son consistentes entre sí, por lo que una recuperación completa del nodo puede reproducir el host y re-adjuntar cada invitado sin drift de versiones." + }, + "modes": { + "heading": "Los dos modos", + "rows": [ + { + "mode": "Nuevo trabajo programado", + "backends": "Local, PBS, Borg", + "schedule": "Expresión OnCalendar propia (sintaxis de calendario systemd — por ejemplo daily, Mon..Fri 03:00).", + "retention": "Se pregunta por separado: keep-last, keep-hourly, keep-daily, keep-weekly, keep-monthly, keep-yearly. La aplica el runner tras cada copia exitosa." + }, + { + "mode": "Adjuntar a un trabajo vzdump de PVE", + "backends": "Local, PBS (Borg no tiene scheduler del lado PVE)", + "schedule": "Heredado del trabajo PVE padre. No se instala timer systemd del lado de ProxMenux.", + "retention": "Heredada de la configuración prune-backups del padre (mapeada uno-a-uno a las variables KEEP_* del runner vía hb_pve_prune_to_keep_env)." + } + ] + }, + "attachDetail": { + "heading": "Cómo funciona el modo adjunto", + "intro": "PVE escribe las tareas vzdump en /etc/pve/jobs.cfg — una entrada por tarea, cada una apuntando a un storage donde caen los dumps de VMs y LXCs. El modo adjunto requiere que ese storage sea un backend que ProxMenux entienda (Local o PBS); cuando la tarea dispara, ProxMenux se ejecuta al mismo tiempo.", + "steps": [ + { + "step": "1", + "detail": "Durante la creación del trabajo, ProxMenux lista las tareas PVE padre compatibles vía hb_pve_list_vzdump_jobs_for_backend. El usuario elige una." + }, + { + "step": "2", + "detail": "El .env del trabajo se escribe con PVE_PARENT_JOB, PVE_STORAGE y los valores heredados de KEEP_*; no se crea timer systemd." + }, + { + "step": "3", + "detail": "hb_install_vzdump_hook registra un script-hook en /etc/vzdump.conf. Cuando PVE ejecuta cualquier tarea vzdump, el script-hook dispara; si el $STOREID que se le pasa coincide con el PVE_STORAGE de un trabajo adjuntado, el runner se invoca para ese trabajo." + }, + { + "step": "4", + "detail": "El archivo aterriza en el mismo storage donde los dumps de vzdump acaban de escribirse: path/dump/ para Local, o el repositorio PBS configurado en la entrada de storage de PVE." + } + ], + "outroBody": "El modo adjunto tiene una implicación operativa: desactivar o eliminar la tarea padre de PVE también desactiva la copia de host — no hay timer propio al que recurrir. La entrada del trabajo permanece en disco para poder re-adjuntarla más tarde." + }, + "storageLayout": { + "heading": "Ficheros que componen un trabajo", + "intro": "Cada trabajo programado queda completamente descrito por tres o cuatro ficheros en disco. Consultarlos permite ver toda la configuración del trabajo sin depender de la interfaz.", + "rows": [ + { + "path": "/var/lib/proxmenux/backup-jobs/JOB_ID.env", + "content": "Backend, backup ID o destino, horario (modo Nuevo) o padre PVE (modo adjunto), flag enabled, valores KEEP_* de retención, punteros a credenciales." + }, + { + "path": "/var/lib/proxmenux/backup-jobs/JOB_ID.paths", + "content": "Una ruta absoluta por línea — la selección congelada resuelta desde el perfil en el momento de creación del trabajo. Editar el fichero re-ejecuta el trabajo con la nueva selección en el siguiente disparo del timer." + }, + { + "path": "/etc/systemd/system/proxmenux-backup-JOB_ID.timer + .service", + "content": "Sólo modo Nuevo. El service invoca run_scheduled_backup.sh JOB_ID. El timer lo programa con Persistent=true (los disparos perdidos se ejecutan al siguiente arranque) y un pequeño RandomizedDelaySec=120 para repartir carga cuando varios trabajos comparten la misma expresión OnCalendar." + }, + { + "path": "script-hook de /etc/vzdump.conf", + "content": "Sólo modo adjunto. Lo instala una vez hb_install_vzdump_hook; casa el PVE_STORAGE de cada trabajo adjuntado contra el $STOREID que PVE pasa al script-hook." + } + ] + }, + "runner": { + "heading": "Qué hace un trabajo cuando se dispara", + "intro": "Cada trabajo — sea del modelo nuevo o adjunto — ejecuta la misma secuencia de tres pasos:", + "steps": [ + "Preparar la copia. Lee la configuración del trabajo (destino, perfil, rutas) y ensambla el árbol de la copia siguiendo exactamente el mismo procedimiento que el flujo interactivo. El resultado es el mismo archivo que produciría una copia manual con esos ajustes.", + "Escribir en el destino. Sube o escribe la copia al destino configurado — un archivo local .tar.zst, un backup PBS o un archivo Borg — usando exactamente las mismas herramientas y credenciales que utilizaría una copia manual al mismo destino.", + "Aplicar retención. Elimina las copias antiguas del destino conservando los valores keep-last / keep-daily / keep-weekly configurados en el trabajo. La poda la realiza el propio destino: PBS mantiene la cuenta en su lado, Borg ejecuta borg prune, y en Local ProxMenux borra los archivos que quedan fuera del keep-last." + ] + }, + "management": { + "heading": "Gestión de trabajos", + "intro": "El menú del scheduler (TUI de Scripts) y la pestaña Backups del Monitor exponen las mismas acciones sobre cualquier trabajo.", + "rows": [ + { + "action": "Run now", + "detail": "Invoca el runner de inmediato, saltándose el timer / hook de vzdump. Útil para verificar una configuración de trabajo sin esperar al siguiente disparo programado." + }, + { + "action": "Enable / Disable", + "detail": "Modo Nuevo: systemctl enable/disable --now sobre el timer. Modo adjunto: cambia el flag ENABLED en el env del trabajo — el script-hook lo respeta en el siguiente disparo de PVE." + }, + { + "action": "Edit", + "detail": "Reabre los prompts de destino, horario, perfil y retención y reescribe los ficheros del trabajo. Preserva el ID del trabajo." + }, + { + "action": "Delete", + "detail": "Elimina el env del trabajo, el fichero de rutas, el timer + service systemd (modo Nuevo) y desactiva el binding del hook (modo adjunto). No toca los archivos ya presentes en el destino." + }, + { + "action": "View log", + "detail": "Muestra en streaming /var/log/proxmenux/backup-jobs/JOB_ID-YYYYMMDD_HHMMSS.log — un fichero de log por ejecución. El runner también emite una línea de estado compacta a journald bajo el service systemd." + } + ] + }, + "notifications": { + "heading": "Notificaciones", + "body": "Cada ejecución programada dispara los mismos eventos hb_notify_lifecycle que el flujo interactivo (start, complete, fail). Si hay canales de notificación configurados en el Monitor, los trabajos desatendidos exponen sus resultados igual que las copias manuales — un usuario no necesita revisar el log para saber si un trabajo tuvo éxito." + }, + "whereNext": { + "heading": "A dónde seguir", + "items": [ + { + "label": "Crear copias", + "href": "/docs/backup-restore/creating-backups", + "tail": " — el flujo interactivo que comparte backend con el scheduler." + }, + { + "label": "Destinos", + "href": "/docs/backup-restore/destinations", + "tail": " — configuración por backend usada tanto por el flujo interactivo como por el scheduler." + }, + { + "label": "Restauración", + "href": "/docs/backup-restore/restoring", + "tail": " — el flujo que consume lo que producen los trabajos." + } + ] + } +} diff --git a/web/messages/es/docs/hardware/coral-tpu-lxc.json b/web/messages/es/docs/hardware/coral-tpu-lxc.json index 4cecdab0..6b169463 100644 --- a/web/messages/es/docs/hardware/coral-tpu-lxc.json +++ b/web/messages/es/docs/hardware/coral-tpu-lxc.json @@ -20,7 +20,7 @@ "title": "Antes de empezar", "drivers": "Drivers de Coral ya instalados en el host. Este script no los instala; solo configura el passthrough al contenedor. Ejecuta Install Coral TPU on the Host primero si no lo has hecho.", "driversCheck": "ls /dev/apex_* 2>/dev/null ; lsusb | grep -E '1a6e:089a|18d1:9302'", - "container": "Un contenedor LXC existente, idealmente con una distro basada en Debian / Ubuntu. La instalación dentro del contenedor usa apt-get; los contenedores Alpine / Arch no están soportados por este script actualmente.", + "container": "Un contenedor LXC existente, idealmente con una distro basada en Debian / Ubuntu — la instalación del runtime dentro del contenedor usa apt-get. Los contenedores no-Debian (Alpine, Arch, RHEL, SUSE…) siguen estando soportados en modo passthrough-only: el script detecta la distro y ofrece un prompt para saltarse la instalación APT de libedgetpu mientras escribe la config de passthrough del dispositivo — útil para contenedores de aplicación que ya incluyen el runtime (p.ej. la imagen Docker de Frigate).", "downtime": "Asume una breve interrupción del contenedor. El script lo para para aplicar los cambios de config y lo arranca de nuevo para instalar los drivers dentro. No hace falta reiniciar el host." }, "hostPrep": { @@ -70,8 +70,8 @@ ], "noIgpuTitle": "¿Por qué no hay drivers de iGPU aquí?", "noIgpuBody": "Versiones anteriores de este script también instalaban Intel va-driver-all, intel-opencl-icd y compañía para que el mismo contenedor pudiera hacer decode de vídeo Quick Sync junto a la inferencia de Coral. Esa doble responsabilidad causaba fallos confusos cuando el usuario solo quería Coral. El lado iGPU es ahora trabajo exclusivo de Añadir GPU a LXC — ejecútalo primero si también quieres decode de vídeo por hardware en el contenedor.", - "debianTitle": "Solo contenedores Debian / Ubuntu", - "debianBody": "La instalación dentro del contenedor usa apt-get directamente. Los contenedores Alpine, Arch o basados en RHEL no están soportados actualmente — el paso de instalación fallará y dejará el LXC con la config de passthrough pero sin drivers dentro. Para esas distros, instala el runtime de Coral manualmente siguiendo la guía oficial de Google después del paso de config del LXC." + "debianTitle": "Contenedores no-Debian — modo passthrough-only", + "debianBody": "La instalación del runtime dentro del contenedor usa apt-get, que solo viene con distros de la familia Debian/Ubuntu. En contenedores Alpine, Arch, RHEL o SUSE el script detecta la distro vía /etc/os-release y muestra un prompt de confirmación: continuar en modo passthrough-only (escribe la config del dispositivo en /etc/pve/lxc/<ctid>.conf y se salta la instalación APT de libedgetpu) o cancelar. Passthrough-only es la opción correcta si el contenedor de aplicación que va a usar Coral ya incluye el runtime — el ejemplo canónico es la imagen Docker de Frigate. Si no, sigue la guía oficial de Google para instalar libedgetpu manualmente después de que el script haya escrito la config del LXC." }, "summary": { "title": "Resumen", @@ -95,8 +95,8 @@ "apexBody": "El módulo apex del host no está cargado. En el host: lsmod | grep apex — si está vacío, ejecuta modprobe apex, o reinicia si acabas de instalar los drivers de Coral. Una vez el host tenga /dev/apex_0, reinicia el contenedor: pct stop <ctid> && pct start <ctid>.", "replugTitle": "La Coral USB desaparece al reconectarla en otro puerto", "replugBody": "Justo por eso el script monta /dev/bus/usb en lugar del symlink /dev/coral. Si te pasa esto, comprueba que tu config del LXC tiene lxc.mount.entry: /dev/bus/usb dev/bus/usb ... y no una referencia directa a /dev/coral. Las configs viejas de versiones anteriores del script pueden necesitar actualizarse — vuelve a ejecutar el script sobre el mismo contenedor y la config se refresca.", - "alpineTitle": "La instalación dentro del contenedor falla en un contenedor Alpine", - "alpineBody": "El script usa apt-get, que Alpine no tiene. La config de passthrough del LXC sigue siendo válida — solo instala el runtime de Coral manualmente con apk add siguiendo la guía de Google para Alpine, o usa un contenedor basado en Debian si no necesitas la huella más pequeña.", + "alpineTitle": "Contenedor Alpine / Arch / RHEL / SUSE — runtime no instalado", + "alpineBody": "Si elegiste modo passthrough-only cuando el script lo preguntó, la config del LXC se escribió y el dispositivo Coral es visible dentro del contenedor, pero el runtime libedgetpu no está instalado. Es así por diseño: el repo APT de Google solo se publica para Debian/Ubuntu. Instala el runtime manualmente con el gestor de paquetes de tu distro (Alpine: apk add; Arch: AUR; RHEL/SUSE: compilar desde fuente) siguiendo la guía oficial de Google, o usa un contenedor de aplicación que incluya el runtime — la imagen Docker de Frigate es el ejemplo canónico: solo expone el dispositivo con --device /dev/apex_0:/dev/apex_0 (M.2) o el bind mount USB que el script ya escribió (USB).", "frigateTitle": "Frigate dice 'Coral EdgeTPU detected but not available'", "frigateBody": "Casi siempre es un problema de permisos dentro del contenedor. Frigate corre como root por defecto; comprueba que el usuario root está en el grupo plugdev dentro del contenedor (para USB), y que el proceso puede leer /dev/apex_0 (para M.2). ls -l /dev/apex_0 desde dentro del contenedor debería mostrar el grupo apex — si no, añade el alineamiento de GID a /etc/group o cambia el contenedor a modo privilegiado.", "logsTitle": "Revisa los logs del host y del contenedor", diff --git a/web/messages/es/docs/monitor/dashboard/network.json b/web/messages/es/docs/monitor/dashboard/network.json index 0382c461..7ab83bba 100644 --- a/web/messages/es/docs/monitor/dashboard/network.json +++ b/web/messages/es/docs/monitor/dashboard/network.json @@ -35,6 +35,32 @@ } ] }, + "flow": { + "heading": "Diagrama Network Flow", + "intro": "Entre la fila superior y las tres tarjetas de grupos, la pestaña renderiza una vista de topología en vivo llamada Network Flow. Dibuja cada camino que un paquete puede tomar a través del host — NICs físicas en un extremo, bridges en el medio, VMs y contenedores en el otro — con pulsos animados que muestran el tráfico rx/tx en tiempo real sobre cada enlace.", + "imageAlt": "Diagrama Network Flow — NICs a la izquierda, host y bridges en el medio, VMs y LXCs a la derecha, con pulsos animados en cada conexión", + "imageCaption": "Network Flow — nodos con código de color más cometas animados cuya dirección y grosor reflejan el tráfico en vivo.", + "elementsTitle": "Qué muestra el diagrama", + "elementsIntro": "Cada nodo es una entidad del mismo inventario que usan las tarjetas de abajo, pero organizadas como topología para que las relaciones se vean de un vistazo:", + "elements": [ + "NICs (ámbar) — cada interfaz física con enlace activo. Las interfaces reportadas como down se dibujan atenuadas.", + "Host (ámbar) — el propio host Proxmox, situado entre las NICs y los bridges. Renderizado como anclaje fijo; no clicable.", + "Bridges (cian) — bridges Linux y OVS. Sólo se dibujan los bridges que llevan al menos un guest activo; los bridges sin uso se ocultan para que el diagrama muestre lo que realmente mueve tráfico.", + "LXCs (cian) — contenedores en ejecución conectados a un bridge.", + "VMs (púrpura) — máquinas virtuales en ejecución conectadas a un bridge.", + "Los guests offline (VMs / contenedores parados) se ocultan." + ], + "pulsesTitle": "Qué codifica la animación", + "pulsesBody": "Los cometas animados que viajan por cada arista representan tráfico en vivo:", + "pulses": [ + "Dirección — un pulso que fluye hacia la NIC es tx del guest; hacia el guest es rx.", + "Grosor de trazo escala con el rate combinado rx+tx del guest. Un guest inactivo dibuja una línea animada tenue; uno con carga la dibuja gruesa.", + "Halo de la cabeza del cometa — templado hacia ~1 MB/s, caliente a ≥30 MB/s. Útil para detectar de un vistazo qué guest está dominando una NIC.", + "Click — cada nodo excepto el host es clicable y abre el mismo modal de detalle por interfaz documentado más abajo. Pulsar el host es intencionadamente un no-op — no hay modal a nivel de host en esta vista." + ], + "useTitle": "Cuándo es útil", + "useBody": "Las tarjetas de grupos de abajo te dicen qué existe; el diagrama te dice cómo está conectado y por dónde fluye el tráfico ahora mismo. Patrones fáciles de ver de un vistazo: un bridge sin ningún guest activo conectado, dos guests pesados usando la misma NIC (candidato a cuello de botella), o una única VM saturando un enlace mientras el resto está tranquilo." + }, "groups": { "heading": "Tres grupos de interfaces", "intro": "Bajo la fila superior, tres tarjetas dividen el inventario por rol. Cada tarjeta tiene su propia insignia de recuento de activas en la cabecera. El tipo de interfaz se identifica de un vistazo mediante una insignia coloreada en cada fila:", diff --git a/web/messages/es/docs/monitor/dashboard/system-overview.json b/web/messages/es/docs/monitor/dashboard/system-overview.json index 2f686c13..0f0f76b6 100644 --- a/web/messages/es/docs/monitor/dashboard/system-overview.json +++ b/web/messages/es/docs/monitor/dashboard/system-overview.json @@ -53,6 +53,30 @@ "sparklineTitle": "El sparkline es significativo", "sparklineBody": "La tarjeta de temperatura dibuja una traza de 5 minutos bajo el valor, con la línea y el degradado siguiendo el mismo par Warning/Critical documentado arriba. Es la forma más rápida de ver si el host está en escalada térmica sin abrir la modal de detalle." }, + "processes": { + "heading": "Top procesos por CPU / Memoria", + "intro": "Las tarjetas CPU Usage y Memory de la pestaña Resumen son clicables. Al pulsar cualquiera de ellas se abre una lista ordenable con los 25 procesos top — ordenada por uso de CPU si la abriste desde la tarjeta de CPU, o por memoria residente si la abriste desde la de Memory.", + "listTitle": "El diálogo con la lista", + "listItems": [ + "Auto-refresco — la lista se actualiza cada 5 segundos mientras el diálogo está abierto y deja de pedir datos en cuanto se cierra.", + "Filtro — la caja de búsqueda acota la lista por command, user o PID.", + "Barra en línea — la columna de la métrica principal dibuja una barra pequeña escalada al mayor valor de la lista filtrada, para que el orden siga siendo visible aunque ningún proceso esté cerca del 100 %.", + "Layout móvil — por debajo de 640 px las columnas PID y User se ocultan para que Command, CPU % y Memory sigan cabiendo en la pantalla de un teléfono sin scroll horizontal." + ], + "captureListAlt": "Modal Top processes by Memory — tabla con columnas PID, USER, COMMAND, CPU %, Memory ordenada por RSS", + "captureListCaption": "La tarjeta Memory abre la lista ordenada por RSS (acento índigo). La tarjeta CPU abre la misma lista ordenada por uso de CPU (acento azul).", + "detailTitle": "Detalle por proceso", + "detailIntro": "Al pulsar cualquier fila de la lista se abre un segundo diálogo con la foto en vivo de ese proceso, organizada en cuatro secciones:", + "detailItems": [ + "Overview — estado, proceso padre, número de hilos, descriptores de fichero abiertos, usuario y grupo.", + "Resources — CPU %, Memoria %, Resident (RSS), Virtual size, Swap, totales de I/O de lectura y escritura.", + "Command — nombre corto, línea de comandos completa, ruta del ejecutable y directorio de trabajo.", + "Lifetime — timestamp de arranque y tiempo transcurrido en ejecución." + ], + "detailRefresh": "El diálogo de detalle se refresca cada 3 segundos mientras está abierto. Si el proceso termina con el diálogo abierto, el polling se detiene, aparece un banner ámbar This process has finished y el último snapshot capturado se queda en pantalla (atenuado) para que sigas viendo qué estaba pasando justo antes de que terminara.", + "captureDetailAlt": "Modal de detalle de proceso — secciones Overview, Resources, Command y Lifetime para un único PID", + "captureDetailCaption": "Diálogo de detalle por proceso abierto desde una fila de la lista. El color de acento sigue al de la tarjeta que lo abrió (azul para CPU, índigo para Memory)." + }, "middle": { "heading": "Medio: gráficas de métricas del nodo", "body1": "Bajo la fila superior se encuentra el componente NodeMetricsCharts — gráficas históricas de CPU, memoria y E/S de disco tomadas del propio almacén RRD de Proxmox vía /api/node/metrics. Un selector de timeframe alterna entre 1 hora / 24 horas / 7 días / 30 días / 1 año; la resolución de los datos baja a medida que crece la ventana para que la gráfica se mantenga fluida.", diff --git a/web/messages/es/docs/post-install/automated.json b/web/messages/es/docs/post-install/automated.json index 252f8092..3a220f49 100644 --- a/web/messages/es/docs/post-install/automated.json +++ b/web/messages/es/docs/post-install/automated.json @@ -79,13 +79,13 @@ }, { "tool": "Log2RAM (consciente de SSD, automático)", - "what": "Detecta si el disco raíz es SSD / NVMe e instala Log2RAM desde el git upstream. Dimensiona el ramdisk según la RAM del host (128M / 256M / 512M), programa sync periódico y un auto-sync con umbral del 95 %. Ajusta los límites de journald para caber en el ramdisk.", + "what": "Reduce el desgaste del SSD/NVMe moviendo /var/log a una ramdisk tmpfs con sync periódico al disco. Se instala desde el proyecto oficial cuando el disco raíz es SSD o NVMe. Dimensiona la ramdisk según la RAM (128M / 256M / 512M), programa el sync periódico y un guardián de auto-sync que compacta al 80% y trunca los logs calientes al 92% antes de escribir. Ajusta los límites de journald para caber en la ramdisk. Detalle completo en Log2RAM.", "category": "Storage", "categorySlug": "storage" }, { "tool": "ZFS autotrim (solo SSD)", - "what": "Activa zpool autotrim=on en cada pool ZFS cuyos vdevs sean todos SSD/NVMe con soporte TRIM (comprueba /sys/block//queue/rotational y discard_granularity). Los pools respaldados por HDDs se saltan automáticamente. Solo los pools realmente cambiados por ProxMenux quedan registrados para el uninstall — los pools en los que activaste autotrim a mano se dejan en paz.", + "what": "Activa zpool autotrim=on en cada pool ZFS cuyos vdevs sean todos SSD/NVMe con soporte TRIM (comprueba /sys/block/[dev]/queue/rotational y discard_granularity). Los pools respaldados por HDDs se saltan automáticamente. Solo los pools realmente cambiados por ProxMenux quedan registrados para el uninstall — los pools en los que activaste autotrim a mano se dejan en paz.", "category": "Storage", "categorySlug": "storage" }, diff --git a/web/messages/es/docs/post-install/customizable.json b/web/messages/es/docs/post-install/customizable.json index b1d2507c..7bcab5fb 100644 --- a/web/messages/es/docs/post-install/customizable.json +++ b/web/messages/es/docs/post-install/customizable.json @@ -25,43 +25,43 @@ "categories": [ { "name": "Basic Settings", - "description": "Repositorios, upgrade del sistema, zona horaria, locale, utilidades comunes." + "description": "Sanea las fuentes APT, ejecuta el upgrade oficial de Proxmox y fija zona horaria, locale y utilidades básicas. Es la base sobre la que apoyan todas las demás categorías." }, { "name": "System", - "description": "Journald, logrotate, límites del kernel, tuning de memoria, kernel panic, reinicios rápidos." + "description": "Ajusta el subsistema de logs y los límites del kernel para un host que aloja muchas VMs y contenedores. Cubre journald, logrotate, sysctl (memoria, límites de ficheros), comportamiento en panic y reinicios rápidos." }, { "name": "Virtualization", - "description": "Auto-instalación de guest agent, activación de IOMMU/VFIO para passthrough de PCI." + "description": "Prepara el host para virtualización avanzada. Auto-instala el guest agent en las plantillas y activa IOMMU/VFIO para passthrough de dispositivos PCI (GPU, controladoras, TPU)." }, { "name": "Network", - "description": "APT sobre IPv4, tuning de sysctl de red, Open vSwitch, TCP BBR, nombres de interfaz persistentes." + "description": "Endurece y afina la pila de red del host. Fuerza APT sobre IPv4, aplica sysctl con hardening y tuning de buffers TCP, ofrece Open vSwitch y BBR, y fija nombres persistentes de interfaces por MAC." }, { "name": "Storage", - "description": "Dimensionado del ARC de ZFS, ZFS auto-snapshot, límites de velocidad de backups vzdump." + "description": "Configura los subsistemas de almacenamiento habituales de Proxmox: ARC de ZFS, auto-snapshots y límites de velocidad de vzdump para evitar saturar el disco durante backups." }, { "name": "Security", - "description": "Desactivar portmapper/rpcbind para reducir la superficie de ataque." + "description": "Reduce la superficie de ataque expuesta por defecto. Desactiva los servicios RPC (portmapper/rpcbind) que Proxmox no necesita pero deja escuchando." }, { "name": "Customization", - "description": "Colores y aliases de bashrc, banner MOTD, eliminación del aviso de suscripción." + "description": "Cambia la experiencia visual y de shell del host: colores y aliases en bashrc, banner MOTD y eliminación del aviso de suscripción del web UI." }, { "name": "Monitoring", - "description": "OVH Real-Time Monitoring (solo en servidores detectados como OVH)." + "description": "Integra el host con Real-Time Monitoring de OVH. Sólo aparece cuando ProxMenux detecta un servidor OVH." }, { "name": "Performance", - "description": "gzip paralelo (pigz) para compresión más rápida en backups y transferencias." + "description": "Acelera las operaciones de compresión (backups, transferencias) sustituyendo gzip por su versión paralela pigz." }, { "name": "Optional", - "description": "Fixes de CPU AMD, Fastfetch, Figurine, repo de Ceph, High Availability, Log2RAM." + "description": "Piezas de nicho que no todo host necesita: fixes de CPU AMD, banner Fastfetch, hostname 3D con Figurine, repositorio de Ceph, servicios de Alta Disponibilidad y Log2RAM para reducir el desgaste del SSD." } ], "mixTip": { diff --git a/web/messages/es/docs/post-install/optional.json b/web/messages/es/docs/post-install/optional.json index 168f806b..ce5ea577 100644 --- a/web/messages/es/docs/post-install/optional.json +++ b/web/messages/es/docs/post-install/optional.json @@ -136,6 +136,27 @@ "automates": "Este ajuste automatiza el siguiente proceso:", "outro": "Tras la instalación, verás tu hostname mostrado en ASCII art 3D cada vez que hagas login, dejando claro inmediatamente con qué nodo Proxmox estás trabajando." }, + "log2ram": { + "title": "Instalar Log2RAM (reducción de desgaste en SSD/NVMe)", + "intro": "Log2RAM monta /var/log sobre una ramdisk tmpfs y sincroniza periódicamente el contenido al disco subyacente. En un hipervisor el churn de journald golpea el SSD del sistema cada pocos segundos; mover las escrituras a RAM reduce el desgaste y la I/O de disco sin perder logs — la sincronización periódica los vuelca a disco y un apagado limpio también los vuelca.", + "upstreamLabel": "Proyecto oficial:", + "upstreamUrl": "https://github.com/azlux/log2ram", + "upstreamLinkLabel": "azlux/log2ram en GitHub", + "doesLabel": "Qué hace ProxMenux:", + "doesItems": [ + "Detecta si el disco raíz es SSD/NVMe leyendo /sys/block/<dev>/queue/rotational. En un disco rotacional el flujo Automatizado pregunta antes de instalar.", + "Clona el repositorio oficial (azlux/log2ram) en /tmp/log2ram y ejecuta su install.sh, luego habilita la unit systemd log2ram.", + "Dimensiona la ramdisk según la RAM del host: ≤ 8 GB → 128M, ≤ 16 GB → 256M, > 16 GB → 512M. Escribe el valor en SIZE= de /etc/log2ram.conf.", + "Programa una sincronización periódica a disco vía /etc/cron.d/log2ram: cada 1h / 3h / 6h según el mismo tramo de RAM.", + "Instala un guardián de auto-sync en /usr/local/bin/log2ram-check.sh, conectado a /etc/cron.d/log2ram-auto-sync para ejecutarse cada 10 minutos. Cuando /var/log alcanza el 80% del tamaño de la ramdisk el guardián compacta journald; al 92% además trunca pveproxy access/error y pveam.log antes de sincronizar — el comando log2ram write por sí solo copia tmpfs a disco pero NO reduce la tmpfs, así que este guardián evita que PVE caiga con No space left on device cuando los logs crecen sin control.", + "Ajusta los límites de systemd-journald (SystemMaxUse, RuntimeMaxUse) para que quepan en la ramdisk y así una ráfaga puntual no la llene.", + "Se registra en installed_tools.json para poder revertirlo desde Uninstall Optimizations." + ], + "howUseLabel": "Cómo se usa:", + "howUseBody": "En el flujo Automatizado Log2RAM se aplica sin intervención cuando se detecta un disco raíz SSD/NVMe. En el flujo Personalizable el script pregunta por el tamaño, el intervalo de sincronización y si activar el guardián de auto-sync al 90%.", + "verifyLabel": "Verificar y gestionar desde la shell:", + "verifyCode": "# Estado del servicio y timers configurados\nsystemctl status log2ram\nsystemctl list-timers log2ram*\n\n# Uso actual de la ramdisk — /var/log ES la tmpfs\ndf -h /var/log\n\n# Forzar sincronización ahora (tmpfs → disco); NO reduce la tmpfs\nlog2ram write\n\n# Disparar el guardián de auto-sync manualmente (compacta + sincroniza si el uso es alto)\n/usr/local/bin/log2ram-check.sh\n\n# Configuración actual: SIZE, mail, path\ncat /etc/log2ram.conf\n\n# Crons específicos de ProxMenux\ncat /etc/cron.d/log2ram /etc/cron.d/log2ram-auto-sync\n\n# Actividad reciente de Log2RAM\njournalctl -u log2ram -n 50 --no-pager" + }, "autoApplication": { "title": "Aplicación automática", "body": "Estas funcionalidades opcionales solo se aplican cuando se seleccionan específicamente durante el proceso post-instalación. Cada funcionalidad puede elegirse individualmente según tus necesidades y preferencias específicas." diff --git a/web/messages/es/docs/post-install/storage.json b/web/messages/es/docs/post-install/storage.json index e350eb79..25944e0e 100644 --- a/web/messages/es/docs/post-install/storage.json +++ b/web/messages/es/docs/post-install/storage.json @@ -9,7 +9,7 @@ }, "intro": { "title": "Qué cubre esta categoría", - "body": "Tres optimizaciones relacionadas con almacenamiento: tunear el tamaño de la caché ARC de ZFS a una fracción sensata de la RAM del host, instalar y programar auto-snapshots de ZFS, y eliminar throttles de vzdump para que los backups corran a máxima velocidad. Las tres son independientes — elige las que se ajusten a tu setup." + "body": "Tres optimizaciones relacionadas con almacenamiento: tunear el tamaño de la caché ARC de ZFS a una fracción sensata de la RAM del host, instalar y programar auto-snapshots de ZFS, y eliminar throttles de vzdump para que los backups corran a máxima velocidad. Las tres son independientes — elige las que se ajusten a tu setup. Una cuarta optimización cercana al almacenamiento, Log2RAM, reduce el desgaste del SSD/NVMe moviendo /var/log a una ramdisk — vive en la página Optional porque el menú Personalizable de ProxMenux la agrupa allí." }, "notTrackedTitle": "Ninguna de estas está en el menú Uninstall", "notTrackedBody": "A diferencia de la mayoría de optimizaciones post-instalación, las tres opciones de Almacenamiento no se registran actualmente en el flujo Uninstall Optimizations. Si las aplicas y más tarde quieres revertir, tendrás que hacerlo a mano. Los comandos manuales de rollback se muestran bajo cada sección.", @@ -148,6 +148,11 @@ "href": "/docs/post-install/uninstall", "tail": " — revierte cambios de ARC / vzdump." }, + { + "label": "Log2RAM", + "href": "/docs/post-install/optional#log2ram", + "tail": " — reduce el desgaste del SSD/NVMe usando una ramdisk para /var/log; documentado en Optional." + }, { "label": "Customizable Post-Install", "href": "/docs/post-install/customizable", diff --git a/web/public/images/docs/backup-restore/backup-finished-monitor.png b/web/public/images/docs/backup-restore/backup-finished-monitor.png new file mode 100644 index 00000000..03fcaf0e Binary files /dev/null and b/web/public/images/docs/backup-restore/backup-finished-monitor.png differ diff --git a/web/public/images/docs/backup-restore/backup-finished-scripts.png b/web/public/images/docs/backup-restore/backup-finished-scripts.png new file mode 100644 index 00000000..ec823296 Binary files /dev/null and b/web/public/images/docs/backup-restore/backup-finished-scripts.png differ diff --git a/web/public/images/docs/backup-restore/custom-picker.png b/web/public/images/docs/backup-restore/custom-picker.png new file mode 100644 index 00000000..45873941 Binary files /dev/null and b/web/public/images/docs/backup-restore/custom-picker.png differ diff --git a/web/public/images/docs/backup-restore/manage-custom-paths.png b/web/public/images/docs/backup-restore/manage-custom-paths.png new file mode 100644 index 00000000..d53ed0a3 Binary files /dev/null and b/web/public/images/docs/backup-restore/manage-custom-paths.png differ diff --git a/web/public/images/docs/backup-restore/pbs-paired-backup-groups.png b/web/public/images/docs/backup-restore/pbs-paired-backup-groups.png new file mode 100644 index 00000000..b1e14cb7 Binary files /dev/null and b/web/public/images/docs/backup-restore/pbs-paired-backup-groups.png differ diff --git a/web/public/images/docs/backup-restore/postboot-completion-console.png b/web/public/images/docs/backup-restore/postboot-completion-console.png new file mode 100644 index 00000000..ac90c9d7 Binary files /dev/null and b/web/public/images/docs/backup-restore/postboot-completion-console.png differ diff --git a/web/public/images/docs/backup-restore/scheduled-backup-monitor.png b/web/public/images/docs/backup-restore/scheduled-backup-monitor.png new file mode 100644 index 00000000..8e6bbf9d Binary files /dev/null and b/web/public/images/docs/backup-restore/scheduled-backup-monitor.png differ diff --git a/web/public/monitor/dashboard-home.png b/web/public/monitor/dashboard-home.png index fbfe6856..d468d4a8 100644 Binary files a/web/public/monitor/dashboard-home.png and b/web/public/monitor/dashboard-home.png differ diff --git a/web/public/monitor/network-flow-overview.png b/web/public/monitor/network-flow-overview.png new file mode 100644 index 00000000..edccd481 Binary files /dev/null and b/web/public/monitor/network-flow-overview.png differ diff --git a/web/public/monitor/storage-top-row.png b/web/public/monitor/storage-top-row.png index 325a32cb..a97d96c6 100644 Binary files a/web/public/monitor/storage-top-row.png and b/web/public/monitor/storage-top-row.png differ diff --git a/web/public/monitor/system-overview-process-detail.png b/web/public/monitor/system-overview-process-detail.png new file mode 100644 index 00000000..7c792413 Binary files /dev/null and b/web/public/monitor/system-overview-process-detail.png differ diff --git a/web/public/monitor/system-overview-top-processes.png b/web/public/monitor/system-overview-top-processes.png new file mode 100644 index 00000000..5f43e9ac Binary files /dev/null and b/web/public/monitor/system-overview-top-processes.png differ diff --git a/web/public/monitor/vms-modal-backups.png b/web/public/monitor/vms-modal-backups.png index 8e67b8e4..40ffb8c5 100644 Binary files a/web/public/monitor/vms-modal-backups.png and b/web/public/monitor/vms-modal-backups.png differ diff --git a/web/public/monitor/vms-modal-status.png b/web/public/monitor/vms-modal-status.png index d2cc6046..5c11d3dc 100644 Binary files a/web/public/monitor/vms-modal-status.png and b/web/public/monitor/vms-modal-status.png differ diff --git a/web/public/monitor/vms-top-row.png b/web/public/monitor/vms-top-row.png index cdf423af..c1c9c93a 100644 Binary files a/web/public/monitor/vms-top-row.png and b/web/public/monitor/vms-top-row.png differ