import type { Metadata } from "next" import { getTranslations, getMessages, setRequestLocale } from "next-intl/server" import { Link } from "@/i18n/navigation" import Image from "next/image" import { DocHeader } from "@/components/ui/doc-header" import { Callout } from "@/components/ui/callout" import CopyableCode from "@/components/CopyableCode" export async function generateMetadata({ params, }: { params: Promise<{ locale: string }> }): Promise { const { locale } = await params const t = await getTranslations({ locale, namespace: "docs.diskManager.addControllerNvmeVm.meta" }) return { title: t("title"), description: t("description"), openGraph: { title: t("ogTitle"), description: t("ogDescription"), type: "article", url: "https://macrimi.github.io/ProxMenux/docs/disk-manager/add-controller-nvme-vm", }, } } type StepData = { title: string body?: string bodyRich?: string items?: string[] outro?: string img?: string alt?: string caption?: string } type StringItem = string type RelatedItem = { href: string; label: string; tail?: string } export default async function AddControllerNVMeVMPage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.diskManager.addControllerNvmeVm" }) const messages = (await getMessages({ locale })) as unknown as { docs: { diskManager: { addControllerNvmeVm: { prereqs: { items: StringItem[] } steps: { list: StepData[] } related: { items: RelatedItem[] } } } } } const prereqItems = messages.docs.diskManager.addControllerNvmeVm.prereqs.items const stepList = messages.docs.diskManager.addControllerNvmeVm.steps.list const relatedItems = messages.docs.diskManager.addControllerNvmeVm.related.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} return (
{t.rich("intro.body", { strong, code })}

{t("howRuns.heading")}

{t("howRuns.body")}

{`┌─────────────────────────────────────────────┐
│  PHASE 1 — Detect, validate, plan           │
│  (nothing touched yet)                      │
└──────────────────┬──────────────────────────┘
                   ▼
      qm list — user picks target VM
                   │
                   ▼
      IOMMU status on the running kernel
      ├─ /sys/kernel/iommu_groups/* exists?
      │
      ├─ Yes → IOMMU active, continue
      │
      └─ No  → cmdline check + offer to enable
            CPU vendor detect (cat /proc/cpuinfo)
            ├─ Intel → write intel_iommu=on
            └─ AMD   → write amd_iommu=on
            Into:
            ├─ /etc/kernel/cmdline  (systemd-boot)
            └─ /etc/default/grub    (GRUB)
            + update-initramfs -u -k all
            + offer reboot now
            ├─ reboot accepted → reboot
            └─ reboot declined → abort
                 (re-run after reboot)
                   │
                   ▼
      Enumerate storage-class PCI devices
      lspci -Dnn filtered by class:
      ├─ SATA / SAS / SCSI / NVMe controllers
      ├─ Resolve IOMMU group via /sys path
      └─ For HBAs: list disks currently behind
                   │
                   ▼
      Conflict / eligibility filter
      ├─ Already in this VM's hostpci? → hide
      ├─ Already in another VM's hostpci?
      │    → block (shown with owner VM id)
      ├─ Carries the Proxmox root disk
      │    or any disk referenced by an LXC
      │    → block
      └─ Shared IOMMU group
         with non-storage members?
            → show ⚠ warning inline
                   │
                   ▼
      User selects device(s) via checklist
                   │
                   ▼
      Summary:
      (VM + each PCI device + IOMMU group
       membership + reboot status)
                   │
   ┌──────── Cancel   OR   Confirm ────┐
   ▼                                   ▼
Exit, nothing        ┌─────────────────┴─────────────────┐
was changed          │  PHASE 2 — Apply                   │
                     └─────────────────┬─────────────────┘
                                       ▼
                       Host side (once per session):
                       ├─ Add vfio-pci to /etc/modules
                       ├─ Append the device vendor:device
                       │  IDs to /etc/modprobe.d/vfio.conf
                       └─ update-initramfs -u -k all
                       (so the device is bound to vfio-pci
                        at next boot, not the native driver)
                                       │
                                       ▼
                       For each selected device:
                       ├─ Find next free hostpciN slot
                       │   (scans qm config)
                       └─ qm set  --hostpciN \\
                             ,pcie=1
                             (e.g. 0000:01:00.0,pcie=1)
                                       │
                                       ▼
                       Verify: qm config  shows
                       the new hostpciN entries
                                       │
                                       ▼
                       If IOMMU was just enabled:
                       └─ reminder to reboot before
                          starting the VM
                                       │
                                       ▼
                       Guest on next boot sees the
                       controller directly + every disk
                       behind it (full SMART, native
                       firmware features, no Proxmox layer)`}
      

{t("iommu.heading")}

{t.rich("iommu.body", { strong })}

{`          Host PCIe bus — grouped by IOMMU
                      │
                      ▼
    ┌─────────────────────────────────────────┐
    │  Group 12                               │
    │  ────────                               │
    │  00:17.0   SATA HBA                     │
    │    └── sda sdb sdc sdd                  │
    │                                         │
    │  Pass-through takes:                    │
    │    the HBA + every disk on it           │
    │                                         │
    │  ✓ clean — no extra members in group    │
    └──────────────────┬──────────────────────┘
                       │
                       ▼
    ┌─────────────────────────────────────────┐
    │  Group 13                               │
    │  ────────                               │
    │  01:00.0   NVMe controller              │
    │                                         │
    │  Pass-through takes:                    │
    │    the NVMe controller itself           │
    │                                         │
    │  ✓ clean — NVMe alone in its group      │
    └──────────────────┬──────────────────────┘
                       │
                       ▼
    ┌─────────────────────────────────────────┐
    │  Group 14                               │
    │  ────────                               │
    │  02:00.0   SATA HBA                     │
    │    └── sde sdf                          │
    │  02:00.1   USB 3.0 controller           │
    │                                         │
    │  Pass-through takes:                    │
    │    SATA HBA + USB 3.0 controller        │
    │    (whole group leaves together)        │
    │                                         │
    │  ⚠ shared group — the USB ports will    │
    │    also leave the host. Review whether  │
    │    that is acceptable before confirming.│
    └─────────────────────────────────────────┘`}
        

{t("iommu.outro")}

{t("prereqs.heading")}

    {prereqItems.map((_, idx) => (
  • {t.rich(`prereqs.items.${idx}`, { strong, em })}
  • ))}
{t("prereqs.warnBody")}

{t("steps.heading")}

{stepList.map((step, idx) => (
{t("steps.stepLabel")} {idx + 1}

{step.title}

{step.bodyRich ? (

{t.rich(`steps.list.${idx}.bodyRich`, { code, strong })}

) : step.body &&

{step.body}

} {step.items && (
    {step.items.map((_, i) => (
  • {t.rich(`steps.list.${idx}.items.${i}`, { code })}
  • ))}
)} {step.outro &&

{step.outro}

}
{step.img && (
{step.alt
{step.caption && {step.caption}}
)}
))}

{t("manual.heading")}

{t("troubleshoot.heading")}

{t("troubleshoot.noGroupsBody")} {t.rich("troubleshoot.busyBody", { code })} {t("troubleshoot.noDisksBody")} {t.rich("troubleshoot.sharedBody", { em })}

{t("related.heading")}

    {relatedItems.map((item) => (
  • {item.label} {item.tail}
  • ))}
) }