Skip to content

Instantly share code, notes, and snippets.

@NorkzYT
Last active July 1, 2025 18:26
Show Gist options
  • Save NorkzYT/14449b247dae9ac81ba4664564669299 to your computer and use it in GitHub Desktop.
Save NorkzYT/14449b247dae9ac81ba4664564669299 to your computer and use it in GitHub Desktop.
Proxmox CIFS Share Mount Wizard Script

Proxmox LXC ⇆ CIFS Mount Wizard

proxmox-lxc-cifs-share.sh is an interactive helper that mounts a network CIFS/SMB share on a Proxmox VE node and bind-mounts it into an unprivileged LXC container in one pass.
It automates:

  1. Creating a host-side mountpoint under /mnt/lxc_shares/<folder>.
  2. Writing a properly-tuned /etc/fstab entry (persistent, systemd.automount, uid/gid mapping, etc.).
  3. Mounting the share immediately.
  4. Creating/ensuring a lxc_shares group inside the container (GID 10000) and adding your target user.
  5. Injecting the bind-mount (mpX:) into the live LXC configuration with pct set.
  6. Restarting the container and handing you a ready-to-use path at /mnt/<folder>.

Note: the script assumes the target LXC is currently running. It will stop and restart that container briefly as part of its process.


Features

  • Idempotent — safe to re-run; existing fstab lines, groups or mp-entries are skipped.
  • Snapshot-safe — uses pct set, thus bind-mounts are applied to the active config, not stale snapshots.
  • Clean dependency checks — aborts if pct or mount.cifs are missing or you are not on a Proxmox host.
  • Diagnostic output — numbered steps tell you exactly what happens and where (mp0, mp1, …).
  • Optional --noserverino flag to fix “Stale file handle” errors on Synology/TrueNAS shares.

Prerequisites

Item Notes
Proxmox VE node Tested on Proxmox VE 8 / Debian 12 host. Run on the node, not inside a container.
Unprivileged LXC container Must already exist. The script stops and restarts it briefly.
CIFS/SMB share Hostname/IP, share name, and valid credentials.
Root privileges Use sudo or run as root.
cifs-utils package apt update && apt install cifs-utils (usually present on Proxmox).

Quick-start

# 1. Get the script
curl -o proxmox-lxc-cifs-share.sh https://gist.githubusercontent.com/NorkzYT/14449b247dae9ac81ba4664564669299/raw/66b73972f7054a13fb5fb2a7646f7bb004827763/proxmox-lxc-cifs-share.sh
chmod +x proxmox-lxc-cifs-share.sh

# 2. Run it (on the Proxmox node)
sudo ./proxmox-lxc-cifs-share.sh
root@proxmox-node:/opt# chmod +x proxmox-lxc-cifs-share.sh
root@proxmox-node:/opt# ./proxmox-lxc-cifs-share.sh 
=== Proxmox CIFS Share Mount Wizard ===
1) Folder name under /mnt/lxc_shares (e.g. nas_rwx): appdata
2) CIFS host (IP/DNS, e.g. 10.0.0.2): 10.0.0.2
3) Share name (e.g. media): appdata
4) SMB username: xxx
5) SMB password: xxx
6) LXC numeric ID (e.g. 105): 103
7) Container username (e.g. ubuntu): ubuntu

Validating container 103...
Retrieving UID/GID for 'ubuntu' inside LXC 103...
  ↳ container UID=1000, GID=1000
Parsing idmap offset from container config...
  ↳ Warning: idmap offset not found; defaulting to 100000
  ↳ host idmap offset=100000
  ↳ will mount with host UID=101000, GID=101000
Stopping LXC 103...
Ensuring host mount point exists at /mnt/lxc_shares/appdata...
Building fstab entry...
  ↳ Removing old /etc/fstab entry...
  ↳ Adding new /etc/fstab entry...
Reloading systemd daemon...
Stopping potential systemd units mnt-lxc_shares-appdata.automount & mnt-lxc_shares-appdata.mount...
Mounting //10.0.0.2/appdata → /mnt/lxc_shares/appdata...
Configuring bind-mount into LXC (no backups)...
  ↳ Bind-mount set as mp0 → /mnt/appdata
Starting LXC 103...

✅  Configuration complete!
Inspect inside the container with:
    ls -ld /mnt/appdata
