ProxMenux 1.2.6.2-beta: OCI containers in the Monitor, docs and fixes

OCI manager Apps
- App tab: containers installed from an OCI image are identified from their
  installation record; the application and image versions are shown and an
  update is detected by image digest; repository link; Refresh data.
- Updates tab for OCI containers: Update and Recreate run the same flow as the
  OCI menu in the Monitor terminal; the pre-update backup can be kept in a
  backup storage; scheduled image updates with an optional minimum age.
- Logs tab: console output of the application, kept on the host
  (lxc.console.logfile + logrotate) and followed live.
- The Proxmox console opens a shell (cmode: shell) when the image has one.
- A damaged image download is fetched again before failing.
- Multi-container applications open at their LAN address; volume mount
  points on block storage report their usage.

Monitor
- Proxmox notifications are delivered to a loopback-only HTTP listener when
  HTTPS is enabled, so they no longer fail certificate verification.
- Log persistence counts recurring patterns only; an ended burst is not
  reported as persistent and its warning clears on its own (#386).
- Proxmox notification config backups are deduplicated and capped at three.
- The update icon on the Apps page opens the container on its Updates tab.
- Version 1.2.6.2-beta and its release notes in every Monitor language.

Docs
- OCI manager Apps and Audit & Report rebuilt as per-page message files,
  with a new page for OCI containers in the Monitor.
- Seven pages fixed where rich-text tags were missing from t.rich.

Translations
- Spanish fixes across the OCI engine, the Monitor and the TUI menus.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MacRimi
2026-09-25 21:51:12 +02:00
co-authored by Claude Opus 5.5
parent 386d33df6e
commit 4437a671d2
524 changed files with 14459 additions and 3841 deletions
@@ -288,7 +288,7 @@ export default async function GpuVmPassthroughPage({
<p className="mb-3 text-gray-800">{t("walkthrough.switchMode.intro")}</p>
<ul className="list-disc pl-6 space-y-1 text-gray-800 mb-3">
{switchModeItems.map((_, idx) => (
<li key={idx}>{t.rich(`walkthrough.switchMode.items.${idx}`, { strong, code })}</li>
<li key={idx}>{t.rich(`walkthrough.switchMode.items.${idx}`, { strong, code, em })}</li>
))}
</ul>
<Image
@@ -333,7 +333,7 @@ export default async function GpuVmPassthroughPage({
<p className="mb-3 text-gray-800">{t("walkthrough.hostApply.intro")}</p>
<ul className="list-disc pl-6 space-y-1 text-gray-800 mb-3">
{hostApplyItems.map((_, idx) => (
<li key={idx}>{t.rich(`walkthrough.hostApply.items.${idx}`, { strong, code })}</li>
<li key={idx}>{t.rich(`walkthrough.hostApply.items.${idx}`, { strong, code, em })}</li>
))}
</ul>
</Steps.Step>
@@ -249,7 +249,7 @@ export default async function NvidiaHostPage({
</Steps.Step>
<Steps.Step title={t("walkthrough.version.title")}>
<p className="mb-3 text-gray-800">{t.rich("walkthrough.version.body1", { strong, em })}</p>
<p className="mb-3 text-gray-800">{t.rich("walkthrough.version.body1", { strong, em, code })}</p>
<p className="mb-3 text-gray-800">{t("walkthrough.version.body2")}</p>
<Callout variant="tip" title={t("walkthrough.version.whyTitle")}>
@@ -312,7 +312,7 @@ sh NVIDIA-Linux-x86_64-<version>.run \\
<Steps.Step title={t("walkthrough.propagate.title")}>
<p className="mb-3 text-gray-800">{t.rich("walkthrough.propagate.body1", { code, strong })}</p>
<p className="mb-3 text-gray-800">{t.rich("walkthrough.propagate.body2", { code })}</p>
<p className="mb-3 text-gray-800">{t.rich("walkthrough.propagate.body2", { code, strong })}</p>
<Image
src="/gpu-tpu/nvidia-host-05-lxc-update.png"
alt={t("walkthrough.propagate.imageAlt")}
@@ -353,7 +353,7 @@ sh NVIDIA-Linux-x86_64-<version>.run \\
<p className="mb-4 text-gray-800 leading-relaxed">{t("reinstallUninstall.uninstallIntro")}</p>
<ul className="list-disc pl-6 mb-4 text-gray-800 leading-relaxed space-y-1">
{uninstallItems.map((_, idx) => (
<li key={idx}>{t.rich(`reinstallUninstall.uninstallItems.${idx}`, { code })}</li>
<li key={idx}>{t.rich(`reinstallUninstall.uninstallItems.${idx}`, { code, em })}</li>
))}
</ul>
@@ -119,7 +119,7 @@ export default async function SwitchGpuModePage({
items={[
{ label: <>{t.rich("prereqs.assigned", { strong })}</> },
{
label: <>{t.rich("prereqs.iommu", { strong, em })}</>,
label: <>{t.rich("prereqs.iommu", { strong, em, code })}</>,
check: t("prereqs.iommuCheck"),
},
{ label: <>{t.rich("prereqs.reboot", { strong })}</> },
+4 -4
View File
@@ -63,7 +63,7 @@ export default async function MonitorApiPage({
auth: { rows: EndpointRow[]; items: string[] }
conventions: { items: string[] }
system: { rows: EndpointRow[] }
actions: { rows: EndpointRow[] }
actions: { rows: EndpointRow[]; shapeCode: string; curlCode: string; haCode: string }
health: { rows: EndpointRow[] }
storage: { rows: EndpointRow[] }
network: { rows: EndpointRow[] }
@@ -188,7 +188,7 @@ export default async function MonitorApiPage({
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.shapeTitle")}</h3>
<p className="mb-2 text-gray-800 leading-relaxed">{t("actions.shapeIntro")}</p>
<CopyableCode code={t("actions.shapeCode")} className="my-4" />
<CopyableCode code={messages.docs.monitor.apiReference.actions.shapeCode} className="my-4" />
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.concurrencyTitle")}</h3>
<p className="mb-4 text-gray-800 leading-relaxed">{t.rich("actions.concurrencyBody", { code })}</p>
@@ -200,11 +200,11 @@ export default async function MonitorApiPage({
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.curlTitle")}</h3>
<p className="mb-2 text-gray-800 leading-relaxed">{t("actions.curlBody")}</p>
<CopyableCode code={t("actions.curlCode")} className="my-4" />
<CopyableCode code={messages.docs.monitor.apiReference.actions.curlCode} className="my-4" />
<h3 className="text-lg font-semibold mt-6 mb-2 text-gray-900">{t("actions.haTitle")}</h3>
<p className="mb-2 text-gray-800 leading-relaxed">{t.rich("actions.haBody", { code })}</p>
<CopyableCode code={t("actions.haCode")} className="my-4" />
<CopyableCode code={messages.docs.monitor.apiReference.actions.haCode} className="my-4" />
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("health.heading")}</h2>
{endpointTable(healthRows, "health.rows")}
@@ -0,0 +1,26 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.assessment"
const LINKS = {
reportsLink: "/docs/monitor/audit-report/reports",
}
const URL = "https://proxmenux.com/docs/monitor/audit-report/assessment"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={6} links={LINKS} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.changes"
const URL = "https://proxmenux.com/docs/monitor/audit-report/changes"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={5} />
}
@@ -0,0 +1,26 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport"
const LINKS = {
healthLink: "/docs/monitor/health-monitor",
}
const URL = "https://proxmenux.com/docs/monitor/audit-report"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={3} links={LINKS} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.policy"
const URL = "https://proxmenux.com/docs/monitor/audit-report/policy"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={4} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.reports"
const URL = "https://proxmenux.com/docs/monitor/audit-report/reports"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={6} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.monitor.auditReport.scope"
const URL = "https://proxmenux.com/docs/monitor/audit-report/scope"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={3} />
}
@@ -292,6 +292,16 @@ export default async function VmsLxcsTabPage({
{t("drillIn.mountsCalloutBody")}
</Callout>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.logsTitle")}</h3>
<p className="mb-4 text-gray-800 leading-relaxed">
{t.rich("drillIn.logsBody", {
code,
ociLink: (chunks: React.ReactNode) => (
<Link href="/docs/oci-manager/monitor" className="text-blue-600 hover:underline">{chunks}</Link>
),
})}
</p>
<h3 className="text-lg font-semibold mt-8 mb-2 text-gray-900">{t("drillIn.backupsTitle")}</h3>
<figure className="my-4">
@@ -173,7 +173,7 @@ export default async function HealthMonitorPage({
<tr key={row.category} className={idx < categoryRows.length - 1 ? "border-b border-gray-100" : ""}>
<td className="px-3 py-2 align-top whitespace-nowrap"><strong>{row.category}</strong></td>
<td className="px-3 py-2 align-top">{t.rich(`categories.rows.${idx}.checks`, { code })}</td>
<td className="px-3 py-2 align-top">{t.rich(`categories.rows.${idx}.events`, { code })}</td>
<td className="px-3 py-2 align-top">{t.rich(`categories.rows.${idx}.events`, { code, em })}</td>
</tr>
))}
</tbody>
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager.architecture"
const URL = "https://proxmenux.com/docs/oci-manager/architecture"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={6} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager.customImage"
const URL = "https://proxmenux.com/docs/oci-manager/custom-image"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={6} />
}
@@ -0,0 +1,29 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager.hardware"
const URL = "https://proxmenux.com/docs/oci-manager/hardware"
const LINKS = {
nvidiaLink: "/docs/hardware/nvidia-host",
auditLink: "/docs/monitor/audit-report/changes",
toolkitLink: "https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html",
}
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={9} links={LINKS} />
}
@@ -0,0 +1,27 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager.lifecycle"
const URL = "https://proxmenux.com/docs/oci-manager/lifecycle"
const LINKS = {
monitorLink: "/docs/oci-manager/monitor",
}
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={7} links={LINKS} />
}
@@ -0,0 +1,30 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager.monitor"
const URL = "https://proxmenux.com/docs/oci-manager/monitor"
const LINKS = {
appLink: "/docs/monitor/dashboard/vms-lxcs/app",
notificationsLink: "/docs/monitor/notifications",
lifecycleLink: "/docs/oci-manager/lifecycle",
mountsLink: "/docs/monitor/dashboard/vms-lxcs",
}
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={6} links={LINKS} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager"
const URL = "https://proxmenux.com/docs/oci-manager"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={4} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager.stacks"
const URL = "https://proxmenux.com/docs/oci-manager/stacks"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={10} />
}
@@ -0,0 +1,23 @@
import type { Metadata } from "next"
import { getTranslations, setRequestLocale } from "next-intl/server"
import { DocPage } from "@/components/docs/DocBlocks"
const NAMESPACE = "docs.ociManager.storageNetwork"
const URL = "https://proxmenux.com/docs/oci-manager/storage-network"
export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: `${NAMESPACE}.meta` })
return {
title: t("title"),
description: t("description"),
alternates: { canonical: URL },
openGraph: { title: t("title"), description: t("description"), type: "article", url: URL },
}
}
export default async function Page({ params }: { params: Promise<{ locale: string }> }) {
const { locale } = await params
setRequestLocale(locale)
return <DocPage locale={locale} namespace={NAMESPACE} minutes={8} />
}
@@ -175,11 +175,11 @@ export default async function SystemUpdatePage({
<h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t("noSub.heading")}</h2>
<p className="mb-6 text-gray-800 leading-relaxed">
{t.rich("noSub.intro", { code })}
{t.rich("noSub.intro", { code, strong })}
</p>
<ol className="list-decimal pl-6 mb-6 text-gray-800 leading-relaxed space-y-1">
{noSubItems.map((_, idx) => (
<li key={idx}>{t.rich(`noSub.items.${idx}`, { code })}</li>
<li key={idx}>{t.rich(`noSub.items.${idx}`, { code, strong })}</li>
))}
</ol>
<p className="mb-6 text-gray-800 leading-relaxed">{t("noSub.outro")}</p>
+29
View File
@@ -80,6 +80,19 @@ export const sidebarItems: MenuItem[] = [
],
},
{ title: "Health Monitor", i18nKey: "healthMonitor", href: "/docs/monitor/health-monitor" },
{
title: "Audit & Report",
i18nKey: "auditReport",
href: "/docs/monitor/audit-report",
submenu: [
{ title: "Overview", i18nKey: "auditReportOverview", href: "/docs/monitor/audit-report" },
{ title: "Assessment & inventory", i18nKey: "auditReportAssessment", href: "/docs/monitor/audit-report/assessment" },
{ title: "Changes", i18nKey: "auditReportChanges", href: "/docs/monitor/audit-report/changes" },
{ title: "Policy", i18nKey: "auditReportPolicy", href: "/docs/monitor/audit-report/policy" },
{ title: "Reports & comparisons", i18nKey: "auditReportReports", href: "/docs/monitor/audit-report/reports" },
{ title: "Scope & guarantees", i18nKey: "auditReportScope", href: "/docs/monitor/audit-report/scope" },
],
},
{ title: "Notifications", i18nKey: "notifications", href: "/docs/monitor/notifications" },
{ title: "AI Assistant", i18nKey: "aiAssistant", href: "/docs/monitor/ai-assistant" },
{ title: "API Reference", i18nKey: "apiReference", href: "/docs/monitor/api" },
@@ -87,6 +100,22 @@ export const sidebarItems: MenuItem[] = [
],
},
{
title: "OCI manager Apps",
i18nKey: "ociManager",
href: "/docs/oci-manager",
submenu: [
{ title: "Overview", i18nKey: "ociOverview", href: "/docs/oci-manager" },
{ title: "How an OCI image is translated", i18nKey: "ociArchitecture", href: "/docs/oci-manager/architecture" },
{ title: "Image not in the catalog", i18nKey: "ociCustomImage", href: "/docs/oci-manager/custom-image" },
{ title: "Data, paths and networking", i18nKey: "ociStorageNetwork", href: "/docs/oci-manager/storage-network" },
{ title: "Devices and acceleration", i18nKey: "ociHardware", href: "/docs/oci-manager/hardware" },
{ title: "Multi-container applications", i18nKey: "ociStacks", href: "/docs/oci-manager/stacks" },
{ title: "Install, update and recreate", i18nKey: "ociLifecycle", href: "/docs/oci-manager/lifecycle" },
{ title: "In ProxMenux Monitor", i18nKey: "ociMonitor", href: "/docs/oci-manager/monitor" },
],
},
{
title: "ProxMenux Scripts",
i18nKey: "proxmenuxScripts",
+334
View File
@@ -0,0 +1,334 @@
import type { ReactNode } from "react"
import Image from "next/image"
import { getMessages, getTranslations } from "next-intl/server"
import {
Archive,
Boxes,
Braces,
CheckCircle2,
Cpu,
Database,
Download,
ExternalLink,
FileText,
FolderTree,
HardDrive,
Layers3,
Network,
RefreshCw,
ShieldCheck,
SquareTerminal,
Waypoints,
} from "lucide-react"
import { Link } from "@/i18n/navigation"
import { Callout } from "@/components/ui/callout"
import { DataFlowDiagram } from "@/components/ui/data-flow-diagram"
import { DocHeader } from "@/components/ui/doc-header"
import { Mermaid } from "@/components/ui/mermaid"
import { STACK_DEPENDENCY_HOOK_SOURCE } from "@/components/oci/stackDependencyHookSource"
/*
* A documentation page described entirely by its message file.
*
* The page JSON holds `header` and an ordered list of `sections`, each with
* an optional title and intro and a list of blocks. A block is an object
* with a single key naming its kind ({"p": "..."}, {"table": {...}}), so no
* structural value is ever a translatable string. Values the translation
* pipeline must leave alone live under keys it copies verbatim: `code`,
* `chartCode`, `icon`, `src`, `href`, `id`.
*
* Prose goes through t.rich, so it may use <code>, <strong>, <em> and the
* link tags the page declares.
*/
type Rich = (chunks: ReactNode) => ReactNode
type Json = Record<string, any>
const ICONS: Record<string, ReactNode> = {
archive: <Archive className="h-5 w-5" />,
boxes: <Boxes className="h-5 w-5" />,
braces: <Braces className="h-5 w-5" />,
cpu: <Cpu className="h-5 w-5" />,
database: <Database className="h-5 w-5" />,
fileText: <FileText className="h-5 w-5" />,
folderTree: <FolderTree className="h-5 w-5" />,
hardDrive: <HardDrive className="h-5 w-5" />,
layers: <Layers3 className="h-5 w-5" />,
network: <Network className="h-5 w-5" />,
refresh: <RefreshCw className="h-5 w-5" />,
shield: <ShieldCheck className="h-5 w-5" />,
terminal: <SquareTerminal className="h-5 w-5" />,
waypoints: <Waypoints className="h-5 w-5" />,
}
const CALLOUTS = {
calloutInfo: "info",
calloutTip: "tip",
calloutWarning: "warning",
calloutDanger: "danger",
} as const
const SNIPPETS: Record<string, string> = {
stackDependencyHook: STACK_DEPENDENCY_HOOK_SOURCE,
}
function at(root: Json, path: string): any {
return path.split(".").reduce((node, key) => (node == null ? undefined : node[key]), root as any)
}
export async function DocPage({
locale,
namespace,
minutes,
links = {},
}: {
locale: string
namespace: string
minutes: number
links?: Record<string, string>
}) {
const t = await getTranslations({ locale, namespace })
const messages = (await getMessages({ locale })) as Json
const page = at(messages, namespace) as Json
const tags: Record<string, Rich> = {
code: (chunks) => <code>{chunks}</code>,
strong: (chunks) => <strong>{chunks}</strong>,
em: (chunks) => <em>{chunks}</em>,
}
for (const [name, href] of Object.entries(links)) {
tags[name] = href.startsWith("http")
? (chunks) => (
<a href={href} target="_blank" rel="noopener noreferrer" className="inline-flex items-center gap-1 text-blue-600 hover:underline">
{chunks}
<ExternalLink className="h-3.5 w-3.5" />
</a>
)
: (chunks) => <Link href={href} className="text-blue-600 hover:underline">{chunks}</Link>
}
const rich = (key: string) => t.rich(key, tags)
const block = (key: string, value: Json, index: number): ReactNode => {
const [kind] = Object.keys(value)
const base = `${key}.${kind}`
const data = value[kind]
switch (kind) {
case "p":
return <p key={index} className="mb-4 text-gray-800 leading-relaxed">{rich(base)}</p>
case "calloutInfo":
case "calloutTip":
case "calloutWarning":
case "calloutDanger":
return (
<Callout key={index} variant={CALLOUTS[kind]} title={t(`${base}.title`)}>
{rich(`${base}.body`)}
</Callout>
)
case "list":
return (
<ul key={index} className="mb-6 space-y-2 text-gray-800">
{data.items.map((_: string, i: number) => (
<li key={i} className="flex gap-3 leading-relaxed">
<CheckCircle2 className="mt-1 h-4 w-4 shrink-0 text-blue-600" />
<span>{rich(`${base}.items.${i}`)}</span>
</li>
))}
</ul>
)
case "steps":
return (
<ol key={index} className="mb-6 space-y-3">
{data.items.map((_: Json, i: number) => (
<li key={i} className="grid grid-cols-[2.25rem_1fr] gap-4 rounded-lg border border-gray-200 bg-gray-50 p-4">
<span className="flex h-9 w-9 items-center justify-center rounded-full bg-blue-700 text-sm font-bold text-white">{i + 1}</span>
<div>
<h3 className="font-semibold text-gray-900">{t(`${base}.items.${i}.title`)}</h3>
<p className="mt-1 text-sm leading-6 text-gray-700">{rich(`${base}.items.${i}.body`)}</p>
</div>
</li>
))}
</ol>
)
case "cards":
return (
<div key={index} className="mb-6 grid gap-4 md:grid-cols-2">
{data.items.map((item: Json, i: number) => (
<article key={i} className="rounded-lg border border-gray-200 bg-white p-5">
{item.icon && ICONS[item.icon] && (
<div className="mb-3 inline-flex rounded-lg border border-blue-100 bg-blue-50 p-2 text-blue-700">{ICONS[item.icon]}</div>
)}
<h3 className="font-semibold text-gray-900">{t(`${base}.items.${i}.title`)}</h3>
<p className="mt-2 text-sm leading-6 text-gray-700">{rich(`${base}.items.${i}.body`)}</p>
</article>
))}
</div>
)
case "table":
return (
<div key={index} className="mb-6 overflow-x-auto">
<table className="w-full text-sm border border-gray-200 rounded-md">
<thead className="bg-gray-50 text-gray-900">
<tr>
{data.headers.map((_: string, i: number) => (
<th key={i} className="text-left px-3 py-2 border-b border-gray-200 font-semibold">{t(`${base}.headers.${i}`)}</th>
))}
</tr>
</thead>
<tbody className="text-gray-800">
{data.rows.map((row: string[], r: number) => (
<tr key={r} className={r < data.rows.length - 1 ? "border-b border-gray-100" : ""}>
{row.map((_: string, c: number) => (
<td key={c} className={`px-3 py-2 align-top leading-6 ${c === 0 ? "font-medium text-gray-900" : ""}`}>
{rich(`${base}.rows.${r}.${c}`)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
)
case "flow":
return (
<DataFlowDiagram
key={index}
className="mb-6"
nodes={data.nodes.map((_: Json, i: number) => ({
label: t(`${base}.nodes.${i}.label`),
detail: data.nodes[i].detail ? t(`${base}.nodes.${i}.detail`) : undefined,
variant: i === 0 ? "source" : i === data.nodes.length - 1 ? "target" : "bridge",
}))}
caption={data.caption ? t(`${base}.caption`) : undefined}
/>
)
case "code":
return (
<div key={index} className="mb-6 overflow-hidden rounded-lg border border-gray-200">
{data.title && <div className="border-b border-gray-200 bg-gray-50 px-4 py-2 text-sm font-semibold text-gray-900">{t(`${base}.title`)}</div>}
<pre className="overflow-x-auto bg-white p-4 text-xs leading-6 text-gray-800">{data.code}</pre>
</div>
)
case "codeGrid":
return (
<div key={index} className="mb-6 grid gap-4 lg:grid-cols-2">
{data.items.map((item: Json, i: number) => (
<div key={i} className="overflow-hidden rounded-lg border border-gray-200">
<div className="border-b border-gray-200 bg-gray-50 px-4 py-2 text-sm font-semibold text-gray-900">{t(`${base}.items.${i}.title`)}</div>
<pre className="overflow-x-auto bg-white p-4 text-xs leading-6 text-gray-800">{item.code}</pre>
</div>
))}
</div>
)
case "shell":
return (
<pre key={index} className="mb-6 overflow-x-auto rounded-lg bg-gray-950 p-4 text-xs leading-6 text-gray-100">{data.code}</pre>
)
case "mermaid": {
let chart: string = data.chartCode
for (const name of Object.keys(data.labels ?? {})) {
chart = chart.split(`{{${name}}}`).join(t(`${base}.labels.${name}`).replace(/"/g, "'"))
}
return <div key={index} className="mb-6"><Mermaid chart={chart} /></div>
}
case "figure":
return (
<figure key={index} className="my-6">
<Image
src={data.src}
alt={t(`${base}.alt`)}
width={data.width ?? 1600}
height={data.height ?? 1000}
className="rounded-lg border border-gray-200 shadow-sm w-full h-auto"
/>
{data.caption && (
<figcaption className="text-sm text-gray-500 mt-2 text-center italic">{rich(`${base}.caption`)}</figcaption>
)}
</figure>
)
case "snippet":
return (
<details key={index} className="group mb-6 overflow-hidden rounded-lg border border-blue-200 bg-white">
<summary className="flex cursor-pointer list-none items-center justify-between gap-4 bg-blue-50 px-5 py-3 font-semibold text-gray-900 marker:content-none">
<span>{t(`${base}.summary`)}</span>
<code className="text-xs font-normal text-gray-600">{data.pathCode}</code>
</summary>
<pre className="max-h-[42rem] overflow-auto border-t border-blue-200 bg-gray-950 p-5 text-xs leading-6 text-gray-100">
<code>{SNIPPETS[data.snippetCode] ?? ""}</code>
</pre>
</details>
)
case "downloads":
return (
<div key={index} className="mb-6 space-y-4">
{data.items.map((item: Json, i: number) => (
<article key={i} className="overflow-hidden rounded-lg border border-gray-200 bg-white">
<div className="flex items-start gap-4 border-b border-gray-200 bg-gray-50 px-4 py-3">
<div className="min-w-0 flex-1">
<h3 className="font-semibold text-gray-900">{t(`${base}.items.${i}.title`)}</h3>
<p className="mt-1 text-sm leading-6 text-gray-700">{rich(`${base}.items.${i}.body`)}</p>
</div>
<a href={item.href} target="_blank" rel="noopener noreferrer" className="inline-flex shrink-0 items-center gap-2 rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-sm font-medium text-blue-800 hover:bg-blue-100">
<Download className="h-4 w-4" />PDF
</a>
</div>
<dl className="grid text-sm md:grid-cols-[11rem_1fr]">
{(item.facts ?? []).map((_: Json, f: number) => (
<div key={f} className="contents">
<dt className="border-t border-gray-200 bg-white px-4 py-2 font-semibold text-gray-900">{t(`${base}.items.${i}.facts.${f}.label`)}</dt>
<dd className="border-t border-gray-200 bg-white px-4 py-2 leading-6 text-gray-700">{rich(`${base}.items.${i}.facts.${f}.value`)}</dd>
</div>
))}
</dl>
</article>
))}
</div>
)
case "next":
return (
<ul key={index} className="mb-6 list-disc pl-6 space-y-1 text-gray-800">
{data.items.map((item: Json, i: number) => (
<li key={i}>
<Link href={item.href} className="text-blue-600 hover:underline">{t(`${base}.items.${i}.label`)}</Link>
{item.tail ? <> — {t(`${base}.items.${i}.tail`)}</> : null}
</li>
))}
</ul>
)
default:
return null
}
}
return (
<div className="pb-16">
<DocHeader
title={t("header.title")}
description={t("header.description")}
section={t("header.section")}
estimatedMinutes={minutes}
/>
{(page.sections as Json[]).map((section, s) => (
<section key={s} id={section.id} className="scroll-mt-24">
{section.title && <h2 className="text-2xl font-semibold mt-10 mb-4 text-gray-900">{t(`sections.${s}.title`)}</h2>}
{section.intro && <p className="mb-4 text-gray-800 leading-relaxed">{rich(`sections.${s}.intro`)}</p>}
{(section.blocks as Json[]).map((value, b) => block(`sections.${s}.blocks.${b}`, value, b))}
</section>
))}
</div>
)
}
@@ -0,0 +1,4 @@
// Generated documentation snapshot of oci/remote/stack_dependency_hook.sh.
// Keep this file synchronized when the installed hook changes.
export const STACK_DEPENDENCY_HOOK_SOURCE = "#!/usr/bin/env bash\nset -Eeuo pipefail\n\nPATH=/usr/sbin:/usr/bin:/sbin:/bin\n\ndie() {\n printf 'ERROR: %s\\n' \"$*\" >&2\n exit 1\n}\n\nfind_snippet_storage() {\n local storage\n if pvesm status --content snippets 2>/dev/null \\\n | awk 'NR > 1 && $1 == \"local\" && $3 == \"active\" {found=1} END {exit !found}'; then\n printf 'local'\n return\n fi\n storage=$(pvesm status --content snippets 2>/dev/null \\\n | awk 'NR > 1 && $3 == \"active\" {print $1; exit}')\n [[ -n $storage ]] || die \"No active storage supports snippets\"\n printf '%s' \"$storage\"\n}\n\ninstall_hook() {\n local main_id=${1:?missing main VMID} source_config=${2:?missing lifecycle JSON}\n local storage hook_volume hook_path target_config\n [[ $main_id =~ ^[0-9]+$ ]] || die \"Invalid main VMID\"\n jq -e '\n .schema == 1 and\n (.dependencies | type == \"array\") and\n (.dependencies | length > 0) and\n (all(.dependencies[];\n (.vmid | type == \"number\") and\n (.label | type == \"string\") and\n (.healthcheck.type | IN(\"exec\", \"http\", \"running\")) and\n (.healthcheck.timeout_seconds | type == \"number\")\n ))\n ' \"$source_config\" >/dev/null || die \"Invalid dependency contract\"\n\n storage=$(find_snippet_storage)\n hook_volume=\"${storage}:snippets/proxmenux-stack-dependencies.sh\"\n hook_path=$(pvesm path \"$hook_volume\")\n install -D -m 0755 \"$0\" \"$hook_path\"\n\n target_config=\"/etc/pve/priv/proxmenux-stack-${main_id}.json\"\n umask 077\n cat \"$source_config\" >\"$target_config\"\n pct set \"$main_id\" --hookscript \"$hook_volume\" >/dev/null\n printf 'Proxmox hookscript installed: CT %s starts its dependencies through %s\\n' \\\n \"$main_id\" \"$hook_volume\"\n}\n\ndependency_is_healthy() {\n local id=$1 healthcheck=$2 type url\n type=$(jq -r '.type' <<<\"$healthcheck\")\n case \"$type\" in\n running)\n [[ $(pct status \"$id\" 2>/dev/null || true) == \"status: running\" ]]\n ;;\n exec)\n local -a command=()\n mapfile -t command < <(jq -r '.argv[]' <<<\"$healthcheck\")\n ((${#command[@]} > 0)) || return 1\n pct exec \"$id\" -- \"${command[@]}\" >/dev/null 2>&1\n ;;\n http)\n url=$(jq -r '.url' <<<\"$healthcheck\")\n curl -fsS --max-time 3 \"$url\" >/dev/null 2>&1\n ;;\n *) return 1 ;;\n esac\n}\n\nstart_dependencies() {\n local main_id=$1 config=\"/etc/pve/priv/proxmenux-stack-${1}.json\"\n local encoded dependency id label healthcheck timeout elapsed\n [[ -r $config ]] || die \"Missing dependency contract for main CT $main_id\"\n\n exec 9>\"/run/lock/proxmenux-stack-${main_id}.lock\"\n flock 9\n while IFS= read -r encoded; do\n [[ -n $encoded ]] || continue\n dependency=$(base64 -d <<<\"$encoded\")\n id=$(jq -r '.vmid' <<<\"$dependency\")\n label=$(jq -r '.label' <<<\"$dependency\")\n healthcheck=$(jq -c '.healthcheck' <<<\"$dependency\")\n timeout=$(jq -r '.healthcheck.timeout_seconds' <<<\"$dependency\")\n [[ $id =~ ^[0-9]+$ && $timeout =~ ^[0-9]+$ && $timeout -gt 0 ]] \\\n || die \"Invalid dependency in $config\"\n pct config \"$id\" >/dev/null 2>&1 \\\n || die \"Dependency $label (CT $id) does not exist\"\n\n if [[ $(pct status \"$id\" 2>/dev/null || true) != \"status: running\" ]]; then\n printf 'Starting dependency %s (CT %s)...\\n' \"$label\" \"$id\"\n pct start \"$id\" || die \"Could not start $label (CT $id)\"\n else\n printf 'Dependency %s (CT %s) was already running.\\n' \"$label\" \"$id\"\n fi\n\n elapsed=0\n while (( elapsed < timeout )); do\n if dependency_is_healthy \"$id\" \"$healthcheck\"; then\n printf 'Dependency %s (CT %s): ready.\\n' \"$label\" \"$id\"\n break\n fi\n [[ $(pct status \"$id\" 2>/dev/null || true) == \"status: running\" ]] \\\n || die \"$label (CT $id) stopped while starting\"\n sleep 2\n elapsed=$((elapsed + 2))\n done\n (( elapsed < timeout )) \\\n || die \"$label (CT $id) did not pass its health check within ${timeout}s\"\n done < <(jq -r '.dependencies[] | @base64' \"$config\")\n}\n\nif [[ ${1:-} == \"--install\" ]]; then\n shift\n install_hook \"$@\"\n exit 0\nfi\n\nvmid=${1:?missing VMID}\nphase=${2:?missing lifecycle phase}\ncase \"$phase\" in\n pre-start) start_dependencies \"$vmid\" ;;\n post-start|pre-stop|post-stop) ;;\n *) die \"Unknown lifecycle phase: $phase\" ;;\nesac\n"
+16
View File
@@ -71,10 +71,26 @@
"dashboardSecurity": "Security tab",
"dashboardSettings": "Settings tab",
"healthMonitor": "Health Monitor",
"auditReport": "Audit & Report",
"auditReportOverview": "Overview",
"auditReportAssessment": "Assessment & inventory",
"auditReportPolicy": "Policy",
"auditReportChanges": "Changes",
"auditReportReports": "Reports & comparisons",
"auditReportScope": "Scope & guarantees",
"notifications": "Notifications",
"aiAssistant": "AI Assistant",
"apiReference": "API Reference",
"integrations": "Integrations",
"ociManager": "OCI manager Apps",
"ociOverview": "Overview",
"ociArchitecture": "How an OCI image is translated",
"ociCustomImage": "Image not in the catalog",
"ociStorageNetwork": "Data, paths and networking",
"ociHardware": "Devices and acceleration",
"ociStacks": "Multi-container applications",
"ociLifecycle": "Install, update and recreate",
"ociMonitor": "In ProxMenux Monitor",
"proxmenuxScripts": "ProxMenux Scripts",
"postInstallScript": "Post-Install Script",
"postInstallOverview": "Overview",
@@ -157,12 +157,12 @@
{
"endpoint": "/api/vms/<vmid>/control",
"method": "POST",
"use": "Start / stop / shutdown / reboot a VM or LXC container (the same operations the Monitor's VM & LXC modal exposes). Body: {\"action\": \"start|stop|shutdown|reboot\"}. Synchronous — returns the outcome directly with no polling needed."
"use": "Start / stop / shutdown / reboot a VM or LXC container (the same operations the Monitor's VM & LXC modal exposes). The body carries <code>action</code>: <code>start</code>, <code>stop</code>, <code>shutdown</code> or <code>reboot</code>. Synchronous — returns the outcome directly with no polling needed."
},
{
"endpoint": "/api/vms/<vmid>/backup",
"method": "POST",
"use": "Create a vzdump backup of a VM or LXC. Body (all optional except when the defaults don't match your storage layout): {\"storage\": \"<pve-storage>\", \"mode\": \"snapshot|suspend|stop\", \"compress\": \"zstd|lzo|gz|none\", \"protected\": true, \"notes\": \"…\", \"notification\": \"auto|always|failure|never\", \"pbs_change_detection\": \"default|legacy|data\"}. Returns the PVE task UPID."
"use": "Create a vzdump backup of a VM or LXC. Body fields, all optional unless the defaults do not match the storage layout: <code>storage</code> (a PVE storage), <code>mode</code> (<code>snapshot</code>, <code>suspend</code> or <code>stop</code>), <code>compress</code> (<code>zstd</code>, <code>lzo</code>, <code>gz</code> or <code>none</code>), <code>protected</code> (<code>true</code> or <code>false</code>), <code>notes</code>, <code>notification</code> (<code>auto</code>, <code>always</code>, <code>failure</code> or <code>never</code>) and <code>pbs_change_detection</code> (<code>default</code>, <code>legacy</code> or <code>data</code>). Returns the PVE task UPID."
},
{
"endpoint": "/api/vms/<vmid>/backups",
@@ -0,0 +1,146 @@
{
"meta": {
"title": "Assessment and inventory | Audit & Report",
"description": "The Audit & Report assessment: report profiles, how results are classified, accepted risks, unreadable sources, Lynis and the inventory of the node."
},
"header": {
"title": "Assessment and inventory",
"description": "The assessment inspects the node, keeps its findings and answers different questions depending on the report profile.",
"section": "Audit & Report"
},
"sections": [
{
"id": "read-only",
"blocks": [
{
"calloutInfo": {
"title": "An assessment inspects; it does not change the configuration",
"body": "It reads the configuration and state of the host. It writes its results, reports and logs, and the boot checks can mount EFI system partitions for a moment. The configuration it assesses is not modified."
}
},
{
"p": "<strong>Run assessment</strong> starts a run with the profile selected in <strong>Report</strong>. The view shows the date of the last run and how long ago it was."
}
]
},
{
"id": "profiles",
"title": "Report profiles",
"intro": "Each profile answers a different question. The checks and sections are selected before the document is composed.",
"blocks": [
{
"table": {
"headers": ["Profile", "What it covers"],
"rows": [
["Full audit", "Every check and all the structure available."],
["Quick diagnosis", "Every check; the result opens with the critical findings, the warnings and the readings that could not be verified."],
["Inventory", "A description of the node, with no checks and no classification."],
["Security review", "Exposure, access, privileges, certificates, updates, repositories and Lynis."],
["Backup assurance", "Coverage, age, results, verification, retention and recovery of the backups."],
["Capacity and wear", "Growth margin, memory, space usage and the service life of the disks."]
]
}
},
{
"p": "The contents of each document are described in <reportsLink>Reports and comparisons</reportsLink>."
}
]
},
{
"id": "results",
"title": "How results are classified",
"blocks": [
{
"table": {
"headers": ["Classification", "Meaning"],
"rows": [
["Critical", "A failed condition that has priority."],
["Warning", "A condition that needs a review, given the evidence or the policy."],
["Observation", "Information about the node that is not reported as a failure."],
["Unverified", "The source the check needs could not be read. It does not mean the problem is absent."],
["Conformant", "The condition meets the criterion applied."],
["Not applicable", "Nothing within the scope of the check applies."],
["Accepted risk", "The finding exists and a decision about it has been recorded."],
["Excluded by policy", "The policy declares that the element is left out of the count."]
]
}
},
{
"p": "Findings can be filtered by area: System, Storage, Network, Security, Backup, Guests and Hardware. Each finding keeps its evidence, with the source it was read from."
}
]
},
{
"id": "unverified",
"title": "When a source cannot be read",
"blocks": [
{
"table": {
"headers": ["Case", "Behaviour"],
"rows": [
["Unverified", "The check keeps its identity, states in its evidence which source failed and does not turn missing data into a conformant result."],
["Incomplete evidence", "The report names the source and the time of collection, so a real problem can be told apart from an insufficient reading."],
["A new run", "Once the access, package or service is corrected, the same profile runs again and the comparison shows whether the result could be verified."]
]
}
}
]
},
{
"id": "lynis",
"title": "Lynis",
"blocks": [
{
"p": "The security review uses the Lynis report of the host. When Lynis has not been run yet, or its report is older than the threshold of the policy, a dialog offers <strong>Run with Lynis</strong>, which takes a few minutes longer, or <strong>Run without Lynis</strong>, which uses the existing report."
},
{
"figure": {
"src": "/monitor/audit/lynis-dialog.png",
"alt": "Dialog that offers to run the assessment with or without Lynis",
"caption": "The Lynis dialog before a security review."
}
}
]
},
{
"id": "accept",
"title": "Accepting a risk",
"blocks": [
{
"p": "<strong>Accept risk</strong> records a decision on a finding. The reason is required and is stored with the author and the date."
},
{
"table": {
"headers": ["Field", "Options"],
"rows": [
["Reason", "Free text, required."],
["Stops applying after", "90 days, 180 days, 1 year or does not expire. When the period ends the finding becomes active again."],
["Remind me to review", "A reminder that brings the decision back to attention while it stays in force."]
]
}
},
{
"p": "An accepted finding stays visible with its decision, and <strong>Return to active</strong> revokes it. An accepted risk is not a correction: the comparison reports it as accepted, not as resolved."
}
]
},
{
"id": "inventory",
"title": "Inventory",
"blocks": [
{
"list": {
"items": [
"Identity, Proxmox VE version, kernel, subscription and cluster.",
"CPU, memory, board, BIOS, controllers and IOMMU.",
"Disks, SMART, power-on hours and recorded events.",
"Adapters, bonds, bridges, latency and connections.",
"Storage, guests with their disks and interfaces, and backups.",
"PCI passthrough and the software ProxMenux manages."
]
}
}
]
}
]
}
@@ -0,0 +1,148 @@
{
"meta": {
"title": "Changes | Audit & Report",
"description": "The change journal of Audit & Report: what ProxMenux changed on the host, what was there before, the difference and whether it can be undone."
},
"header": {
"title": "Changes",
"description": "The journal of the operations ProxMenux performs on the host and, where it was captured, the state before and after each one.",
"section": "Audit & Report"
},
"sections": [
{
"id": "purpose",
"blocks": [
{
"calloutInfo": {
"title": "From the script that ran to the change it made",
"body": "A long function may change only two lines. The journal keeps each concrete operation with the script, function and version responsible, the resource affected, the difference and whether it can be undone."
}
},
{
"flow": {
"nodes": [
{ "label": "Script", "detail": "function + version" },
{ "label": "Capture", "detail": "content before" },
{ "label": "Operation", "detail": "file · package · service" },
{ "label": "Journal", "detail": "attribution + difference" }
],
"caption": "The capture is taken when the operation runs and is consolidated when the Monitor reads the journal."
}
},
{
"figure": {
"src": "/monitor/audit/changes-view.png",
"alt": "Changes view of Audit & Report with the entries grouped by post-install option and script",
"caption": "The Changes view, with the difference of a file edited by ProxMenux."
}
}
]
},
{
"id": "types",
"title": "Kinds of entry",
"intro": "The filter at the top separates the entries by kind.",
"blocks": [
{
"table": {
"headers": ["Kind", "What it records"],
"rows": [
["Configuration", "ProxMenux wrote, edited or removed a file, changed a setting or altered a service."],
["Installations", "ProxMenux added a package or component, and the packages that actually appeared are recorded."],
["Executions", "ProxMenux ran a command; what it changed is up to the command itself."],
["Applied", "A function was applied before the journal existed; the state before it was not captured."]
]
}
}
]
},
{
"id": "groups",
"title": "How the view is organised",
"blocks": [
{
"table": {
"headers": ["Section", "Contents"],
"rows": [
["Post-install optimizations", "Grouped by the post-install option the user selected."],
["ProxMenux scripts", "GPU, Coral, network, storage, security, utilities and the other instrumented scripts, including the ProxMenux installer and ProxMenux Monitor."],
["Installed packages and utilities", "Software ProxMenux installed on the host."]
]
}
},
{
"p": "Within each section, <strong>By function</strong> groups the entries under the function that made them."
}
]
},
{
"id": "entry",
"title": "What each entry shows",
"blocks": [
{
"list": {
"items": [
"The script, function and version responsible.",
"The date and the affected resource.",
"The known state before and after the change.",
"The lines added and removed.",
"The packages that were actually added.",
"The state transition of a service.",
"<strong>Undoing this</strong>: <em>Restores exactly what was there</em>, <em>Deletes the file (there was none before)</em>, <em>The package can be uninstalled</em>, a partial undo, or <em>Cannot be undone from the journal</em>."
]
}
}
]
},
{
"id": "before",
"title": "What the state before means",
"blocks": [
{
"p": "The state before can be captured content, a file created for the first time or an unknown state. Changes made before the journal existed cannot be reconstructed; running the function again captures first the state found at that moment."
}
]
},
{
"id": "limits",
"title": "Scope of the journal",
"blocks": [
{
"list": {
"items": [
"Manual changes and changes made by other software are not recorded.",
"Only operations that go through the ProxMenux audit primitives are covered.",
"Recording never blocks the operation it describes: if the entry cannot be written, the operation goes on.",
"Successive changes to one resource are shown as the known origin against the current state."
]
}
}
]
},
{
"id": "retention",
"title": "Stored evidence",
"blocks": [
{
"table": {
"headers": ["Element", "Behaviour"],
"rows": [
["Entries", "They are kept on the host; nothing is removed from the journal automatically."],
["Captured content", "A captured object is kept while an entry refers to it."],
["Size of a capture", "Content above 1 MiB is not stored whole; the entry records that the capture was skipped because of its size."],
["Reading", "The Monitor does not load stored objects above 2 MiB."],
["Difference", "At most 400 lines are shown, and a longer difference is marked as truncated."],
["Listing", "The API returns 200 entries per request by default and up to 1000."]
]
}
},
{
"calloutWarning": {
"title": "Kept content is not an automatic undo",
"body": "The content before a change can be inspected and restored by hand, but the Changes view does not revert operations. An execution entry records the command without knowing every effect of the tool it ran."
}
}
]
}
]
}
@@ -0,0 +1,83 @@
{
"meta": {
"title": "Audit & Report | ProxMenux Monitor",
"description": "Assess a Proxmox VE node, record what ProxMenux changed on it and declare what is expected from its guests and storage, with printable reports."
},
"header": {
"title": "Audit & Report",
"description": "An assessment of the node, the journal of what ProxMenux changed on it and the declaration of what is expected from it, with documents that can be printed or saved as PDF.",
"section": "ProxMenux Monitor"
},
"sections": [
{
"id": "views",
"blocks": [
{
"calloutInfo": {
"title": "Three views, three questions",
"body": "Audit & Report keeps apart the facts of the node, the interventions of ProxMenux and the expectations declared for it. A configuration the assessment cannot place against a declared purpose is described, not reported as a failure."
}
},
{
"cards": {
"items": [
{ "icon": "shield", "title": "Assessment — how is the node?", "body": "Runs the checks of the chosen profile, composes the inventory and classifies what needs attention, with the evidence of each result." },
{ "icon": "refresh", "title": "Changes — what did ProxMenux do?", "body": "Lists the files, packages, services and commands the instrumented ProxMenux scripts changed, with what was there before when it was captured." },
{ "icon": "fileText", "title": "Policy — what is expected?", "body": "Declares which guests need a backup or must start with the host, which storage is essential and the thresholds of the checks." }
]
}
},
{
"flow": {
"nodes": [
{ "label": "Assessment", "detail": "facts" },
{ "label": "Policy", "detail": "context" },
{ "label": "Changes", "detail": "interventions" }
],
"caption": "The assessment provides the facts, the policy gives them context and the journal records what ProxMenux did."
}
},
{
"figure": {
"src": "/monitor/audit/assessment-view.png",
"alt": "Audit & Report in ProxMenux Monitor with the Assessment, Changes and Policy views",
"caption": "Audit & Report, with the Assessment view open."
}
}
]
},
{
"id": "boundaries",
"title": "Three different functions",
"blocks": [
{
"table": {
"headers": ["Function", "What it does"],
"rows": [
["<healthLink>Health Monitor</healthLink>", "Observes metrics and events continuously and can raise notifications."],
["Audit & Report", "Runs an assessment when it is asked for, documents the node and compares runs with each other."],
["Change journal", "Records the operations ProxMenux performs through its audit primitives."]
]
}
}
]
},
{
"id": "pages",
"title": "Pages of this section",
"blocks": [
{
"next": {
"items": [
{ "label": "Assessment and inventory", "href": "/docs/monitor/audit-report/assessment", "tail": "profiles, results, accepted risks and inventory." },
{ "label": "Changes", "href": "/docs/monitor/audit-report/changes", "tail": "the journal of what ProxMenux changed on the host." },
{ "label": "Policy", "href": "/docs/monitor/audit-report/policy", "tail": "guests, storage and thresholds." },
{ "label": "Reports and comparisons", "href": "/docs/monitor/audit-report/reports", "tail": "the six documents and the reference run." },
{ "label": "Scope and guarantees", "href": "/docs/monitor/audit-report/scope", "tail": "sources, limits and stored data." }
]
}
}
]
}
]
}
@@ -0,0 +1,111 @@
{
"meta": {
"title": "Policy | Audit & Report",
"description": "The node policy of Audit & Report: backup, autostart and recovery objective of each guest, the role of each storage and the thresholds of the checks."
},
"header": {
"title": "Policy",
"description": "The policy declares what no inspection can deduce: what each guest and storage is for and the thresholds the checks apply.",
"section": "Audit & Report"
},
"sections": [
{
"id": "principle",
"blocks": [
{
"calloutInfo": {
"title": "With no declaration, the report describes; with one, it assesses",
"body": "An assessment sees what the host does, not what it is for. A guest without a backup whose purpose is not declared is reported as an observation. If its backup is declared required, the same absence is reported as a warning. Nothing has to be declared."
}
},
{
"figure": {
"src": "/monitor/audit/policy-view.png",
"alt": "Policy view with the guests, the storage and the thresholds of the node",
"caption": "The Policy view."
}
}
]
},
{
"id": "guests",
"title": "Guests",
"intro": "Each VM and LXC of the node has three fields. A value left as default takes the general value, shown next to it.",
"blocks": [
{
"table": {
"headers": [
"Field",
"Values",
"Effect on the assessment"
],
"rows": [
[
"Backup",
"Required, Not required, Not stated",
"A missing backup is a warning when it is required, an observation when it is not stated, and it is left out of the count when it is not required."
],
[
"Autostart",
"Required, Not required, Not stated",
"Whether the guest has to start with the host."
],
[
"Recovery objective",
"Hours",
"The maximum acceptable age of the last backup."
]
]
}
}
]
},
{
"id": "storage",
"title": "Storage",
"blocks": [
{
"p": "An unreachable storage is reported as critical when it is declared essential or serves a running guest, as a warning when its role is not stated, and as an observation when it is declared optional."
}
],
"intro": "Each storage of the node is declared Essential, Optional or Not stated."
},
{
"id": "thresholds",
"title": "Thresholds",
"intro": "An empty threshold uses the shipped value, shown as its placeholder.",
"blocks": [
{
"list": {
"items": [
"Storage capacity review (%) and thin pool fill review (%).",
"Thin overprovisioning ratio and memory overcommit ratio.",
"ZFS scrub interval (days).",
"Backup age fallback (days) and schedule grace (ratio).",
"Certificate expiry notice (days).",
"Disk service life (hours) and recent disk error window (days).",
"Lynis report age (days) and package index age (days).",
"Journal against its cap (%).",
"Filesystem space review (%) and filesystem inode review (%)."
]
}
}
]
},
{
"id": "save",
"title": "How the policy is saved",
"blocks": [
{
"p": "The declaration is validated and saved atomically in <code>/usr/local/share/proxmenux/audit_policy.json</code>. Each save carries a revision: if the declaration changed in another session, the draft is not saved and the view offers to reload the saved declaration."
},
{
"calloutWarning": {
"title": "The policy is never inferred",
"body": "ProxMenux adds no requirement on its own. An empty field keeps the shipped value or stays not stated, and a partial declaration only affects the elements it names."
}
}
]
}
]
}
@@ -0,0 +1,160 @@
{
"meta": {
"title": "Reports and comparisons | Audit & Report",
"description": "The six Audit & Report documents, how they are printed or saved as PDF, and how runs are compared with a reference run."
},
"header": {
"title": "Reports and comparisons",
"description": "Six documents, each composed for a different question, and the comparison of every run with a reference run.",
"section": "Audit & Report"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "Six documents, not six styles",
"body": "The engine selects checks and sections before composing the document. A quick diagnosis is not a full audit with fewer pages, and an inventory presents no assessment results. The sample documents below use fictitious data."
}
}
]
},
{
"id": "documents",
"title": "The six documents",
"blocks": [
{
"downloads": {
"items": [
{
"title": "Full audit",
"body": "Documents the node end to end as a technical record.",
"href": "/monitor/audit/sample-audit-full-report.pdf",
"facts": [
{ "label": "Checks", "value": "Every available check." },
{ "label": "Contents", "value": "Executive summary; identity and cluster; hardware; network and latency; storage; guests; passthrough; applications; findings with evidence; sources and scope." }
]
},
{
"title": "Quick diagnosis",
"body": "Shows what needs attention without the complete inventory.",
"href": "/monitor/audit/sample-audit-diagnostic-report.pdf",
"facts": [
{ "label": "Checks", "value": "The same checks as the full audit." },
{ "label": "Contents", "value": "Minimal identity; critical findings; warnings; relevant observations; unverified readings and priority actions. Diagrams, inventory and long annexes are left out." }
]
},
{
"title": "Inventory",
"body": "Describes what exists on the node without assessing it.",
"href": "/monitor/audit/sample-audit-inventory-report.pdf",
"facts": [
{ "label": "Checks", "value": "None; no finding is classified." },
{ "label": "Contents", "value": "Identity; cluster; CPU and memory; board, BIOS and controllers; network; storage; VMs and LXCs; passthrough; applications and elements managed by ProxMenux." }
]
},
{
"title": "Security review",
"body": "Covers exposure and the controls over access to the node.",
"href": "/monitor/audit/sample-audit-security-report.pdf",
"facts": [
{ "label": "Checks", "value": "The security area, plus container privileges, updates, repositories and the APT chain." },
{ "label": "Contents", "value": "Identity and cluster; network and latency; access; firewall; 2FA; certificates; privileges; updates; repositories; state and age of Lynis." }
]
},
{
"title": "Backup assurance",
"body": "Checks that the declared protection exists and that its backups are usable.",
"href": "/monitor/audit/sample-audit-backup-report.pdf",
"facts": [
{ "label": "Checks", "value": "The backup area, plus storage connectivity and notification delivery." },
{ "label": "Contents", "value": "Coverage per guest; declared recovery objective; age and result; destination; retention; verification; failed jobs; recovery and incomplete sources." }
]
},
{
"title": "Capacity and wear",
"body": "Measures growth margin and signs of exhaustion or ageing.",
"href": "/monitor/audit/sample-audit-capacity-report.pdf",
"facts": [
{ "label": "Checks", "value": "The storage and hardware areas, plus memory, swap, journal and the host filesystem." },
{ "label": "Contents", "value": "Usage and thresholds; thin pools; overprovisioning; memory; inodes; ZFS; temperatures; power-on hours; SMART errors and NVMe/SSD service life." }
]
}
]
}
},
{
"figure": {
"src": "/monitor/audit/audit-report-preview.png",
"alt": "First page of a full audit report",
"caption": "Every document shares the report identifier, numbered sections, date, node, profile and footer."
}
},
{
"p": "A document contains the identity of the node and the date of the assessment, the executive result, a summary by area, the structure of hardware, network and storage, the protection of the guests, the findings with their evidence, the sources that could not be read, and the scope and policy applied, in the measure each profile includes them."
}
]
},
{
"id": "print",
"title": "Printing or saving as PDF",
"blocks": [
{
"steps": {
"items": [
{ "title": "Run", "body": "An assessment runs with the profile selected in <strong>Report</strong>." },
{ "title": "Review", "body": "The finished run shows its findings, the sources it could not read and the policy it applied." },
{ "title": "Generate report", "body": "<strong>Generate report</strong> opens the view prepared as a document." },
{ "title": "Print", "body": "<strong>Print or save as PDF</strong> opens the print dialog of the browser, where a printer or <em>Save as PDF</em> is selected." }
]
}
},
{
"calloutTip": {
"title": "The printout is a document, not a screenshot",
"body": "The action bar is hidden, a table that continues on the next page repeats its header, blocks avoid unnecessary breaks and every page keeps the identification and numbering."
}
}
]
},
{
"id": "compare",
"title": "Comparing with a reference run",
"intro": "<strong>Use as reference</strong> marks a finished run as the reference. Later runs are compared with it and their findings are separated into:",
"blocks": [
{
"table": {
"headers": ["Group", "Meaning"],
"rows": [
["New", "Reported now and not before."],
["Worse", "Still reported, and graver or reaching further than before."],
["Better", "Still reported, but less grave or reaching less far than before."],
["Resolved", "No longer reported, and nobody accepted them."],
["Accepted", "No longer counted because a risk was accepted, not because the host changed."],
["No longer assessed", "Present before and absent from this run; nothing verified that they stopped."]
]
}
},
{
"table": {
"headers": ["Reference run", "Behaviour"],
"rows": [
["Setting it", "Any finished run can be marked; none is marked automatically."],
["Changing it", "Marking another run moves the reference without deleting the history."],
["Comparing", "When no two runs are selected, the comparison uses the reference and the selected or most recent run."],
["Keeping runs", "The 30 most recent runs are kept, and the reference run is never removed."],
["Without a reference", "The view states that no reference run has been chosen yet, so there is nothing to compare against."]
]
}
},
{
"calloutWarning": {
"title": "A report describes one moment",
"body": "An assessment does not certify a whole period. Older results stop describing the current state; the view shows the age of the last run and each source keeps the time it was collected."
}
}
]
}
]
}
@@ -0,0 +1,97 @@
{
"meta": {
"title": "Scope and guarantees | Audit & Report",
"description": "Where the data of Audit & Report comes from, what is outside its view, the guarantees of its design and where its data is stored."
},
"header": {
"title": "Scope and guarantees",
"description": "Where the data of an assessment comes from, what stays outside its view and where Audit & Report keeps its data.",
"section": "Audit & Report"
},
"sections": [
{
"id": "sees",
"title": "What it observes",
"blocks": [
{
"list": {
"items": [
"The configuration and state of the local node.",
"The declared configuration of the VMs and LXCs.",
"The state of the storage as Proxmox VE knows it.",
"The available history of backups and verifications.",
"SMART data and the events the Monitor has collected.",
"The state of services, cluster, HA and local sources."
]
}
}
]
},
{
"id": "outside",
"title": "What is outside its view",
"blocks": [
{
"list": {
"items": [
"The interior of the guests, beyond what they declare to Proxmox VE.",
"Network equipment outside the host.",
"Remote dependencies the node cannot observe.",
"Physical state the hardware does not expose.",
"Manual actions or actions of other software, in the change journal."
]
}
},
{
"calloutWarning": {
"title": "Missing evidence is not evidence of absence",
"body": "A source that cannot be read gives an unverified or incomplete result, never a conformant one. The report lists the sources that were not available."
}
}
]
},
{
"id": "guarantees",
"title": "Guarantees of the design",
"blocks": [
{
"list": {
"items": [
"Stable identifiers for the checks.",
"History of runs and findings.",
"A validated policy, saved atomically.",
"Accepted risks with a reason, visible.",
"Attribution of changes to script and function.",
"Capture before and after, where it is available.",
"An explicit result when something cannot be measured or captured."
]
}
}
]
},
{
"id": "storage",
"title": "Where the data is stored",
"blocks": [
{
"table": {
"headers": ["Data", "Location"],
"rows": [
["Policy", "<code>/usr/local/share/proxmenux/audit_policy.json</code>"],
["Assessments", "The audit database of the Monitor"],
["Captured objects", "<code>/usr/local/share/proxmenux/changes/objects/</code>"],
["Pending entries", "<code>/usr/local/share/proxmenux/changes/spool/</code>"],
["Consolidated journal", "<code>/usr/local/share/proxmenux/changes.db</code>"]
]
}
},
{
"calloutInfo": {
"title": "A tool to review, not a certification",
"body": "Audit & Report reviews, compares and documents the node. Its results are read together with the purpose of the system, the declared policy and the sources available."
}
}
]
}
]
}
@@ -96,7 +96,8 @@
{ "method": "docker label / docker exec", "use": "Reads an OCI version label or runs a version command inside a Docker container." },
{ "method": "python distribution", "use": "Uses importlib.metadata through the selected Python interpreter." },
{ "method": "command", "use": "Runs an advanced argv-style command without a shell and extracts the version from its output." },
{ "method": "manual", "use": "Stores a version entered manually; it must be changed after upgrading the app." }
{ "method": "manual", "use": "Stores a version entered manually; it must be changed after upgrading the app." },
{ "method": "OCI image", "use": "For containers installed by OCI manager Apps. Reads the application and image versions from the installation record and compares the installed digest with the one the registry publishes; it needs no configuration." }
],
"sourcesHeading": "Available-version sources",
"sources": [
@@ -52,7 +52,7 @@
},
"drillIn": {
"heading": "Per-guest drill-in modal",
"intro": "The modal opens with the guest name, VMID, type, state and uptime. Its navigation adapts to the guest: <strong>Status</strong>, <strong>App</strong> and <strong>Updates</strong> for LXC application management, <strong>Mounts</strong> when an LXC has mount points, plus <strong>Backups</strong> and <strong>Firewall</strong>. The fixed action bar keeps the lifecycle controls and the LXC terminal available from every tab.",
"intro": "The modal opens with the guest name, VMID, type, state and uptime. Its navigation adapts to the guest: <strong>Status</strong>, <strong>App</strong> and <strong>Updates</strong> for LXC application management, <strong>Mounts</strong> when an LXC has mount points, <strong>Logs</strong> for containers installed by OCI manager Apps, plus <strong>Backups</strong> and <strong>Firewall</strong>. The fixed action bar keeps the lifecycle controls and the LXC terminal available from every tab.",
"statusTitle": "Tab 1 — Status",
"statusImageAlt": "Per-guest drill-in modal — Status tab with CPU / Memory / Disk live cards, Disk and Network I/O totals, the OS distro logo, and the Resources / IP Addresses block",
"statusImageCaption": "Status tab — live CPU / Memory / Disk with progress bars at the top, accumulated I/O totals (disk read/write, network down/up) below, then the static Resources block with Notes and + Info expansions and the IP Addresses pill list.",
@@ -106,7 +106,9 @@
],
"mountsCalloutTitle": "What this gives you over the native UI",
"mountsCalloutBody": "A truthful, capacity-aware view of every place the container reads or writes. NFS or CIFS shares mounted from inside the CT — invisible to the Proxmox web UI — appear here with the same look and the same health probe as any configured mount point. Stale remote mounts and zombie binds are flagged before they bite during a backup.",
"backupsTitle": "Tab 5 — Backups",
"logsTitle": "Tab 5 — Logs (OCI containers only)",
"logsBody": "Appears only for containers installed by OCI manager Apps. It shows the console output of the main process of the image, kept on the host in <code>/var/log/proxmenux/oci/VMID.console.log</code>: the last 100, 500 or 1000 lines, followed live while the container runs, with a filter and a download. It reads the file on every open, so it also works with the container stopped. The details are in <ociLink>OCI containers in ProxMenux Monitor</ociLink>.",
"backupsTitle": "Tab 6 — Backups",
"backupsImageAlt": "Per-guest drill-in modal — Backups tab with the available backups list, destination tag, sizes and the Create Backup button",
"backupsImageCaption": "Backups tab — every backup stored on configured Proxmox storages for this guest, sorted newest first. The tab header carries the count badge.",
"backupsIntro": "Lists every backup stored across configured Proxmox storages for this guest, sorted newest first. The tab title carries a count badge so you see at a glance whether the guest is backed up. Per row:",
@@ -116,7 +118,7 @@
"<strong>Size</strong> — final on-disk size of the backup."
],
"backupsOutro": "The <strong>+ Create Backup</strong> button at the top right kicks off a new run on the storage marked as \"Backup target\" in the Proxmox storage config. Restore lives in the Proxmox web UI — the Monitor exposes the \"is this guest backed up recently?\" view, not the recovery flow.",
"firewallTitle": "Tab 6 — Firewall",
"firewallTitle": "Tab 7 — Firewall",
"firewallIntro": "Reads the per-guest Proxmox firewall log straight from the host (no extra service, no polling). The tab is always present in the navigation strip; the panel decides what to render depending on whether the firewall is enabled for that guest and whether any rule is actually logging:",
"firewallItems": [
"<strong>Firewall disabled</strong> — an amber notice explains exactly where to enable it in the Proxmox UI (<em>&lt;Container|VM&gt; → Firewall → Options</em>) and reminds you that at least one rule needs <code>log: info</code> (or higher) before packets show up.",
@@ -0,0 +1,114 @@
{
"meta": {
"title": "How an OCI image is translated | ProxMenux",
"description": "From the image repository and its Compose file to a reviewable template, a deployment plan and a native Proxmox VE LXC, without Docker inside."
},
"header": {
"title": "How an OCI image is translated",
"description": "From the image repository and its Compose file to a reviewable template, a deployment plan and a native LXC, without installing Docker inside.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "pipeline",
"title": "The translation pipeline",
"blocks": [
{
"mermaid": {
"chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]",
"labels": {
"repo": "Image repository",
"converter": "Converter",
"template": "JSON template",
"blockers": "No blockers?",
"no": "No",
"yes": "Yes",
"review": "Review / overlay",
"plan": "Deployment plan",
"lxc": "Native LXC"
}
}
},
{
"p": "The converter reads the image and the Compose file its project publishes and writes a JSON template. A template with untranslated blockers goes through review, where a curated overlay resolves them, before it is published in the catalog. Only templates without blockers are offered for installation."
}
]
},
{
"id": "template",
"title": "What the template keeps",
"blocks": [
{
"cards": {
"items": [
{ "icon": "archive", "title": "Image identity", "body": "Repository, rolling tag, architecture, resolved digest and source revision." },
{ "icon": "braces", "title": "Container contract", "body": "Entrypoint, Cmd, environment, user, working directory, stop signal, ports and volumes." },
{ "icon": "layers", "title": "Proxmox VE translation", "body": "Resources, security, mount points, devices, sysctls, healthchecks and the adaptations each one needs, with their reason." },
{ "icon": "shield", "title": "Compatibility", "body": "Supported keys, untranslated blockers and the state of each validation." }
]
}
}
]
},
{
"id": "sources",
"title": "OCI provides the process; Compose provides the environment",
"blocks": [
{
"table": {
"headers": ["Source", "Example", "Native result"],
"rows": [
["OCI metadata", "<code>Entrypoint</code>, <code>Cmd</code>, <code>User</code>", "Proxmox VE imports them when the CT is created"],
["Docker Compose", "<code>environment</code>, <code>volumes</code>, <code>devices</code>", "LXC environment entries, <code>mpN</code> and <code>devN</code>"],
["ProxMenux profile", "GPU, healthcheck, credentials", "questions and reviewed adaptations"],
["User", "VMID, storage, network", "the instance contract"]
]
}
}
]
},
{
"id": "install",
"title": "What happens during an installation",
"blocks": [
{
"steps": {
"items": [
{ "title": "Resolve", "body": "The registry is queried, the host architecture is selected and the effective digest of the rolling tag is fixed." },
{ "title": "Download and verify", "body": "Skopeo downloads the image as an OCI archive, and every layer is checked against its digest and decompressed before anything is created. A damaged download is fetched a second time before the installation stops." },
{ "title": "Build", "body": "<code>pct create</code> builds the rootfs from the archive and keeps the official process metadata of the image." },
{ "title": "Connect", "body": "The declared volumes, network, environment, devices and security profiles are attached." },
{ "title": "Console", "body": "The console output of the container is kept on the host, and the Proxmox VE console opens a shell when the image ships one." },
{ "title": "Check", "body": "The first start waits for an address and for the service to answer; a failure is not reported as a successful installation." },
{ "title": "Register", "body": "The effective configuration is written to the instance contract that updates and recreations use." }
]
}
}
]
},
{
"id": "example",
"title": "Example: an image with /config and /downloads",
"intro": "A common Compose definition and the Proxmox VE configuration it becomes. The paths the application expects do not change.",
"blocks": [
{
"codeGrid": {
"items": [
{
"title": "Docker Compose",
"code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080"
},
{
"title": "/etc/pve/lxc/VMID.conf (excerpt)",
"code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000"
}
]
}
},
{
"p": "<code>mp0</code> is a second disk that belongs to the container, named <code>vm-VMID-disk-N</code> on the selected storage. It is mounted at <code>/config</code> and, with <code>backup=1</code>, it is part of the container backup. <code>mp1</code> creates no disk: it binds the host directory <code>/srv/downloads</code> to <code>/downloads</code> inside the LXC. Port 8080 is not mapped: the LXC has an address of its own and the service answers on it."
}
]
}
]
}
@@ -0,0 +1,134 @@
{
"meta": {
"title": "Install an image that is not in the catalog | ProxMenux",
"description": "Translate an OCI image from a Compose file, a URL, a docker run command or an image reference, and review what ProxMenux can reproduce before installing it."
},
"header": {
"title": "Install an image that is not in the catalog",
"description": "An image of your own is translated from its Compose file, a docker run command or its reference alone, and the result is shown for review before any LXC is created.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "Same converter, same installer",
"body": "The option <strong>Install an image that is not in the catalog</strong> uses the converter and the installer of the catalog templates. The difference is that the contract is generated at that moment from the definition given, and validated before any LXC is created."
}
}
]
},
{
"id": "sources",
"title": "How the image is described",
"intro": "The first screen asks how the image is described. There are four ways to give a complete definition and a fifth that uses only the image name.",
"blocks": [
{
"table": {
"headers": ["Option", "What it reads"],
"rows": [
["Paste its Compose file in the terminal", "The YAML is pasted in the terminal and ends with Ctrl+D on an empty line."],
["Read its Compose file from a file of this host", "A path on the node, <code>/root/docker-compose.yml</code> by default. The file must exist and be at most 256 KiB."],
["Download its Compose file from an address", "An <code>http://</code> or <code>https://</code> address that serves the raw YAML. At most 256 KiB are downloaded."],
["Paste its docker run command in the terminal", "The command published by the project. Ports, environment, volumes, devices, capabilities, user, shared memory and the other supported options are translated."],
["Only the image reference, with no Compose file", "A reference such as <code>ghcr.io/user/application:latest</code>. The registry is queried and the OCI metadata of the image is kept."]
]
}
},
{
"figure": {
"src": "/oci-manager/custom-source-menu.png",
"alt": "Menu that asks how an image that is not in the catalog is described",
"caption": "The five ways to describe an image that is not in the catalog."
}
}
]
},
{
"id": "reference-only",
"title": "What an image reference alone provides",
"intro": "An image carries its process metadata, but not everything a Compose file usually adds around it.",
"blocks": [
{
"table": {
"headers": ["Read from the image", "Not in the image", "Consequence"],
"rows": [
["<code>Entrypoint</code>, <code>Cmd</code>, <code>User</code>, <code>WorkingDir</code> and embedded environment", "Variables that only appear in the documentation", "They are added during the installation or a recreation"],
["Volumes declared by the image", "Host directories that only a Compose file names", "Additional paths are added before installing"],
["<code>EXPOSE</code> ports", "The URL, protocol or functional healthcheck", "The port and the way the service is checked are confirmed"],
["Architectures and digest in the registry", "Devices, privileges or external dependencies", "None of them is enabled without a definition that asks for it"]
]
}
}
]
},
{
"id": "review",
"title": "Review before installing",
"intro": "After translating the definition, ProxMenux shows what it understood before asking for VMID, resources or storage.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Analyse", "body": "Services, image, ports, paths, environment, devices, security and healthcheck are read." },
{ "title": "Query the registry", "body": "The image must exist in a public registry, and its published architectures are listed." },
{ "title": "List what is not applied", "body": "Labels, Docker networks, Swarm settings and other keys that have no effect on an LXC are listed." },
{ "title": "Block what cannot be translated", "body": "A key with no safe equivalent is shown under <strong>What cannot be translated</strong> and the installation does not continue. Nothing is dropped silently." },
{ "title": "Ask for secrets", "body": "Variables whose names read as a password, token or key become sensitive questions of the installation." }
]
}
},
{
"figure": {
"src": "/oci-manager/custom-review.png",
"alt": "Summary of what ProxMenux understood from a Compose file",
"caption": "The summary shown before the installation asks for VMID and resources."
}
},
{
"p": "After accepting the summary, the flow is the one of the catalog: name, default or advanced mode, VMID, CPU, memory, network, start with the node, persistent paths, additional paths, compatible devices and a final summary."
}
]
},
{
"id": "examples",
"title": "Input examples",
"blocks": [
{
"codeGrid": {
"items": [
{
"title": "Compose",
"code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid"
},
{
"title": "docker run",
"code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest"
}
]
}
}
]
},
{
"id": "limits",
"title": "Definitions that are not installed",
"blocks": [
{
"list": {
"items": [
"Images in private registries: registry credentials are not requested.",
"A Dockerfile without a published image: the image has to be built and published in an OCI registry first.",
"A Compose file with several services: it is blocked because it describes more than one image. This option installs a single container.",
"<code>configs</code>, external secrets or device formats that cannot be translated unambiguously.",
"Shell substitutions such as <code>$(command)</code>: the resulting value has to be written instead.",
"<code>docker run</code> options that are not recognised, and <code>--env-file</code>: an explicit Compose file is read instead."
]
}
}
]
}
]
}
@@ -0,0 +1,232 @@
{
"meta": {
"title": "Devices and acceleration | ProxMenux",
"description": "How OCI manager Apps passes GPU, NVIDIA, Coral, USB, FUSE and other devices to an OCI container as validated native Proxmox VE resources."
},
"header": {
"title": "Devices and acceleration",
"description": "GPU, NVIDIA, Coral, USB, FUSE and block devices become validated native Proxmox VE resources of the container.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "principle",
"title": "The device the application needs, not the whole host",
"blocks": [
{
"p": "A device requested by the Compose file or by the application profile becomes a concrete <code>devN</code> entry or LXC mount. Asking for a GPU, a USB device or a Coral does not make the container privileged."
},
{
"flow": {
"nodes": [
{ "label": "Host inventory", "detail": "/dev/dri/renderD128\nGID 993 · Intel" },
{ "label": "ProxMenux", "detail": "vendor and\npermissions checked" },
{ "label": "LXC", "detail": "same device\neffective GID" }
]
}
}
]
},
{
"id": "origin",
"title": "Where the device request comes from",
"blocks": [
{
"table": {
"headers": ["Source", "What is read", "What the installer does"],
"rows": [
["Docker Compose", "<code>devices</code>, <code>group_add</code>, <code>deploy.resources</code> and NVIDIA requests", "Each requirement becomes a device request shown for review"],
["Catalog profile", "The GPU, Coral, OpenCL, USB or FUSE support the application actually has", "Only the options validated for that image are offered"],
["Image metadata and documentation", "VA-API, Selkies, LinuxServer mods or the NVIDIA runtime", "The documented variables and preparation are added"],
["User selection", "CPU only, Intel/AMD, OpenCL, NVIDIA or an optional device", "The selection is stored in the instance contract"]
]
}
},
{
"calloutWarning": {
"title": "Detected devices are not attached on their own",
"body": "The host is inventoried, but only devices declared by the Compose file or by a compatible profile are offered and attached. A GPU, USB dongle or Coral present on the host is not exposed to every LXC."
}
}
]
},
{
"id": "identify",
"title": "How the host device is identified",
"intro": "Before the LXC is modified, the device is read on the host and matched against the chosen profile.",
"blocks": [
{
"table": {
"headers": ["Type", "Identity", "Validation"],
"rows": [
["Intel/AMD DRM", "<code>/dev/dri/renderD*</code> and <code>/sys/class/drm/NODE/device/vendor</code>", "A character device with vendor <code>0x8086</code> (Intel) or <code>0x1002</code> (AMD)"],
["AMD OpenCL", "The render node, plus <code>/dev/kfd</code> when the profile needs it", "Existence, type, vendor, permissions and declared compatibility"],
["NVIDIA", "<code>nvidia-smi</code> and <code>nvidia-container-cli</code>", "GPU, UUID, PCI bus, driver version, Toolkit, <code>/dev/nvidia*</code> nodes, binaries and libraries"],
["Coral PCIe/M.2", "<code>/dev/apex_N</code> and its link in <code>/sys/dev/char/MAJOR:MINOR</code>", "Character node, major/minor, owner, GID and permissions"],
["USB and serial", "<code>/dev/ttyUSB*</code>, <code>/dev/ttyACM*</code> or <code>/dev/bus/usb/BBB/DDD</code>", "Character node; for USB also vendor, product and serial when sysfs publishes them"],
["KVM, TUN, FUSE, video and generic SCSI", "<code>/dev/kvm</code>, <code>/dev/net/tun</code>, <code>/dev/fuse</code>, <code>/dev/videoN</code> or <code>/dev/sgN</code>", "Supported path, node type and effective permissions"],
["Optical drive", "<code>/dev/srN</code>", "A block device"]
]
}
}
]
},
{
"id": "install",
"title": "What happens during the installation",
"blocks": [
{
"steps": {
"items": [
{ "title": "The template offers its profiles", "body": "For example CPU only, Intel/AMD VA-API, AMD OpenCL, Intel OpenCL or NVIDIA. The options belong to the image, not to a common menu." },
{ "title": "A profile is chosen", "body": "It defines the device nodes, environment, mods or runtime the application needs." },
{ "title": "A path is proposed", "body": "For DRM, <code>/dev/dri/renderD128</code>, which can be changed on a host with several render nodes. For USB or serial, the concrete node is selected." },
{ "title": "Validation", "body": "Existence, type, allowed vendor, permissions and GID are checked. A mismatch stops the operation." },
{ "title": "The contract is written", "body": "Path, mode, GID, write access and profile are recorded for updates and recreations." },
{ "title": "Attach and test", "body": "<code>pct set</code> adds the <code>devN</code> entry and access is then checked inside the LXC. LinuxServer images are also checked as user <code>abc</code>." }
]
}
}
]
},
{
"id": "config",
"title": "How it appears in the LXC configuration",
"intro": "Illustrative values: <code>dev0</code> and <code>dev1</code> are the free slots Proxmox VE assigns, and <code>renderD128</code>, <code>apex_0</code> and the GID depend on the hardware of the node.",
"blocks": [
{
"codeGrid": {
"items": [
{ "title": "Intel/AMD VA-API", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" },
{ "title": "Coral PCIe/M.2", "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" },
{ "title": "A specific USB device", "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" },
{ "title": "AMD OpenCL", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" }
]
}
},
{
"p": "The GID is read with <code>stat</code> on the host and written to the <code>devN</code> entry; the <code>render</code> and <code>video</code> groups are not assumed to have a fixed number. The device keeps the same <code>/dev</code> path inside the LXC, where the application's own mechanisms look for it."
}
]
},
{
"id": "profiles",
"title": "Profiles an image can offer",
"blocks": [
{
"table": {
"headers": ["Profile", "Translation", "Offered when"],
"rows": [
["Intel/AMD VA-API", "<code>/dev/dri</code> render node", "The application supports video acceleration"],
["OpenCL", "Render node, <code>/dev/kfd</code> when needed and the official mod", "The image or profile documents it"],
["NVIDIA", "Driver devices and libraries of the host", "The host has a working driver and the NVIDIA Container Toolkit"],
["Coral", "<code>/dev/apex_0</code> or the USB bus", "The profile declares Coral support (Frigate)"],
["USB, serial, FUSE", "A single device, a validated tree or an LXC feature", "The contract asks for it"]
]
}
}
]
},
{
"id": "nvidia",
"title": "NVIDIA",
"blocks": [
{
"calloutWarning": {
"title": "Node requirement: NVIDIA Container Toolkit",
"body": "A working driver on Proxmox VE is not enough to give an NVIDIA GPU to an OCI image. OCI manager Apps uses <code>nvidia-container-cli</code>, from the NVIDIA Container Toolkit, to identify the devices and to obtain the binaries and libraries that match the loaded driver."
}
},
{
"p": "The <nvidiaLink>ProxMenux NVIDIA installer</nvidiaLink> installs the NVIDIA Container Toolkit from the official NVIDIA repository together with the driver. It checks its four packages, validates <code>nvidia-container-cli</code> and records the result in the change journal of <auditLink>Audit & Report</auditLink>."
},
{
"p": "These two commands on the host show whether the driver and the Toolkit are available:"
},
{
"shell": { "code": "nvidia-smi -L\nnvidia-container-cli --version" }
},
{
"p": "On a host where the driver was installed by other means, the Toolkit is installed from the official stable repository:"
},
{
"shell": {
"code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools"
}
},
{
"p": "The inventory OCI manager Apps uses is the output of:"
},
{
"shell": { "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" }
},
{
"calloutInfo": {
"title": "Docker runtime configuration is not involved",
"body": "The containers are native LXCs and no Docker daemon is used, so <code>nvidia-ctk runtime configure --runtime=docker</code> plays no part: ProxMenux queries <code>nvidia-container-cli</code> directly and writes the LXC devices and mounts. The commands and supported platforms are maintained in the <toolkitLink>NVIDIA Container Toolkit installation guide</toolkitLink>."
}
},
{
"cards": {
"items": [
{ "icon": "cpu", "title": "Inventory from the driver", "body": "<code>nvidia-container-cli</code> lists the device nodes, binaries, firmware and libraries of the installed driver." },
{ "icon": "refresh", "title": "No fixed version", "body": "The template does not name library files. The profile is generated from the current host." },
{ "icon": "shield", "title": "Read-only mounts", "body": "The host libraries are mounted read-only instead of being copied into the container." },
{ "icon": "hardDrive", "title": "Driver changes", "body": "After a driver change the inventory is generated again before the affected LXCs start." }
]
}
},
{
"code": {
"title": "NVIDIA result (simplified)",
"code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0"
}
},
{
"p": "Passing only <code>/dev/nvidia0</code> is not enough. The user-space components of the loaded driver are mounted read-only, and <code>nvidia-smi</code> then runs inside the LXC to compare GPU, UUID, PCI bus and version with the host inventory."
}
]
},
{
"id": "usb",
"title": "USB, serial and USB Coral",
"blocks": [
{
"calloutWarning": {
"title": "USB numbering can change",
"body": "A path such as <code>/dev/bus/usb/003/004</code> can change when the device is reconnected or the host restarts. The profile records vendor, product and serial when they are available, but a new bus address is not remapped automatically."
}
},
{
"p": "A peripheral is given by its concrete node: <code>/dev/ttyUSB0</code>, <code>/dev/ttyACM0</code>, <code>/dev/apex_0</code> or <code>/dev/bus/usb/BBB/DDD</code>. Passing the whole of <code>/dev</code> is not accepted. Coral is offered only to applications whose profile declares it."
}
]
},
{
"id": "trees",
"title": "Device trees and LXC features",
"blocks": [
{
"table": {
"headers": ["Request", "Translation", "Scope"],
"rows": [
["<code>/dev/dvb</code>, <code>/dev/snd</code> or <code>/dev/bus/usb</code>", "Each character node of the tree gets its own <code>devN</code> entry with the host mode and GID", "Only the requested tree, not the rest of <code>/dev</code>"],
["<code>/dev/fuse</code>", "The node and, when the profile needs it, the <code>fuse=1</code> feature", "FUSE alone does not publish mounts to other LXCs"],
["<code>/dev/net/tun</code>", "A <code>devN</code> entry at the same path inside the LXC", "The VPN or network configuration stays in the application"],
["<code>/dev/kvm</code>", "A validated <code>devN</code> entry", "Offered only when the contract asks for it"]
]
}
}
]
},
{
"id": "security",
"title": "Confirmations by level of risk",
"blocks": [
{
"p": "Concrete devices, optional privilege, required privilege, AppArmor or seccomp relaxation and access to the host PID namespace are treated as separate cases, not under one generic privileged label. Each option with a risk is explained and confirmed during the installation."
}
]
}
]
}
+116
View File
@@ -0,0 +1,116 @@
{
"meta": {
"title": "OCI manager Apps | ProxMenux",
"description": "Run official OCI images as native Proxmox VE LXC containers, with persistent data, devices, multi-container applications and transactional updates."
},
"header": {
"title": "OCI manager Apps",
"description": "Official OCI images run as native Proxmox VE LXC containers. The image stays the one its maintainer publishes; ProxMenux reproduces the environment Docker Compose would have created around it.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "model",
"blocks": [
{
"calloutInfo": {
"title": "One OCI image, one native LXC",
"body": "No Docker engine runs inside the containers. Proxmox VE imports the image's filesystem and its OCI metadata, and the application becomes the main process of a native LXC, managed with the same tools as any other container of the node."
}
},
{
"flow": {
"nodes": [
{ "label": "Source", "detail": "OCI image\nCompose and documentation" },
{ "label": "ProxMenux", "detail": "JSON contract\nvalidation and plan" },
{ "label": "Proxmox VE", "detail": "native LXC\nvolumes and devices" }
],
"caption": "The application is not rebuilt: its environment is reproduced declaratively."
}
}
]
},
{
"id": "where",
"title": "Where it is",
"intro": "OCI manager Apps opens from option <strong>OCI manager Apps (beta)</strong> of the ProxMenux main menu, on the Proxmox node and as root. Its first screen offers:",
"blocks": [
{
"list": {
"items": [
"<strong>Search applications</strong> — search by name across the catalog.",
"<strong>All applications</strong> — the complete catalog, with the number of applications.",
"<strong>Manage installed OCI applications</strong> — update, recreate, remove or recover what was installed.",
"<strong>Install an image that is not in the catalog</strong> — translate a Compose file, a <code>docker run</code> command or an image reference.",
"The catalog categories, each with its number of applications."
]
}
},
{
"figure": {
"src": "/oci-manager/main-menu.png",
"alt": "Main screen of OCI manager Apps with search, all applications, management, custom image and the catalog categories",
"caption": "Main screen of OCI manager Apps."
}
},
{
"p": "Selecting an application shows its description, the image it runs and two installation modes: <strong>Install with default settings</strong>, which asks almost nothing, and <strong>Install with advanced settings</strong>, which offers the real storage, bridge and resource selectors of the node."
}
]
},
{
"id": "translation",
"title": "What Docker expresses and what OCI manager Apps turns it into",
"blocks": [
{
"table": {
"headers": ["Requirement", "Docker expresses it as", "OCI manager Apps turns it into"],
"rows": [
["Run the application", "<code>image</code>, <code>entrypoint</code>, <code>command</code>", "OCI rootfs and the main process of the LXC"],
["Keep configuration", "<code>volumes: /config</code>", "a persistent <code>mpN</code> disk included in the backup, or a host directory"],
["Publish the service", "<code>ports</code>", "an address of its own for the LXC and the access URL of the service"],
["Use hardware", "<code>devices</code>, <code>group_add</code>", "<code>devN</code> entries with the effective host GID and a validated profile"],
["Connect dependencies", "<code>networks</code>, <code>depends_on</code>", "a private bridge, fixed addresses, start order and healthchecks"],
["Update", "<code>pull</code> and recreate", "a new rootfs with the same persistent contract"]
]
}
}
]
},
{
"id": "kept",
"title": "What is kept and what changes",
"blocks": [
{
"cards": {
"items": [
{ "icon": "archive", "title": "Kept", "body": "The official image, its internal paths, its environment, its startup process and its functional documentation." },
{ "icon": "boxes", "title": "Adapted", "body": "The runtime environment: network, persistence, devices, permissions and dependencies use native Proxmox VE LXC primitives." },
{ "icon": "shield", "title": "Not translated", "body": "A Compose key with no safe equivalent is listed as a blocker. Templates with blockers are not offered, and sensitive options are confirmed during the installation." },
{ "icon": "refresh", "title": "Recorded", "body": "Image digest, resources, paths, network, devices and stack membership are stored in the instance contract, which updates and recreations reproduce." }
]
}
}
]
},
{
"id": "pages",
"title": "Pages of this section",
"blocks": [
{
"next": {
"items": [
{ "label": "How an OCI image is translated", "href": "/docs/oci-manager/architecture", "tail": "from the image and its Compose file to a native LXC." },
{ "label": "Install an image that is not in the catalog", "href": "/docs/oci-manager/custom-image", "tail": "Compose, docker run or an image reference." },
{ "label": "Data, paths and networking", "href": "/docs/oci-manager/storage-network", "tail": "container disks, host directories, Rclone mounts and addresses." },
{ "label": "Devices and acceleration", "href": "/docs/oci-manager/hardware", "tail": "GPU, NVIDIA, Coral, USB and other devices." },
{ "label": "Multi-container applications", "href": "/docs/oci-manager/stacks", "tail": "application, database and cache as coordinated LXCs." },
{ "label": "Install, update and recreate", "href": "/docs/oci-manager/lifecycle", "tail": "the instance contract and its operations." },
{ "label": "In ProxMenux Monitor", "href": "/docs/oci-manager/monitor", "tail": "versions, updates, console output and terminal." }
]
}
}
]
}
]
}
@@ -0,0 +1,243 @@
{
"meta": {
"title": "Install, update and recreate | ProxMenux",
"description": "The instance contract of an OCI container and the operations that use it: update, recreate, remove and recovery of an interrupted operation."
},
"header": {
"title": "Install, update and recreate",
"description": "Each instance keeps a reproducible contract, so its rootfs can be replaced without losing configuration or persistent data.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "contract",
"title": "The instance contract",
"blocks": [
{
"p": "After an installation, the effective configuration is stored in <code>/usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json</code>. It is not a copy of the original Compose file: it is the reproducible contract of the LXC that exists on this host."
},
{
"code": {
"code": "instances/\n└── 105/\n └── oci-compose.json\n ├── image and resolved digest\n ├── resources and network\n ├── environment (secrets protected)\n ├── container disks and host directories\n ├── hardware profile and devices\n ├── console log and terminal mode\n └── stack membership and lifecycle"
}
}
]
},
{
"id": "manage",
"title": "Manage installed OCI applications",
"intro": "This option of the main screen lists the registered instances with their application and image. Each one is checked against its contract before any action; a CT that no longer matches its record is neither modified nor deleted. The same update and recreation are offered in the Updates tab of <monitorLink>ProxMenux Monitor</monitorLink>.",
"blocks": [
{
"table": {
"headers": [
"Option",
"What changes",
"What is kept"
],
"rows": [
[
"Update the image with the saved configuration",
"The rootfs is replaced by the image the saved channel publishes today",
"Contract, container disks, host directories, network and devices"
],
[
"Recreate: edit resources, network, paths and GPU",
"The editor opens with the current contract; the CT is rebuilt with the changes",
"The data of container disks and host directories"
],
[
"Remove: delete the application and its containers",
"The LXC, or every member of a stack, and their contracts are deleted",
"Host directories, with their content"
]
]
}
},
{
"p": "For a multi-container application the menu offers <strong>Update every container of the application</strong> and the removal. A stack is not recreated."
},
{
"figure": {
"src": "/oci-manager/manage-menu.png",
"alt": "List of installed OCI applications and the update, recreate and remove options",
"caption": "Manage installed OCI applications."
}
}
]
},
{
"id": "update",
"title": "A transactional update",
"blocks": [
{
"mermaid": {
"chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end",
"labels": {
"user": "User",
"registry": "OCI registry",
"update": "Update instance",
"resolve": "Resolve the tag",
"verify": "Verify archive and preflight",
"backup": "Stop and back up the CT",
"import": "Import the new rootfs",
"reapply": "Apply the contract again",
"healthy": "healthy",
"failure": "failure",
"commit": "Contract published with the new digest",
"restored": "Previous instance restored"
}
}
},
{
"list": {
"items": [
"When the registry still serves the installed digest, nothing is downloaded and the instance is not touched.",
"The new image is verified layer by layer before the CT stops. A download that arrives damaged is fetched a second time; an image already cached that fails the check is downloaded again.",
"Settings changed in Proxmox VE after the installation (memory, swap, cores, CPU limit, CPU priority, start with the node) are kept and carried into the new contract.",
"Any other difference between the CT and its contract stops the update before the CT is stopped."
]
}
},
{
"flow": {
"nodes": [
{
"label": "Before",
"detail": "rootfs A\n/config mp0\n/media host directory"
},
{
"label": "Update",
"detail": "replaces only\nthe rootfs"
},
{
"label": "After",
"detail": "rootfs B\n/config mp0\n/media host directory"
}
]
}
}
]
},
{
"id": "recovery",
"title": "Recovering an interrupted operation",
"intro": "Update and recreation use a persistent transaction. If the process, the terminal or the node is interrupted after the CT stops, the operation is not taken as finished.",
"blocks": [
{
"steps": {
"items": [
{
"title": "Pending marker",
"body": "When Manage installed OCI applications opens, the saved state shows that the replacement was never published."
},
{
"title": "View status",
"body": "Shows the phase that was reached without changing containers or data."
},
{
"title": "Recover the previous installation",
"body": "Restores the verified native backup taken before the replacement and the previous contract."
},
{
"title": "Stacks as a unit",
"body": "For a multi-container application, every member is recovered from the same transaction point, not only the selected one."
}
]
}
},
{
"calloutWarning": {
"title": "Host directories are outside the rollback",
"body": "The backup covers the rootfs and the container disks it includes. A host directory is not reverted, because other LXCs may use its data. Updating, recreating or recovering an instance with host directories asks for a confirmation of this first."
}
}
]
},
{
"id": "remove",
"title": "Removing an OCI application",
"blocks": [
{
"p": "Before the confirmation, a summary is built from the real configuration: the containers that are removed, the data deleted with them, the private network that is released and the host directories that are kept. The confirmation defaults to <strong>No</strong>, since the data of the deleted disks cannot be recovered afterwards."
},
{
"table": {
"headers": [
"Resource",
"On removal",
"Reason"
],
"rows": [
[
"rootfs and container disks",
"Deleted",
"They belong only to the container"
],
[
"Host directory",
"Kept, with its content",
"Other applications may use it"
],
[
"Instance contract",
"Retired after a successful removal",
"No CT is associated with it any more"
],
[
"Private bridge of a stack",
"Released with the stack",
"It has no members left to connect"
],
[
"A single member of a stack",
"Not removed on its own",
"The whole application is removed, so no stack is left incomplete"
]
]
}
},
{
"figure": {
"src": "/oci-manager/remove-summary.png",
"alt": "Removal summary with the containers, data and host directories affected",
"caption": "The removal summary, built from the real configuration."
}
}
]
},
{
"id": "archives",
"title": "Downloaded images and host space",
"blocks": [
{
"p": "The OCI archive is used to build the rootfs; the running CT does not read it. After an installation or an update, the downloaded archives of that operation are listed with their size and their deletion is offered. Deleting them frees the space without affecting the container or its data."
},
{
"calloutInfo": {
"title": "Updates do not need the archive",
"body": "Without the archive, an update resolves the saved channel and downloads the new digest. With it, an archive is reused only when its reference and integrity match what is requested."
}
}
]
},
{
"id": "registry",
"title": "Registry and cleanup",
"blocks": [
{
"p": "The registered contracts are compared with the real CTs. A contract is orphaned only when its VMID no longer exists or no longer carries the expected instance identity. The cleanup does not delete volumes or external data by inference."
}
]
},
{
"id": "channel",
"title": "A rolling tag is not an unattended update",
"blocks": [
{
"p": "The catalog installs the rolling tag its maintainer publishes, but the effective digest is resolved, recorded and changed only through an explicit update with preflight and rollback. <monitorLink>ProxMenux Monitor</monitorLink> compares the installed digest with the one the registry publishes and shows, and notifies, when a new image is available."
}
]
}
]
}
@@ -0,0 +1,203 @@
{
"meta": {
"title": "OCI containers in ProxMenux Monitor | ProxMenux",
"description": "What ProxMenux Monitor shows for a container installed by OCI manager Apps: application and image versions, new images, console output and the Proxmox VE terminal."
},
"header": {
"title": "In ProxMenux Monitor",
"description": "A container installed by OCI manager Apps appears in VMs & LXCs like any other LXC. Its modal reads the installation record: application and image versions, new images, console output and terminal.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "The record is the source",
"body": "OCI manager Apps knows what it installed and where it came from. The Monitor reads that installation record instead of probing the container, so the data is the same whether the container is running or stopped."
}
},
{
"table": {
"headers": [
"Tab",
"What changes for an OCI container"
],
"rows": [
[
"App",
"The application is identified from the record, and updates are tracked by image"
],
[
"Updates",
"The image is updated or the container recreated, with the same flow as the OCI menu"
],
[
"Mounts",
"Container disks and host directories, with their usage"
],
[
"Logs",
"Only for OCI containers: the console output of the application"
]
]
}
}
]
},
{
"id": "app",
"title": "App",
"intro": "The <appLink>App</appLink> tab offers the installed application as a detection, with the name, logo, port and scheme of the record. Registering it opens the editor with the method <strong>OCI image (installed by ProxMenux)</strong>, which needs no configuration.",
"blocks": [
{
"cards": {
"items": [
{
"icon": "boxes",
"title": "Application",
"body": "The version of the application inside the image, read from the image itself: its environment or its <code>org.opencontainers.image.version</code> label. It is informative."
},
{
"icon": "archive",
"title": "Image",
"body": "The build date and digest of the installed image. The update is decided here: the installed digest is compared with the one the registry publishes today for the same tag."
}
]
}
},
{
"list": {
"items": [
"When the registry publishes a new digest, the card shows <strong>New image</strong> with its date and digest, even if the application version inside did not change: a rebuild on an updated base is an update.",
"When the digests match, the card reads <strong>Version</strong>.",
"The card links to the repository of the image: its GitHub project, or its Docker Hub page for an official image.",
"The access link uses the LAN address of the container, also for the main member of a multi-container application, which has a second address on its private network.",
"The button <strong>Refresh data</strong> reads the record and the registry again. The options to search for applications or register another one are not offered: the container holds exactly the application of its record."
]
}
},
{
"figure": {
"src": "/oci-manager/monitor-app-tab.png",
"alt": "App tab of an OCI container with the application version, the image and the repository link",
"caption": "App tab of a container installed by OCI manager Apps."
}
},
{
"p": "The check runs once a day with the update checks of the Monitor, and <strong>Refresh data</strong> runs it at once. A new image is sent as a notification through the channels configured in <notificationsLink>Notifications</notificationsLink>. The update is applied from the Updates tab."
}
]
},
{
"id": "updates",
"title": "Updates",
"intro": "For a container installed by OCI manager Apps the Updates tab shows the application with its installed image and, when the registry publishes one, the new image, in the same format as any other application. The package and application updaters of an ordinary LXC do not appear.",
"blocks": [
{
"table": {
"headers": [
"Button",
"What it opens"
],
"rows": [
[
"Update",
"The update of <lifecycleLink>Manage installed OCI applications</lifecycleLink> for this container, in the Monitor terminal. It can run whether or not there is a new image; with none, nothing is changed. In a multi-container application it updates every member."
],
[
"Recreate",
"The recreation editor (resources, network, paths and GPU). It is not offered for a multi-container application."
],
[
"Recover",
"Replaces Update when an operation on the container was interrupted, and opens its recovery."
]
]
}
},
{
"p": "External changes, host directories and multi-container applications are handled as in the OCI menu. When the terminal closes, the image, the record and the mounts are read again."
},
{
"table": {
"headers": [
"Option",
"Behaviour"
],
"rows": [
[
"Keep the backup taken before updating",
"Every update backs up the container to restore it if the update fails. With this option that same backup is kept in the chosen storage and appears among the backups of the CT; on Proxmox Backup Server a backup is written before the update."
],
[
"Scheduled updates",
"The image is updated at the chosen time only when the registry publishes a new one, and optionally only once it is 1, 3, 7 or 14 days old. A container with changes made outside ProxMenux is skipped and reported; one with host directories runs only when that was confirmed when the schedule was saved."
]
]
}
}
]
},
{
"id": "logs",
"title": "Logs",
"intro": "The Logs tab appears only for containers installed by OCI manager Apps, between Mounts and Backups. It shows the standard output and error of the main process of the image, the same output <code>docker logs</code> shows for a Docker container.",
"blocks": [
{
"list": {
"items": [
"The output is kept on the host in <code>/var/log/proxmenux/oci/VMID.console.log</code> (mode 0600), from the first start and across restarts, so it can be read with the container stopped.",
"The file is rotated at 10 MB, keeping three compressed copies (<code>/etc/logrotate.d/proxmenux-oci</code>).",
"The last 100, 500 or 1000 lines are shown. While the container runs, new lines are followed live; scrolling up pauses the follow, and <strong>Follow</strong> resumes it.",
"A filter shows only the lines that contain a text, and <strong>Download</strong> saves the lines loaded.",
"Colour codes are removed and a line that a progress bar redraws is shown in its final state.",
"The tab reads the file on every open; it is not cached."
]
}
},
{
"calloutInfo": {
"title": "First-start credentials",
"body": "Images that print a generated password on their first start leave it in this output, as <code>docker logs</code> does. The installer reads it from there to show it in its summary."
}
},
{
"figure": {
"src": "/oci-manager/monitor-logs-tab.png",
"alt": "Logs tab with the console output of an OCI container, the line selector, the filter and the follow button",
"caption": "Console output of an OCI container."
}
}
]
},
{
"id": "terminal",
"title": "Proxmox VE console",
"blocks": [
{
"p": "An OCI image runs its own process as PID 1 and no login service, so the default console of an LXC would open a terminal nothing answers on. Containers installed by OCI manager Apps are created with <code>cmode: shell</code>: the Proxmox VE console opens a shell with <code>lxc-attach</code>, the equivalent of <code>docker exec</code>, with the running application untouched."
},
{
"list": {
"items": [
"The shell is the one <code>/etc/passwd</code> gives to root in the image. An image whose root has no shell, or ships none, keeps the default console.",
"The shell is root inside the container, without a password. Who can open it is decided by the <code>VM.Console</code> permission of Proxmox VE.",
"The terminal of the Monitor enters the container with <code>pct enter</code>, which works the same way."
]
}
}
]
},
{
"id": "mounts",
"title": "Mounts",
"blocks": [
{
"p": "The <mountsLink>Mounts</mountsLink> tab lists the container disks and host directories of the container. The usage of a container disk on block storage such as LVM-thin is read from the filesystem of the disk, which is mounted only inside the container."
}
]
}
]
}
@@ -0,0 +1,383 @@
{
"meta": {
"title": "Multi-container applications | ProxMenux",
"description": "How OCI manager Apps turns an application with its database and cache into coordinated native LXCs: plan, private network, dependency hook, validation and transactional updates."
},
"header": {
"title": "Multi-container applications",
"description": "An application with its database, cache and other services becomes several coordinated native LXCs, installed and updated as one.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "One application for the user, several LXCs for Proxmox VE",
"body": "A multi-container definition is one entry of the catalog. The installer creates one native LXC per service and keeps their dependencies explicit. Immich, Nextcloud, Paperless-ngx and Tandoor are installed this way."
}
},
{
"mermaid": {
"chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}<br/>{{appNet}}\"]\n O --> D[\"{{db}}<br/>{{private}}\"]\n O --> R[\"{{cache}}<br/>{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A",
"labels": {
"plan": "Stack plan",
"app": "Application LXC",
"appNet": "LAN + private",
"db": "PostgreSQL LXC",
"cache": "Valkey LXC",
"private": "private"
}
}
}
]
},
{
"id": "plan",
"title": "1. The plan, before any container exists",
"blocks": [
{
"p": "No LXC is created while the Compose definition is interpreted. A complete plan comes first, with every member, image, VMID, network, path, secret, order and health check. If the plan is not consistent, the installation does not start."
},
{
"table": {
"headers": [
"Plan element",
"Contents",
"Checked before creating anything"
],
"rows": [
[
"Members",
"Main application, database, cache, machine learning and other dependencies",
"Unique VMIDs, known roles and exactly one main member"
],
[
"Images",
"Rolling reference, architecture and resolved digest of each service",
"All exist, support the architecture and pass the OCI integrity check"
],
[
"Network",
"Private bridge, subnet, a fixed address per service and LAN access for the main member",
"No collision with existing bridges or subnets and no repeated address"
],
[
"Persistence",
"Container disks, host directories, owners and backup",
"No overlapping paths, storage available and declared permissions"
],
[
"Secrets",
"Database password, application keys and initial credentials",
"Generated once and given only to the members that use them"
],
[
"Lifecycle",
"Start order, stop order and a health check per member",
"The main application starts last and stops first"
]
]
}
}
]
},
{
"id": "create",
"title": "2. Creation, member by member",
"blocks": [
{
"steps": {
"items": [
{
"title": "Reserve every VMID",
"body": "The Proxmox VE inventory and the instance registry are checked. An existing CT is not adopted and a contract that still belongs to another instance is not reused."
},
{
"title": "Prepare every image",
"body": "All images are resolved, downloaded and verified before the first container is created."
},
{
"title": "Create each rootfs",
"body": "The official OCI metadata is imported, and the ProxMenux instance identity and the member role are added."
},
{
"title": "Private network",
"body": "A bridge and subnet are created and each member gets its fixed address; only the main member also gets the LAN interface."
},
{
"title": "Persistence",
"body": "Each database and configuration gets its own container disk; only data meant to be shared uses host directories."
},
{
"title": "Environment",
"body": "Internal endpoints, shared secrets and service variables are written. The application reaches its dependencies at their reserved private addresses."
},
{
"title": "Record",
"body": "Each member records its native configuration, the rootfs changes that updates have to reproduce and its relation to the stack."
}
]
}
}
]
},
{
"id": "checks",
"title": "3. Start and check each container",
"intro": "A created LXC does not mean a ready service. Dependencies start in order and each one passes a check specific to its service.",
"blocks": [
{
"table": {
"headers": [
"Service",
"Check",
"What it shows"
],
"rows": [
[
"PostgreSQL",
"<code>pg_isready</code> in the CT with the expected host, user and database",
"The server accepts connections for the configured database"
],
[
"Redis / Valkey",
"<code>redis-cli</code> or <code>valkey-cli</code> <code>PING</code> against its private address",
"The broker listens and answers"
],
[
"Immich machine learning",
"HTTP <code>GET /ping</code>, plus a check of the selected GPU runtime",
"The service answers and the requested acceleration has not fallen back to CPU"
],
[
"Nextcloud",
"<code>GET /status.php</code> with <code>installed=true</code>, <code>maintenance=false</code> and <code>needsDbUpgrade=false</code>",
"Initialisation finished with no pending migration"
],
[
"Paperless-ngx / Tandoor",
"HTTP on the LAN address and real port of the service",
"The frontend and its dependencies serve the application"
],
[
"Main application",
"The endpoint of its template, for example <code>/api/server/ping</code> in Immich",
"The whole stack works through the application that uses the dependencies"
]
]
}
},
{
"calloutWarning": {
"title": "running is not healthy",
"body": "The running state only says that the LXC process exists. Where the service offers a better check, an exec or HTTP check with a timeout is used. If a member stops or fails its check, the stack is not declared installed."
}
}
]
},
{
"id": "hook",
"title": "4. The dependency hook",
"blocks": [
{
"p": "The hook is set only on the main container of a dependent stack. Proxmox VE keeps the script as a snippet and runs it as the <code>hookscript</code> of that CT. The stack recipe is not written in the script: it lives in a separate private contract."
},
{
"codeGrid": {
"items": [
{
"title": "Main CT configuration",
"code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh"
},
{
"title": "Private contract (example)",
"code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}"
}
]
}
},
{
"snippet": {
"summary": "Complete source of proxmenux-stack-dependencies.sh",
"pathCode": "local:snippets/proxmenux-stack-dependencies.sh",
"snippetCode": "stackDependencyHook"
}
},
{
"p": "The script is the same for every stack. VMIDs, names, check types and timeouts come from the private contract <code>/etc/pve/priv/proxmenux-stack-VMID.json</code> of each stack."
},
{
"steps": {
"items": [
{
"title": "Proxmox VE calls pre-start",
"body": "Before the main CT starts, the hook runs with its VMID and the lifecycle phase."
},
{
"title": "A lock per stack",
"body": "<code>flock</code> on <code>/run/lock/proxmenux-stack-VMID.lock</code> prevents two start sequences at the same time."
},
{
"title": "The contract is read and validated",
"body": "A known schema, numeric dependencies, labels, an exec, http or running check and a positive timeout are required."
},
{
"title": "Dependencies in order",
"body": "Each CT must exist; a stopped one is started and one already running is not restarted."
},
{
"title": "Wait for health",
"body": "The check runs every two seconds, and the CT is also confirmed to be still running."
},
{
"title": "The main CT starts",
"body": "When every dependency is ready, pre-start ends and Proxmox VE starts the application."
}
]
}
},
{
"calloutInfo": {
"title": "The hook does not stop dependencies",
"body": "The post-start, pre-stop and post-stop phases do nothing. Stopping the main CT leaves PostgreSQL, Redis, Valkey or machine learning running. The hook orders the start; it does not turn several LXCs into one process."
}
},
{
"table": {
"headers": [
"Type",
"Example",
"Later starts"
],
"rows": [
[
"Dependent stack",
"Immich, Nextcloud, application with PostgreSQL",
"The hook of the main CT starts the dependencies and waits for them"
],
[
"Application suite",
"Arr suite",
"No main member: each LXC follows its own <code>onboot</code>"
],
[
"Single application",
"Jellyfin",
"Proxmox VE starts that LXC directly"
]
]
}
}
]
},
{
"id": "stack-checks",
"title": "5. Checks on the stack as a whole",
"blocks": [
{
"list": {
"items": [
"Every VMID exists, is unique and keeps the expected instance identity.",
"Every contract belongs to the same stack, keeps its role and its recorded native configuration.",
"The main member is last in <code>start_order</code> and first in <code>stop_order</code>.",
"No member, volume or rootfs adaptation is missing.",
"The hook points to the official snippet, its content is unchanged and its contract matches the stack recipe.",
"The observed images and digests match the prepared OCI archives.",
"Devices, GPU profiles, mounts, secrets and endpoints still match the contracts.",
"After every dependency passes, the endpoint of the main application checks the integration between members."
]
}
}
]
},
{
"id": "manage",
"title": "Managing an installed stack",
"intro": "In <strong>Manage installed OCI applications</strong>, any member leads to the whole stack. The menu of a stack offers <strong>Update every container of the application</strong> and <strong>Remove: delete the application and its containers</strong>.",
"blocks": [
{
"steps": {
"items": [
{
"title": "Any member",
"body": "The contract of the member names the main VMID and the complete list of members."
},
{
"title": "The stack is reproducible",
"body": "Identities, contracts, hook, adaptations and a coordinated replay for that recipe are validated."
},
{
"title": "Images first",
"body": "The application is not stopped until every digest has been resolved, downloaded and verified."
},
{
"title": "Stop and back up the set",
"body": "Verified native backups are taken with the stack stopped, so application and databases belong to the same moment."
},
{
"title": "Update and check each member",
"body": "Adaptation, network, mounts, secrets and devices are applied again before the health check of each member."
},
{
"title": "Publish or recover everything",
"body": "The new contracts are published only when every member passes. If one fails, every member is restored."
}
]
}
},
{
"calloutWarning": {
"title": "A stack without a coordinated replay is not updated",
"body": "If the preparation of a stack cannot be reproduced, the update is refused before the stack is stopped. The application keeps running as it is."
}
}
]
},
{
"id": "failure",
"title": "6. When something fails",
"blocks": [
{
"p": "During an installation, an error stops and removes the incomplete containers that operation created, and the private bridge if the operation created it. A stack is not published as valid until the whole sequence finishes."
},
{
"p": "An update is transactional: all images are prepared first, then the stack stops, each member is backed up and the backup verified, and only then is each rootfs replaced. Members start one by one with their health check; if one fails, every member is restored from the same set of backups, so the database and the application never belong to different moments."
},
{
"mermaid": {
"chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]",
"labels": {
"prepare": "Prepare every image",
"stop": "Stop, main member first",
"backup": "Verified backup of every CT",
"replace": "Replace rootfs",
"healthy": "All healthy?",
"yes": "Yes",
"no": "No",
"commit": "Publish contracts",
"rollback": "Restore every member"
}
}
}
]
},
{
"id": "not-assumed",
"title": "What a stack does not include",
"blocks": [
{
"list": {
"items": [
"A suite installed in one flow, such as the Arr suite, is not a dependent stack: its containers have no start order between them.",
"The private network does not replace authentication, TLS or the configuration of each application.",
"The backup of one LXC does not contain the bridge, the hook or the contracts of the rest of the stack.",
"Prowlarr, Sonarr or Radarr receive no indexers, profiles or providers from ProxMenux."
]
}
}
]
}
]
}
@@ -0,0 +1,190 @@
{
"meta": {
"title": "Data, paths and networking | ProxMenux",
"description": "How OCI manager Apps keeps the data of an OCI container: container disks, host directories, Rclone mounts, addresses and private networks."
},
"header": {
"title": "Data, paths and networking",
"description": "What lives in the rootfs, what survives its replacement, how data is shared between containers and how each container gets its address.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "Persistence is decided before the LXC exists",
"body": "The volumes published by the image and by its Compose file are read before the container is created. Every path that has to survive an update becomes a mount point independent of the rootfs, so the rootfs only holds what belongs to the image and can be replaced."
}
}
]
},
{
"id": "questions",
"title": "What the installer asks",
"intro": "The template supplies the paths the application needs. Each one is placed on a container disk or on a host directory, and more paths can be added before the summary.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Required paths", "body": "<code>/config</code>, <code>/data</code>, libraries, downloads and every volume the application declares are listed." },
{ "title": "Location", "body": "Each path is placed on a disk of the container or on an existing host directory." },
{ "title": "Storage and size", "body": "A container disk is created on a Proxmox VE storage, <code>local-lvm</code> by default, with the size given. It appears as <code>vm-VMID-disk-N</code> and is attached as <code>mpN</code>." },
{ "title": "Host directory", "body": "A host directory is given by its path. A directory that does not exist is created, owned by the user the container maps." },
{ "title": "Additional paths", "body": "More pairs of host path or volume and container path can be added before installing." },
{ "title": "Summary", "body": "The complete mapping is shown before the CT is created and is stored in its instance contract." }
]
}
}
]
},
{
"id": "options",
"title": "The two persistent locations",
"blocks": [
{
"table": {
"headers": ["Property", "Container disk", "Host directory"],
"rows": [
["In the configuration", "<code>mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G</code>", "<code>mpN: /mnt/oci-shared/media,mp=/data/media</code>"],
["Container backup (vzdump)", "Included, with <code>backup=1</code>", "Not included"],
["Size", "Fixed; grown with a resize of the mount point", "The free space of the host filesystem or dataset"],
["Other containers", "Mounted only by its own container", "The same directory can be mounted in several containers"],
["Snapshots and restore", "Managed by Proxmox VE together with the CT", "Managed on the host storage"],
["Removing the application", "Deleted with the container", "Kept, with its content"],
["Moving the CT to another node", "Moves with the CT", "The same path has to exist on the other node"]
]
}
},
{
"p": "The rootfs is reserved for the binaries and the content of the image. An update or a recreation replaces it without touching either kind of mount point."
}
]
},
{
"id": "example",
"title": "Example: a container with both locations",
"blocks": [
{
"code": {
"title": "Jellyfin installed as CT 151 on local-lvm (excerpt)",
"code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Container disk, part of the CT backup\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Host directory, outside the CT backup\nmp1: /mnt/oci-shared/media,mp=/data/media"
}
},
{
"p": "An update or a recreation replaces only the rootfs: <code>mp0</code> keeps users, libraries and settings, and <code>mp1</code> keeps showing the same media. Restoring the CT backup brings back <code>/config</code>; the media directory is restored, if needed, from the backup of the host storage."
},
{
"flow": {
"nodes": [
{ "label": "Contract", "detail": "/config" },
{ "label": "Location", "detail": "container disk\nor host directory" },
{ "label": "LXC", "detail": "always /config\nfor the application" }
],
"caption": "The application sees the path the image publishes; only where it is stored changes."
}
}
]
},
{
"id": "shared",
"title": "One host directory, several containers",
"blocks": [
{
"mermaid": {
"chartCode": "flowchart TB\n H[\"{{host}}<br/>/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent<br/>/data\"]\n H --> J[\"Jellyfin<br/>/data\"]\n H --> R[\"Radarr / Sonarr<br/>/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]",
"labels": { "host": "Host directory", "config": "own configuration" }
}
},
{
"p": "Each container keeps its configuration on its own disk. The library or the downloads are one host directory mounted at the same internal path in every container, so a path that one application writes is the same path another one reads."
}
]
},
{
"id": "rclone",
"title": "Cloud storage through the Rclone application",
"intro": "The Rclone application of the catalog offers, besides its installation, <strong>Enable a mount on an existing Rclone OCI container</strong>. It mounts a remote already created and authorised in the Rclone web UI and publishes it on the host, where other containers can use it as a host directory.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Container and remote", "body": "The VMID of the Rclone container, the exact name of the remote and, optionally, a path inside it." },
{ "title": "Mount name and cache", "body": "The name of the mount and the VFS cache mode: <code>off</code>, <code>minimal</code>, <code>writes</code> or <code>full</code> (default)." },
{ "title": "Published views", "body": "A common root, <code>/mnt/oci-shared</code> by default, holds a read/write view in <code>/mnt/oci-shared/remotes/NAME</code> and a read-only view in <code>/mnt/oci-shared/remotes-ro/NAME</code>." },
{ "title": "Activation", "body": "After a confirmation, the CT is stopped, its start command and a Proxmox VE hookscript are set, and it is started again. The operation waits until both views are mounted on the host." }
]
}
},
{
"calloutInfo": {
"title": "If the mount does not come up",
"body": "The previous configuration of the container is restored and it is started again, so a failed activation leaves Rclone as it was."
}
}
]
},
{
"id": "network",
"title": "Addresses and networks",
"intro": "A single application needs an address. A multi-container application also needs a stable network between its members.",
"blocks": [
{
"cards": {
"items": [
{ "icon": "network", "title": "Single application", "body": "Bridge and DHCP or a fixed CIDR address are chosen. The LXC has an address of its own and the summary shows the complete URLs of the services." },
{ "icon": "waypoints", "title": "Multi-container application", "body": "A free subnet is found, a persistent private bridge is created and each member (application, database, cache) gets a fixed address on it." }
]
}
},
{
"flow": {
"nodes": [
{ "label": "LAN", "detail": "reachable address\nmain service only" },
{ "label": "Main LXC", "detail": "web / API\nLAN + private network" },
{ "label": "Private network", "detail": "PostgreSQL · Valkey · ML\nfixed addresses" }
],
"caption": "Dependencies talk over the private network and have no address on the LAN."
}
},
{
"p": "The stack contract stores bridge, subnet, addresses and the relations between services. Updating or recreating a member reuses the same topology. ProxMenux Monitor opens the main container at its LAN address, not at its address on the private network."
}
]
},
{
"id": "ownership",
"title": "Ownership",
"blocks": [
{
"list": {
"items": [
"New directories are created with the UID and GID the unprivileged LXC maps.",
"Existing directories are not re-owned recursively.",
"Sockets, system files and sensitive paths are not offered as generic host directories."
]
}
}
]
},
{
"id": "backup",
"title": "What a container backup contains",
"blocks": [
{
"table": {
"headers": ["Element", "In the CT vzdump", "Where it is kept"],
"rows": [
["OCI rootfs", "Yes", "The CT backup; it can also be rebuilt from the image and the contract"],
["Container disk with <code>backup=1</code>", "Yes", "The CT backup"],
["Host directory", "No", "The backup of the host storage"],
["Instance contract", "No", "<code>/usr/local/share/proxmenux/oci/instances/VMID/</code> on the host"],
["Multi-container application", "Each member in its own backup", "The backups of every member, plus the stack contract and its bridge on the host"]
]
}
}
]
}
]
}
@@ -64,7 +64,7 @@
"heading": "How it works under the hood",
"items": [
"Dialog menu lists the 6 codes; you pick one.",
"If <code>config.json</code> exists: <code>jq --arg lang \"$new_language\" '.language = $lang'</code> updates the field in place.",
"If <code>config.json</code> exists: <code>jq --arg lang \"$new_language\" '.language = $lang''</code> updates the field in place.",
"If <code>config.json</code> doesn't exist: a fresh one is created with the language code in a single-field object.",
"Confirmation dialog: <em>\"Language changed to [code]\"</em>.",
"<code>exec bash config_menu.sh</code> reloads the Settings menu with the new language active."
+16
View File
@@ -71,10 +71,26 @@
"dashboardSecurity": "Pestaña Seguridad",
"dashboardSettings": "Pestaña Ajustes",
"healthMonitor": "Monitor de salud",
"auditReport": "Auditoría e informes",
"auditReportOverview": "Resumen",
"auditReportAssessment": "Evaluación e inventario",
"auditReportPolicy": "Política",
"auditReportChanges": "Cambios",
"auditReportReports": "Informes y comparaciones",
"auditReportScope": "Alcance y garantías",
"notifications": "Notificaciones",
"aiAssistant": "Asistente de IA",
"apiReference": "Referencia API",
"integrations": "Integraciones",
"ociManager": "OCI manager Apps",
"ociOverview": "Resumen",
"ociArchitecture": "Cómo se traduce una imagen OCI",
"ociCustomImage": "Imagen fuera del catálogo",
"ociStorageNetwork": "Datos, rutas y red",
"ociHardware": "Dispositivos y aceleración",
"ociStacks": "Aplicaciones multicontenedor",
"ociLifecycle": "Instalar, actualizar y recrear",
"ociMonitor": "En ProxMenux Monitor",
"proxmenuxScripts": "ProxMenux Scripts",
"postInstallScript": "Script Post-instalación",
"postInstallOverview": "Resumen",
@@ -157,12 +157,12 @@
{
"endpoint": "/api/vms/<vmid>/control",
"method": "POST",
"use": "Encender / apagar / apagar limpio / reiniciar una VM o contenedor LXC (las mismas operaciones que expone la modal VM & LXC del Monitor). Body: {\"action\": \"start|stop|shutdown|reboot\"}. Síncrono — devuelve el resultado directo sin necesidad de polling."
"use": "Encender / apagar / apagar limpio / reiniciar una VM o contenedor LXC (las mismas operaciones que expone la modal VM & LXC del Monitor). El cuerpo lleva <code>action</code>: <code>start</code>, <code>stop</code>, <code>shutdown</code> o <code>reboot</code>. Síncrono — devuelve el resultado directo sin necesidad de polling."
},
{
"endpoint": "/api/vms/<vmid>/backup",
"method": "POST",
"use": "Crear un backup vzdump de una VM o LXC. Body (todo opcional excepto cuando los defaults no encajan con tu layout de storage): {\"storage\": \"<pve-storage>\", \"mode\": \"snapshot|suspend|stop\", \"compress\": \"zstd|lzo|gz|none\", \"protected\": true, \"notes\": \"…\", \"notification\": \"auto|always|failure|never\", \"pbs_change_detection\": \"default|legacy|data\"}. Devuelve el UPID de la tarea PVE."
"use": "Crear un backup vzdump de una VM o LXC. Campos del cuerpo, todos opcionales salvo cuando los valores por defecto no encajan con la distribución del almacenamiento: <code>storage</code> (un almacenamiento de PVE), <code>mode</code> (<code>snapshot</code>, <code>suspend</code> o <code>stop</code>), <code>compress</code> (<code>zstd</code>, <code>lzo</code>, <code>gz</code> o <code>none</code>), <code>protected</code> (<code>true</code> o <code>false</code>), <code>notes</code>, <code>notification</code> (<code>auto</code>, <code>always</code>, <code>failure</code> o <code>never</code>) y <code>pbs_change_detection</code> (<code>default</code>, <code>legacy</code> o <code>data</code>). Devuelve el UPID de la tarea PVE."
},
{
"endpoint": "/api/vms/<vmid>/backups",
@@ -0,0 +1,146 @@
{
"meta": {
"title": "Evaluación e inventario | Auditoría e informes",
"description": "La evaluación de Auditoría e informes: perfiles de informe, clasificación de los resultados, riesgos aceptados, fuentes ilegibles, Lynis e inventario del nodo."
},
"header": {
"title": "Evaluación e inventario",
"description": "La evaluación inspecciona el nodo, conserva sus hallazgos y responde a preguntas distintas según el perfil de informe.",
"section": "Auditoría e informes"
},
"sections": [
{
"id": "read-only",
"blocks": [
{
"calloutInfo": {
"title": "Una evaluación inspecciona; no cambia la configuración",
"body": "Lee la configuración y el estado del host. Escribe sus resultados, informes y logs, y las comprobaciones de arranque pueden montar un momento las particiones de sistema EFI. La configuración que evalúa no se modifica."
}
},
{
"p": "<strong>Ejecutar evaluación</strong> inicia una ejecución con el perfil seleccionado en <strong>Informe</strong>. La vista muestra la fecha de la última ejecución y cuánto tiempo ha pasado."
}
]
},
{
"id": "profiles",
"title": "Perfiles de informe",
"intro": "Cada perfil responde a una pregunta distinta. Las comprobaciones y las secciones se seleccionan antes de componer el documento.",
"blocks": [
{
"table": {
"headers": ["Perfil", "Qué abarca"],
"rows": [
["Auditoría completa", "Todas las comprobaciones y toda la estructura disponible."],
["Diagnóstico rápido", "Todas las comprobaciones; el resultado empieza por los hallazgos críticos, las advertencias y las lecturas que no pudieron verificarse."],
["Inventario", "Una descripción del nodo, sin comprobaciones ni clasificación."],
["Revisión de seguridad", "Exposición, acceso, privilegios, certificados, actualizaciones, repositorios y Lynis."],
["Garantía de backups", "Cobertura, antigüedad, resultados, verificación, retención y recuperación de los backups."],
["Capacidad y desgaste", "Margen de crecimiento, memoria, ocupación y vida útil de los discos."]
]
}
},
{
"p": "El contenido de cada documento se describe en <reportsLink>Informes y comparaciones</reportsLink>."
}
]
},
{
"id": "results",
"title": "Cómo se clasifican los resultados",
"blocks": [
{
"table": {
"headers": ["Clasificación", "Significado"],
"rows": [
["Crítico", "Una condición fallida con prioridad."],
["Advertencia", "Una condición que requiere revisión, según la evidencia o la política."],
["Observación", "Información sobre el nodo que no se presenta como fallo."],
["Sin verificar", "La fuente que necesita la comprobación no pudo leerse. No significa que el problema no exista."],
["Conforme", "La condición cumple el criterio aplicado."],
["No aplicable", "Dentro del alcance de la comprobación no hay nada a lo que aplicarla."],
["Riesgo aceptado", "El hallazgo existe y hay registrada una decisión sobre él."],
["Excluido por política", "La política declara que el elemento queda fuera del recuento."]
]
}
},
{
"p": "Los hallazgos pueden filtrarse por área: Sistema, Almacenamiento, Red, Seguridad, Backups, Invitados y Hardware. Cada hallazgo conserva su evidencia, con la fuente de la que se leyó."
}
]
},
{
"id": "unverified",
"title": "Cuando una fuente no puede leerse",
"blocks": [
{
"table": {
"headers": ["Caso", "Comportamiento"],
"rows": [
["Sin verificar", "La comprobación conserva su identidad, indica en su evidencia qué fuente falló y no convierte la falta de datos en un resultado conforme."],
["Evidencia incompleta", "El informe nombra la fuente y el momento de la recogida, de modo que un problema real puede distinguirse de una lectura insuficiente."],
["Una nueva ejecución", "Una vez corregido el acceso, el paquete o el servicio, el mismo perfil se ejecuta de nuevo y la comparación muestra si el resultado pudo verificarse."]
]
}
}
]
},
{
"id": "lynis",
"title": "Lynis",
"blocks": [
{
"p": "La revisión de seguridad usa el informe de Lynis del host. Cuando Lynis no se ha ejecutado todavía, o su informe supera el umbral de antigüedad de la política, un diálogo ofrece <strong>Ejecutar con Lynis</strong>, que tarda unos minutos más, o <strong>Ejecutar sin Lynis</strong>, que usa el informe existente."
},
{
"figure": {
"src": "/monitor/audit/lynis-dialog.png",
"alt": "Diálogo que ofrece ejecutar la evaluación con o sin Lynis",
"caption": "El diálogo de Lynis antes de una revisión de seguridad."
}
}
]
},
{
"id": "accept",
"title": "Aceptar un riesgo",
"blocks": [
{
"p": "<strong>Aceptar riesgo</strong> registra una decisión sobre un hallazgo. El motivo es obligatorio y se guarda junto al autor y la fecha."
},
{
"table": {
"headers": ["Campo", "Opciones"],
"rows": [
["Motivo", "Texto libre, obligatorio."],
["Deja de aplicarse tras", "90 días, 180 días, 1 año o no caduca. Al cumplirse el plazo, el hallazgo vuelve a estar activo."],
["Recordarme revisarla", "Un recordatorio que devuelve la decisión a la atención mientras sigue en vigor."]
]
}
},
{
"p": "Un hallazgo aceptado sigue visible con su decisión, y <strong>Volver a activo</strong> la revoca. Un riesgo aceptado no es una corrección: la comparación lo presenta como aceptado, no como resuelto."
}
]
},
{
"id": "inventory",
"title": "Inventario",
"blocks": [
{
"list": {
"items": [
"Identidad, versión de Proxmox VE, kernel, suscripción y clúster.",
"CPU, memoria, placa, BIOS, controladoras e IOMMU.",
"Discos, SMART, horas de funcionamiento y eventos registrados.",
"Adaptadores, bonds, bridges, latencia y conexiones.",
"Almacenamiento, invitados con sus discos e interfaces, y backups.",
"Passthrough PCI y el software que gestiona ProxMenux."
]
}
}
]
}
]
}
@@ -0,0 +1,148 @@
{
"meta": {
"title": "Cambios | Auditoría e informes",
"description": "El registro de cambios de Auditoría e informes: qué ha cambiado ProxMenux en el host, qué había antes, la diferencia y si puede deshacerse."
},
"header": {
"title": "Cambios",
"description": "El registro de las operaciones que ProxMenux realiza en el host y, cuando se capturó, el estado antes y después de cada una.",
"section": "Auditoría e informes"
},
"sections": [
{
"id": "purpose",
"blocks": [
{
"calloutInfo": {
"title": "Del script que se ejecutó al cambio que hizo",
"body": "Una función extensa puede cambiar solo dos líneas. El registro conserva cada operación concreta con el script, la función y la versión responsables, el recurso afectado, la diferencia y si puede deshacerse."
}
},
{
"flow": {
"nodes": [
{ "label": "Script", "detail": "función + versión" },
{ "label": "Captura", "detail": "contenido previo" },
{ "label": "Operación", "detail": "archivo · paquete · servicio" },
{ "label": "Registro", "detail": "atribución + diferencia" }
],
"caption": "La captura se toma al ejecutarse la operación y se consolida cuando el Monitor lee el registro."
}
},
{
"figure": {
"src": "/monitor/audit/changes-view.png",
"alt": "Vista Cambios de Auditoría e informes con las entradas agrupadas por opción de post-install y por script",
"caption": "La vista Cambios, con la diferencia de un archivo editado por ProxMenux."
}
}
]
},
{
"id": "types",
"title": "Tipos de entrada",
"intro": "El filtro de la parte superior separa las entradas por tipo.",
"blocks": [
{
"table": {
"headers": ["Tipo", "Qué registra"],
"rows": [
["Configuración", "ProxMenux escribió, editó o eliminó un archivo, cambió un ajuste o alteró un servicio."],
["Instalaciones", "ProxMenux añadió un paquete o componente, y se registran los paquetes que aparecieron realmente."],
["Ejecuciones", "ProxMenux ejecutó un comando; lo que cambió depende del propio comando."],
["Aplicado", "Una función se aplicó antes de que existiera el registro; el estado previo no se capturó."]
]
}
}
]
},
{
"id": "groups",
"title": "Cómo se organiza la vista",
"blocks": [
{
"table": {
"headers": ["Sección", "Contenido"],
"rows": [
["Optimizaciones de post-install", "Agrupadas por la opción de post-install que seleccionó el usuario."],
["Scripts de ProxMenux", "GPU, Coral, red, almacenamiento, seguridad, utilidades y el resto de scripts instrumentados, incluidos el instalador de ProxMenux y ProxMenux Monitor."],
["Paquetes y utilidades instalados", "Software que ProxMenux instaló en el host."]
]
}
},
{
"p": "Dentro de cada sección, <strong>Por función</strong> agrupa las entradas bajo la función que las hizo."
}
]
},
{
"id": "entry",
"title": "Qué muestra cada entrada",
"blocks": [
{
"list": {
"items": [
"El script, la función y la versión responsables.",
"La fecha y el recurso afectado.",
"El estado conocido antes y después del cambio.",
"Las líneas añadidas y eliminadas.",
"Los paquetes que se añadieron realmente.",
"La transición de estado de un servicio.",
"<strong>Deshacer esto</strong>: <em>Restaura exactamente lo que había</em>, <em>Elimina el fichero (no había ninguno antes)</em>, <em>Se puede desinstalar el paquete</em>, un deshacer parcial o <em>No se puede deshacer desde el registro</em>."
]
}
}
]
},
{
"id": "before",
"title": "Qué significa el estado previo",
"blocks": [
{
"p": "El estado previo puede ser contenido capturado, un archivo creado por primera vez o un estado desconocido. Los cambios hechos antes de que existiera el registro no pueden reconstruirse; al volver a ejecutar la función se captura primero el estado encontrado en ese momento."
}
]
},
{
"id": "limits",
"title": "Alcance del registro",
"blocks": [
{
"list": {
"items": [
"Los cambios manuales y los de otro software no se registran.",
"Solo se cubren las operaciones que pasan por las primitivas de auditoría de ProxMenux.",
"El registro nunca bloquea la operación que describe: si la entrada no puede escribirse, la operación continúa.",
"Los cambios sucesivos de un mismo recurso se muestran como el origen conocido frente al estado actual."
]
}
}
]
},
{
"id": "retention",
"title": "Evidencia guardada",
"blocks": [
{
"table": {
"headers": ["Elemento", "Comportamiento"],
"rows": [
["Entradas", "Se conservan en el host; del registro no se elimina nada automáticamente."],
["Contenido capturado", "Un objeto capturado se conserva mientras alguna entrada lo referencia."],
["Tamaño de una captura", "El contenido de más de 1 MiB no se guarda entero; la entrada registra que la captura se omitió por su tamaño."],
["Lectura", "El Monitor no carga objetos guardados de más de 2 MiB."],
["Diferencia", "Se muestran como máximo 400 líneas, y una diferencia más larga se marca como truncada."],
["Listado", "La API devuelve 200 entradas por petición por defecto y hasta 1000."]
]
}
},
{
"calloutWarning": {
"title": "Conservar el contenido no es deshacer automáticamente",
"body": "El contenido previo a un cambio puede consultarse y restaurarse a mano, pero la vista Cambios no revierte operaciones. Una entrada de ejecución registra el comando sin conocer todos los efectos de la herramienta que ejecutó."
}
}
]
}
]
}
@@ -0,0 +1,83 @@
{
"meta": {
"title": "Auditoría e informes | ProxMenux Monitor",
"description": "Evaluación de un nodo Proxmox VE, registro de lo que ProxMenux ha cambiado en él y declaración de lo que se espera de sus invitados y almacenamientos, con informes imprimibles."
},
"header": {
"title": "Auditoría e informes",
"description": "Una evaluación del nodo, el registro de lo que ProxMenux ha cambiado en él y la declaración de lo que se espera de él, con documentos que pueden imprimirse o guardarse como PDF.",
"section": "ProxMenux Monitor"
},
"sections": [
{
"id": "views",
"blocks": [
{
"calloutInfo": {
"title": "Tres vistas, tres preguntas",
"body": "Auditoría e informes separa los hechos del nodo, las intervenciones de ProxMenux y las expectativas declaradas para él. Una configuración que la evaluación no puede contrastar con una finalidad declarada se describe, no se presenta como un fallo."
}
},
{
"cards": {
"items": [
{ "icon": "shield", "title": "Evaluación: ¿cómo está el nodo?", "body": "Ejecuta las comprobaciones del perfil elegido, compone el inventario y clasifica lo que requiere atención, con la evidencia de cada resultado." },
{ "icon": "refresh", "title": "Cambios: ¿qué ha hecho ProxMenux?", "body": "Enumera los archivos, paquetes, servicios y comandos que han cambiado los scripts instrumentados de ProxMenux, con lo que había antes cuando se capturó." },
{ "icon": "fileText", "title": "Política: ¿qué se espera?", "body": "Declara qué invitados necesitan backup o deben arrancar con el host, qué almacenamiento es esencial y los umbrales de las comprobaciones." }
]
}
},
{
"flow": {
"nodes": [
{ "label": "Evaluación", "detail": "hechos" },
{ "label": "Política", "detail": "contexto" },
{ "label": "Cambios", "detail": "intervenciones" }
],
"caption": "La evaluación aporta los hechos, la política les da contexto y el registro recoge lo que ha hecho ProxMenux."
}
},
{
"figure": {
"src": "/monitor/audit/assessment-view.png",
"alt": "Auditoría e informes en ProxMenux Monitor con las vistas Evaluación, Cambios y Política",
"caption": "Auditoría e informes, con la vista Evaluación abierta."
}
}
]
},
{
"id": "boundaries",
"title": "Tres funciones distintas",
"blocks": [
{
"table": {
"headers": ["Función", "Qué hace"],
"rows": [
["<healthLink>Health Monitor</healthLink>", "Observa métricas y eventos de forma continua y puede generar notificaciones."],
["Auditoría e informes", "Ejecuta una evaluación cuando se pide, documenta el nodo y compara ejecuciones entre sí."],
["Registro de cambios", "Registra las operaciones que ProxMenux realiza a través de sus primitivas de auditoría."]
]
}
}
]
},
{
"id": "pages",
"title": "Páginas de esta sección",
"blocks": [
{
"next": {
"items": [
{ "label": "Evaluación e inventario", "href": "/docs/monitor/audit-report/assessment", "tail": "perfiles, resultados, riesgos aceptados e inventario." },
{ "label": "Cambios", "href": "/docs/monitor/audit-report/changes", "tail": "el registro de lo que ProxMenux ha cambiado en el host." },
{ "label": "Política", "href": "/docs/monitor/audit-report/policy", "tail": "invitados, almacenamiento y umbrales." },
{ "label": "Informes y comparaciones", "href": "/docs/monitor/audit-report/reports", "tail": "los seis documentos y la ejecución de referencia." },
{ "label": "Alcance y garantías", "href": "/docs/monitor/audit-report/scope", "tail": "fuentes, límites y datos guardados." }
]
}
}
]
}
]
}
@@ -0,0 +1,95 @@
{
"meta": {
"title": "Política | Auditoría e informes",
"description": "La política del nodo en Auditoría e informes: backup, arranque y objetivo de recuperación de cada invitado, el papel de cada almacenamiento y los umbrales de las comprobaciones."
},
"header": {
"title": "Política",
"description": "La política declara lo que ninguna inspección puede deducir: para qué sirve cada invitado y almacenamiento y los umbrales que aplican las comprobaciones.",
"section": "Auditoría e informes"
},
"sections": [
{
"id": "principle",
"blocks": [
{
"calloutInfo": {
"title": "Sin declaración, el informe describe; con ella, evalúa",
"body": "Una evaluación ve lo que hace el host, no para qué sirve. Un invitado sin backup cuya finalidad no está declarada se presenta como observación. Si su backup se declara obligatorio, la misma ausencia se presenta como advertencia. No es obligatorio declarar nada."
}
},
{
"figure": {
"src": "/monitor/audit/policy-view.png",
"alt": "Vista Política con los invitados, el almacenamiento y los umbrales del nodo",
"caption": "La vista Política."
}
}
]
},
{
"id": "guests",
"title": "Invitados",
"intro": "Cada VM y LXC del nodo tiene tres campos. Un valor que se deja por defecto toma el valor general, que se muestra a su lado.",
"blocks": [
{
"table": {
"headers": ["Campo", "Valores", "Efecto en la evaluación"],
"rows": [
["Backup", "Obligatorio, No obligatorio, Sin especificar", "La falta de backup es una advertencia si es obligatorio, una observación si no se especifica, y queda fuera del recuento si no es obligatorio."],
["Arranque", "Obligatorio, No obligatorio, Sin especificar", "Si el invitado debe arrancar con el host."],
["Objetivo de recuperación", "Horas", "La antigüedad máxima aceptable del último backup."]
]
}
}
]
},
{
"id": "storage",
"title": "Almacenamiento",
"intro": "Cada almacenamiento del nodo se declara Esencial, Opcional o Sin especificar.",
"blocks": [
{
"p": "Un almacenamiento inaccesible se presenta como crítico cuando se declara esencial o sirve a un invitado en marcha, como advertencia cuando su papel no se especifica, y como observación cuando se declara opcional."
}
]
},
{
"id": "thresholds",
"title": "Umbrales",
"intro": "Un umbral vacío usa el valor de fábrica, que se muestra como texto de ejemplo.",
"blocks": [
{
"list": {
"items": [
"Revisión de capacidad de almacenamiento (%) y revisión de ocupación de thin pool (%).",
"Ratio de sobreaprovisionamiento thin y ratio de sobreasignación de memoria.",
"Intervalo de scrub de ZFS (días).",
"Plazo de respaldo para la antigüedad (días) y margen sobre el calendario (ratio).",
"Aviso de caducidad de certificado (días).",
"Vida útil del disco (horas) y ventana de errores de disco recientes (días).",
"Antigüedad del informe de Lynis (días) y antigüedad de los índices de paquetes (días).",
"Journal frente a su tope (%).",
"Revisión de espacio del sistema de archivos (%) y revisión de inodos del sistema de archivos (%)."
]
}
}
]
},
{
"id": "save",
"title": "Cómo se guarda la política",
"blocks": [
{
"p": "La declaración se valida y se guarda de forma atómica en <code>/usr/local/share/proxmenux/audit_policy.json</code>. Cada guardado lleva una revisión: si la declaración cambió en otra sesión, el borrador no se guarda y la vista ofrece recargar la declaración guardada."
},
{
"calloutWarning": {
"title": "La política nunca se deduce",
"body": "ProxMenux no añade ningún requisito por su cuenta. Un campo vacío conserva el valor de fábrica o queda sin especificar, y una declaración parcial solo afecta a los elementos que nombra."
}
}
]
}
]
}
@@ -0,0 +1,160 @@
{
"meta": {
"title": "Informes y comparaciones | Auditoría e informes",
"description": "Los seis documentos de Auditoría e informes, cómo se imprimen o guardan como PDF y cómo se comparan las ejecuciones con una ejecución de referencia."
},
"header": {
"title": "Informes y comparaciones",
"description": "Seis documentos, cada uno compuesto para una pregunta distinta, y la comparación de cada ejecución con una ejecución de referencia.",
"section": "Auditoría e informes"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "Seis documentos, no seis estilos",
"body": "El motor selecciona las comprobaciones y las secciones antes de componer el documento. Un diagnóstico rápido no es una auditoría completa con menos páginas, y un inventario no presenta resultados de evaluación. Los documentos de muestra usan datos ficticios."
}
}
]
},
{
"id": "documents",
"title": "Los seis documentos",
"blocks": [
{
"downloads": {
"items": [
{
"title": "Auditoría completa",
"body": "Documenta el nodo de extremo a extremo como registro técnico.",
"href": "/monitor/audit/sample-audit-full-report.pdf",
"facts": [
{ "label": "Comprobaciones", "value": "Todas las disponibles." },
{ "label": "Contenido", "value": "Resumen ejecutivo; identidad y clúster; hardware; red y latencia; almacenamiento; invitados; passthrough; aplicaciones; hallazgos con evidencia; fuentes y alcance." }
]
},
{
"title": "Diagnóstico rápido",
"body": "Muestra lo que requiere atención sin el inventario completo.",
"href": "/monitor/audit/sample-audit-diagnostic-report.pdf",
"facts": [
{ "label": "Comprobaciones", "value": "Las mismas que la auditoría completa." },
{ "label": "Contenido", "value": "Identidad mínima; hallazgos críticos; advertencias; observaciones relevantes; lecturas sin verificar y acciones prioritarias. Deja fuera diagramas, inventario y anexos extensos." }
]
},
{
"title": "Inventario",
"body": "Describe lo que existe en el nodo sin evaluarlo.",
"href": "/monitor/audit/sample-audit-inventory-report.pdf",
"facts": [
{ "label": "Comprobaciones", "value": "Ninguna; no se clasifica ningún hallazgo." },
{ "label": "Contenido", "value": "Identidad; clúster; CPU y memoria; placa, BIOS y controladoras; red; almacenamiento; VM y LXC; passthrough; aplicaciones y elementos gestionados por ProxMenux." }
]
},
{
"title": "Revisión de seguridad",
"body": "Abarca la exposición y los controles de acceso al nodo.",
"href": "/monitor/audit/sample-audit-security-report.pdf",
"facts": [
{ "label": "Comprobaciones", "value": "El área de seguridad, más los privilegios de los contenedores, las actualizaciones, los repositorios y la cadena APT." },
{ "label": "Contenido", "value": "Identidad y clúster; red y latencia; acceso; cortafuegos; 2FA; certificados; privilegios; actualizaciones; repositorios; estado y antigüedad de Lynis." }
]
},
{
"title": "Garantía de backups",
"body": "Comprueba que la protección declarada existe y que sus backups son utilizables.",
"href": "/monitor/audit/sample-audit-backup-report.pdf",
"facts": [
{ "label": "Comprobaciones", "value": "El área de backups, más la conectividad del almacenamiento y la entrega de notificaciones." },
{ "label": "Contenido", "value": "Cobertura por invitado; objetivo de recuperación declarado; antigüedad y resultado; destino; retención; verificación; trabajos fallidos; recuperación y fuentes incompletas." }
]
},
{
"title": "Capacidad y desgaste",
"body": "Mide el margen de crecimiento y las señales de agotamiento o envejecimiento.",
"href": "/monitor/audit/sample-audit-capacity-report.pdf",
"facts": [
{ "label": "Comprobaciones", "value": "Las áreas de almacenamiento y hardware, más memoria, swap, journal y el sistema de archivos del host." },
{ "label": "Contenido", "value": "Ocupación y umbrales; thin pools; sobreaprovisionamiento; memoria; inodos; ZFS; temperaturas; horas de funcionamiento; errores SMART y vida útil de NVMe/SSD." }
]
}
]
}
},
{
"figure": {
"src": "/monitor/audit/audit-report-preview.png",
"alt": "Primera página de un informe de auditoría completa",
"caption": "Todos los documentos comparten el identificador del informe, las secciones numeradas, la fecha, el nodo, el perfil y el pie de página."
}
},
{
"p": "Un documento contiene la identidad del nodo y la fecha de la evaluación, el resultado ejecutivo, un resumen por áreas, la estructura de hardware, red y almacenamiento, la protección de los invitados, los hallazgos con su evidencia, las fuentes que no pudieron leerse, y el alcance y la política aplicada, en la medida en que cada perfil los incluye."
}
]
},
{
"id": "print",
"title": "Imprimir o guardar como PDF",
"blocks": [
{
"steps": {
"items": [
{ "title": "Ejecutar", "body": "Una evaluación se ejecuta con el perfil seleccionado en <strong>Informe</strong>." },
{ "title": "Revisar", "body": "La ejecución terminada muestra sus hallazgos, las fuentes que no pudo leer y la política que aplicó." },
{ "title": "Generar informe", "body": "<strong>Generar informe</strong> abre la vista preparada como documento." },
{ "title": "Imprimir", "body": "<strong>Imprimir o guardar como PDF</strong> abre el diálogo de impresión del navegador, donde se selecciona una impresora o <em>Guardar como PDF</em>." }
]
}
},
{
"calloutTip": {
"title": "La impresión es un documento, no una captura",
"body": "La barra de acciones se oculta, una tabla que continúa en la página siguiente repite su cabecera, los bloques evitan cortes innecesarios y cada página conserva la identificación y la numeración."
}
}
]
},
{
"id": "compare",
"title": "Comparar con una ejecución de referencia",
"intro": "<strong>Usar como referencia</strong> marca una ejecución terminada como referencia. Las ejecuciones posteriores se comparan con ella y sus hallazgos se separan en:",
"blocks": [
{
"table": {
"headers": ["Grupo", "Significado"],
"rows": [
["Nuevos", "Presentes ahora y no antes."],
["Empeoraron", "Siguen presentes, y son más graves o afectan a más que antes."],
["Mejoraron", "Siguen presentes, pero son menos graves o afectan a menos que antes."],
["Resueltos", "Ya no aparecen, y nadie los aceptó."],
["Aceptados", "Ya no cuentan porque se aceptó un riesgo, no porque haya cambiado el host."],
["Ya no se evalúan", "Estaban antes y no aparecen en esta ejecución; nada ha verificado que hayan dejado de existir."]
]
}
},
{
"table": {
"headers": ["Ejecución de referencia", "Comportamiento"],
"rows": [
["Marcarla", "Puede marcarse cualquier ejecución terminada; ninguna se marca automáticamente."],
["Cambiarla", "Marcar otra ejecución mueve la referencia sin borrar el historial."],
["Comparar", "Si no se seleccionan dos ejecuciones, la comparación usa la referencia y la ejecución seleccionada o la más reciente."],
["Conservar ejecuciones", "Se conservan las 30 ejecuciones más recientes, y la ejecución de referencia nunca se elimina."],
["Sin referencia", "La vista indica que todavía no se ha elegido una ejecución de referencia, así que no hay con qué comparar."]
]
}
},
{
"calloutWarning": {
"title": "Un informe describe un momento",
"body": "Una evaluación no certifica un periodo completo. Los resultados antiguos dejan de describir el estado actual; la vista muestra la antigüedad de la última ejecución y cada fuente conserva el momento en que se recogió."
}
}
]
}
]
}
@@ -0,0 +1,97 @@
{
"meta": {
"title": "Alcance y garantías | Auditoría e informes",
"description": "De dónde proceden los datos de Auditoría e informes, qué queda fuera de su alcance, las garantías de su diseño y dónde se guardan sus datos."
},
"header": {
"title": "Alcance y garantías",
"description": "De dónde proceden los datos de una evaluación, qué queda fuera de su alcance y dónde guarda Auditoría e informes sus datos.",
"section": "Auditoría e informes"
},
"sections": [
{
"id": "sees",
"title": "Qué observa",
"blocks": [
{
"list": {
"items": [
"La configuración y el estado del nodo local.",
"La configuración declarada de las VM y los LXC.",
"El estado del almacenamiento tal como lo conoce Proxmox VE.",
"El historial disponible de backups y verificaciones.",
"Los datos SMART y los eventos que ha recogido el Monitor.",
"El estado de los servicios, el clúster, HA y las fuentes locales."
]
}
}
]
},
{
"id": "outside",
"title": "Qué queda fuera de su alcance",
"blocks": [
{
"list": {
"items": [
"El interior de los invitados, más allá de lo que declaran a Proxmox VE.",
"El equipamiento de red fuera del host.",
"Las dependencias remotas que el nodo no puede observar.",
"El estado físico que el hardware no expone.",
"Las acciones manuales o de otro software, en el registro de cambios."
]
}
},
{
"calloutWarning": {
"title": "La ausencia de evidencia no es evidencia de ausencia",
"body": "Una fuente que no puede leerse da un resultado sin verificar o incompleto, nunca uno conforme. El informe enumera las fuentes que no estuvieron disponibles."
}
}
]
},
{
"id": "guarantees",
"title": "Garantías del diseño",
"blocks": [
{
"list": {
"items": [
"Identificadores estables para las comprobaciones.",
"Historial de ejecuciones y hallazgos.",
"Una política validada y guardada de forma atómica.",
"Riesgos aceptados con motivo y visibles.",
"Atribución de los cambios al script y a la función.",
"Captura del estado anterior y posterior cuando está disponible.",
"Un resultado explícito cuando algo no puede medirse o capturarse."
]
}
}
]
},
{
"id": "storage",
"title": "Dónde se guardan los datos",
"blocks": [
{
"table": {
"headers": ["Datos", "Ubicación"],
"rows": [
["Política", "<code>/usr/local/share/proxmenux/audit_policy.json</code>"],
["Evaluaciones", "La base de datos de auditoría del Monitor"],
["Objetos capturados", "<code>/usr/local/share/proxmenux/changes/objects/</code>"],
["Entradas pendientes", "<code>/usr/local/share/proxmenux/changes/spool/</code>"],
["Registro consolidado", "<code>/usr/local/share/proxmenux/changes.db</code>"]
]
}
},
{
"calloutInfo": {
"title": "Una herramienta de revisión, no una certificación",
"body": "Auditoría e informes revisa, compara y documenta el nodo. Sus resultados se leen junto con la finalidad del sistema, la política declarada y las fuentes disponibles."
}
}
]
}
]
}
@@ -96,7 +96,8 @@
{ "method": "docker label / docker exec", "use": "Lee una etiqueta de versión OCI o ejecuta un comando de versión dentro de un contenedor Docker." },
{ "method": "python distribution", "use": "Usa importlib.metadata mediante el intérprete de Python seleccionado." },
{ "method": "command", "use": "Ejecuta un comando avanzado en formato argv, sin shell, y extrae la versión de su salida." },
{ "method": "manual", "use": "Guarda una versión introducida manualmente; debe cambiarse después de actualizar la app." }
{ "method": "manual", "use": "Guarda una versión introducida manualmente; debe cambiarse después de actualizar la app." },
{ "method": "OCI image", "use": "Para contenedores instalados por OCI manager Apps. Lee las versiones de la aplicación y de la imagen del registro de la instalación y compara el digest instalado con el que publica el registro; no necesita configuración." }
],
"sourcesHeading": "Fuentes para la versión disponible",
"sources": [
@@ -52,8 +52,8 @@
},
"drillIn": {
"heading": "Modal de vista en detalle por guest",
"intro": "El modal se abre con el nombre, VMID, tipo, estado y tiempo de actividad del sistema. La navegación se adapta al elemento: <strong>Estado</strong>, <strong>App</strong> y <strong>Actualizaciones</strong> para gestionar aplicaciones LXC, <strong>Montajes</strong> cuando existen puntos de montaje, además de <strong>Copias</strong> y <strong>Cortafuegos</strong>. La barra inferior mantiene los controles de ciclo de vida y el terminal LXC disponibles desde cualquier pestaña.",
"statusTitle": "Pestaña 1 — Status",
"intro": "El modal se abre con el nombre, VMID, tipo, estado y tiempo de actividad del sistema. La navegación se adapta al elemento: <strong>Estado</strong>, <strong>App</strong> y <strong>Actualizaciones</strong> para gestionar aplicaciones LXC, <strong>Montajes</strong> cuando existen puntos de montaje, <strong>Logs</strong> en los contenedores instalados por OCI manager Apps, además de <strong>Backups</strong> y <strong>Cortafuegos</strong>. La barra inferior mantiene los controles de ciclo de vida y el terminal LXC disponibles desde cualquier pestaña.",
"statusTitle": "Pestaña 1 — Estado",
"statusImageAlt": "Modal de vista en detalle por guest — pestaña Status con tarjetas en vivo de CPU / Memoria / Disco, totales de E/S de disco y red, el logo de distro del SO y el bloque Resources / IP Addresses",
"statusImageCaption": "Pestaña Status — CPU / Memoria / Disco en vivo con barras de progreso arriba, totales de E/S acumulados (lectura/escritura de disco, descarga/subida de red) abajo, después el bloque estático Resources con expansiones de Notes y + Info y la lista de pastillas IP Addresses.",
"statusIntro": "La pestaña por defecto — la vista \"¿este guest se está portando?\". Tres bloques:",
@@ -106,7 +106,9 @@
],
"mountsCalloutTitle": "Lo que esto te da sobre la UI nativa",
"mountsCalloutBody": "Una vista veraz y consciente de la capacidad de cada sitio donde el contenedor lee o escribe. Shares NFS o CIFS montados desde dentro del CT — invisibles para la UI web de Proxmox — aparecen aquí con el mismo aspecto y la misma sonda de salud que cualquier mount point configurado. Mounts remotos stale y zombie binds salen marcados antes de que muerdan durante un backup.",
"backupsTitle": "Pestaña 5 — Copias",
"logsTitle": "Pestaña 5 — Logs (solo contenedores OCI)",
"logsBody": "Solo aparece en los contenedores instalados por OCI manager Apps. Muestra la salida de consola del proceso principal de la imagen, guardada en el host en <code>/var/log/proxmenux/oci/VMID.console.log</code>: las últimas 100, 500 o 1000 líneas, seguidas en directo con el contenedor en marcha, con un filtro y una descarga. Lee el archivo cada vez que se abre, así que también funciona con el contenedor detenido. Los detalles están en <ociLink>Contenedores OCI en ProxMenux Monitor</ociLink>.",
"backupsTitle": "Pestaña 6 — Backups",
"backupsImageAlt": "Modal de vista en detalle por guest — pestaña Backups con la lista de backups disponibles, etiqueta de destino, tamaños y el botón Create Backup",
"backupsImageCaption": "Pestaña Backups — cada backup almacenado en los almacenamientos Proxmox configurados para este guest, ordenados de más nuevo a más viejo. La cabecera de la pestaña lleva la insignia de recuento.",
"backupsIntro": "Lista cada backup almacenado en los almacenamientos Proxmox configurados para este guest, ordenados de más nuevo a más viejo. El título de la pestaña lleva una insignia de recuento para que veas de un vistazo si el guest está backupeado. Por fila:",
@@ -116,7 +118,7 @@
"<strong>Size</strong> — tamaño final en disco del backup."
],
"backupsOutro": "El botón <strong>+ Create Backup</strong> arriba a la derecha arranca una nueva ejecución en el almacenamiento marcado como \"Backup target\" en la config de almacenamiento de Proxmox. El restore vive en la UI web de Proxmox — el Monitor expone la vista \"¿este guest tiene backup reciente?\", no el flujo de recuperación.",
"firewallTitle": "Pestaña 6 — Cortafuegos",
"firewallTitle": "Pestaña 7 — Cortafuegos",
"firewallIntro": "Lee el log de firewall de Proxmox por guest directamente del host (sin servicio extra, sin polling). La pestaña siempre está presente en la barra de navegación; el panel decide qué renderizar dependiendo de si el firewall está activo para ese guest y si alguna regla está logueando realmente:",
"firewallItems": [
"<strong>Firewall disabled</strong> — un aviso ámbar explica exactamente dónde activarlo en la UI de Proxmox (<em>&lt;Container|VM&gt; → Firewall → Options</em>) y te recuerda que al menos una regla necesita <code>log: info</code> (o superior) antes de que aparezcan paquetes.",
@@ -0,0 +1,114 @@
{
"meta": {
"title": "Cómo se traduce una imagen OCI | ProxMenux",
"description": "Del repositorio de la imagen y su archivo Compose a una plantilla revisable, un plan de despliegue y un LXC nativo de Proxmox VE, sin Docker dentro."
},
"header": {
"title": "Cómo se traduce una imagen OCI",
"description": "Del repositorio de la imagen y su archivo Compose a una plantilla revisable, un plan de despliegue y un LXC nativo, sin instalar Docker dentro.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "pipeline",
"title": "La cadena de traducción",
"blocks": [
{
"mermaid": {
"chartCode": "flowchart LR\n A[\"{{repo}}\"] --> B[\"Compose + README\"]\n B --> C[\"{{converter}}\"]\n C --> D[\"{{template}}\"]\n D --> E{\"{{blockers}}\"}\n E -- \"{{no}}\" --> F[\"{{review}}\"]\n F --> D\n E -- \"{{yes}}\" --> G[\"{{plan}}\"]\n G --> H[\"pct create\"]\n H --> I[\"{{lxc}}\"]",
"labels": {
"repo": "Repositorio de la imagen",
"converter": "Conversor",
"template": "Plantilla JSON",
"blockers": "¿Sin bloqueos?",
"no": "No",
"yes": "Sí",
"review": "Revisión / overlay",
"plan": "Plan de despliegue",
"lxc": "LXC nativo"
}
}
},
{
"p": "El conversor lee la imagen y el archivo Compose que publica su proyecto y escribe una plantilla JSON. Una plantilla con bloqueos sin traducir pasa por revisión, donde un overlay curado los resuelve, antes de publicarse en el catálogo. Solo las plantillas sin bloqueos se ofrecen para instalar."
}
]
},
{
"id": "template",
"title": "Qué conserva la plantilla",
"blocks": [
{
"cards": {
"items": [
{ "icon": "archive", "title": "Identidad de la imagen", "body": "Repositorio, etiqueta rolling, arquitectura, digest resuelto y revisión del origen." },
{ "icon": "braces", "title": "Contrato del contenedor", "body": "Entrypoint, Cmd, entorno, usuario, directorio de trabajo, señal de parada, puertos y volúmenes." },
{ "icon": "layers", "title": "Traducción a Proxmox VE", "body": "Recursos, seguridad, puntos de montaje, dispositivos, sysctls, healthchecks y las adaptaciones que necesita cada uno, con su motivo." },
{ "icon": "shield", "title": "Compatibilidad", "body": "Claves admitidas, bloqueos sin traducir y el estado de cada validación." }
]
}
}
]
},
{
"id": "sources",
"title": "OCI aporta el proceso; Compose aporta el entorno",
"blocks": [
{
"table": {
"headers": ["Origen", "Ejemplo", "Resultado nativo"],
"rows": [
["Metadatos OCI", "<code>Entrypoint</code>, <code>Cmd</code>, <code>User</code>", "Proxmox VE los importa al crear el CT"],
["Docker Compose", "<code>environment</code>, <code>volumes</code>, <code>devices</code>", "entradas de entorno del LXC, <code>mpN</code> y <code>devN</code>"],
["Perfil de ProxMenux", "GPU, healthcheck, credenciales", "preguntas y adaptaciones revisadas"],
["Usuario", "VMID, almacenamiento, red", "el contrato de la instancia"]
]
}
}
]
},
{
"id": "install",
"title": "Qué ocurre durante una instalación",
"blocks": [
{
"steps": {
"items": [
{ "title": "Resolver", "body": "Se consulta el registro, se selecciona la arquitectura del host y se fija el digest efectivo de la etiqueta rolling." },
{ "title": "Descargar y verificar", "body": "Skopeo descarga la imagen como archivo OCI, y cada capa se comprueba contra su digest y se descomprime antes de crear nada. Una descarga dañada se repite una vez antes de detener la instalación." },
{ "title": "Construir", "body": "<code>pct create</code> construye el rootfs a partir del archivo y conserva los metadatos oficiales de proceso de la imagen." },
{ "title": "Conectar", "body": "Se añaden los volúmenes, la red, el entorno, los dispositivos y los perfiles de seguridad declarados." },
{ "title": "Consola", "body": "La salida de consola del contenedor se guarda en el host, y la consola de Proxmox VE abre un shell cuando la imagen incluye uno." },
{ "title": "Comprobar", "body": "El primer arranque espera una dirección y la respuesta del servicio; un fallo no se presenta como instalación correcta." },
{ "title": "Registrar", "body": "La configuración efectiva se escribe en el contrato de la instancia que usan las actualizaciones y las recreaciones." }
]
}
}
]
},
{
"id": "example",
"title": "Ejemplo: una imagen con /config y /downloads",
"intro": "Una definición habitual de Compose y la configuración de Proxmox VE en la que se convierte. Las rutas que espera la aplicación no cambian.",
"blocks": [
{
"codeGrid": {
"items": [
{
"title": "Docker Compose",
"code": "image: lscr.io/linuxserver/example:latest\nenvironment:\n - PUID=1000\n - PGID=1000\nvolumes:\n - config:/config\n - /srv/downloads:/downloads\nports:\n - 8080:8080"
},
{
"title": "/etc/pve/lxc/VMID.conf (extracto)",
"code": "entrypoint: /init\nmp0: local-lvm:vm-VMID-disk-1,mp=/config,backup=1,size=8G\nmp1: /srv/downloads,mp=/downloads\nnet0: name=eth0,bridge=vmbr0,ip=dhcp,type=veth\nlxc.environment.runtime: PUID=1000\nlxc.environment.runtime: PGID=1000"
}
]
}
},
{
"p": "<code>mp0</code> es un segundo disco que pertenece al contenedor, con el nombre <code>vm-VMID-disk-N</code> en el almacenamiento elegido. Se monta en <code>/config</code> y, con <code>backup=1</code>, forma parte del backup del contenedor. <code>mp1</code> no crea ningún disco: enlaza el directorio del host <code>/srv/downloads</code> con <code>/downloads</code> dentro del LXC. El puerto 8080 no se mapea: el LXC tiene una dirección propia y el servicio responde en ella."
}
]
}
]
}
@@ -0,0 +1,134 @@
{
"meta": {
"title": "Instalar una imagen que no está en el catálogo | ProxMenux",
"description": "Traducción de una imagen OCI desde un archivo Compose, una URL, un comando docker run o una referencia de imagen, con revisión de lo que ProxMenux puede reproducir antes de instalarla."
},
"header": {
"title": "Instalar una imagen que no está en el catálogo",
"description": "Una imagen propia se traduce desde su archivo Compose, un comando docker run o solo su referencia, y el resultado se presenta para revisión antes de crear ningún LXC.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "El mismo conversor, el mismo instalador",
"body": "La opción <strong>Instalar una imagen que no está en el catálogo</strong> utiliza el conversor y el instalador de las plantillas del catálogo. La diferencia es que el contrato se genera en ese momento a partir de la definición aportada y se valida antes de crear ningún LXC."
}
}
]
},
{
"id": "sources",
"title": "Cómo se describe la imagen",
"intro": "La primera pantalla pregunta cómo se describe la imagen. Hay cuatro formas de aportar una definición completa y una quinta que usa solo el nombre de la imagen.",
"blocks": [
{
"table": {
"headers": ["Opción", "Qué lee"],
"rows": [
["Pegar su archivo Compose en la terminal", "El YAML se pega en la terminal y termina con Ctrl+D en una línea vacía."],
["Leer su archivo Compose desde un archivo de este host", "Una ruta del nodo, <code>/root/docker-compose.yml</code> por defecto. El archivo debe existir y ocupar como máximo 256 KiB."],
["Descargar su archivo Compose desde una dirección", "Una dirección <code>http://</code> o <code>https://</code> que sirva el YAML sin procesar. Se descargan como máximo 256 KiB."],
["Pegar su comando docker run en la terminal", "El comando que publica el proyecto. Se traducen puertos, entorno, volúmenes, dispositivos, capacidades, usuario, memoria compartida y el resto de opciones admitidas."],
["Solo la referencia de la imagen, sin archivo Compose", "Una referencia como <code>ghcr.io/usuario/aplicacion:latest</code>. Se consulta el registro y se conservan los metadatos OCI de la imagen."]
]
}
},
{
"figure": {
"src": "/oci-manager/custom-source-menu.png",
"alt": "Menú que pregunta cómo se describe una imagen que no está en el catálogo",
"caption": "Las cinco formas de describir una imagen que no está en el catálogo."
}
}
]
},
{
"id": "reference-only",
"title": "Qué aporta solo la referencia de la imagen",
"intro": "Una imagen lleva sus metadatos de proceso, pero no todo lo que un archivo Compose suele añadir a su alrededor.",
"blocks": [
{
"table": {
"headers": ["Se lee de la imagen", "No está en la imagen", "Consecuencia"],
"rows": [
["<code>Entrypoint</code>, <code>Cmd</code>, <code>User</code>, <code>WorkingDir</code> y entorno integrado", "Variables que solo aparecen en la documentación", "Se añaden durante la instalación o una recreación"],
["Volúmenes declarados por la imagen", "Directorios del host que solo nombra un archivo Compose", "Las rutas adicionales se añaden antes de instalar"],
["Puertos <code>EXPOSE</code>", "La URL, el protocolo o un healthcheck funcional", "Se confirman el puerto y la forma de comprobar el servicio"],
["Arquitecturas y digest del registro", "Dispositivos, privilegios o dependencias externas", "Ninguno se activa sin una definición que lo pida"]
]
}
}
]
},
{
"id": "review",
"title": "Revisión antes de instalar",
"intro": "Tras traducir la definición, ProxMenux muestra lo que ha entendido antes de pedir VMID, recursos o almacenamiento.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Analizar", "body": "Se leen servicios, imagen, puertos, rutas, entorno, dispositivos, seguridad y healthcheck." },
{ "title": "Consultar el registro", "body": "La imagen debe existir en un registro público, y se enumeran las arquitecturas que publica." },
{ "title": "Enumerar lo que no se aplica", "body": "Se enumeran las etiquetas, las redes Docker, los ajustes de Swarm y el resto de claves que no tienen efecto en un LXC." },
{ "title": "Bloquear lo que no puede traducirse", "body": "Una clave sin equivalencia segura aparece en <strong>Lo que no puede traducirse</strong> y la instalación no continúa. Nada se descarta en silencio." },
{ "title": "Pedir los secretos", "body": "Las variables cuyo nombre corresponde a una contraseña, un token o una clave se convierten en preguntas sensibles de la instalación." }
]
}
},
{
"figure": {
"src": "/oci-manager/custom-review.png",
"alt": "Resumen de lo que ProxMenux ha entendido de un archivo Compose",
"caption": "El resumen que se muestra antes de que la instalación pida VMID y recursos."
}
},
{
"p": "Tras aceptar el resumen, el recorrido es el del catálogo: nombre, modo predeterminado o avanzado, VMID, CPU, memoria, red, arranque con el nodo, rutas persistentes, rutas adicionales, dispositivos compatibles y resumen final."
}
]
},
{
"id": "examples",
"title": "Ejemplos de entrada",
"blocks": [
{
"codeGrid": {
"items": [
{
"title": "Compose",
"code": "services:\n app:\n image: ghcr.io/example/app:latest\n ports:\n - \"8080:8080\"\n volumes:\n - ./config:/config\n - /srv/media:/media\n environment:\n TZ: Europe/Madrid"
},
{
"title": "docker run",
"code": "docker run -d \\\n --name app \\\n -p 8080:8080 \\\n -e TZ=Europe/Madrid \\\n -v app-config:/config \\\n -v /srv/media:/media \\\n ghcr.io/example/app:latest"
}
]
}
}
]
},
{
"id": "limits",
"title": "Definiciones que no se instalan",
"blocks": [
{
"list": {
"items": [
"Imágenes en registros privados: no se piden credenciales del registro.",
"Un Dockerfile sin imagen publicada: la imagen debe construirse y publicarse antes en un registro OCI.",
"Un archivo Compose con varios servicios: queda bloqueado porque describe más de una imagen. Esta opción instala un único contenedor.",
"<code>configs</code>, secretos externos o formatos de dispositivo que no pueden traducirse sin ambigüedad.",
"Sustituciones del shell como <code>$(comando)</code>: debe escribirse el valor resultante.",
"Opciones de <code>docker run</code> no reconocidas y <code>--env-file</code>: se lee en su lugar un archivo Compose explícito."
]
}
}
]
}
]
}
@@ -0,0 +1,232 @@
{
"meta": {
"title": "Dispositivos y aceleración | ProxMenux",
"description": "Cómo entrega OCI manager Apps GPU, NVIDIA, Coral, USB, FUSE y otros dispositivos a un contenedor OCI como recursos nativos y validados de Proxmox VE."
},
"header": {
"title": "Dispositivos y aceleración",
"description": "GPU, NVIDIA, Coral, USB, FUSE y dispositivos de bloques se convierten en recursos nativos y validados de Proxmox VE del contenedor.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "principle",
"title": "El dispositivo que necesita la aplicación, no todo el host",
"blocks": [
{
"p": "Un dispositivo solicitado por el archivo Compose o por el perfil de la aplicación se convierte en una entrada <code>devN</code> o un montaje LXC concreto. Pedir una GPU, un dispositivo USB o un Coral no convierte el contenedor en privilegiado."
},
{
"flow": {
"nodes": [
{ "label": "Inventario del host", "detail": "/dev/dri/renderD128\nGID 993 · Intel" },
{ "label": "ProxMenux", "detail": "fabricante y\npermisos comprobados" },
{ "label": "LXC", "detail": "mismo dispositivo\nGID efectivo" }
]
}
}
]
},
{
"id": "origin",
"title": "De dónde procede la petición de un dispositivo",
"blocks": [
{
"table": {
"headers": ["Origen", "Qué se lee", "Qué hace el instalador"],
"rows": [
["Docker Compose", "<code>devices</code>, <code>group_add</code>, <code>deploy.resources</code> y peticiones NVIDIA", "Cada requisito se convierte en una petición de dispositivo que se muestra para revisión"],
["Perfil del catálogo", "El soporte de GPU, Coral, OpenCL, USB o FUSE que tiene realmente la aplicación", "Solo se ofrecen las opciones validadas para esa imagen"],
["Metadatos y documentación de la imagen", "VA-API, Selkies, mods de LinuxServer o el runtime NVIDIA", "Se añaden las variables y la preparación documentadas"],
["Selección del usuario", "Solo CPU, Intel/AMD, OpenCL, NVIDIA o un dispositivo opcional", "La selección se guarda en el contrato de la instancia"]
]
}
},
{
"calloutWarning": {
"title": "Los dispositivos detectados no se conectan por sí solos",
"body": "El host se inventaría, pero solo se ofrecen y conectan los dispositivos que declaran el archivo Compose o un perfil compatible. Una GPU, un dongle USB o un Coral presentes en el host no se exponen a todos los LXC."
}
}
]
},
{
"id": "identify",
"title": "Cómo se identifica el dispositivo en el host",
"intro": "Antes de modificar el LXC, el dispositivo se lee en el host y se compara con el perfil elegido.",
"blocks": [
{
"table": {
"headers": ["Tipo", "Identificación", "Validación"],
"rows": [
["Intel/AMD DRM", "<code>/dev/dri/renderD*</code> y <code>/sys/class/drm/NODO/device/vendor</code>", "Un dispositivo de caracteres con fabricante <code>0x8086</code> (Intel) o <code>0x1002</code> (AMD)"],
["OpenCL AMD", "El render node, más <code>/dev/kfd</code> cuando el perfil lo necesita", "Existencia, tipo, fabricante, permisos y compatibilidad declarada"],
["NVIDIA", "<code>nvidia-smi</code> y <code>nvidia-container-cli</code>", "GPU, UUID, bus PCI, versión del driver, Toolkit, nodos <code>/dev/nvidia*</code>, binarios y librerías"],
["Coral PCIe/M.2", "<code>/dev/apex_N</code> y su enlace en <code>/sys/dev/char/MAJOR:MINOR</code>", "Nodo de caracteres, major/minor, propietario, GID y permisos"],
["USB y serie", "<code>/dev/ttyUSB*</code>, <code>/dev/ttyACM*</code> o <code>/dev/bus/usb/BBB/DDD</code>", "Nodo de caracteres; en USB también fabricante, producto y número de serie cuando sysfs los publica"],
["KVM, TUN, FUSE, vídeo y SCSI genérico", "<code>/dev/kvm</code>, <code>/dev/net/tun</code>, <code>/dev/fuse</code>, <code>/dev/videoN</code> o <code>/dev/sgN</code>", "Ruta admitida, tipo de nodo y permisos efectivos"],
["Unidad óptica", "<code>/dev/srN</code>", "Un dispositivo de bloques"]
]
}
}
]
},
{
"id": "install",
"title": "Qué ocurre durante la instalación",
"blocks": [
{
"steps": {
"items": [
{ "title": "La plantilla ofrece sus perfiles", "body": "Por ejemplo solo CPU, Intel/AMD VA-API, OpenCL AMD, OpenCL Intel o NVIDIA. Las opciones pertenecen a la imagen, no a un menú común." },
{ "title": "Se elige un perfil", "body": "Define los nodos de dispositivo, el entorno, los mods o el runtime que necesita la aplicación." },
{ "title": "Se propone una ruta", "body": "Para DRM, <code>/dev/dri/renderD128</code>, que puede cambiarse en un host con varios render nodes. Para USB o serie se selecciona el nodo concreto." },
{ "title": "Validación", "body": "Se comprueban existencia, tipo, fabricante admitido, permisos y GID. Una discrepancia detiene la operación." },
{ "title": "Se escribe el contrato", "body": "Ruta, modo, GID, acceso de escritura y perfil quedan registrados para las actualizaciones y las recreaciones." },
{ "title": "Conexión y prueba", "body": "<code>pct set</code> añade la entrada <code>devN</code> y después se comprueba el acceso dentro del LXC. Las imágenes de LinuxServer se comprueban también como usuario <code>abc</code>." }
]
}
}
]
},
{
"id": "config",
"title": "Cómo aparece en la configuración del LXC",
"intro": "Valores ilustrativos: <code>dev0</code> y <code>dev1</code> son las posiciones libres que asigna Proxmox VE, y <code>renderD128</code>, <code>apex_0</code> y el GID dependen del hardware del nodo.",
"blocks": [
{
"codeGrid": {
"items": [
{ "title": "Intel/AMD VA-API", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=993,deny-write=0" },
{ "title": "Coral PCIe/M.2", "code": "dev0: path=/dev/apex_0,mode=0660,gid=GID,deny-write=0" },
{ "title": "Un dispositivo USB concreto", "code": "dev0: path=/dev/bus/usb/003/004,mode=0660,gid=GID,deny-write=0" },
{ "title": "OpenCL AMD", "code": "dev0: path=/dev/dri/renderD128,mode=0660,gid=GID,deny-write=0\ndev1: path=/dev/kfd,mode=0660,gid=GID,deny-write=0" }
]
}
},
{
"p": "El GID se lee con <code>stat</code> en el host y se escribe en la entrada <code>devN</code>; no se da por hecho que los grupos <code>render</code> y <code>video</code> tengan un número fijo. El dispositivo conserva la misma ruta <code>/dev</code> dentro del LXC, que es donde lo buscan los mecanismos propios de la aplicación."
}
]
},
{
"id": "profiles",
"title": "Perfiles que puede ofrecer una imagen",
"blocks": [
{
"table": {
"headers": ["Perfil", "Traducción", "Se ofrece cuando"],
"rows": [
["Intel/AMD VA-API", "Render node de <code>/dev/dri</code>", "La aplicación admite aceleración de vídeo"],
["OpenCL", "Render node, <code>/dev/kfd</code> si hace falta y el mod oficial", "La imagen o el perfil lo documentan"],
["NVIDIA", "Dispositivos y librerías del driver del host", "El host tiene un driver operativo y NVIDIA Container Toolkit"],
["Coral", "<code>/dev/apex_0</code> o el bus USB", "El perfil declara soporte de Coral (Frigate)"],
["USB, serie, FUSE", "Un dispositivo concreto, un árbol validado o una función LXC", "El contrato lo pide"]
]
}
}
]
},
{
"id": "nvidia",
"title": "NVIDIA",
"blocks": [
{
"calloutWarning": {
"title": "Requisito del nodo: NVIDIA Container Toolkit",
"body": "Un driver operativo en Proxmox VE no basta para entregar una GPU NVIDIA a una imagen OCI. OCI manager Apps usa <code>nvidia-container-cli</code>, de NVIDIA Container Toolkit, para identificar los dispositivos y obtener los binarios y las librerías que corresponden al driver cargado."
}
},
{
"p": "El <nvidiaLink>instalador NVIDIA de ProxMenux</nvidiaLink> instala NVIDIA Container Toolkit desde el repositorio oficial de NVIDIA junto con el driver. Comprueba sus cuatro paquetes, valida <code>nvidia-container-cli</code> y registra el resultado en el diario de cambios de <auditLink>Auditoría e informes</auditLink>."
},
{
"p": "Estos dos comandos, en el host, muestran si el driver y el Toolkit están disponibles:"
},
{
"shell": { "code": "nvidia-smi -L\nnvidia-container-cli --version" }
},
{
"p": "En un host cuyo driver se instaló por otros medios, el Toolkit se instala desde el repositorio estable oficial:"
},
{
"shell": {
"code": "apt-get update\napt-get install -y --no-install-recommends ca-certificates curl gnupg2\n\ncurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \\\n | gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg\n\ncurl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \\\n | sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \\\n > /etc/apt/sources.list.d/nvidia-container-toolkit.list\n\napt-get update\napt-get install -y nvidia-container-toolkit libnvidia-container-tools"
}
},
{
"p": "El inventario que usa OCI manager Apps es la salida de:"
},
{
"shell": { "code": "nvidia-container-cli list --device all --libraries --binaries --firmwares --ipcs" }
},
{
"calloutInfo": {
"title": "La configuración del runtime de Docker no interviene",
"body": "Los contenedores son LXC nativos y no se usa ningún daemon de Docker, así que <code>nvidia-ctk runtime configure --runtime=docker</code> no forma parte del proceso: ProxMenux consulta directamente <code>nvidia-container-cli</code> y escribe los dispositivos y montajes del LXC. Los comandos y las plataformas compatibles se mantienen en la <toolkitLink>guía de instalación de NVIDIA Container Toolkit</toolkitLink>."
}
},
{
"cards": {
"items": [
{ "icon": "cpu", "title": "Inventario desde el driver", "body": "<code>nvidia-container-cli</code> enumera los nodos de dispositivo, los binarios, el firmware y las librerías del driver instalado." },
{ "icon": "refresh", "title": "Sin versión fija", "body": "La plantilla no nombra archivos de librería. El perfil se genera a partir del host actual." },
{ "icon": "shield", "title": "Montajes de solo lectura", "body": "Las librerías del host se montan en solo lectura en lugar de copiarse dentro del contenedor." },
{ "icon": "hardDrive", "title": "Cambios de driver", "body": "Tras un cambio de driver, el inventario se genera de nuevo antes de que arranquen los LXC afectados." }
]
}
},
{
"code": {
"title": "Resultado NVIDIA (simplificado)",
"code": "devN: path=/dev/nvidia0,...\ndevN: path=/dev/nvidiactl,...\ndevN: path=/dev/nvidia-uvm,...\nlxc.mount.entry: HOST_LIBRARY CONTAINER_LIBRARY none ro,bind,create=file 0 0"
}
},
{
"p": "Entregar solo <code>/dev/nvidia0</code> no es suficiente. Los componentes de espacio de usuario del driver cargado se montan en solo lectura y después <code>nvidia-smi</code> se ejecuta dentro del LXC para comparar GPU, UUID, bus PCI y versión con el inventario del host."
}
]
},
{
"id": "usb",
"title": "USB, serie y Coral USB",
"blocks": [
{
"calloutWarning": {
"title": "La numeración USB puede cambiar",
"body": "Una ruta como <code>/dev/bus/usb/003/004</code> puede cambiar al reconectar el dispositivo o reiniciar el host. El perfil registra fabricante, producto y número de serie cuando están disponibles, pero una nueva dirección de bus no se reasigna automáticamente."
}
},
{
"p": "Un periférico se indica por su nodo concreto: <code>/dev/ttyUSB0</code>, <code>/dev/ttyACM0</code>, <code>/dev/apex_0</code> o <code>/dev/bus/usb/BBB/DDD</code>. No se admite entregar todo <code>/dev</code>. Coral solo se ofrece a las aplicaciones cuyo perfil lo declara."
}
]
},
{
"id": "trees",
"title": "Árboles de dispositivos y funciones LXC",
"blocks": [
{
"table": {
"headers": ["Petición", "Traducción", "Alcance"],
"rows": [
["<code>/dev/dvb</code>, <code>/dev/snd</code> o <code>/dev/bus/usb</code>", "Cada nodo de caracteres del árbol recibe su propia entrada <code>devN</code> con el modo y el GID del host", "Solo el árbol pedido, no el resto de <code>/dev</code>"],
["<code>/dev/fuse</code>", "El nodo y, cuando el perfil lo necesita, la función <code>fuse=1</code>", "FUSE por sí solo no publica montajes a otros LXC"],
["<code>/dev/net/tun</code>", "Una entrada <code>devN</code> en la misma ruta dentro del LXC", "La configuración de la VPN o de la red queda en la aplicación"],
["<code>/dev/kvm</code>", "Una entrada <code>devN</code> validada", "Solo se ofrece cuando el contrato lo pide"]
]
}
}
]
},
{
"id": "security",
"title": "Confirmaciones según el nivel de riesgo",
"blocks": [
{
"p": "Los dispositivos concretos, el privilegio opcional, el privilegio obligatorio, la relajación de AppArmor o seccomp y el acceso al espacio de nombres PID del host se tratan como casos distintos, no bajo una etiqueta genérica de privilegiado. Cada opción con riesgo se explica y se confirma durante la instalación."
}
]
}
]
}
+116
View File
@@ -0,0 +1,116 @@
{
"meta": {
"title": "OCI manager Apps | ProxMenux",
"description": "Imágenes OCI oficiales ejecutadas como contenedores LXC nativos de Proxmox VE, con datos persistentes, dispositivos, aplicaciones multicontenedor y actualizaciones transaccionales."
},
"header": {
"title": "OCI manager Apps",
"description": "Las imágenes OCI oficiales se ejecutan como contenedores LXC nativos de Proxmox VE. La imagen sigue siendo la que publica su responsable; ProxMenux reproduce el entorno que Docker Compose habría creado a su alrededor.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "model",
"blocks": [
{
"calloutInfo": {
"title": "Una imagen OCI, un LXC nativo",
"body": "Dentro de los contenedores no se ejecuta ningún motor Docker. Proxmox VE importa el sistema de archivos de la imagen y sus metadatos OCI, y la aplicación pasa a ser el proceso principal de un LXC nativo, gestionado con las mismas herramientas que cualquier otro contenedor del nodo."
}
},
{
"flow": {
"nodes": [
{ "label": "Origen", "detail": "Imagen OCI\nCompose y documentación" },
{ "label": "ProxMenux", "detail": "Contrato JSON\nvalidación y plan" },
{ "label": "Proxmox VE", "detail": "LXC nativo\nvolúmenes y dispositivos" }
],
"caption": "La aplicación no se reconstruye: su entorno se reproduce de forma declarativa."
}
}
]
},
{
"id": "where",
"title": "Dónde se encuentra",
"intro": "OCI manager Apps se abre desde la opción <strong>OCI manager Apps (beta)</strong> del menú principal de ProxMenux, en el nodo Proxmox y como root. Su primera pantalla ofrece:",
"blocks": [
{
"list": {
"items": [
"<strong>Buscar aplicaciones</strong>: búsqueda por nombre en todo el catálogo.",
"<strong>Todas las aplicaciones</strong>: el catálogo completo, con el número de aplicaciones.",
"<strong>Gestionar aplicaciones OCI instaladas</strong>: actualizar, recrear, eliminar o recuperar lo instalado.",
"<strong>Instalar una imagen que no está en el catálogo</strong>: traducción de un archivo Compose, un comando <code>docker run</code> o una referencia de imagen.",
"Las categorías del catálogo, cada una con su número de aplicaciones."
]
}
},
{
"figure": {
"src": "/oci-manager/main-menu.png",
"alt": "Pantalla principal de OCI manager Apps con la búsqueda, todas las aplicaciones, la gestión, la imagen propia y las categorías del catálogo",
"caption": "Pantalla principal de OCI manager Apps."
}
},
{
"p": "Al seleccionar una aplicación se muestra su descripción, la imagen que ejecuta y dos modos de instalación: <strong>Instalar con la configuración predeterminada</strong>, que apenas hace preguntas, e <strong>Instalar con la configuración avanzada</strong>, que ofrece los selectores reales de almacenamiento, bridge y recursos del nodo."
}
]
},
{
"id": "translation",
"title": "Lo que Docker expresa y en qué lo convierte OCI manager Apps",
"blocks": [
{
"table": {
"headers": ["Necesidad", "Docker la expresa como", "OCI manager Apps la convierte en"],
"rows": [
["Ejecutar la aplicación", "<code>image</code>, <code>entrypoint</code>, <code>command</code>", "rootfs OCI y proceso principal del LXC"],
["Conservar la configuración", "<code>volumes: /config</code>", "un disco <code>mpN</code> persistente incluido en el backup, o un directorio del host"],
["Publicar el servicio", "<code>ports</code>", "una dirección propia del LXC y la URL de acceso al servicio"],
["Usar hardware", "<code>devices</code>, <code>group_add</code>", "entradas <code>devN</code> con el GID efectivo del host y un perfil validado"],
["Conectar dependencias", "<code>networks</code>, <code>depends_on</code>", "un bridge privado, direcciones fijas, orden de arranque y healthchecks"],
["Actualizar", "<code>pull</code> y recrear", "un rootfs nuevo con el mismo contrato persistente"]
]
}
}
]
},
{
"id": "kept",
"title": "Qué se mantiene y qué cambia",
"blocks": [
{
"cards": {
"items": [
{ "icon": "archive", "title": "Se mantiene", "body": "La imagen oficial, sus rutas internas, su entorno, su proceso de arranque y su documentación funcional." },
{ "icon": "boxes", "title": "Se adapta", "body": "El entorno de ejecución: red, persistencia, dispositivos, permisos y dependencias usan primitivas LXC nativas de Proxmox VE." },
{ "icon": "shield", "title": "No se traduce", "body": "Una clave de Compose sin equivalencia segura figura como bloqueo. Las plantillas con bloqueos no se ofrecen, y las opciones sensibles se confirman durante la instalación." },
{ "icon": "refresh", "title": "Se registra", "body": "Digest de la imagen, recursos, rutas, red, dispositivos y pertenencia a una pila quedan en el contrato de la instancia, que las actualizaciones y las recreaciones reproducen." }
]
}
}
]
},
{
"id": "pages",
"title": "Páginas de esta sección",
"blocks": [
{
"next": {
"items": [
{ "label": "Cómo se traduce una imagen OCI", "href": "/docs/oci-manager/architecture", "tail": "de la imagen y su archivo Compose a un LXC nativo." },
{ "label": "Instalar una imagen que no está en el catálogo", "href": "/docs/oci-manager/custom-image", "tail": "Compose, docker run o una referencia de imagen." },
{ "label": "Datos, rutas y red", "href": "/docs/oci-manager/storage-network", "tail": "discos del contenedor, directorios del host, montajes Rclone y direcciones." },
{ "label": "Dispositivos y aceleración", "href": "/docs/oci-manager/hardware", "tail": "GPU, NVIDIA, Coral, USB y otros dispositivos." },
{ "label": "Aplicaciones multicontenedor", "href": "/docs/oci-manager/stacks", "tail": "aplicación, base de datos y caché como LXC coordinados." },
{ "label": "Instalar, actualizar y recrear", "href": "/docs/oci-manager/lifecycle", "tail": "el contrato de la instancia y sus operaciones." },
{ "label": "En ProxMenux Monitor", "href": "/docs/oci-manager/monitor", "tail": "versiones, actualizaciones, salida de consola y terminal." }
]
}
}
]
}
]
}
@@ -0,0 +1,243 @@
{
"meta": {
"title": "Instalar, actualizar y recrear | ProxMenux",
"description": "El contrato de instancia de un contenedor OCI y las operaciones que lo usan: actualizar, recrear, eliminar y recuperar una operación interrumpida."
},
"header": {
"title": "Instalar, actualizar y recrear",
"description": "Cada instancia conserva un contrato reproducible, de modo que su rootfs puede sustituirse sin perder la configuración ni los datos persistentes.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "contract",
"title": "El contrato de la instancia",
"blocks": [
{
"p": "Tras una instalación, la configuración efectiva se guarda en <code>/usr/local/share/proxmenux/oci/instances/VMID/oci-compose.json</code>. No es una copia del archivo Compose original: es el contrato reproducible del LXC que existe en este host."
},
{
"code": {
"code": "instances/\n└── 105/\n └── oci-compose.json\n ├── imagen y digest resuelto\n ├── recursos y red\n ├── entorno (secretos protegidos)\n ├── discos del contenedor y directorios del host\n ├── perfil de hardware y dispositivos\n ├── log de consola y modo de terminal\n └── pertenencia a una pila y ciclo de vida"
}
}
]
},
{
"id": "manage",
"title": "Gestionar aplicaciones OCI instaladas",
"intro": "Esta opción de la pantalla principal enumera las instancias registradas con su aplicación y su imagen. Cada una se contrasta con su contrato antes de cualquier acción; un CT que ya no coincide con su registro no se modifica ni se elimina. La misma actualización y la misma recreación se ofrecen en la pestaña Actualizaciones de <monitorLink>ProxMenux Monitor</monitorLink>.",
"blocks": [
{
"table": {
"headers": [
"Opción",
"Qué cambia",
"Qué se conserva"
],
"rows": [
[
"Actualizar la imagen con la configuración guardada",
"El rootfs se sustituye por la imagen que publica hoy el canal guardado",
"Contrato, discos del contenedor, directorios del host, red y dispositivos"
],
[
"Recrear: editar recursos, red, rutas y GPU",
"El editor se abre con el contrato actual; el CT se reconstruye con los cambios",
"Los datos de los discos del contenedor y de los directorios del host"
],
[
"Eliminar: la aplicación y sus contenedores",
"Se eliminan el LXC, o todos los miembros de una pila, y sus contratos",
"Los directorios del host, con su contenido"
]
]
}
},
{
"p": "Para una aplicación multicontenedor el menú ofrece <strong>Actualizar cada contenedor de la aplicación</strong> y la eliminación. Una pila no se recrea."
},
{
"figure": {
"src": "/oci-manager/manage-menu.png",
"alt": "Lista de aplicaciones OCI instaladas y las opciones de actualizar, recrear y eliminar",
"caption": "Gestionar aplicaciones OCI instaladas."
}
}
]
},
{
"id": "update",
"title": "Una actualización transaccional",
"blocks": [
{
"mermaid": {
"chartCode": "sequenceDiagram\n participant U as {{user}}\n participant P as ProxMenux\n participant R as {{registry}}\n participant X as Proxmox VE\n U->>P: {{update}}\n P->>R: {{resolve}}\n R-->>P: digest\n P->>P: {{verify}}\n P->>X: {{backup}}\n P->>X: {{import}}\n P->>X: {{reapply}}\n X-->>P: healthcheck\n alt {{healthy}}\n P-->>U: {{commit}}\n else {{failure}}\n P->>X: Rollback\n P-->>U: {{restored}}\n end",
"labels": {
"user": "Usuario",
"registry": "Registro OCI",
"update": "Actualizar la instancia",
"resolve": "Resolver la etiqueta",
"verify": "Verificar el archivo y preflight",
"backup": "Detener y respaldar el CT",
"import": "Importar el rootfs nuevo",
"reapply": "Aplicar de nuevo el contrato",
"healthy": "correcto",
"failure": "fallo",
"commit": "Contrato publicado con el digest nuevo",
"restored": "Instancia anterior restaurada"
}
}
},
{
"list": {
"items": [
"Cuando el registro sigue sirviendo el digest instalado, no se descarga nada y la instancia no se toca.",
"La imagen nueva se verifica capa a capa antes de detener el CT. Una descarga que llega dañada se repite una vez; una imagen ya almacenada que no supera la comprobación se descarga de nuevo.",
"Los ajustes cambiados en Proxmox VE después de la instalación (memoria, swap, núcleos, límite de CPU, prioridad de CPU, arranque con el nodo) se conservan y pasan al contrato nuevo.",
"Cualquier otra diferencia entre el CT y su contrato detiene la actualización antes de detener el CT."
]
}
},
{
"flow": {
"nodes": [
{
"label": "Antes",
"detail": "rootfs A\n/config mp0\n/media directorio del host"
},
{
"label": "Actualización",
"detail": "sustituye solo\nel rootfs"
},
{
"label": "Después",
"detail": "rootfs B\n/config mp0\n/media directorio del host"
}
]
}
}
]
},
{
"id": "recovery",
"title": "Recuperar una operación interrumpida",
"intro": "La actualización y la recreación usan una transacción persistente. Si el proceso, la terminal o el nodo se interrumpen después de detener el CT, la operación no se da por terminada.",
"blocks": [
{
"steps": {
"items": [
{
"title": "Marcador pendiente",
"body": "Al abrir Gestionar aplicaciones OCI instaladas, el estado guardado indica que la sustitución no llegó a publicarse."
},
{
"title": "Ver estado",
"body": "Muestra la fase alcanzada sin modificar contenedores ni datos."
},
{
"title": "Recuperar la instalación anterior",
"body": "Restaura el backup nativo verificado tomado antes de la sustitución y el contrato anterior."
},
{
"title": "Pilas como una unidad",
"body": "En una aplicación multicontenedor se recuperan todos los miembros desde el mismo punto de la transacción, no solo el seleccionado."
}
]
}
},
{
"calloutWarning": {
"title": "Los directorios del host quedan fuera de la restauración",
"body": "El backup cubre el rootfs y los discos del contenedor que incluye. Un directorio del host no se revierte, porque otros LXC pueden usar sus datos. Actualizar, recrear o recuperar una instancia con directorios del host pide antes una confirmación de ello."
}
}
]
},
{
"id": "remove",
"title": "Eliminar una aplicación OCI",
"blocks": [
{
"p": "Antes de la confirmación se compone un resumen a partir de la configuración real: los contenedores que se eliminan, los datos que se eliminan con ellos, la red privada que se libera y los directorios del host que se conservan. La confirmación sale en <strong>No</strong> por defecto, porque los datos de los discos eliminados no pueden recuperarse después."
},
{
"table": {
"headers": [
"Recurso",
"Al eliminar",
"Motivo"
],
"rows": [
[
"rootfs y discos del contenedor",
"Se eliminan",
"Solo pertenecen al contenedor"
],
[
"Directorio del host",
"Se conserva, con su contenido",
"Otras aplicaciones pueden usarlo"
],
[
"Contrato de la instancia",
"Se retira tras una eliminación correcta",
"Ya no tiene ningún CT asociado"
],
[
"Bridge privado de una pila",
"Se libera con la pila",
"Ya no le quedan miembros que conectar"
],
[
"Un único miembro de una pila",
"No se elimina por separado",
"Se elimina la aplicación completa, para no dejar ninguna pila incompleta"
]
]
}
},
{
"figure": {
"src": "/oci-manager/remove-summary.png",
"alt": "Resumen de eliminación con los contenedores, los datos y los directorios del host afectados",
"caption": "El resumen de eliminación, compuesto a partir de la configuración real."
}
}
]
},
{
"id": "archives",
"title": "Imágenes descargadas y espacio del host",
"blocks": [
{
"p": "El archivo OCI se usa para construir el rootfs; el CT en marcha no lo lee. Tras una instalación o una actualización se enumeran los archivos descargados en esa operación con su tamaño y se ofrece eliminarlos. Eliminarlos libera el espacio sin afectar al contenedor ni a sus datos."
},
{
"calloutInfo": {
"title": "Las actualizaciones no necesitan el archivo",
"body": "Sin el archivo, una actualización resuelve el canal guardado y descarga el digest nuevo. Con él, un archivo solo se reutiliza cuando su referencia y su integridad coinciden con lo pedido."
}
}
]
},
{
"id": "registry",
"title": "Registro y limpieza",
"blocks": [
{
"p": "Los contratos registrados se comparan con los CT reales. Un contrato solo queda huérfano cuando su VMID ya no existe o ya no lleva la identidad de instancia esperada. La limpieza no elimina volúmenes ni datos externos por deducción."
}
]
},
{
"id": "channel",
"title": "Una etiqueta rolling no es una actualización desatendida",
"blocks": [
{
"p": "El catálogo instala la etiqueta rolling que publica su responsable, pero el digest efectivo se resuelve, se registra y solo cambia con una actualización explícita, con preflight y restauración. <monitorLink>ProxMenux Monitor</monitorLink> compara el digest instalado con el que publica el registro y muestra, y notifica, cuándo hay una imagen nueva."
}
]
}
]
}
@@ -0,0 +1,203 @@
{
"meta": {
"title": "Contenedores OCI en ProxMenux Monitor | ProxMenux",
"description": "Qué muestra ProxMenux Monitor de un contenedor instalado por OCI manager Apps: versiones de la aplicación y de la imagen, imágenes nuevas, salida de consola y terminal de Proxmox VE."
},
"header": {
"title": "En ProxMenux Monitor",
"description": "Un contenedor instalado por OCI manager Apps aparece en VMs y LXCs como cualquier otro LXC. Su ventana lee el registro de la instalación: versiones de la aplicación y de la imagen, imágenes nuevas, salida de consola y terminal.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "El registro es la fuente",
"body": "OCI manager Apps sabe qué instaló y de dónde procede. El Monitor lee ese registro de la instalación en lugar de sondear el contenedor, así que los datos son los mismos con el contenedor en marcha o detenido."
}
},
{
"table": {
"headers": [
"Pestaña",
"Qué cambia en un contenedor OCI"
],
"rows": [
[
"App",
"La aplicación se identifica desde el registro y sus actualizaciones se siguen por la imagen"
],
[
"Actualizaciones",
"Se actualiza la imagen o se recrea el contenedor, con el mismo recorrido que el menú OCI"
],
[
"Montajes",
"Discos del contenedor y directorios del host, con su uso"
],
[
"Logs",
"Solo en contenedores OCI: la salida de consola de la aplicación"
]
]
}
}
]
},
{
"id": "app",
"title": "App",
"intro": "La pestaña <appLink>App</appLink> ofrece la aplicación instalada como una detección, con el nombre, el logo, el puerto y el esquema del registro. Al registrarla se abre el editor con el método <strong>Imagen OCI (instalada por ProxMenux)</strong>, que no necesita configuración.",
"blocks": [
{
"cards": {
"items": [
{
"icon": "boxes",
"title": "Aplicación",
"body": "La versión de la aplicación dentro de la imagen, leída de la propia imagen: su entorno o su etiqueta <code>org.opencontainers.image.version</code>. Es informativa."
},
{
"icon": "archive",
"title": "Imagen",
"body": "La fecha de construcción y el digest de la imagen instalada. La actualización se decide aquí: el digest instalado se compara con el que publica hoy el registro para la misma etiqueta."
}
]
}
},
{
"list": {
"items": [
"Cuando el registro publica un digest nuevo, la tarjeta muestra <strong>Nueva imagen</strong> con su fecha y su digest, aunque la versión de la aplicación no haya cambiado: una reconstrucción sobre una base actualizada es una actualización.",
"Cuando los digests coinciden, la tarjeta indica <strong>Versión</strong>.",
"La tarjeta enlaza con el repositorio de la imagen: su proyecto en GitHub, o su página de Docker Hub en una imagen oficial.",
"El enlace de acceso usa la dirección de la LAN del contenedor, también en el miembro principal de una aplicación multicontenedor, que tiene una segunda dirección en su red privada.",
"El botón <strong>Actualizar datos</strong> vuelve a leer el registro y el registro de imágenes. No se ofrecen las opciones de buscar aplicaciones ni de registrar otra: el contenedor contiene exactamente la aplicación de su registro."
]
}
},
{
"figure": {
"src": "/oci-manager/monitor-app-tab.png",
"alt": "Pestaña App de un contenedor OCI con la versión de la aplicación, la imagen y el enlace al repositorio",
"caption": "Pestaña App de un contenedor instalado por OCI manager Apps."
}
},
{
"p": "La comprobación se ejecuta una vez al día con las comprobaciones de actualizaciones del Monitor, y <strong>Actualizar datos</strong> la ejecuta al momento. Una imagen nueva se envía como notificación por los canales configurados en <notificationsLink>Notificaciones</notificationsLink>. La actualización se aplica desde la pestaña Actualizaciones."
}
]
},
{
"id": "updates",
"title": "Actualizaciones",
"intro": "En un contenedor instalado por OCI manager Apps, la pestaña Actualizaciones muestra la aplicación con su imagen instalada y, cuando el registro publica una, la imagen nueva, con el mismo formato que cualquier otra aplicación. Los actualizadores de paquetes y de aplicaciones de un LXC normal no aparecen.",
"blocks": [
{
"table": {
"headers": [
"Botón",
"Qué abre"
],
"rows": [
[
"Actualizar",
"La actualización de <lifecycleLink>Gestionar aplicaciones OCI instaladas</lifecycleLink> para este contenedor, en la terminal del Monitor. Puede ejecutarse haya o no imagen nueva; sin ella, no cambia nada. En una aplicación multicontenedor actualiza todos los miembros."
],
[
"Recrear",
"El editor de la recreación (recursos, red, rutas y GPU). No se ofrece en una aplicación multicontenedor."
],
[
"Recuperar",
"Sustituye a Actualizar cuando una operación sobre el contenedor quedó interrumpida, y abre su recuperación."
]
]
}
},
{
"p": "Los cambios externos, los directorios del host y las aplicaciones multicontenedor se tratan como en el menú OCI. Al cerrar la terminal se vuelven a leer la imagen, el registro y los montajes."
},
{
"table": {
"headers": [
"Opción",
"Comportamiento"
],
"rows": [
[
"Conservar el backup previo a la actualización",
"Cada actualización hace un backup del contenedor para restaurarlo si falla. Con esta opción ese mismo backup se conserva en el almacenamiento elegido y aparece entre los backups del CT; en Proxmox Backup Server se escribe un backup antes de actualizar."
],
[
"Actualizaciones programadas",
"La imagen se actualiza a la hora elegida solo cuando el registro publica una nueva y, opcionalmente, solo cuando tiene 1, 3, 7 o 14 días. Un contenedor con cambios hechos fuera de ProxMenux se salta y se notifica; uno con directorios del host solo se actualiza si se confirmó al guardar la programación."
]
]
}
}
]
},
{
"id": "logs",
"title": "Logs",
"intro": "La pestaña Logs solo aparece en los contenedores instalados por OCI manager Apps, entre Montajes y Backups. Muestra la salida estándar y de errores del proceso principal de la imagen, la misma que muestra <code>docker logs</code> en un contenedor Docker.",
"blocks": [
{
"list": {
"items": [
"La salida se guarda en el host en <code>/var/log/proxmenux/oci/VMID.console.log</code> (modo 0600), desde el primer arranque y entre reinicios, así que puede leerse con el contenedor detenido.",
"El archivo se rota al llegar a 10 MB y se conservan tres copias comprimidas (<code>/etc/logrotate.d/proxmenux-oci</code>).",
"Se muestran las últimas 100, 500 o 1000 líneas. Con el contenedor en marcha, las líneas nuevas se siguen en directo; al desplazarse hacia arriba el seguimiento se pausa, y <strong>Seguir</strong> lo reanuda.",
"Un filtro muestra solo las líneas que contienen un texto, y <strong>Descargar</strong> guarda las líneas cargadas.",
"Los códigos de color se eliminan y una línea que redibuja una barra de progreso se muestra en su estado final.",
"La pestaña lee el archivo cada vez que se abre; no se guarda en caché."
]
}
},
{
"calloutInfo": {
"title": "Credenciales del primer arranque",
"body": "Las imágenes que imprimen una contraseña generada en su primer arranque la dejan en esta salida, como hace <code>docker logs</code>. El instalador la lee de ahí para mostrarla en su resumen."
}
},
{
"figure": {
"src": "/oci-manager/monitor-logs-tab.png",
"alt": "Pestaña Logs con la salida de consola de un contenedor OCI, el selector de líneas, el filtro y el botón de seguimiento",
"caption": "Salida de consola de un contenedor OCI."
}
}
]
},
{
"id": "terminal",
"title": "Consola de Proxmox VE",
"blocks": [
{
"p": "Una imagen OCI ejecuta su propio proceso como PID 1 y ningún servicio de inicio de sesión, así que la consola predeterminada de un LXC abriría un terminal en el que no responde nadie. Los contenedores instalados por OCI manager Apps se crean con <code>cmode: shell</code>: la consola de Proxmox VE abre un shell con <code>lxc-attach</code>, el equivalente a <code>docker exec</code>, sin tocar la aplicación en marcha."
},
{
"list": {
"items": [
"El shell es el que <code>/etc/passwd</code> asigna a root en la imagen. Una imagen cuyo root no tiene shell, o que no incluye ninguno, mantiene la consola predeterminada.",
"El shell es root dentro del contenedor, sin contraseña. Quién puede abrirlo lo decide el permiso <code>VM.Console</code> de Proxmox VE.",
"La terminal del Monitor entra en el contenedor con <code>pct enter</code>, que funciona del mismo modo."
]
}
}
]
},
{
"id": "mounts",
"title": "Montajes",
"blocks": [
{
"p": "La pestaña <mountsLink>Montajes</mountsLink> enumera los discos del contenedor y los directorios del host. El uso de un disco del contenedor en almacenamiento de bloques, como LVM-thin, se lee del sistema de archivos del disco, que solo está montado dentro del contenedor."
}
]
}
]
}
@@ -0,0 +1,241 @@
{
"meta": {
"title": "Aplicaciones multicontenedor | ProxMenux",
"description": "Cómo convierte OCI manager Apps una aplicación con su base de datos y su caché en LXC nativos coordinados: plan, red privada, hook de dependencias, validación y actualizaciones transaccionales."
},
"header": {
"title": "Aplicaciones multicontenedor",
"description": "Una aplicación con su base de datos, su caché y otros servicios se convierte en varios LXC nativos coordinados, que se instalan y actualizan como uno solo.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "Una aplicación para el usuario, varios LXC para Proxmox VE",
"body": "Una definición multicontenedor es una sola entrada del catálogo. El instalador crea un LXC nativo por servicio y mantiene explícitas sus dependencias. Immich, Nextcloud, Paperless-ngx y Tandoor se instalan de esta forma."
}
},
{
"mermaid": {
"chartCode": "flowchart LR\n C[\"Docker Compose\"] --> O[\"{{plan}}\"]\n O --> A[\"{{app}}<br/>{{appNet}}\"]\n O --> D[\"{{db}}<br/>{{private}}\"]\n O --> R[\"{{cache}}<br/>{{private}}\"]\n V1[(\"config\")] --> A\n V2[(\"database\")] --> D\n V3[(\"cache\")] --> R\n D --> A\n R --> A",
"labels": { "plan": "Plan de la pila", "app": "LXC de la aplicación", "appNet": "LAN + privada", "db": "LXC PostgreSQL", "cache": "LXC Valkey", "private": "privada" }
}
}
]
},
{
"id": "plan",
"title": "1. El plan, antes de que exista ningún contenedor",
"blocks": [
{
"p": "Mientras se interpreta la definición Compose no se crea ningún LXC. Primero se construye un plan completo con cada miembro, imagen, VMID, red, ruta, secreto, orden y comprobación de salud. Si el plan no es coherente, la instalación no empieza."
},
{
"table": {
"headers": ["Elemento del plan", "Contenido", "Comprobado antes de crear nada"],
"rows": [
["Miembros", "Aplicación principal, base de datos, caché, aprendizaje automático y demás dependencias", "VMID únicos, roles conocidos y exactamente un miembro principal"],
["Imágenes", "Referencia rolling, arquitectura y digest resuelto de cada servicio", "Todas existen, admiten la arquitectura y superan la verificación de integridad OCI"],
["Red", "Bridge privado, subred, una dirección fija por servicio y acceso LAN para el miembro principal", "Sin colisión con bridges o subredes existentes y sin direcciones repetidas"],
["Persistencia", "Discos del contenedor, directorios del host, propietarios y backup", "Rutas sin solapamientos, almacenamiento disponible y permisos declarados"],
["Secretos", "Contraseña de la base de datos, claves de la aplicación y credenciales iniciales", "Se generan una vez y solo se entregan a los miembros que los usan"],
["Ciclo de vida", "Orden de arranque, orden de parada y una comprobación de salud por miembro", "La aplicación principal arranca la última y se detiene la primera"]
]
}
}
]
},
{
"id": "create",
"title": "2. Creación, miembro a miembro",
"blocks": [
{
"steps": {
"items": [
{ "title": "Reservar todos los VMID", "body": "Se comprueban el inventario de Proxmox VE y el registro de instancias. No se adopta un CT existente ni se reutiliza un contrato que todavía pertenece a otra instancia." },
{ "title": "Preparar todas las imágenes", "body": "Todas las imágenes se resuelven, descargan y verifican antes de crear el primer contenedor." },
{ "title": "Crear cada rootfs", "body": "Se importan los metadatos OCI oficiales y se añaden la identidad de instancia de ProxMenux y el rol del miembro." },
{ "title": "Red privada", "body": "Se crean un bridge y una subred y cada miembro recibe su dirección fija; solo el miembro principal recibe además la interfaz de la LAN." },
{ "title": "Persistencia", "body": "Cada base de datos y configuración recibe su propio disco del contenedor; solo los datos destinados a compartirse usan directorios del host." },
{ "title": "Entorno", "body": "Se escriben los endpoints internos, los secretos compartidos y las variables de servicio. La aplicación llega a sus dependencias en sus direcciones privadas reservadas." },
{ "title": "Registro", "body": "Cada miembro registra su configuración nativa, los cambios del rootfs que las actualizaciones deben reproducir y su relación con la pila." }
]
}
}
]
},
{
"id": "checks",
"title": "3. Arranque y comprobación de cada contenedor",
"intro": "Un LXC creado no significa un servicio listo. Las dependencias arrancan en orden y cada una supera una comprobación propia de su servicio.",
"blocks": [
{
"table": {
"headers": ["Servicio", "Comprobación", "Qué demuestra"],
"rows": [
["PostgreSQL", "<code>pg_isready</code> en el CT con el host, el usuario y la base de datos esperados", "El servidor acepta conexiones para la base de datos configurada"],
["Redis / Valkey", "<code>redis-cli</code> o <code>valkey-cli</code> <code>PING</code> contra su dirección privada", "El broker escucha y responde"],
["Aprendizaje automático de Immich", "HTTP <code>GET /ping</code>, más una comprobación del runtime GPU seleccionado", "El servicio responde y la aceleración pedida no ha pasado a CPU"],
["Nextcloud", "<code>GET /status.php</code> con <code>installed=true</code>, <code>maintenance=false</code> y <code>needsDbUpgrade=false</code>", "La inicialización terminó sin migraciones pendientes"],
["Paperless-ngx / Tandoor", "HTTP en la dirección de la LAN y el puerto real del servicio", "El frontend y sus dependencias sirven la aplicación"],
["Aplicación principal", "El endpoint de su plantilla, por ejemplo <code>/api/server/ping</code> en Immich", "La pila completa funciona a través de la aplicación que usa las dependencias"]
]
}
},
{
"calloutWarning": {
"title": "running no significa healthy",
"body": "El estado running solo indica que existe el proceso del LXC. Cuando el servicio ofrece una comprobación mejor, se usa una comprobación exec o HTTP con un tiempo límite. Si un miembro se detiene o no supera su comprobación, la pila no se declara instalada."
}
}
]
},
{
"id": "hook",
"title": "4. El hook de dependencias",
"blocks": [
{
"p": "El hook solo se configura en el contenedor principal de una pila dependiente. Proxmox VE guarda el script como snippet y lo ejecuta como <code>hookscript</code> de ese CT. La receta de la pila no está escrita en el script: vive en un contrato privado aparte."
},
{
"codeGrid": {
"items": [
{ "title": "Configuración del CT principal", "code": "hookscript: local:snippets/proxmenux-stack-dependencies.sh" },
{ "title": "Contrato privado (ejemplo)", "code": "/etc/pve/priv/proxmenux-stack-VMID.json\n{\n \"schema\": 1,\n \"stack\": \"immich\",\n \"dependencies\": [\n {\"vmid\": 107, \"label\": \"PostgreSQL\", \"healthcheck\": {...}},\n {\"vmid\": 108, \"label\": \"Valkey\", \"healthcheck\": {...}},\n {\"vmid\": 106, \"label\": \"Machine Learning\", \"healthcheck\": {...}}\n ]\n}" }
]
}
},
{
"snippet": {
"summary": "Código completo de proxmenux-stack-dependencies.sh",
"pathCode": "local:snippets/proxmenux-stack-dependencies.sh",
"snippetCode": "stackDependencyHook"
}
},
{
"p": "El script es el mismo para todas las pilas. Los VMID, los nombres, los tipos de comprobación y los tiempos límite proceden del contrato privado <code>/etc/pve/priv/proxmenux-stack-VMID.json</code> de cada pila."
},
{
"steps": {
"items": [
{ "title": "Proxmox VE llama a pre-start", "body": "Antes de arrancar el CT principal, el hook se ejecuta con su VMID y la fase del ciclo de vida." },
{ "title": "Un bloqueo por pila", "body": "<code>flock</code> sobre <code>/run/lock/proxmenux-stack-VMID.lock</code> impide dos secuencias de arranque a la vez." },
{ "title": "Se lee y valida el contrato", "body": "Se exigen un esquema conocido, dependencias numéricas, etiquetas, una comprobación exec, http o running y un tiempo límite positivo." },
{ "title": "Dependencias en orden", "body": "Cada CT debe existir; uno detenido se arranca y uno que ya está en marcha no se reinicia." },
{ "title": "Espera de salud", "body": "La comprobación se ejecuta cada dos segundos y se confirma además que el CT sigue en marcha." },
{ "title": "Arranca el CT principal", "body": "Cuando todas las dependencias están listas, pre-start termina y Proxmox VE arranca la aplicación." }
]
}
},
{
"calloutInfo": {
"title": "El hook no detiene dependencias",
"body": "Las fases post-start, pre-stop y post-stop no hacen nada. Detener el CT principal deja en marcha PostgreSQL, Redis, Valkey o el aprendizaje automático. El hook ordena el arranque; no convierte varios LXC en un único proceso."
}
},
{
"table": {
"headers": ["Tipo", "Ejemplo", "Arranques posteriores"],
"rows": [
["Pila dependiente", "Immich, Nextcloud, aplicación con PostgreSQL", "El hook del CT principal arranca las dependencias y las espera"],
["Suite de aplicaciones", "Suite Arr", "Sin miembro principal: cada LXC sigue su propio <code>onboot</code>"],
["Aplicación simple", "Jellyfin", "Proxmox VE arranca directamente ese LXC"]
]
}
}
]
},
{
"id": "stack-checks",
"title": "5. Comprobaciones sobre la pila completa",
"blocks": [
{
"list": {
"items": [
"Cada VMID existe, es único y conserva la identidad de instancia esperada.",
"Cada contrato pertenece a la misma pila y conserva su rol y su configuración nativa registrada.",
"El miembro principal es el último en <code>start_order</code> y el primero en <code>stop_order</code>.",
"No falta ningún miembro, volumen ni adaptación del rootfs.",
"El hook apunta al snippet oficial, su contenido no ha cambiado y su contrato coincide con la receta de la pila.",
"Las imágenes y los digests observados coinciden con los archivos OCI preparados.",
"Dispositivos, perfiles GPU, montajes, secretos y endpoints siguen coincidiendo con los contratos.",
"Cuando todas las dependencias pasan, el endpoint de la aplicación principal comprueba la integración entre miembros."
]
}
}
]
},
{
"id": "manage",
"title": "Gestión de una pila instalada",
"intro": "En <strong>Gestionar aplicaciones OCI instaladas</strong>, cualquier miembro lleva a la pila completa. El menú de una pila ofrece <strong>Actualizar cada contenedor de la aplicación</strong> y <strong>Eliminar: la aplicación y sus contenedores</strong>.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Cualquier miembro", "body": "El contrato del miembro indica el VMID principal y la lista completa de miembros." },
{ "title": "La pila es reproducible", "body": "Se validan identidades, contratos, hook, adaptaciones y la existencia de una reproducción coordinada para esa receta." },
{ "title": "Primero las imágenes", "body": "La aplicación no se detiene hasta que todos los digests están resueltos, descargados y verificados." },
{ "title": "Detener y respaldar el conjunto", "body": "Los backups nativos verificados se toman con la pila detenida, de modo que aplicación y bases de datos corresponden al mismo momento." },
{ "title": "Actualizar y comprobar cada miembro", "body": "Adaptación, red, montajes, secretos y dispositivos se aplican de nuevo antes de la comprobación de salud de cada miembro." },
{ "title": "Publicar o recuperar todo", "body": "Los contratos nuevos solo se publican cuando todos los miembros pasan. Si uno falla, se restauran todos." }
]
}
},
{
"calloutWarning": {
"title": "Una pila sin reproducción coordinada no se actualiza",
"body": "Si la preparación de una pila no puede reproducirse, la actualización se rechaza antes de detenerla. La aplicación sigue funcionando tal como está."
}
}
]
},
{
"id": "failure",
"title": "6. Cuando algo falla",
"blocks": [
{
"p": "Durante una instalación, un error detiene y elimina los contenedores incompletos que creó esa operación, y el bridge privado si lo creó ella. Una pila no se publica como válida hasta que termina la secuencia completa."
},
{
"p": "Una actualización es transaccional: primero se preparan todas las imágenes, después se detiene la pila, se hace el backup de cada miembro y se verifica, y solo entonces se sustituye cada rootfs. Los miembros arrancan uno a uno con su comprobación de salud; si uno falla, se restauran todos desde el mismo conjunto de backups, de modo que la base de datos y la aplicación nunca corresponden a momentos distintos."
},
{
"mermaid": {
"chartCode": "flowchart LR\n P[\"{{prepare}}\"] --> S[\"{{stop}}\"]\n S --> B[\"{{backup}}\"]\n B --> R[\"{{replace}}\"]\n R --> H{\"{{healthy}}\"}\n H -- \"{{yes}}\" --> C[\"{{commit}}\"]\n H -- \"{{no}}\" --> X[\"{{rollback}}\"]",
"labels": {
"prepare": "Preparar todas las imágenes",
"stop": "Detener, primero el principal",
"backup": "Backup verificado de cada CT",
"replace": "Sustituir el rootfs",
"healthy": "¿Todos sanos?",
"yes": "Sí",
"no": "No",
"commit": "Publicar los contratos",
"rollback": "Restaurar todos los miembros"
}
}
}
]
},
{
"id": "not-assumed",
"title": "Qué no incluye una pila",
"blocks": [
{
"list": {
"items": [
"Una suite instalada en un único recorrido, como la Suite Arr, no es una pila dependiente: sus contenedores no tienen orden de arranque entre ellos.",
"La red privada no sustituye la autenticación, TLS ni la configuración de cada aplicación.",
"El backup de un LXC no contiene el bridge, el hook ni los contratos del resto de la pila.",
"Prowlarr, Sonarr o Radarr no reciben indexadores, perfiles ni proveedores de ProxMenux."
]
}
}
]
}
]
}
@@ -0,0 +1,190 @@
{
"meta": {
"title": "Datos, rutas y red | ProxMenux",
"description": "Cómo conserva OCI manager Apps los datos de un contenedor OCI: discos del contenedor, directorios del host, montajes Rclone, direcciones y redes privadas."
},
"header": {
"title": "Datos, rutas y red",
"description": "Qué vive en el rootfs, qué sobrevive a su sustitución, cómo se comparten datos entre contenedores y cómo obtiene cada contenedor su dirección.",
"section": "OCI manager Apps"
},
"sections": [
{
"id": "intro",
"blocks": [
{
"calloutInfo": {
"title": "La persistencia se decide antes de que exista el LXC",
"body": "Los volúmenes que publican la imagen y su archivo Compose se leen antes de crear el contenedor. Cada ruta que debe sobrevivir a una actualización se convierte en un punto de montaje independiente del rootfs, de modo que el rootfs solo contiene lo que pertenece a la imagen y puede sustituirse."
}
}
]
},
{
"id": "questions",
"title": "Qué pregunta el instalador",
"intro": "La plantilla aporta las rutas que necesita la aplicación. Cada una se sitúa en un disco del contenedor o en un directorio del host, y pueden añadirse más rutas antes del resumen.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Rutas requeridas", "body": "Se enumeran <code>/config</code>, <code>/data</code>, bibliotecas, descargas y cualquier volumen que declare la aplicación." },
{ "title": "Ubicación", "body": "Cada ruta se sitúa en un disco del contenedor o en un directorio existente del host." },
{ "title": "Almacenamiento y tamaño", "body": "Un disco del contenedor se crea en un almacenamiento de Proxmox VE, <code>local-lvm</code> por defecto, con el tamaño indicado. Aparece como <code>vm-VMID-disk-N</code> y se conecta como <code>mpN</code>." },
{ "title": "Directorio del host", "body": "Un directorio del host se indica por su ruta. Si no existe, se crea con el propietario que mapea el contenedor." },
{ "title": "Rutas adicionales", "body": "Antes de instalar pueden añadirse más pares de ruta del host o volumen y ruta del contenedor." },
{ "title": "Resumen", "body": "La relación completa se muestra antes de crear el CT y se guarda en su contrato de instancia." }
]
}
}
]
},
{
"id": "options",
"title": "Las dos ubicaciones persistentes",
"blocks": [
{
"table": {
"headers": ["Propiedad", "Disco del contenedor", "Directorio del host"],
"rows": [
["En la configuración", "<code>mpN: STORAGE:vm-VMID-disk-N,mp=/config,backup=1,size=16G</code>", "<code>mpN: /mnt/oci-shared/media,mp=/data/media</code>"],
["Backup del contenedor (vzdump)", "Incluido, con <code>backup=1</code>", "No incluido"],
["Tamaño", "Fijo; crece con un redimensionado del punto de montaje", "El espacio libre del sistema de archivos o dataset del host"],
["Otros contenedores", "Solo lo monta su propio contenedor", "El mismo directorio puede montarse en varios contenedores"],
["Snapshots y restauración", "Los gestiona Proxmox VE junto con el CT", "Se gestionan en el almacenamiento del host"],
["Al eliminar la aplicación", "Se elimina con el contenedor", "Se conserva, con su contenido"],
["Al mover el CT a otro nodo", "Se mueve con el CT", "La misma ruta debe existir en el otro nodo"]
]
}
},
{
"p": "El rootfs queda reservado para los binarios y el contenido de la imagen. Una actualización o una recreación lo sustituye sin tocar ninguno de los dos tipos de punto de montaje."
}
]
},
{
"id": "example",
"title": "Ejemplo: un contenedor con las dos ubicaciones",
"blocks": [
{
"code": {
"title": "Jellyfin instalado como CT 151 en local-lvm (extracto)",
"code": "rootfs: local-lvm:vm-151-disk-0,size=8G\n# Disco del contenedor, incluido en el backup del CT\nmp0: local-lvm:vm-151-disk-1,mp=/config,backup=1,size=16G\n\n# Directorio del host, fuera del backup del CT\nmp1: /mnt/oci-shared/media,mp=/data/media"
}
},
{
"p": "Una actualización o una recreación sustituye solo el rootfs: <code>mp0</code> conserva usuarios, bibliotecas y ajustes, y <code>mp1</code> sigue mostrando los mismos archivos multimedia. Restaurar el backup del CT devuelve <code>/config</code>; el directorio multimedia se restaura, si hace falta, desde el backup del almacenamiento del host."
},
{
"flow": {
"nodes": [
{ "label": "Contrato", "detail": "/config" },
{ "label": "Ubicación", "detail": "disco del contenedor\no directorio del host" },
{ "label": "LXC", "detail": "siempre /config\npara la aplicación" }
],
"caption": "La aplicación ve la ruta que publica la imagen; solo cambia dónde se guarda."
}
}
]
},
{
"id": "shared",
"title": "Un directorio del host, varios contenedores",
"blocks": [
{
"mermaid": {
"chartCode": "flowchart TB\n H[\"{{host}}<br/>/mnt/oci-shared/media\"]\n H --> Q[\"qBittorrent<br/>/data\"]\n H --> J[\"Jellyfin<br/>/data\"]\n H --> R[\"Radarr / Sonarr<br/>/data\"]\n Q -. \"{{config}}\" .-> QV[(\"/config mpN\")]\n J -. \"{{config}}\" .-> JV[(\"/config mpN\")]\n R -. \"{{config}}\" .-> RV[(\"/config mpN\")]",
"labels": { "host": "Directorio del host", "config": "configuración propia" }
}
},
{
"p": "Cada contenedor guarda su configuración en su propio disco. La biblioteca o las descargas son un único directorio del host montado en la misma ruta interna de todos los contenedores, de modo que la ruta en la que escribe una aplicación es la misma en la que lee otra."
}
]
},
{
"id": "rclone",
"title": "Almacenamiento en la nube con la aplicación Rclone",
"intro": "La aplicación Rclone del catálogo ofrece, además de su instalación, <strong>Activar un montaje en un contenedor Rclone OCI existente</strong>. Monta un remoto ya creado y autorizado en la interfaz web de Rclone y lo publica en el host, donde otros contenedores pueden usarlo como directorio del host.",
"blocks": [
{
"steps": {
"items": [
{ "title": "Contenedor y remoto", "body": "El VMID del contenedor Rclone, el nombre exacto del remoto y, opcionalmente, una ruta dentro de él." },
{ "title": "Nombre y caché", "body": "El nombre del montaje y el modo de caché VFS: <code>off</code>, <code>minimal</code>, <code>writes</code> o <code>full</code> (por defecto)." },
{ "title": "Vistas publicadas", "body": "Una raíz común, <code>/mnt/oci-shared</code> por defecto, contiene una vista de lectura y escritura en <code>/mnt/oci-shared/remotes/NOMBRE</code> y una de solo lectura en <code>/mnt/oci-shared/remotes-ro/NOMBRE</code>." },
{ "title": "Activación", "body": "Tras una confirmación, el CT se detiene, se configuran su orden de arranque y un hookscript de Proxmox VE, y se vuelve a iniciar. La operación espera hasta que las dos vistas están montadas en el host." }
]
}
},
{
"calloutInfo": {
"title": "Si el montaje no llega a activarse",
"body": "Se restaura la configuración anterior del contenedor y se vuelve a iniciar, de modo que una activación fallida deja Rclone como estaba."
}
}
]
},
{
"id": "network",
"title": "Direcciones y redes",
"intro": "Una aplicación simple necesita una dirección. Una aplicación multicontenedor necesita además una red estable entre sus miembros.",
"blocks": [
{
"cards": {
"items": [
{ "icon": "network", "title": "Aplicación simple", "body": "Se eligen el bridge y DHCP o una dirección CIDR fija. El LXC tiene una dirección propia y el resumen muestra las URL completas de los servicios." },
{ "icon": "waypoints", "title": "Aplicación multicontenedor", "body": "Se busca una subred libre, se crea un bridge privado persistente y cada miembro (aplicación, base de datos, caché) recibe en él una dirección fija." }
]
}
},
{
"flow": {
"nodes": [
{ "label": "LAN", "detail": "dirección accesible\nsolo el servicio principal" },
{ "label": "LXC principal", "detail": "web / API\nLAN + red privada" },
{ "label": "Red privada", "detail": "PostgreSQL · Valkey · ML\ndirecciones fijas" }
],
"caption": "Las dependencias se comunican por la red privada y no tienen dirección en la LAN."
}
},
{
"p": "El contrato de la pila guarda el bridge, la subred, las direcciones y las relaciones entre servicios. Actualizar o recrear un miembro reutiliza la misma topología. ProxMenux Monitor abre el contenedor principal en su dirección de la LAN, no en la de la red privada."
}
]
},
{
"id": "ownership",
"title": "Propietarios",
"blocks": [
{
"list": {
"items": [
"Los directorios nuevos se crean con el UID y el GID que mapea el LXC no privilegiado.",
"Los directorios existentes no cambian de propietario de forma recursiva.",
"Los sockets, los archivos del sistema y las rutas sensibles no se ofrecen como directorios del host genéricos."
]
}
}
]
},
{
"id": "backup",
"title": "Qué contiene el backup de un contenedor",
"blocks": [
{
"table": {
"headers": ["Elemento", "En el vzdump del CT", "Dónde se conserva"],
"rows": [
["rootfs OCI", "Sí", "El backup del CT; también puede reconstruirse desde la imagen y el contrato"],
["Disco del contenedor con <code>backup=1</code>", "Sí", "El backup del CT"],
["Directorio del host", "No", "El backup del almacenamiento del host"],
["Contrato de la instancia", "No", "<code>/usr/local/share/proxmenux/oci/instances/VMID/</code> en el host"],
["Aplicación multicontenedor", "Cada miembro en su propio backup", "Los backups de cada miembro, más el contrato de la pila y su bridge en el host"]
]
}
}
]
}
]
}
@@ -64,7 +64,7 @@
"heading": "Cómo funciona por dentro",
"items": [
"El menú de diálogo lista los 6 códigos; eliges uno.",
"Si <code>config.json</code> existe: <code>jq --arg lang \"$new_language\" '.language = $lang'</code> actualiza el campo en sitio.",
"Si <code>config.json</code> existe: <code>jq --arg lang \"$new_language\" '.language = $lang''</code> actualiza el campo en sitio.",
"Si <code>config.json</code> no existe: se crea uno nuevo con el código de idioma en un objeto de un solo campo.",
"Diálogo de confirmación: <em>\"Idioma cambiado a [code]\"</em>.",
"<code>exec bash config_menu.sh</code> recarga el menú Settings con el nuevo idioma activo."