Bash Scripting: Automating User Management in Linux
Tags: devops, bash, linux, automation
Published: 2024-07-01 17:15:52

## Why do we automate?
Managing users and groups on a Linux system can be a tedious task, especially if you have to handle multiple accounts. Fortunately, with a bit of scripting knowledge, you can automate this process. In this article, I will walk you through a simple Bash script designed to create users, assign them to groups, and set their passwords automatically.

Why Automate User Management?
- Efficiency: Automating repetitive tasks saves time.
- Consistency: Ensures the same process is followed every time.
- Error Reduction: Minimizes the risk of human error.

## New to Bash scripting?
For those trying to learn about bash scripting, kindly visit [here](https://dev.to/alexindevs/bash-scripting-for-software-engineers-a-beginners-guide-1j65)).

## Script Overview

This project had me creating a simple script that performs the following functions:
- Reads the users and groups supplied in the .txt file
- Creates the users and groups from read data
- Assigns randomly generated password to each user. Passwords are saved in `/var/secure/user_passwords.csv`
- Adds users to appropriate groups
- Creates a `$HOME` directory for each user with appropriate permission
- Logs all script actions in `/var/log/user_management.log`
Each step will be explained.

The project's repository can be found [here](https://github.com/xtasysensei/create-users-bash).

## Prerequisites
Before running the script, ensure you have:

- Root privileges (the script needs to be run as root).
- An input file containing user information (e.g., employees.txt).

## Detailed Explanation
### Checking for Root Privileges
The script starts by checking if it is being run as root. This is crucial because user and group management operations require root permissions.It does this by verifying the`$EUID` variable is 0.
```bash
if [[ "$EUID" -ne 0 ]]; then
  echo "Error: This script needs to run as root. Use sudo"
  exit 1
fi
```
### Creating Necessary Directories
If the required directories (/var/log and /var/secure) do not exist, the script creates them.
```bash
# Create necessary directories if they don't exist
if [ ! -d /var/log ]; then
    mkdir -p /var/log
fi

if [ ! -d /var/secure ]; then
    mkdir -p /var/secure
fi
```
### Logging Function
A logging function log_message is defined to log messages with timestamps to a log file.

```bash
log_message() {
    local message="$1"
    echo "$(date +"%Y-%m-%d %H:%M:%S") - $message" >> $log_file
}
```
### Validating Input Arguments
The script expects exactly one argument, the path to the employee file. If the argument is not provided or the file does not exist, it logs an error and exits.
```bash
# Check for correct number of arguments
if [ "$#" -ne 1 ]; then
    arg_err="Usage: $0 <file_path>"
    echo "$arg_err"
    log_message "$arg_err"
    exit 1
fi  

employee_file=$1

# Check if the employee file exists
if [ ! -f "$employee_file" ]; then
    file_missing_err="Error: File '$employee_file' not found."
    echo "$file_missing_err"
    log_message "$file_missing_err"
    exit 1
fi
```
### Securing the Password File
The password file is created and secures by setting correct permissions so that only the owner can read the file created for users and passwords. Permission **600** only gives the owner read privileges

```bash
# Secure password file
log_message "Securing $password_file"
if [ ! -f $password_file ]; then
	echo "User,password" >> $password_file
	chmod 600 $password_file
fi	
```
### Processing Users and Groups
The snippets below read the employee file line by line, processes each user and their associated groups, and performs the following operations:
1. User Creation: Checks if the user exists, creates the user if not, and sets a random password.
2. Group Creation: Creates a group with the same name as the user and adds the user to it. Also, adds the user to additional specified groups.
2. Home Directory: Ensures the user has a home directory.

### Let's break it down
- Reading the Input File
The snippet below reads each line from a file, splits the line into user and groups based on the semicolon, and then trims any extra whitespace from these variables.

```bash
while IFS=';' read -r user groups; do
    # Read users and groups from the .txt file
    user=$(echo "$user" | xargs)
    groups=$(echo "$groups" | xargs)
```
It also checks if there is a blank line in between entries. The condition checks if either user or groups is empty (-z checks for a zero-length string).
```bash
if [[ -z "$user" || -z "$groups" ]]; then
        newline_err="Skipping invalid line in the input file"
        echo "$newline_err"
        log_message "$newline_err"
        continue
fi
```
- Creating user and group array
The snippet below logs and prints the user being processed, splits their groups into an array, trims any whitespace from each group, and then logs and prints the cleaned-up list of group

```bash
echo "=> Processing user: $user"
    log_message "Processing user: $user"
    IFS=',' read -r -a group_array <<< "$groups"

    for i in "${!group_array[@]}"; do
        group_array[$i]=$(echo "${group_array[$i]}" | xargs)
    done
    
    echo "=> Groups for $user: ${group_array[*]}"
    log_message "Groups for $user: ${group_array[*]}
```
- Creating group with the same name as the user
This snippet checks if a group exists, creates the group if it does not exist, and logs the actions taken

```bash
 # Create group with the same name as the user
if getent group "$user" &>/dev/null; then
      echo "Group $user already exists."
      log_message "Group $user already exists."
else
      if groupadd "$user"; then
         echo "=> Group $user created."
         log_message "Group $user created."
      else
         echo "Error creating group $user."
         log_message "Error creating group $user."
         continue
      fi
fi   
```
If a user exists our script logs it and moves on else the user is created and then logged.

NOTE: `> /dev/null` suppresses the response so that we don't have to see the output
- Creating `$HOME` directory for user and generating passwords
This snippet handles the creation of users, including setting up their home directory and assigning a password, while logging all actions and errors appropriately
```bash
 # Creating user, user's home directory and assigning a randomly generated password
    echo "=> Creating user: $user..."
    log_message "Creating user: $user"
    if id "$user" &>/dev/null; then
        echo "User $user already exists."
        log_message "User $user already exists."
    else
        if useradd -m -s "$shell" -g "$user" "$user"; then
            echo "=> User $user created with home directory /home/$user."
            log_message "User $user created with home directory /home/$user."
            
            password=$(head /dev/urandom | tr -dc A-Za-z0-9 | fold -w 16 | head -n 1)
            if echo "$user:$password" | chpasswd; then
                echo "$user,$password" >> $password_file
                echo "=> Password set for $user"
                log_message "Password set for $user"
            else
                echo "Error setting password for $user."
                log_message "Error setting password for $user."
                continue
            fi
        else
            echo "Error creating user $user."
            log_message "Error creating user $user."
            continue
        fi
    fi
```
The `if useradd -m -s "$shell" -g "$user" "$user"` attempts to create a new user with a home directory (-m), specified shell (-s "$shell"), and primary group (-g "$user").
`password=$(head /dev/urandom | tr -dc A-Za-z0-9 | fold -w 16 | head -n 1)` generates a random 16-character password using `urandom`.
`if echo "$user:$password" | chpasswd; then` sets the generated password for the user using the `chpasswd` command.
Finally, `echo "$user,$password" >> $password_file` appends the user and their password to the specified password file.

- Adding user to specified groups
This snippet processes each user and adds them to the specified groups, creating the groups if they don't already exist
```bash
for group in "${group_array[@]}"; do
        if getent group "$group" &>/dev/null; then
            echo "Group $group already exists."
            log_message "Group $group already exists."
        else
            if groupadd "$group"; then
                echo "Group $group created."
                log_message "Group $group created."
            else
                echo "Error creating group $group."
                log_message "Error creating group $group."
                continue
            fi
        fi
        if usermod -aG "$group" "$user"; then
            echo "=> Added $user to group $group."
            log_message "Added $user to group $group."
        else
            echo "Error adding $user to group $group."
            log_message "Error adding $user to group $group."
            continue
        fi
done
```
It loops through the group, groups that don't exist are created with the `groupadd` command.
`if usermod -aG "$group" "$user"` attempts to add the user to the group. It throws an error if the user is already a member of the group

- End of Loop and File Processing

```bash
  echo "--------------------"
done < "$employee_file"
```
`echo "--------------------"` prints a separator line for clarity in the output.
`done < "$employee_file"` continues processing the next user and group pairs from the employee_file.

### The complete script looks like this

```bash
while IFS=';' read -r user groups; do
    # Read users and groups from the .txt file
    user=$(echo "$user" | xargs)
    groups=$(echo "$groups" | xargs)
    
    if [[ -z "$user" || -z "$groups" ]]; then
        newline_err="Skipping invalid line in the input file"
        echo "$newline_err"
        log_message "$newline_err"
        continue
    fi
    
    echo "=> Processing user: $user"
    log_message "Processing user: $user"
    IFS=',' read -r -a group_array <<< "$groups"

    for i in "${!group_array[@]}"; do
        group_array[$i]=$(echo "${group_array[$i]}" | xargs)
    done
    
    echo "=> Groups for $user: ${group_array[*]}"
    log_message "Groups for $user: ${group_array[*]}"
    
    # Create group with the same name as the user
    if getent group "$user" &>/dev/null; then
        echo "Group $user already exists."
        log_message "Group $user already exists."
    else
        if groupadd "$user"; then
            echo "=> Group $user created."
            log_message "Group $user created."
        else
            echo "Error creating group $user."
            log_message "Error creating group $user."
            continue
        fi
    fi
    
    # Creating user, user's home directory and assigning a randomly generated password
    echo "=> Creating user: $user..."
    log_message "Creating user: $user"
    if id "$user" &>/dev/null; then
        echo "User $user already exists."
        log_message "User $user already exists."
    else
        if useradd -m -s "$shell" -g "$user" "$user"; then
            echo "=> User $user created with home directory /home/$user."
            log_message "User $user created with home directory /home/$user."
            
            password=$(head /dev/urandom | tr -dc A-Za-z0-9 | fold -w 6 | head -n 1)
            if echo "$user:$password" | chpasswd; then
                echo "$user,$password" >> $password_file
                echo "=> Password set for $user"
                log_message "Password set for $user"
            else
                echo "Error setting password for $user."
                log_message "Error setting password for $user."
                continue
            fi
        else
            echo "Error creating user $user."
            log_message "Error creating user $user."
            continue
        fi
    fi

    # Add the user to other specified groups
    for group in "${group_array[@]}"; do
        if getent group "$group" &>/dev/null; then
            echo "Group $group already exists."
            log_message "Group $group already exists."
        else
            if groupadd "$group"; then
                echo "Group $group created."
                log_message "Group $group created."
            else
                echo "Error creating group $group."
                log_message "Error creating group $group."
                continue
            fi
        fi
        if usermod -aG "$group" "$user"; then
            echo "=> Added $user to group $group."
            log_message "Added $user to group $group."
        else
            echo "Error adding $user to group $group."
            log_message "Error adding $user to group $group."
            continue
        fi
    done
    
    echo "--------------------"
done < "$employee_file"
```
### Conclusion
This project has taught me a lot about Linux permission system, user and group management, loops, conditionals and file manipulation in bash. For those interested in putting their skills to the test, you can head over to [HNG Internship](https://hng.tech/internship) where they accept DevOps interns. It is totally free but a premium version  is available, visit [here](https://hng.tech/premium) for more info.

P.S: Any feedback will be appreciated. Thank you for reading.