#!/usr/bin/env bash
# proxmox-lxc-cifs-share.sh
# https://gist.github.com/NorkzYT/14449b247dae9ac81ba4664564669299
#
# Mount a CIFS/SMB share on a Proxmox VE host and bind-mount it into
# an unprivileged LXC container as any container user, by dynamically
# mapping UIDs/GIDs.
#
# ---------------------------------------------------------------
# Compatible with Proxmox VE 7-8. Tested on Debian 12 host.
# ---------------------------------------------------------------
set -euo pipefail
#
#----------------------#
#----- Preconditions --#
#----------------------#
#
# Must be root
if [[ $EUID -ne 0 ]]; then
echo "ERROR: Please run as root." >&2
exit 1
fi
# Helper for error + exit
error_exit() {
echo "ERROR: $1" >&2
exit 1
}
# Ensure required commands exist
command -v bash >/dev/null 2>&1 || error_exit "Please run with bash: bash $0"
command -v pct >/dev/null 2>&1 || error_exit "'pct' command not found. Run this on the Proxmox host."
command -v mount.cifs >/dev/null 2>&1 || error_exit "cifs-utils missing. Install: apt update && apt install cifs-utils"
command -v systemd-escape >/dev/null 2>&1 || error_exit "systemd-escape missing. Required for systemd unit management."
#
#----------------------#
#----- Parse Flags ----#
#----------------------#
#
NOSERVERINO=0
if [[ "${1:-}" == "--noserverino" ]]; then
NOSERVERINO=1
shift
fi
#
#----------------------#
#----- User Input -----#
#----------------------#
#
echo "=== Proxmox CIFS Share Mount Wizard ==="
read -r -p "1) Folder name under /mnt/lxc_shares (e.g. nas_rwx): " folder_name
read -r -p "2) CIFS host (IP/DNS, e.g. 10.0.0.2): " cifs_host
read -r -p "3) Share name (e.g. media): " share_name
read -r -p "4) SMB username: " smb_username
read -s -p "5) SMB password: " smb_password && echo
read -r -p "6) LXC numeric ID (e.g. 105): " lxc_id
read -r -p "7) Container username (e.g. ubuntu): " lxc_username
echo
#
#----------------------#
#----- Validation -----#
#----------------------#
#
echo "Validating container ${lxc_id}..."
pct status "$lxc_id" &>/dev/null \
|| error_exit "LXC ${lxc_id} does not exist."
echo "Retrieving UID/GID for '${lxc_username}' inside LXC ${lxc_id}..."
if ! container_uid=$(pct exec "$lxc_id" -- id -u "$lxc_username" 2>/dev/null); then
error_exit "User '${lxc_username}' not found in container ${lxc_id}."
fi
container_gid=$(pct exec "$lxc_id" -- id -g "$lxc_username")
echo " ↳ container UID=${container_uid}, GID=${container_gid}"
echo "Parsing idmap offset from container config..."
idmap_offset=$(pct config "$lxc_id" \
| awk '/^lxc.idmap: u 0 /{print $4; exit}')
if [[ -z "$idmap_offset" ]]; then
echo " ↳ Warning: idmap offset not found; defaulting to 100000"
idmap_offset=100000
fi
echo " ↳ host idmap offset=${idmap_offset}"
host_uid=$(( idmap_offset + container_uid ))
host_gid=$(( idmap_offset + container_gid ))
echo " ↳ will mount with host UID=${host_uid}, GID=${host_gid}"
#
#----------------------#
#--- Stop Container ----#
#----------------------#
#
echo "Stopping LXC ${lxc_id}..."
pct stop "$lxc_id"
while [[ "$(pct status "$lxc_id")" != "status: stopped" ]]; do
sleep 1
done
#
#----------------------#
#--- Host-side Mount --#
#----------------------#
#
mnt_root="/mnt/lxc_shares/${folder_name}"
echo "Ensuring host mount point exists at ${mnt_root}..."
mkdir -p "$mnt_root"
echo "Building fstab entry..."
options="_netdev,x-systemd.automount,noatime,nobrl"
options+=",uid=${host_uid},gid=${host_gid},dir_mode=0770,file_mode=0770"
options+=",username=${smb_username},password=${smb_password}"
(( NOSERVERINO )) && options+=",noserverino"
fstab_entry="//${cifs_host}/${share_name} ${mnt_root} cifs ${options} 0 0"
# Remove any stale entry
if grep -Eqs "^//${cifs_host}/${share_name}[[:space:]]+${mnt_root}[[:space:]]+cifs" /etc/fstab; then
echo " ↳ Removing old /etc/fstab entry..."
sed -i "\|^//${cifs_host}/${share_name} ${mnt_root} .*|d" /etc/fstab
fi
echo " ↳ Adding new /etc/fstab entry..."
echo "$fstab_entry" >> /etc/fstab
echo "Reloading systemd daemon..."
systemctl daemon-reload
unit_base=$(systemd-escape --path "$mnt_root")
echo "Stopping potential systemd units ${unit_base}.automount & ${unit_base}.mount..."
systemctl stop "${unit_base}.automount" "${unit_base}.mount" >/dev/null 2>&1 || true
if mountpoint -q "$mnt_root"; then
echo " ↳ Unmounting existing mount..."
umount -l "$mnt_root"
fi
echo "Mounting //${cifs_host}/${share_name}${mnt_root}..."
mount "$mnt_root"
#
#----------------------#
#--- Container Bind ----#
#----------------------#
#
echo "Configuring bind-mount into LXC (no backups)..."
pct set "$lxc_id" \
--mp0 "${mnt_root},mp=/mnt/${folder_name},backup=0"
echo " ↳ Bind-mount set as mp0 → /mnt/${folder_name}"
echo "Starting LXC ${lxc_id}..."
pct start "$lxc_id"
#
#----------------------#
#----- Completion ------#
#----------------------#
#
echo -e "\n✅ Configuration complete!"
echo "Inspect inside the container with:"
echo " ls -ld /mnt/${folder_name}"
@neil-bh
Copy link

