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 { DataFlowDiagram } from "@/components/ui/data-flow-diagram" 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.storageShare.lxcMountPoints.meta" }) return { title: t("title"), description: t("description"), openGraph: { title: t("ogTitle"), description: t("ogDescription"), type: "article", url: "https://macrimi.github.io/ProxMenux/docs/storage-share/lxc-mount-points", }, } } type StringItem = string type SourceRow = { source: string; where?: string; whereRich?: string; labelRich: string } type StringList = string[] type RelatedItem = { href: string label: string extraHref?: string extraLabel?: string joiner?: string tail?: string tailRich?: string } export default async function LxcMountPointsPage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.storageShare.lxcMountPoints" }) const messages = (await getMessages({ locale })) as unknown as { docs: { storageShare: { lxcMountPoints: { bigPicture: { items: StringItem[] } sources: { rows: SourceRow[] } troubleshoot: { nfsItems: StringList } related: { items: RelatedItem[] } } } } } const bigPictureItems = messages.docs.storageShare.lxcMountPoints.bigPicture.items const sourceRows = messages.docs.storageShare.lxcMountPoints.sources.rows const nfsItems = messages.docs.storageShare.lxcMountPoints.troubleshoot.nfsItems const relatedItems = messages.docs.storageShare.lxcMountPoints.related.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} return (
{t("intro.body")}

{t("bigPicture.heading")}

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

-mpN /mnt/data, mp=/mnt/data, shared=1, backup=0`} />

{t.rich("bigPicture.outro", { code })}

    {bigPictureItems.map((_, idx) => (
  • {t.rich(`bigPicture.items.${idx}`, { code })}
  • ))}

{t("perms.heading")}

{t.rich("perms.intro", { strong, em })}

{t("perms.headerType")} {t("perms.headerAction")}
{t("perms.localType")}
{t("perms.localTypeSub")}
{t.rich("perms.localActionRich", { code })}
{t("perms.cifsType")}
{t("perms.cifsTypeSub")}
{t.rich("perms.cifsActionRich", { code })}
{t("perms.nfsType")}
{t("perms.nfsTypeSub")}
{t.rich("perms.nfsActionRich", { code })}
{t("perms.privBody")} {t.rich("perms.noCtBody", { strong, code })}

{t("writes.heading")}

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

{t.rich("writes.outro", { em })}

{t("writes.twoWaysHeading")}

{t("writes.headerApproach")} {t("writes.headerChanges")} {t("writes.headerWhen")}
{t("writes.hostType")}
{t("writes.hostTypeSub")}
{t.rich("writes.hostChangesRich", { code, em })} {t("writes.hostWhen")}
{t.rich("writes.idmapTypeRich", { code })}
{t("writes.idmapTypeSub")}
{t.rich("writes.idmapChangesRich", { code })} {t.rich("writes.idmapWhenRich", { em, code })}
{t.rich("writes.idmapTipBody", { code })}

{t("opening.heading")}

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

{t("opening.imageAlt")}

{t("addFlow.heading")}

{t("addFlow.intro")}

{`┌─────────────────────────────────────────────┐
│  PHASE 1 — Pick CT, host dir, mount point   │
│  (nothing touched yet)                      │
└──────────────────┬──────────────────────────┘
                   ▼
      pct list — pick the target container
                   │
                   ▼
      Unified host-directory picker
      Lists every candidate the script can detect:
      ├─ Mounted CIFS / NFS shares (/proc/mounts)
      ├─ fstab-inactive network mounts (defined
      │   but not currently mounted) — labelled
      │   "fstab(off)-"
      ├─ Local /mnt/* directories
      ├─ Proxmox-managed storages under /mnt/pve/*
      │   (NFS / CIFS shares registered via pvesm)
      │   — labelled "PVE-"
      └─ "Enter path manually"  for anything else
                   │
                   ▼
      Detect the host directory TYPE
      └─ local  /  cifs  /  nfs
         (drives the permission-fix branch later)
                   │
                   ▼
      Container mount point picker
      ├─ Create new directory in /mnt
      │    (auto-suggests basename of host dir)
      ├─ Enter manual path (must be absolute)
      └─ Cancel
      Validates the path is not already used as
      a mount point in this CT.
                   │
                   ▼
      Detect CT type:
      ├─ Privileged   → no UID shift
      └─ Unprivileged → +100000 (default idmap)
                   │
                   ▼
      ACTIVE FIX FOR THE HOST DIRECTORY
      (depends on the type detected earlier)
      ├─ cifs  → offer remount with open uid/gid
      ├─ nfs   → offer chmod + setfacl on share
      └─ local → handled AFTER the bind mount
                 (only if CT is unprivileged)
                   │
   ┌──────── Cancel   OR   Confirm ────┐
   ▼                                   ▼
Exit, nothing        ┌─────────────────┴─────────────────┐
was changed          │  PHASE 2 — Apply                   │
                     └─────────────────┬─────────────────┘
                                       ▼
                       Find next free mpN slot
                       (scans /etc/pve/lxc/.conf)
                                       ▼
                       pct set  -mpN \\
                            ,
                            mp=,
                            shared=1, backup=0
                                       ▼
                       For local + unprivileged:
                       └─ lmm_offer_host_permissions
                          (chmod o+rwx + ACL on host dir,
                           only if perms were insufficient)
                                       ▼
                       Offer to restart the container
                       └─ pct reboot 
                          (mounts only become active on
                           the next CT start)
                                       ▼
                       Verify: pct exec  -- test -d
                         → "accessible"`}
      

{t("sources.heading")}

{t.rich("sources.intro", { em })}

{sourceRows.map((row, idx) => ( ))}
{t("sources.headerSource")} {t("sources.headerWhere")} {t("sources.headerLabel")}
{row.source} {row.whereRich ? t.rich(`sources.rows.${idx}.whereRich`, { code }) : row.where} {t.rich(`sources.rows.${idx}.labelRich`, { code, em })}
{t.rich("sources.tipBody", { code })}

{t("manual.heading")}

{t("manual.privIntro")}

{t("manual.unprivLocalIntro")}

{t("manual.unprivCifsIntro")}

{t("view.heading")}

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

{t("remove.heading")}

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

{t.rich("remove.warnBody", { code })}

{t("troubleshoot.heading")}

{t.rich("troubleshoot.noMountBody", { code })} {t.rich("troubleshoot.noWriteBody", { code })} {t("troubleshoot.alreadyBody")} {t("troubleshoot.nfsIntro")}
    {nfsItems.map((_, idx) => (
  • {t.rich(`troubleshoot.nfsItems.${idx}`, { code })}
  • ))}
{t("troubleshoot.nfsOutro")}
{t.rich("troubleshoot.fstabOffBody", { code })}

{t("related.heading")}

    {relatedItems.map((item, idx) => (
  • {item.label} {item.extraHref && item.extraLabel && ( <> {item.joiner} {item.extraLabel} )} {item.tailRich ? t.rich(`related.items.${idx}.tailRich`, { code }) : item.tail}
  • ))}
) }