Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Multi-Samba User Mode #14

Merged
merged 12 commits into from
Oct 9, 2024
43 changes: 32 additions & 11 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,45 @@ docker run -it --rm -p 445:445 -e "USER=samba" -e "PASS=secret" -v "/home/exampl

## Configuration ⚙️

* ### How do I modify the credentials?
### How do I modify the credentials?

You can set the `USER` and `PASS` environment variables to modify the credentials from their default values: user `samba` with password `secret`.
You can set the `USER` and `PASS` environment variables to modify the credentials from their default values: user `samba` with password `secret`.

* ### How do I modify the permissions?
### How do I modify the permissions?

You can set `UID` and `GID` environment variables to change the user and group ID.
You can set `UID` and `GID` environment variables to change the user and group ID.

To mark the share as read-only, add the variable `RW: false`.
To mark the share as read-only, add the variable `RW: false`.

* ### How do I modify other settings?
### How do I modify other settings?

If you need more advanced features, you can completely override the default configuration by modifying the [smb.conf](https://github.com/dockur/samba/blob/master/smb.conf) file in this repo, and binding your custom config to the container like this:
If you need more advanced features, you can completely override the default configuration by modifying the [smb.conf](https://github.com/dockur/samba/blob/master/smb.conf) file in this repo, and binding your custom config to the container like this:

```yaml
volumes:
- /example/smb.conf:/etc/samba/smb.conf
```
```yaml
volumes:
- /example/smb.conf:/etc/samba/smb.conf
```

### How do I use multiple users?

If you want to use multiple Samba users, you can enable multi-user mode by binding the [smb_user.conf](https://github.com/dockur/samba/blob/master/smb_user.conf) file to the container as follows:

```yaml
volumes:
- /example/smb.conf:/etc/samba/smb.conf
- /example/smb_user.conf:/etc/samba/smb_user.conf
```

You can also modify the [smb.conf](https://github.com/dockur/samba/blob/master/smb.conf) to implement different Samba policies for different users.

> [!NOTE]
> In this mode, you will need to manage the ownership and permissions of the shared folders yourself.
>
> In the [smb_user.conf](https://github.com/dockur/samba/blob/master/smb_user.conf) file, user configurations must follow a specific format. Each line should contain the user information in the following order:
>```yaml
>username:uid:groupname:gid:password
>```
>Each line represents the configuration for a single user, and the parameters must be separated by colons.

## Stars 🌟
[![Stars](https://starchart.cc/dockur/samba.svg?variant=adaptive)](https://starchart.cc/dockur/samba)
Expand Down
170 changes: 115 additions & 55 deletions samba.sh
Original file line number Diff line number Diff line change
@@ -1,84 +1,144 @@
#!/usr/bin/env bash
set -Eeuo pipefail

# Set variables for group and share directory
group="smb"
share="/storage"
secret="/run/secrets/pass"
# This function checks for the existence of a specified Samba user and group. If the user does not exist,
# it creates a new user with the provided username, user ID (UID), group name, group ID (GID), and password.
# If the user already exists, it updates the user's UID and group association as necessary,
# and updates the password in the Samba database. The function ensures that the group also exists,
# creating it if necessary, and modifies the group ID if it differs from the provided value.
add_user() {
local username="$1"
local uid="$2"
local groupname="$3"
local gid="$4"
local password="$5"

# Check if the smb group exists, if not, create it
if ! getent group "$groupname" &>/dev/null; then
echo "Group $groupname does not exist, creating group..."
groupadd -o -g "$gid" "$groupname" || { echo "Failed to create group $groupname"; return 1; }
else
# Check if the gid right,if not, change it
local current_gid
current_gid=$(getent group "$groupname" | cut -d: -f3)
if [[ "$current_gid" != "$gid" ]]; then
echo "Group $groupname exists but GID differs, updating GID..."
groupmod -o -g "$gid" "$groupname" || { echo "Failed to update GID for group $groupname"; return 1; }
fi
fi

# Create shared directory
mkdir -p "$share" || { echo "Failed to create directory $share"; exit 1; }
# Check if the user already exists, if not, create it
if ! id "$username" &>/dev/null; then
echo "User $username does not exist, creating user..."
adduser -S -D -H -h /tmp -s /sbin/nologin -G "$groupname" -u "$uid" -g "Samba User" "$username" || { echo "Failed to create user $username"; return 1; }
else
# Check if the uid right,if not, change it
local current_uid
current_uid=$(id -u "$username")
if [[ "$current_uid" != "$uid" ]]; then
echo "User $username exists but UID differs, updating UID..."
usermod -o -u "$uid" "$username" || { echo "Failed to update UID for user $username"; return 1; }
fi

# Check if the smb group exists, if not, create it
if ! getent group "$group" &>/dev/null; then
groupadd "$group" || { echo "Failed to create group $group"; exit 1; }
fi
# Update user's group
usermod -g "$groupname" "$username" || { echo "Failed to update group for user $username"; return 1; }
fi

# Check if the user already exists, if not, create it
if ! id "$USER" &>/dev/null; then
adduser -S -D -H -h /tmp -s /sbin/nologin -G "$group" -g 'Samba User' "$USER" || { echo "Failed to create user $USER"; exit 1; }
fi
# Check if the user is a samba user
if pdbedit -L | grep -q "^$username:"; then
# if the user is a samba user, change its password
echo -e "$password\n$password" | smbpasswd -s "$username" || { echo "Failed to update Samba password for $username"; return 1; }
echo "Password for existing Samba user $username has been updated."
else
# if the user is not a samba user, create it and set a password
echo -e "$password\n$password" | smbpasswd -a -s "$username" || { echo "Failed to add Samba user $username"; return 1; }
echo "User $username has been added to Samba and password set."
fi
}

# Get the current user and group IDs
OldUID=$(id -u "$USER")
OldGID=$(getent group "$group" | cut -d: -f3)
# External config file
config="/etc/samba/smb.conf"
user_config="/etc/samba/smb_user.conf"

# Change the UID and GID of the user and group if necessary
if [[ "$OldUID" != "$UID" ]]; then
usermod -o -u "$UID" "$USER" || { echo "Failed to change UID for $USER"; exit 1; }
# Check if the user configuration file exists
if [[ -f "$user_config" ]] && [[ ! -f "$config" ]]; then
echo "File $config not found, disabling multi-user mode."
fi

if [[ "$OldGID" != "$GID" ]]; then
groupmod -o -g "$GID" "$group" || { echo "Failed to change GID for group $group"; exit 1; }
fi
# Check if multi-user mode is enabled
if [[ -f "$user_config" ]] && [[ -f "$config" ]]; then

# Check if an external config file was supplied
config="/etc/samba/smb.conf"
while read -r line; do

if [ -f "$config" ]; then
# Skip lines that are comments or empty
[[ "$line" =~ ^#.*$ || -z "$line" ]] && continue

# Inform the user we are using a custom configuration file.
echo "Using provided configuration file: $config."
# Split each line by colon and assign to variables
username=$(echo "$line" | cut -d':' -f1)
uid=$(echo "$line" | cut -d':' -f2)
groupname=$(echo "$line" | cut -d':' -f3)
gid=$(echo "$line" | cut -d':' -f4)
password=$(echo "$line" | cut -d':' -f5)

else
# Check if all required fields are present
if [[ -z "$username" || -z "$uid" || -z "$groupname" || -z "$gid" || -z "$password" ]]; then
echo "Skipping incomplete line: $line"
continue
fi

config="/etc/samba/smb.tmp"
template="/etc/samba/smb.default"
# Call the function with extracted values
add_user "$username" "$uid" "$groupname" "$gid" "$password"

# Generate a config file from template
rm -f "$config"
cp "$template" "$config"
done < "$user_config"

# Update force user and force group in smb.conf
sed -i "s/^\(\s*\)force user =.*/\1force user = $USER/" "$config"
sed -i "s/^\(\s*\)force group =.*/\1force group = $group/" "$config"
else

# Verify if the RW variable is equal to false (indicating read-only mode)
if [[ "$RW" == [Ff0]* ]]; then
# Set variables for group and share directory
group="smb"
share="/storage"
secret="/run/secrets/pass"

# Adjust settings in smb.conf to set share to read-only
sed -i "s/^\(\s*\)writable =.*/\1writable = no/" "$config"
sed -i "s/^\(\s*\)read only =.*/\1read only = yes/" "$config"
# Create shared directory
mkdir -p "$share" || { echo "Failed to create directory $share"; exit 1; }

else
# Check if the secret file exists and if its size is greater than zero
if [ -s "$secret" ]; then
PASS=$(cat "$secret")
fi

# Set permissions for share directory if new (empty), leave untouched if otherwise
if [ -z "$(ls -A "$share")" ]; then
chmod 0770 "$share" || { echo "Failed to set permissions for directory $share"; exit 1; }
chown "$USER:$group" "$share" || { echo "Failed to set ownership for directory $share"; exit 1; }
fi
add_user "$USER" "$UID" "$group" "$GID" "$PASS"

if [ -f "$config" ]; then
# Inform the user we are using a custom configuration file.
echo "Using provided configuration file: $config."
else
config="/etc/samba/smb.tmp"
template="/etc/samba/smb.default"

# Generate a config file from template
rm -f "$config"
cp "$template" "$config"

# Update force user and force group in smb.conf
sed -i "s/^\(\s*\)force user =.*/\1force user = $USER/" "$config"
sed -i "s/^\(\s*\)force group =.*/\1force group = $group/" "$config"

# Verify if the RW variable is equal to false (indicating read-only mode)
if [[ "$RW" == [Ff0]* ]]; then
# Adjust settings in smb.conf to set share to read-only
sed -i "s/^\(\s*\)writable =.*/\1writable = no/" "$config"
sed -i "s/^\(\s*\)read only =.*/\1read only = yes/" "$config"
else
# Set permissions for share directory if new (empty), leave untouched if otherwise
if [ -z "$(ls -A "$share")" ]; then
chmod 0770 "$share" || { echo "Failed to set permissions for directory $share"; exit 1; }
chown "$USER:$group" "$share" || { echo "Failed to set ownership for directory $share"; exit 1; }
fi
fi
fi
fi

# Check if the secret file exists and if its size is greater than zero
if [ -s "$secret" ]; then
PASS=$(cat "$secret")
fi

# Change Samba password
echo -e "$PASS\n$PASS" | smbpasswd -a -c "$config" -s "$USER" > /dev/null || { echo "Failed to change Samba password for $USER"; exit 1; }

# Start the Samba daemon with the following options:
# --foreground: Run in the foreground instead of daemonizing.
# --debug-stdout: Send debug output to stdout.
Expand Down
2 changes: 2 additions & 0 deletions smb_user.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
#username:UID:groupname:GID:password
samba:1000:smb:1000:secret