neil-bh commented Jun 27, 2025

Thanks @NorkzYT - I can confirm I can now configure my docker containers to write to a folder that resides on the share using the UID=10000 and GID=10000 (which I believe is the equivalent of UID 1000 and GID 1000 (myuser1:myuser1) (?)

@NorkzYT
Copy link
Author

NorkzYT commented Jun 28, 2025

@neil-bh

That is exactly what you should be seeing — UID/GID 10000 on the host corresponds to UID/GID 1000 inside your unprivileged LXC.

@rfResearch
Copy link

Hi @NorkzYT - Very useful script, thank you for sharing. I've added a new function to generate a config file for the user inputs and an additional function to take the config file as input. I've also added a fonction to list the LXC IDs and the LXC usernames in the user input section, and a completion check.
I've noticed that the LXC container must be started before the execution of the script in order to work.

#!/usr/bin/env bash
# proxmox-lxc-cifs-share.sh
# https://gist.github.com/NorkzYT/14449b247dae9ac81ba4664564669299
#
# Mount a CIFS/SMB share on a Proxmox VE host and bind-mount it into
# an unprivileged LXC container as any container user, by dynamically
# mapping UIDs/GIDs.
#
# ---------------------------------------------------------------
# Compatible with Proxmox VE 7-8.  Tested on Debian 12 host.
# ---------------------------------------------------------------

set -euo pipefail

#
#----------------------#
#----- Preconditions --#
#----------------------#
#

# Must be root
if [[ $EUID -ne 0 ]]; then
  echo "ERROR: Please run as root." >&2
  exit 1
fi

# Helper for error + exit
error_exit() {
  echo "ERROR: $1" >&2
  exit 1
}

# Function to display help
show_help() {
  echo "Usage: $0 [OPTIONS]"
  echo
  echo "Mount a CIFS/SMB share on a Proxmox VE host and bind-mount it into an unprivileged LXC container."
  echo
  echo "Options:"
  echo "  --config FILE      Specify a configuration file with parameters."
  echo "  --noserverino      Disable serverino option for CIFS mount."
  echo "  --help             Display this help message."
  echo
  echo "Configuration File Format:"
  echo "The configuration file should contain the following parameters:"
  echo "  folder_name=your_folder_name"
  echo "  cifs_host=your_cifs_host_ip_or_dns"
  echo "  share_name=your_share_name"
  echo "  smb_username=your_smb_username"
  echo "  smb_password=your_smb_password"
  echo "  lxc_id=your_lxc_numeric_id"
  echo "  lxc_username=your_container_username"
  echo
  echo "Example configuration file:"
  echo "folder_name=nas_rwx"
  echo "cifs_host=10.0.0.2"
  echo "share_name=media"
  echo "smb_username=your_username"
  echo "smb_password=your_password"
  echo "lxc_id=105"
  echo "lxc_username=ubuntu"
  exit 0
}

# Function to list LXC container IDs
list_lxc_ids() {
  echo "Available LXC container IDs:"
  pct list | awk 'NR>1 {print $1}'  # Ignore the header and print only the IDs
}

# Function to list usernames in the LXC container
list_usernames() {
  local lxc_id="$1"
  echo "Validating container ${lxc_id}..."
  pct status "$lxc_id" &>/dev/null \
    || error_exit "LXC ${lxc_id} does not exist."
  echo "Available usernames in LXC container ${lxc_id}:"
  pct exec "$lxc_id" -- getent passwd | awk -F: '{print $1}'  # Print only the usernames
}

# Ensure required commands exist
command -v bash         >/dev/null 2>&1 || error_exit "Please run with bash: bash $0"
command -v pct          >/dev/null 2>&1 || error_exit "'pct' command not found. Run this on the Proxmox host."
command -v mount.cifs   >/dev/null 2>&1 || error_exit "cifs-utils missing. Install: apt update && apt install cifs-utils"
command -v systemd-escape >/dev/null 2>&1 || error_exit "systemd-escape missing. Required for systemd unit management."

#
#----------------------#
#----- Parse Flags ----#
#----------------------#
#

NOSERVERINO=0
config_file=""

# Parse command line arguments
while [[ $# -gt 0 ]]; do
  case $1 in
    --noserverino)
      NOSERVERINO=1
      shift
      ;;
    --config)
      config_file="$2"
      shift 2
      ;;
    --help)
      show_help
      ;;
    *)
      echo "ERROR: Unknown option $1" >&2
      exit 1
      ;;
  esac
