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.lxcSambaClient.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-samba-client", }, } } type OptionRow = { option: string; effect: string } type StringItem = string type RelatedItem = { href: string; label: string; tail?: string; tailRich?: string } export default async function LxcSambaClientPage({ params, }: { params: Promise<{ locale: string }> }) { const { locale } = await params setRequestLocale(locale) const t = await getTranslations({ locale, namespace: "docs.storageShare.lxcSambaClient" }) const messages = (await getMessages({ locale })) as unknown as { docs: { storageShare: { lxcSambaClient: { options: { rows: OptionRow[] } troubleshoot: { aptItems: StringItem[] } related: { items: RelatedItem[] } } } } } const optionRows = messages.docs.storageShare.lxcSambaClient.options.rows const aptItems = messages.docs.storageShare.lxcSambaClient.troubleshoot.aptItems const relatedItems = messages.docs.storageShare.lxcSambaClient.related.items const code = (chunks: React.ReactNode) => {chunks} const strong = (chunks: React.ReactNode) => {chunks} const em = (chunks: React.ReactNode) => {chunks} const hostSambaLink = (chunks: React.ReactNode) => ( {chunks} ) const mountLink = (chunks: React.ReactNode) => ( {chunks} ) return (
{t.rich("privReq.body", { code, strong, hostSambaLink, mountLink })}

{t("what.heading")}

{t("what.body")}

_.cred # What the script writes inside the CT: pct exec -- mount -t cifs /// /mnt/share \\ -o "rw,file_mode=0664,dir_mode=0775,iocharset=utf8, credentials=/etc/samba/credentials/_.cred"`} />
  • {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, auth, share     │
│  (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 Samba client packages (in CT)
      └─ pct exec apt-get install -y \\
                   cifs-utils smbclient
         (skipped if already installed)
         Verifies: smbclient + mount.cifs both present
         Creates /etc/samba/credentials (mode 0700)
                   │
                   ▼
      Server selection (3 modes)
      ├─ Auto-discover (nmap from HOST on /24,
      │    ports 139/445, then nmblookup -A
      │    for NetBIOS names → "NETBIOS (ip)")
      ├─ Manual: type IP or hostname
      └─ Recent: parses /etc/fstab for previously
         used CIFS servers (one-click selection)
                   │
                   ▼
      Authentication (2 modes)
      ├─ User + password
      │   ├─ Username (whiptail inputbox)
      │   ├─ Password (passwordbox, hidden)
      │   ├─ Confirm password
      │   └─ ACTIVE VALIDATION against the server:
      │       creates a temp credentials file,
      │       runs smbclient -L with -A,
      │       distinguishes "guest fallback" from
      │       real auth success, retries on failure
      └─ Guest: validate guest access first
         (smbclient -L -N must succeed)
                   │
                   ▼
      Share selection
      ├─ Server returns shares → menu
      │   (filters out IPC$, ADMIN$, print$;
      │    for guest: only shares the user
      │    confirmed accessible during validation)
      └─ No shares / blocked → manual input
                   │
                   ▼
      Validate the chosen share still exists
                   │
                   ▼
      Mount-point picker (3 options)
      ├─ 1. Create new folder in /mnt
      │      (default: same name as the share)
      ├─ 2. Select existing folder in /mnt
      └─ 3. Enter custom path
                   │
                   ▼
      Mount-options preset (3 options)
      ├─ 1. Read/write
      │      rw,file_mode=0664,dir_mode=0775,
      │      iocharset=utf8
      ├─ 2. Read-only
      │      ro,file_mode=0444,dir_mode=0555,
      │      iocharset=utf8
      └─ 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
                                       ▼
                       For user auth: write credentials file
                       /etc/samba/credentials/_.cred
                       (root:0600 inside the CT)
                                       ▼
                       pct exec mount -t cifs \\
                            ///  \\
                            -o ,credentials=
                       (or  -o ,guest  for guest)
                                       ▼
                       Smoke test: write a 0-byte file
                       and delete it (.test_write)
                       └─ no write access → "read-only"
                                       ▼
                       If "permanent" was chosen:
                       └─ Append to /etc/fstab inside CT:
                            ///    cifs \\
                              ,credentials=…,
                              _netdev,
                              x-systemd.automount,noauto  0 0
                       (any prior entry for this mp is removed first)
                                       ▼
                       Print summary (server / share / mp /
                       auth mode / permanent yes-no)`}
      

{t("creds.heading")}

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

{`Path:    /etc/samba/credentials/_.cred
Owner:   root
Mode:    0600

Content:
    username=
    password=

Reference in /etc/fstab:
    ///  /mnt/  cifs  rw,...,
        credentials=/etc/samba/credentials/_.cred,
        _netdev,x-systemd.automount,noauto  0  0`}
      
{t.rich("creds.whyBody", { code })}

{t("options.heading")}

{optionRows.map((row) => ( ))}
{t("options.headerOption")} {t("options.headerEffect")}
{row.option} {row.effect}
{t.rich("options.netEffectBody", { em })}

{t("manual.heading")}

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

/etc/samba/credentials/10.0.0.50_share.cred <> /etc/fstab <

{t("manual.guestIntro")}

> /etc/fstab`} />

{t("view.heading")}

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

{t("unmount.heading")}

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

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

{t("test.heading")}

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

{t("troubleshoot.heading")}

{t.rich("troubleshoot.privBody", { code })} {t.rich("troubleshoot.aptIntro", { code })}
    {aptItems.map((_, idx) => (
  • {t.rich(`troubleshoot.aptItems.${idx}`, { code })}
  • ))}
{t("troubleshoot.aptOutro")}
{t("troubleshoot.guestFallbackBody")} {t.rich("troubleshoot.denyBody", { code })} {t.rich("troubleshoot.utf8Body", { code })} {t.rich("troubleshoot.bootBody", { code })}

{t("related.heading")}

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