Linux User Creation Bash Script
Published: 2024-07-03 12:51:58

## Problem Statement
Your company [hng](https://hng.tech/internship) has employed many new developers. As a SysOps engineer, write a bash script called `create_users.sh` that reads a text file containing the employee’s usernames and group names, where each line is formatted as `user;groups`. The script should create users and groups as specified, set up home directories with appropriate permissions and ownership, generate random passwords for the users, and log all actions to `/var/log/user_management.log`. Additionally, store the generated passwords securely in `/var/secure/user_passwords.txt`. Ensure error handling for scenarios like existing users and provide clear documentation and comments within the script.

## Case Study
Let's start with a text file, `developers.txt` for [hng hire](https://hng.tech/hire):
`light;sudo,dev,www-data
idimma;sudo
mayowa;dev,www-data`

The above file lists the developers: light, idimma, and mayowa, with a list of groups separated by commas `sudo, www-data, dev`.

Our job now is to create a bash script that will take this file (`developers.txt`) and process it.

## Solution
1. `#!/usr/bin/bash`
    - The shebang line tells the system that this script should run with the bash shell.

2. 
    `
    log() {
        echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
    }
    `
    - Defines a function `log` to log messages with timestamps to the log file specified by `LOG_FILE`.

3. 
    `
    generate_password() {
        local password_length=8
        tr -dc A-Za-z0-9 </dev/urandom | head -c $password_length
    }
    `
    - Defines a function to generate a random password for developers.

4. 
    `
    if [ "$(id -u)" -ne 0 ]; then
        echo "Run script as root user or try using sudo " >&2
        log "Script not run as root or with sudo privileges"
        exit 1
    fi
    `
    - Verifies that the script is run as root user.

5. 
    `
    USER_FILE="$1"
    LOG_FILE="/var/log/user_management.log"
    PASSWORD_FILE="/var/secure/user_passwords.txt"
    `
    - Define Paths: 
      - `USER_FILE` = first argument
      - `LOG_FILE` = keeps operational logs
      - `PASSWORD_FILE` = stores the randomly generated passwords

6. 
    `
    if [ -z "$USER_FILE" ]; then
        echo "Usage: $0 <name-of-text-file>"
        log "Error: No file provided. Usage: $0 <name-of-text-file>"
        exit 1
    fi
    `
    - Checks if a filename is provided as an argument. If not, logs an error and exits.

7. 
    `
    if [ ! -f "$USER_FILE" ]; then
        echo "File does not exist"
        log "File '$USER_FILE' not found"
        exit 1
    fi
    `
    - Checks if the provided user file exists. If not, logs an error and exits.

8. 
    `
    if [ ! -f "$LOG_FILE" ]; then
        touch "$LOG_FILE"
        chmod 0600 "$LOG_FILE"
        log "Log file created: $LOG_FILE"
    fi
    `
    - Creates the log file if it doesn't exist and sets its permissions to be readable and writable only by the owner.

9. 
    `
    if [ ! -f "$PASSWORD_FILE" ]; then
        mkdir -p /var/secure
        touch "$PASSWORD_FILE"
        chmod 0600 "$PASSWORD_FILE"
        log "Password file created: $PASSWORD_FILE"
    fi
    `
    - Creates the password file if it doesn't exist, ensuring it is stored securely by setting appropriate permissions.

10. 
    `
    users_created=false
    user_creation_failed=false
    password_setting_failed=false
    group_creation_failed=false
    home_directory_setup_failed=false
    all_users_exist=true
    any_users_created=false
    `
    - Initializes various flags to track the success or failure of different operations.

11. 
    `
    validate_username() {
        if [[ ! "$1" =~ ^[a-zA-Z0-9_-]+$ ]]; then
            return 1
        fi
        return 0
    }

    validate_groups() {
        IFS=',' read -ra group_list <<< "$1"
        for group in "${group_list[@]}"; do
            if [[ ! "$group" =~ ^[a-zA-Z0-9_-]+$ ]]; then
                return 1
            fi
        done
        return 0
    }
    `
    - Defines functions `validate_username` and `validate_groups` to ensure usernames and groups are valid, using regex checks.

12. 
    `
    while IFS=';' read -r username groups; do
        # Trim whitespace from username and groups
        username=$(echo "$username" | xargs)
        groups=$(echo "$groups" | xargs)
    `
    - Reads the user file line by line, splitting each line into `username` and `groups` variables, and trims any whitespace.

13. 
    `
        if [ -z "$username" ] || [ -z "$groups" ]; then
            log "Invalid line format in user file: '$username;$groups'"
            user_creation_failed=true
            continue
        fi

        if ! validate_username "$username"; then
            log "Invalid username format: '$username'"
            user_creation_failed=true
            continue
        fi

        if ! validate_groups "$groups"; then
            log "Invalid group format: '$groups'"
            group_creation_failed=true
            continue
        fi
    `
    - Validates the format of each line in the user file, logging errors and setting flags as necessary.

14. 
    `
        if id "$username" &>/dev/null; then
            log "User $username already exists."
            continue
        fi

        # User does not exist, so there's work to be done
        all_users_exist=false
    `
    - Checks if the user already exists. If so, logs a message and skips to the next user. Otherwise, sets a flag indicating new users need to be created.

15. 
    `
        if ! getent group "$username" > /dev/null; then
            if groupadd "$username"; then
                log "Group $username created."
            else
                log "Failed to create group $username."
                group_creation_failed=true
                continue
            fi
        fi
    `
    - Creates a personal group for the user if it doesn't already exist, logging the outcome.

16. 
    `
        IFS=',' read -ra group_list <<< "$groups"
        for group in "${group_list[@]}"; do
            if ! getent group "$group" > /dev/null; then
                if groupadd "$group"; then
                    log "Group $group created."
                else
                    log "Failed to create group $group."
                    group_creation_failed=true
                fi
            fi
        done
        unset IFS
    `
    - Creates any additional groups if they don't already exist, logging the outcome.

17. 
    `
        if useradd -m -g "$username" -G "$groups" "$username"; then
            log "User $username created and added to groups $groups"
            users_created=true
            any_users_created=true
        else
            log "Failed to create user $username"
            user_creation_failed=true
            continue
        fi
    `
    - Creates the user, assigns them to the primary group, and adds them to additional groups, logging the outcome.

18. 
    `
        password=$(generate_password)
        log "Generated password for $username"
    `
    - Generates a random password and sets the user's password, storing it securely.

    `
        # Set the user's password
        if echo "$username:$password" | chpasswd; then
            # Store the password securely in TXT format    
            echo "$username,$password" >> "$PASSWORD_FILE"
            log "Password set for $username and stored securely"
        else
            log "Failed to set password for $username"
            password_setting_failed=true
            continue
        fi
    `

19. 
    `
        if chown "$username:$username" "/home/$username" && chmod 700 "/home/$username"; then
            log "Home directory for $username set up with appropriate permissions."
        else
            log "Failed to set up home directory for $username"
            home_directory_setup_failed=true
        fi
    `
    - Sets the correct permissions and ownership for the user's home directory, logging the outcome.

20. 
    `
    done < "$USER_FILE"
    `
    - Ends the while loop that processes each line of the user file.

21. 
    `
    log "User creation script run completed."

    if [ "$any_users_created" = true ]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') - User creation script completed successfully."
    elif [ "$all_users_exist" = true ]; then
        echo "Users already exist. Nothing left to do"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') - No users were created successfully. Check log file."
        log "No users were created successfully. Please check the input file format: username;group1,group2,group3."
    fi
    `
    - Logs a summary of the script's execution and prints appropriate messages to the console based on the success or failure of user creation.

22. 
    `
    [ "$user_creation_failed" = true ] && echo "Users creation incomplete." && log "Some users were not created due to errors. Check file format"
    [ "$password_setting_failed" = true ] && echo "Users' passwords creation incomplete." && log "Some users' passwords were not set due to errors. Check file format"
    [ "$group_creation_failed" = true ] && echo "Groups creation incomplete." && log "Some groups were not created due to errors. Check file format"
    [ "$home_directory_setup_failed" = true ] && echo "Home directories creation incomplete." && log "Some home directories were not set up due to errors."
    `
    - Checks various flags and logs additional error messages if any failures occurred during the script's execution.

23.   
    `
    exit 0
    `
    - Exits the script with a success status code.

To call the script `create_users.sh`, you need to provide the path to the user file as an argument. The user file should contain lines formatted as `username;group1,group2,group3`.

Here's the general format for calling the script:
`
sudo ./create_users.sh <name-of-text-file>
`