done


#
#----------------------#
#----- User Input -----#
#----------------------#
#
if [[ -n "$config_file" ]]; then
  # Read parameters from the configuration file
  if [[ ! -f "$config_file" ]]; then
    error_exit "Configuration file '$config_file' not found."
  fi
  source "$config_file"
else
  # Ask parameters to the user
  echo "=== Proxmox CIFS Share Mount Wizard ==="
  read -r -p "1) Folder name under /mnt/lxc_shares (e.g. nas_rwx): " folder_name
  read -r -p "2) CIFS host (IP/DNS, e.g. 10.0.0.2): "     cifs_host
  read -r -p "3) Share name (e.g. media): "             share_name
  read -r -p "4) SMB username: "                       smb_username
  read -s -p "5) SMB password: "                       smb_password && echo

  # List available LXC container IDs
  list_lxc_ids
  read -r -p "6) LXC numeric ID (e.g. 105): "           lxc_id

  # List available usernames in the selected LXC container
  list_usernames "$lxc_id" 
 read -r -p "7) Container username (e.g. ubuntu): "   lxc_username
  echo

  # Set default configuration file path
  default_config_file_path="./proxmox-lxc-cifs-share.conf"
  config_file_path="$default_config_file_path"

  # Ask if the user wants to generate a configuration file
  read -r -p "Do you want to generate a configuration file? (y/n): " generate_config

  if [[ "$generate_config" == "y" || "$generate_config" == "Y" ]]; then
    read -r -p "Enter the path for the configuration file [default: $default_config_file_path]: " user_input

    # Use default path if no input is provided
    if [[ -n "$user_input" ]]; then
      config_file_path="$user_input"
    fi

    # Create the configuration file and write parameters to it
    {
      echo "folder_name=${folder_name}"
      echo "cifs_host=${cifs_host}"
      echo "share_name=${share_name}"
      echo "smb_username=${smb_username}"
      echo "smb_password=${smb_password}"
      echo "lxc_id=${lxc_id}"
      echo "lxc_username=${lxc_username}"
    } > "$config_file_path"

    echo "Configuration file created at: $config_file_path"
  fi
fi

#
#----------------------#
#----- Validation -----#
#----------------------#
#

echo "Validating container ${lxc_id}..."
pct status "$lxc_id" &>/dev/null \
  || error_exit "LXC ${lxc_id} does not exist."

echo "Retrieving UID/GID for '${lxc_username}' inside LXC ${lxc_id}..."
if ! container_uid=$(pct exec "$lxc_id" -- id -u "$lxc_username" 2>/dev/null); then
  error_exit "User '${lxc_username}' not found in container ${lxc_id}."
fi
container_gid=$(pct exec "$lxc_id" -- id -g "$lxc_username")
echo "  ↳ container UID=${container_uid}, GID=${container_gid}"

echo "Parsing idmap offset from container config..."
idmap_offset=$(pct config "$lxc_id" \
  | awk '/^lxc.idmap: u 0 /{print $4; exit}')
if [[ -z "$idmap_offset" ]]; then
  echo "  ↳ Warning: idmap offset not found; defaulting to 100000"
  idmap_offset=100000
fi
echo "  ↳ host idmap offset=${idmap_offset}"

host_uid=$(( idmap_offset + container_uid ))
host_gid=$(( idmap_offset + container_gid ))
echo "  ↳ will mount with host UID=${host_uid}, GID=${host_gid}"

