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.lxcNfsClient.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-nfs-client", }, } } type FlagRow = { flag: string; effect?: string; effectRich?: string } type StringItem = string type RelatedItem = { href: string; label: string; tail?: string; tailRich?: string } export default async function LxcNfsClientPage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.storageShare.lxcNfsClient" }) const messages = (await getMessages({ locale })) as unknown as { docs: { storageShare: { lxcNfsClient: { fstabFlags: { rows: FlagRow[] } troubleshoot: { aptItems: StringItem[]; squashItems: StringItem[] } related: { items: RelatedItem[] } } } } } const flagRows = messages.docs.storageShare.lxcNfsClient.fstabFlags.rows const aptItems = messages.docs.storageShare.lxcNfsClient.troubleshoot.aptItems const squashItems = messages.docs.storageShare.lxcNfsClient.troubleshoot.squashItems const relatedItems = messages.docs.storageShare.lxcNfsClient.related.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} const hostNfsLink = (chunks: React.ReactNode) => ( {chunks} ) const mountLink = (chunks: React.ReactNode) => ( {chunks} ) const importLink = (chunks: React.ReactNode) => ( {chunks} ) return (
{t.rich("privReq.body", { code, strong, hostNfsLink, mountLink })}

{t("what.heading")}

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

-- mount -t nfs -o rw,hard,rsize=…,wsize=… \\ :/export/data /mnt/data # Persistent (added to /etc/fstab inside the CT): :/export/data /mnt/data nfs ,_netdev,x-systemd.automount,noauto 0 0`} />
  • {t.rich("what.twoWaysBind", { strong, mountLink })}
  • {t.rich("what.twoWaysDirect", { strong })}

{t("opening.heading")}

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

{t("opening.imageAlt")}

{t("howRuns.heading")}

{`┌─────────────────────────────────────────────┐
│  PHASE 1 — Pick CT, server, export, options │
│  (nothing touched yet)                      │
└──────────────────┬──────────────────────────┘
                   ▼
      Privileged-CT gate (share-common.func)
      ├─ pct list — pick CT
      ├─ Auto-start if stopped
      └─ Reads /etc/pve/lxc/.conf
         └─ unprivileged: 1  → abort with help message
                   │
                   ▼
      Install NFS client packages (in CT)
      └─ pct exec apt-get install -y nfs-common
         (skipped if nfs-common is already installed)
         Verifies: showmount + mount.nfs both present
                   │
                   ▼
      Server selection
      ├─ Auto-discover (nmap from HOST on /24,
      │    port 2049, then showmount -e per result)
      └─ Manual: type IP or hostname
                   │
                   ▼
      Reachability validation chain (from inside CT)
      ├─ pct exec ping -c 1 -W 3  ── fail → abort
      ├─ pct exec nc -z -w 3  2049 ── fail → abort
      └─ pct exec showmount -e       ── fail → abort
                   │
                   ▼
      Export selection
      ├─ Server returns exports → checklist with ACL
      └─ No exports / blocked  → manual input
                   │
                   ▼
      Validate the chosen export still exists
      (re-runs showmount -e | grep )
                   │
                   ▼
      Mount-point picker (3 options)
      ├─ 1. Create new folder in /mnt
      │      (default: nfs__)
      ├─ 2. Select existing folder in /mnt
      │      (warns if folder is not empty —
      │       mounting hides existing files)
      └─ 3. Enter custom path
                   │
                   ▼
      Mount-options preset (3 options)
      ├─ 1. Read/write
      │      rw,hard,rsize=1048576,wsize=1048576,
      │      timeo=600,retrans=2
      ├─ 2. Read-only
      │      ro,hard,rsize=1048576,wsize=1048576,
      │      timeo=600,retrans=2
      └─ 3. Custom — type your own option string
                   │
                   ▼
      Permanent mount? (yes/no)
      └─ yes → write entry to /etc/fstab
                   │
   ┌──────── Cancel   OR   Confirm ────┐
   ▼                                   ▼
Exit, nothing        ┌─────────────────┴─────────────────┐
was changed          │  PHASE 2 — Mount and persist       │
                     └─────────────────┬─────────────────┘
                                       ▼
                       Create mount point if missing
                       (pct exec mkdir -p )
                                       ▼
                       If something is already mounted there,
                       offer to unmount first
                                       ▼
                       pct exec mount -t nfs \\
                            -o  \\
                            : 
                                       ▼
                       Smoke test: write a 0-byte file
                       (.test_write) and delete it
                       └─ no write access → "read-only"
                                       ▼
                       If "permanent" was chosen:
                       └─ Append to /etc/fstab inside CT:
                            :    nfs \\
                              ,_netdev,
                              x-systemd.automount,noauto  0 0
                       (any prior entry for this MP is removed first)
                                       ▼
                       Print summary (server / export / mp /
                       options / permanent yes-no)`}
      

{t("fstabFlags.heading")}

{t.rich("fstabFlags.intro", { code })}

{flagRows.map((row, idx) => ( ))}
{t("fstabFlags.headerFlag")} {t("fstabFlags.headerEffect")}
{row.flag} {row.effectRich ? t.rich(`fstabFlags.rows.${idx}.effectRich`, { code }) : row.effect}
{t.rich("fstabFlags.netEffectBody", { em })}

{t("manual.heading")}

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

> /etc/fstab <

{t("view.heading")}

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

{t("unmount.heading")}

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

{t.rich("unmount.warnBody", { code, em })}

{t("test.heading")}

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

{t("troubleshoot.heading")}

{t.rich("troubleshoot.privBody", { code, importLink })} {t.rich("troubleshoot.aptIntro", { code })}
    {aptItems.map((_, idx) => (
  • {t.rich(`troubleshoot.aptItems.${idx}`, { code })}
  • ))}
{t("troubleshoot.aptOutro")}
{t.rich("troubleshoot.portBody", { code })} {t.rich("troubleshoot.bootBody", { code })} {t.rich("troubleshoot.squashIntro", { code })}
    {squashItems.map((_, idx) => (
  • {t.rich(`troubleshoot.squashItems.${idx}`, { code })}
  • ))}
{t("troubleshoot.squashOutro")}

{t("related.heading")}

    {relatedItems.map((item, idx) => (
  • {item.label} {item.tailRich ? t.rich(`related.items.${idx}.tailRich`, { em }) : item.tail}
  • ))}
) }