Automating Linux User Creation with a Bash Script
Tags: linux, bash, devops, hng
Published: 2024-07-03 21:47:31


## A Practical Script for SysOps Engineers

In the SysOps engineer world, automation is key, especially for repetitive tasks that save valuable time and ensure consistency. As a SysOps engineer, knowing how to write robust and efficient scripts along with strong Linux skills, boosts productivity and gives you a better workflow.  This article will walk you through a bash script designed to automate the creation of Linux users.  

The script creates users and groups as specified, sets up home directories with appropriate permissions and ownership, it generates random passwords for the users, and logs all actions to `/var/log/user_management.log`. Additionally, it stores the generated passwords securely in `/var/secure/user_passwords.txt`. and covers error handling for scenarios like existing users.

## Prerequisite:

- Bash shell environment.
- Text editor for editing the script and preparing the input file.


Let's have a look at our script which we will later use to accomplish this task, and break it down by a detailed explanation of each section:

```bash
#!/bin/bash

# Check if the script is run as root
if [[ "$(id -u)" -ne 0 ]]; then
    echo "You must run the script as root" >&2
    exit 1
fi

# Check if a filename is provided
if [[ -z "$1" ]]; then
    echo "Usage: $0 <name-of-text-file>" >&2
    exit 1
fi

# File paths
LOG_FILE="/var/log/user_management.log"
PASSWORD_FILE="/var/secure/user_passwords.csv"

# Ensure /var/secure storage for passwords
mkdir -p /var/secure
touch $PASSWORD_FILE
chown root:root /var/secure
chmod 700 /var/secure
chmod 600 $PASSWORD_FILE

# Function to log actions
log_action() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}

# Function to generate a random password
generate_password() {
    tr -dc 'A-Za-z0-9!@#$%^&*()_+=-[]{}|;:<>,.?/~' </dev/urandom | head -c 16
}

# Function to hash passwords
hash_password() {
    echo "$1" | openssl passwd -6 -stdin
}

# Read the input file line by line
while IFS=";" read -r username groups; do
    # Trim whitespace
    username=$(echo "$username" | xargs)
    groups=$(echo "$groups" | xargs)

    # Skip empty lines
    if [[ -z "$username" ]]; then
        continue
    fi

    # Check if user already exists
    if id "$username" &>/dev/null; then
        log_action "User $username already exists!"
        continue
    fi

    # Create personal group
    if groupadd "$username"; then
        log_action "Group $username created successfully."
    else
        log_action "Failed to create group $username."
        continue
    fi

    # Create user with a home directory and personal group
    if useradd -m -s /bin/bash -g "$username" "$username"; then
        log_action "User $username created successfully."
    else
        log_action "Failed to create user $username."
        continue
    fi

    # Create groups and add user to them
    IFS=',' read -ra group_array <<< "$groups"
    for group in "${group_array[@]}"; do
        group=$(echo "$group" | xargs)
        if ! getent group "$group" >/dev/null 2>&1; then
            if groupadd "$group"; then
                log_action "Group $group created."
            else
                log_action "Failed to create group $group."
                continue
            fi
        fi
        if usermod -aG "$group" "$username"; then
            log_action "User $username added to group $group."
        else
            log_action "Failed to add user $username to group $group."
        fi
    done

    # Generate random password
    password=$(generate_password)
    hashed_password=$(hash_password "$password")
    if usermod --password "$hashed_password" "$username"; then
        echo "$username,$password" >> $PASSWORD_FILE
        log_action "Password set for user $username."
    else
        log_action "Failed to set password for user $username."
    fi

    # Set home directory permissions
    if mkdir -p "/home/$username" && chown -R "$username:$username" "/home/$username" && chmod 755 "/home/$username"; then
        log_action "Home directory permissions set for user $username."
    else
        log_action "Failed to set home directory permissions for user $username."
    fi

done < "$1"

log_action "User creation process completed. Check $LOG_FILE for details."
```

### Breaking Down the Script

#### 1. Check for Root Privileges

```bash
if [[ "$(id -u)" -ne 0 ]]; then
    echo "You must run the script as root" >&2
    exit 1
fi
```