#
#----------------------#
#--- Stop Container ----#
#----------------------#
#

echo "Stopping LXC ${lxc_id}..."
pct stop "$lxc_id"
while [[ "$(pct status "$lxc_id")" != "status: stopped" ]]; do
  sleep 1
done

#
#----------------------#
#--- Host-side Mount --#
#----------------------#
#

mnt_root="/mnt/lxc_shares/${folder_name}"
echo "Ensuring host mount point exists at ${mnt_root}..."
mkdir -p "$mnt_root"

echo "Building fstab entry..."
options="_netdev,x-systemd.automount,noatime,nobrl"
options+=",uid=${host_uid},gid=${host_gid},dir_mode=0770,file_mode=0770"
options+=",username=${smb_username},password=${smb_password}"
options+=",iocharset=utf8"  # Add option iocharset=utf8
(( NOSERVERINO )) && options+=",noserverino"

fstab_entry="//${cifs_host}/${share_name} ${mnt_root} cifs ${options} 0 0"

# Remove any stale entry
if grep -Eqs "^//${cifs_host}/${share_name}[[:space:]]+${mnt_root}[[:space:]]+cifs" /etc/fstab; then
  echo "  ↳ Removing old /etc/fstab entry..."
  sed -i "\|^//${cifs_host}/${share_name} ${mnt_root} .*|d" /etc/fstab
fi

echo "  ↳ Adding new /etc/fstab entry..."
echo "$fstab_entry" >> /etc/fstab

echo "Reloading systemd daemon..."
systemctl daemon-reload

unit_base=$(systemd-escape --path "$mnt_root")
echo "Stopping potential systemd units ${unit_base}.automount & ${unit_base}.mount..."
systemctl stop "${unit_base}.automount" "${unit_base}.mount" >/dev/null 2>&1 || true

if mountpoint -q "$mnt_root"; then
  echo "  ↳ Unmounting existing mount..."
  umount -l "$mnt_root"
fi

echo "Mounting //${cifs_host}/${share_name} → ${mnt_root}..."
mount "$mnt_root"

#
#----------------------#
#--- Container Bind ----#
#----------------------#
#

echo "Configuring bind-mount into LXC (no backups)..."
pct set "$lxc_id" \
  --mp0 "${mnt_root},mp=/mnt/${folder_name},backup=0"
echo "  ↳ Bind-mount set as mp0 → /mnt/${folder_name}"

echo "Starting LXC ${lxc_id}..."
pct start "$lxc_id"

#
#----------------------#
#--- Access Validation --#
#----------------------#
#

echo "Checking access to the mount from container ${lxc_id}..."
if pct exec "$lxc_id" -- test -d "/mnt/${folder_name}"; then
  echo "  ↳ Access to the mount confirmed."
  
  # Display access rights
  echo "Access rights of the share:"
  pct exec "$lxc_id" -- ls -ld "/mnt/${folder_name}"
else
  error_exit "ERROR: The mount is not accessible from container ${lxc_id}."
fi

#
#----------------------#
#----- Completion ------#
#----------------------#
#

echo -e "\n✅  Configuration complete!"
echo "Inspect inside the container with:"
echo "    ls -ld /mnt/${folder_name}"

@neil-bh
Copy link

neil-bh commented Jul 1, 2025

That suggestion from @rfResearch looks great! If @NorkzYT decides to merge it could I also make a request based on a common use case...

When running the script for the first time, all is great and I end up with CIFS share accessible in my unprivileged LXC container . But then I decide I would like the CIFS share available in another of my unprivileged LXC containers - so I'd run the script again. I am prompted to create the mount again on the host (although it already exists) - I can see from the script that it will remove the existing line in fstab if it already exists, so maybe that's good enough? Or perhaps confirm with the user that the share already exists: "would you like to skip the host mount?"... or... maybe pass in a parameter for executing in this use case to additional LXC containers, e.g. bash ./proxmox-lxc-cifs-share.sh add? Or we could simply even use the config file approach with all the values populated to simplify adding the share to more LXC containers?

Another idea here... Is it possible to provide more than 1 user when prompted at step 7 for the lxc username? (Update: I noticed in @rfResearch's version they are not using lxc_shares as a group, so it's using username:username which I believe is the typical way to do it, and therefore probably negates my idea of being able to add more than one user to the share - after all, I cant think of a situation where i'd need multiple different user accounts to have individual access the share)

@NorkzYT
Copy link
Author

NorkzYT commented Jul 1, 2025

@rfResearch @neil-bh

Thank you for all the information. I will revise the script with your recommendations this weekend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment