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.importDiskVm.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/import-disk-vm", }, } } type StepData = { title: string body?: string bodyRich?: string intro?: string items?: string[] img?: string caption?: string } type StringItem = string type RelatedItem = { href: string; label: string; tail?: string } export default async function ImportDiskVMPage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.diskManager.importDiskVm" }) const messages = (await getMessages({ locale })) as unknown as { docs: { diskManager: { importDiskVm: { prereqs: { items: StringItem[] } steps: { list: StepData[] } troubleshoot: { noDisksItems: StringItem[] } related: { items: RelatedItem[] } } } } } const prereqItems = messages.docs.diskManager.importDiskVm.prereqs.items const stepList = messages.docs.diskManager.importDiskVm.steps.list const noDisksItems = messages.docs.diskManager.importDiskVm.troubleshoot.noDisksItems const relatedItems = messages.docs.diskManager.importDiskVm.related.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} const winLink = (chunks: React.ReactNode) => ( {chunks} ) return (
{t.rich("intro.body", { code })}

{t("howRuns.heading")}

{t("howRuns.body")}

{`┌─────────────────────────────────────────────┐
│  PHASE 1 — Pick VM, detect disks, select    │
│  (nothing touched yet)                      │
└──────────────────┬──────────────────────────┘
                   ▼
      qm list — user picks target VM
                   │
                   ▼
      VM status check
      ├─ running → abort (power off first)
      └─ stopped → continue
                   │
                   ▼
      Detect disks on host (lsblk)
                   │
                   ▼
      Visibility filter
      ├─ Hidden: root / swap / system-mounted
      ├─ Hidden: active ZFS / LVM / RAID members
      ├─ Hidden: already in this VM's config
      ├─ Shown: free disks
      └─ Shown with ⚠ label: stale ZFS/LVM/RAID
                             signatures (not active)
                   │
                   ▼
      User selects disk(s) via checklist
      + picks bus interface:
      SATA  /  SCSI  /  VirtIO  /  IDE
                   │
                   ▼
      Per-disk cross-check
      ├─ Assigned to a RUNNING VM/CT? → skip disk
      ├─ Assigned to stopped VM/CT?   → ask
      │   "continue anyway?" yes/no
      └─ NVMe detected?                → suggest
          using "Add Controller / NVMe"
          (user can still add as disk)
                   │
                   ▼
      Summary of disks to process
                   │
   ┌──────── Cancel   OR   Confirm ────┐
   ▼                                   ▼
Exit, nothing        ┌─────────────────┴─────────────────┐
was changed          │  PHASE 2 — Attach                  │
                     └─────────────────┬─────────────────┘
                                       ▼
                       For each selected disk:
                       ├─ Resolve best persistent path
                       │   preferred order:
                       │   1. /dev/disk/by-id/ata-*
                       │   2. /dev/disk/by-id/nvme-*
                       │   3. /dev/disk/by-id/scsi-*
                       │   4. /dev/disk/by-id/wwn-*
                       │   fallback: raw /dev/sdX
                       ├─ Find next free {bus}N slot
                       │   (scans qm config output)
                       └─ qm set  -{bus}N 
                                       │
                                       ▼
                       Verify: qm config  shows
                       the new slot(s)
                                       │
                                       ▼
                       Guest sees each disk as a native
                       block device under its bus
                       (e.g. /dev/sda, /dev/nvme0n1)`}
      

{t.rich("howRuns.summary", { em })}

{t("prereqs.heading")}

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

{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.intro && ( <>

{step.intro}

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

{t("manual.heading")}

{t.rich("manual.body", { code })}

{t.rich("manual.migrationBody", { strong, code })} {t.rich("manual.shareBody", { code })}

{t("troubleshoot.heading")}

{t("troubleshoot.noDisksIntro")}
    {noDisksItems.map((_, idx) => (
  • {t(`troubleshoot.noDisksItems.${idx}`)}
  • ))}
{t.rich("troubleshoot.noDisksOutro", { code })}
{t.rich("troubleshoot.noVisibleBody", { strong, winLink })}

{t("related.heading")}

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