This part checks if the script is run by the root user, this is because only the root user has permission to create and manage other users. If the script is not run as root, it will print an error message and exit.

#### 2. Check for Filename Argument

```bash
if [[ -z "$1" ]]; then
    echo "Usage: $0 <name-of-text-file>" >&2
    exit 1
fi
```

This section will check if the user provided a filename as an argument when running the script. If no filename is found, it will print a usage message and exit.

#### 3. Define File Paths

```bash
LOG_FILE="/var/log/user_management.log"
PASSWORD_FILE="/var/secure/user_passwords.csv"
```

These lines define the file paths for logging actions and storing passwords.

#### 4. Ensure Secure Storage for Passwords

```bash
mkdir -p /var/secure
touch $PASSWORD_FILE
chown root:root /var/secure
chmod 700 /var/secure
chmod 600 $PASSWORD_FILE
```

This part of the script ensures that the `/var/secure` directory and the password file exist with appropriate permissions for secure storage. `mkdir -p` creates the directory if it doesn't exist, `touch` creates the password file, and `chmod` and `chown` set the permissions so only the root user can read and write to it.

#### 5. Logging Function

```bash
log_action() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}
```

The `log_action` function's job is to log messages with timestamps to the log file. It uses the `date` command to add a timestamp to each log entry and `tee` to append the message to the log file.

#### 6. Password Generation Function

```bash
generate_password() {
    tr -dc 'A-Za-z0-9!@#$%^&*()_+=-[]{}|;:<>,.?/~' </dev/urandom | head -c 16
}
```

The `generate_password` function generates a random password. `tr` is used to filter characters from `/dev/urandom` (a random number generator), and `head -c 16` ensures the password is 16 characters long.

#### 7. Password Hashing Function

```bash
hash_password() {
    echo "$1" | openssl passwd -6 -stdin
}
```

The `hash_password` function hashes the generated password using the `openssl` command with SHA-512 encryption. This makes the password secure before storing it.

#### 8. Read and Process Input File

```bash
while IFS=";" read -r username groups; do
    # Trim whitespace
    username=$(echo "$username" | xargs)
    groups=$(echo "$groups" | xargs)

    # Skip empty lines
    if [[ -z "$username" ]]; then
        continue
    fi
```

This part reads the input file line by line, separating the username and groups by the semicolon (`;`). It also trims any whitespace around the username and groups and skips any empty lines.

#### 9. Check for Existing User

```bash
if id "$username" &>/dev/null; then
    log_action "User $username already exists!"
    continue
fi
```

This checks if the user already exists. If so, it logs the action and skips to the next user.

#### 10. Create Personal Group

```bash
if groupadd "$username"; then
    log_action "Group $username created successfully."
else
    log_action "Failed to create group $username."
    continue
fi
```

This creates a personal group with the same name as the username.

#### 11. Create User

```bash
if useradd -m -s /bin/bash -g "$username" "$username"; then
    log_action "User $username created successfully."
else
    log_action "Failed to create user $username."
    continue
fi
```

This creates the user with a home directory and assigns the personal group as the primary group.

#### 12. Create and Assign Groups

```bash
Copy code
IFS=',' read -ra group_array <<< "$groups"
for group in "${group_array[@]}"; do
    group=$(echo
```

## How to use the script?

1. Save the script as create_users.sh in your preferred directory.
 
2. Prepare a text file (users_and_groups.txt) with one username per line, followed by groups separated by commas. For example: light;sudo,dev,www-data idimma;sudo mayowa;dev,www-data
 
3. Make the script executable with chmod +x create_users.sh.
 
4. Run the script with the path to your text file as an argument: ./create_users.sh users_and_groups.txt.
 

## Conclusion
By following the structured steps outlined above, SysOps engineers can efficiently manage large numbers of user accounts and their associated groups, providing a robust solution for onboarding new developers.


## Acknowledgment

This script was developed as part of the HNG Internship DevOps track. For more details on the program, visit the [HNG Internship Website](https://hng.tech/internship).

You can learn more about the HNG Internship program [here](https://hng.tech/hire) and [here](https://hng.tech/premium).

Thank you for reading.