Skip to content

Instantly share code, notes, and snippets.

@NorkzYT
Last active August 22, 2025 20:07
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 one or more unprivileged LXC containers in one pass.
It automates:

  1. Creating a host-side mountpoint under /mnt/lxc_shares/<folder>.
  2. Writing a properly-tuned /etc/fstab entry (systemd.automount, uid/gid mapping, etc.).
  3. Mounting the share immediately.
  4. Injecting the bind-mount (mpX:) into the live LXC configuration with pct set.
  5. Stopping and restarting each container to apply the mount.

Note: target containers must be running when you start the script. It will stop and restart them briefly.


Usage

proxmox-lxc-cifs-share.sh [OPTIONS]

OPTIONS

  • --config FILE Read all parameters from FILE (no prompts).

  • --add Skip host-side mount; only bind the existing share into containers.

  • --containers IDS Comma-separated LXC IDs (e.g. 105,106).

  • --users NAMES Comma-separated container usernames (must match the count and order of --containers).

  • --noserverino Disable the CIFS serverino option (fixes “Stale file handle” on some NAS).

  • --help Show usage and exit.


Quick-start

# 1. Download and make executable
curl -fsSL -o proxmox-lxc-cifs-share.sh \
  https://…/proxmox-lxc-cifs-share.sh
chmod +x proxmox-lxc-cifs-share.sh

# 2a. Interactive setup (prompts for all values)
sudo ./proxmox-lxc-cifs-share.sh

# 2b. Bind into two containers without touching /etc/fstab
sudo ./proxmox-lxc-cifs-share.sh \
  --add \
  --containers 105,106 \
  --users ubuntu,svcuser

# 2c. From a config file, bind into one container
sudo ./proxmox-lxc-cifs-share.sh \
  --config myshare.conf \
  --add

Config-file format

Save your settings in myshare.conf:

folder_name=nas_rwx
cifs_host=10.0.0.2
share_name=media
smb_username=myuser
smb_password=mypass
containers=105,106
users=ubuntu,svcuser

Then run:

sudo ./proxmox-lxc-cifs-share.sh --config myshare.conf --add

Prerequisites

Item Notes
Proxmox VE node Tested on Proxmox VE 7–8 (Debian 12+). Run on the host.
Unprivileged LXC container(s) Must exist and be running.
CIFS/SMB share Hostname/IP, share name, valid credentials.
Root privileges Use sudo or run as root.
cifs-utils Install with apt update && apt install cifs-utils.

Once complete, verify inside each container:

pct exec <ID> -- ls -ld /mnt/<folder_name>

1) Initial interactive run

root@proxmox-node:/opt# chmod +x proxmox-lxc-cifs-share.sh
root@proxmox-node:/opt# ./proxmox-lxc-cifs-share.sh
=== Share Settings ===
Folder under /mnt/lxc_shares (e.g. nas_rwx): appdata
CIFS host (IP/DNS): 10.0.0.2
Share name: appdata
SMB username: xxx
SMB password: xxx

Available LXC IDs:
102
103
104
105
106
107
108
109
110
111
LXC IDs (comma-separated): 103

Usernames in 103:
root
daemon
bin
sys
sync
games
man
lp
mail
news
uucp
proxy
www-data
backup
list
irc
gnats
nobody
messagebus
syslog
postfix
_apt
sshd
systemd-network
systemd-resolve
systemd-timesync
uuidd
tcpdump
systemd-coredump
ubuntu
root
nobody
Usernames (comma-separated): ubuntu

Generate config file? [y/N]: y
Config path [./share.conf]: ./
ERROR: './' is a directory; please specify a file name.
Config path [./share.conf]: ./share.conf
Config saved to ./share.conf
Host mount exists; skip host-side work? [Y/n]: y
Stopping LXC 103…
Binding into 103 → /mnt/appdata
Starting LXC 103…

Verification:
  ↳ 103: OK

✅  All done.

2) Interactive “add” mode

root@proxmox-node:/opt# ./proxmox-lxc-cifs-share.sh \
  --add \
  --containers 103 \
  --users ubuntu
=== Share Settings ===
Folder under /mnt/lxc_shares (e.g. nas_rwx): appdata
CIFS host (IP/DNS): 10.0.0.2
Share name: appdata
SMB username: xxx
SMB password: xxx

Generate config file? [y/N]: n
Stopping LXC 103…
Binding into 103 → /mnt/appdata
Starting LXC 103…

Verification:
  ↳ 103: OK

✅  All done.

3) Config-file + “add” mode

root@proxmox-node:/opt# ./proxmox-lxc-cifs-share.sh \
  --config share.conf \
  --add

Stopping LXC 103…
Binding into 103 → /mnt/appdata
Starting LXC 103…

Verification:
  ↳ 103: OK

✅  All done.
#!/usr/bin/env bash
# proxmox-lxc-cifs-share.sh
#
# Mount a CIFS/SMB share on a Proxmox VE host and bind-mount it into
# one or more unprivileged LXC containers with dynamic UID/GID mapping.
#
# Usage:
# proxmox-lxc-cifs-share.sh [--config FILE] [--add]
# [--containers IDS] [--users NAMES] [--noserverino] [--help]
#
# Compatible with Proxmox VE 7–8 on Debian 12+.
set -euo pipefail
# ──────────────────────────────────────────────────────────────────────────────
# Helpers
# ──────────────────────────────────────────────────────────────────────────────
error_exit() {
echo "ERROR: $1" >&2
exit 1
}
show_help() {
cat <<EOF
Usage: $0 [OPTIONS]
Options:
--config FILE Read all parameters from FILE.
--add Only bind into containers; skip host mount.
--containers IDS Comma-separated LXC IDs (e.g. 105,106).
--users NAMES Comma-separated usernames matching each ID.
--noserverino Disable CIFS “serverino” option.
--help Show this help and exit.
Examples:
# Interactive:
sudo $0
# Bind into two containers:
sudo $0 --add --containers 105,106 --users ubuntu,svcuser
# From config file:
sudo $0 --config myshare.conf --add
EOF
exit 0
}
list_lxc_ids() {
pct list | awk 'NR>1 {print $1}'
}
list_usernames() {
local id=$1
pct status "$id" &>/dev/null || error_exit "LXC $id not found"
pct exec "$id" -- getent passwd | cut -d: -f1
}
validate_prerequisites() {
(( EUID == 0 )) || error_exit "Run as root or via sudo"
command -v pct >/dev/null 2>&1 || error_exit "'pct' not found"
command -v mount.cifs >/dev/null 2>&1 || error_exit "Install cifs-utils"
command -v systemd-escape \
>/dev/null 2>&1 || error_exit "Install systemd"
}
load_config() {
[[ -f $config_file ]] || error_exit "Config file '$config_file' missing"
source "$config_file"
}
prompt_share_settings() {
echo "=== Share Settings ==="
read -rp "Folder under /mnt/lxc_shares (e.g. nas_rwx): " folder_name
read -rp "CIFS host (IP/DNS): " cifs_host
read -rp "Share name: " share_name
read -rp "SMB username: " smb_username
read -rs -p "SMB password: " smb_password
echo
}
prompt_containers_and_users() {
echo "Available LXC IDs:"
list_lxc_ids
read -rp "LXC IDs (comma-separated): " containers
echo
echo "Usernames in ${containers%%,*}:"
list_usernames "${containers%%,*}"
read -rp "Usernames (comma-separated): " users
echo
}
prompt_generate_config() {
read -rp "Generate config file? [y/N]: " gen
if [[ $gen =~ ^[Yy]$ ]]; then
while true; do
read -rp "Config path [./share.conf]: " cfg
cfg=${cfg:-./share.conf}
if [[ -d $cfg ]]; then
echo "ERROR: '$cfg' is a directory; please specify a file name."
continue
fi
break
done
{
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 "containers=$containers"
echo "users=$users"
} >"$cfg"
echo "Config saved to $cfg"
fi
}
parse_flags() {
NOSERVERINO=0
ADD_MODE=0
config_file=""
containers=""
users=""
while [[ $# -gt 0 ]]; do
case $1 in
--noserverino) NOSERVERINO=1; shift ;;
--add) ADD_MODE=1; shift ;;
--config) config_file=$2; shift 2 ;;
--containers) containers=$2; shift 2 ;;
--users) users=$2; shift 2 ;;
--help) show_help ;;
*) error_exit "Unknown option: $1" ;;
esac
done
}
parse_lists() {
IFS=, read -ra CTIDS <<<"$containers"
IFS=, read -ra USERS <<<"$users"
(( ${#CTIDS[@]} == ${#USERS[@]} )) \
|| error_exit "Count of containers != count of users"
}
# ──────────────────────────────────────────────────────────────────────────────
# Main
# ──────────────────────────────────────────────────────────────────────────────
validate_prerequisites
parse_flags "$@"
if [[ -n $config_file ]]; then
load_config
else
prompt_share_settings
[[ -n $containers && -n $users ]] \
|| prompt_containers_and_users
prompt_generate_config
fi
parse_lists
# Primary container for UID/GID mapping
primary_id="${CTIDS[0]}"
primary_user="${USERS[0]}"
pct status "$primary_id" &>/dev/null || error_exit "LXC $primary_id not found"
container_uid=$(pct exec "$primary_id" -- id -u "$primary_user" 2>/dev/null) \
|| error_exit "User $primary_user not in $primary_id"
container_gid=$(pct exec "$primary_id" -- id -g "$primary_user")
idmap_offset=$(pct config "$primary_id" | awk '/^lxc.idmap: u 0 /{print $4; exit}')
idmap_offset=${idmap_offset:-100000}
host_uid=$(( idmap_offset + container_uid ))
host_gid=$(( idmap_offset + container_gid ))
mnt_root="/mnt/lxc_shares/${folder_name}"
if (( ADD_MODE == 0 )); then
ensure_mount() {
mkdir -p "$mnt_root"
opts="_netdev,x-systemd.automount,noatime,nobrl"
opts+=",uid=${host_uid},gid=${host_gid},dir_mode=0770,file_mode=0770"
opts+=",username=${smb_username},password=${smb_password},iocharset=utf8"
(( NOSERVERINO )) && opts+=",noserverino"
entry="//${cifs_host}/${share_name} ${mnt_root} cifs ${opts} 0 0"
grep -q "^//${cifs_host}/${share_name} ${mnt_root} " /etc/fstab \
&& sed -i "\|^//${cifs_host}/${share_name} ${mnt_root} .*|d" /etc/fstab
echo "$entry" >>/etc/fstab
systemctl daemon-reload
base=$(systemd-escape --path "$mnt_root")
systemctl stop "${base}.automount" "${base}.mount" >/dev/null 2>&1 || true
mount "$mnt_root"
}
if grep -q "^//${cifs_host}/${share_name} ${mnt_root} " /etc/fstab; then
read -rp "Host mount exists; skip host-side work? [Y/n]: " yn
[[ $yn =~ ^[Nn]$ ]] && ensure_mount
else
ensure_mount
fi
fi
for i in "${!CTIDS[@]}"; do
id=${CTIDS[i]}
echo "Stopping LXC $id…"
pct stop "$id"
while [[ $(pct status "$id") != "status: stopped" ]]; do sleep 1; done
echo "Binding into $id → /mnt/$folder_name"
pct set "$id" --mp0 "${mnt_root},mp=/mnt/${folder_name},backup=0"
echo "Starting LXC $id…"
pct start "$id"
done
echo
echo "Verification:"
for id in "${CTIDS[@]}"; do
if pct exec "$id" -- test -d "/mnt/${folder_name}"; then
echo " ↳ $id: OK"
else
echo " ↳ $id: FAILED"
fi
done
echo
echo "✅ All done."
@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.

@NorkzYT
Copy link
Author

NorkzYT commented Jul 6, 2025

All Gist files have been updated.

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