<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Don-Fortune Tangban</title>
    <description>The latest articles on DEV Community by Don-Fortune Tangban (@don-fortune).</description>
    <link>https://dev.to/don-fortune</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1723344%2Feb889548-57f5-4dd4-ac66-b43b28126c9f.jpeg</url>
      <title>DEV Community: Don-Fortune Tangban</title>
      <link>https://dev.to/don-fortune</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/don-fortune"/>
    <language>en</language>
    <item>
      <title>Linux User Creation Bash Script</title>
      <dc:creator>Don-Fortune Tangban</dc:creator>
      <pubDate>Wed, 03 Jul 2024 12:51:58 +0000</pubDate>
      <link>https://dev.to/don-fortune/linux-user-creation-bash-script-73h</link>
      <guid>https://dev.to/don-fortune/linux-user-creation-bash-script-73h</guid>
      <description>&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;Your company &lt;a href="https://hng.tech/internship"&gt;hng&lt;/a&gt; has employed many new developers. As a SysOps engineer, write a bash script called &lt;code&gt;create_users.sh&lt;/code&gt; that reads a text file containing the employee’s usernames and group names, where each line is formatted as &lt;code&gt;user;groups&lt;/code&gt;. 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 &lt;code&gt;/var/log/user_management.log&lt;/code&gt;. Additionally, store the generated passwords securely in &lt;code&gt;/var/secure/user_passwords.txt&lt;/code&gt;. Ensure error handling for scenarios like existing users and provide clear documentation and comments within the script.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case Study
&lt;/h2&gt;

&lt;p&gt;Let's start with a text file, &lt;code&gt;developers.txt&lt;/code&gt; for &lt;a href="https://hng.tech/hire"&gt;hng hire&lt;/a&gt;:&lt;br&gt;
&lt;code&gt;light;sudo,dev,www-data&lt;br&gt;
idimma;sudo&lt;br&gt;
mayowa;dev,www-data&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The above file lists the developers: light, idimma, and mayowa, with a list of groups separated by commas &lt;code&gt;sudo, www-data, dev&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Our job now is to create a bash script that will take this file (&lt;code&gt;developers.txt&lt;/code&gt;) and process it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;#!/usr/bin/bash&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The shebang line tells the system that this script should run with the bash shell.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
log() {&lt;br&gt;
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" &amp;gt;&amp;gt; "$LOG_FILE"&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines a function &lt;code&gt;log&lt;/code&gt; to log messages with timestamps to the log file specified by &lt;code&gt;LOG_FILE&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
generate_password() {&lt;br&gt;
    local password_length=8&lt;br&gt;
    tr -dc A-Za-z0-9 &amp;lt;/dev/urandom | head -c $password_length&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines a function to generate a random password for developers.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
if [ "$(id -u)" -ne 0 ]; then&lt;br&gt;
    echo "Run script as root user or try using sudo " &amp;gt;&amp;amp;2&lt;br&gt;
    log "Script not run as root or with sudo privileges"&lt;br&gt;
    exit 1&lt;br&gt;
fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verifies that the script is run as root user.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
USER_FILE="$1"&lt;br&gt;
LOG_FILE="/var/log/user_management.log"&lt;br&gt;
PASSWORD_FILE="/var/secure/user_passwords.txt"&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Define Paths: 

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;USER_FILE&lt;/code&gt; = first argument&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LOG_FILE&lt;/code&gt; = keeps operational logs&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PASSWORD_FILE&lt;/code&gt; = stores the randomly generated passwords&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
if [ -z "$USER_FILE" ]; then&lt;br&gt;
    echo "Usage: $0 &amp;lt;name-of-text-file&amp;gt;"&lt;br&gt;
    log "Error: No file provided. Usage: $0 &amp;lt;name-of-text-file&amp;gt;"&lt;br&gt;
    exit 1&lt;br&gt;
fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks if a filename is provided as an argument. If not, logs an error and exits.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
if [ ! -f "$USER_FILE" ]; then&lt;br&gt;
    echo "File does not exist"&lt;br&gt;
    log "File '$USER_FILE' not found"&lt;br&gt;
    exit 1&lt;br&gt;
fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks if the provided user file exists. If not, logs an error and exits.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
if [ ! -f "$LOG_FILE" ]; then&lt;br&gt;
    touch "$LOG_FILE"&lt;br&gt;
    chmod 0600 "$LOG_FILE"&lt;br&gt;
    log "Log file created: $LOG_FILE"&lt;br&gt;
fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates the log file if it doesn't exist and sets its permissions to be readable and writable only by the owner.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
if [ ! -f "$PASSWORD_FILE" ]; then&lt;br&gt;
    mkdir -p /var/secure&lt;br&gt;
    touch "$PASSWORD_FILE"&lt;br&gt;
    chmod 0600 "$PASSWORD_FILE"&lt;br&gt;
    log "Password file created: $PASSWORD_FILE"&lt;br&gt;
fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates the password file if it doesn't exist, ensuring it is stored securely by setting appropriate permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
users_created=false&lt;br&gt;
user_creation_failed=false&lt;br&gt;
password_setting_failed=false&lt;br&gt;
group_creation_failed=false&lt;br&gt;
home_directory_setup_failed=false&lt;br&gt;
all_users_exist=true&lt;br&gt;
any_users_created=false&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initializes various flags to track the success or failure of different operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`&lt;br&gt;
validate_username() {&lt;br&gt;
    if [[ ! "$1" =~ ^[a-zA-Z0-9_-]+$ ]]; then&lt;br&gt;
        return 1&lt;br&gt;
    fi&lt;br&gt;
    return 0&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;validate_groups() {&lt;br&gt;
    IFS=',' read -ra group_list &amp;lt;&amp;lt;&amp;lt; "$1"&lt;br&gt;
    for group in "${group_list[@]}"; do&lt;br&gt;
        if [[ ! "$group" =~ ^[a-zA-Z0-9_-]+$ ]]; then&lt;br&gt;
            return 1&lt;br&gt;
        fi&lt;br&gt;
    done&lt;br&gt;
    return 0&lt;br&gt;
}&lt;br&gt;
`&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Defines functions &lt;code&gt;validate_username&lt;/code&gt; and &lt;code&gt;validate_groups&lt;/code&gt; to ensure usernames and groups are valid, using regex checks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
while IFS=';' read -r username groups; do&lt;br&gt;
    # Trim whitespace from username and groups&lt;br&gt;
    username=$(echo "$username" | xargs)&lt;br&gt;
    groups=$(echo "$groups" | xargs)&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads the user file line by line, splitting each line into &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;groups&lt;/code&gt; variables, and trims any whitespace.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`&lt;br&gt;
    if [ -z "$username" ] || [ -z "$groups" ]; then&lt;br&gt;
        log "Invalid line format in user file: '$username;$groups'"&lt;br&gt;
        user_creation_failed=true&lt;br&gt;
        continue&lt;br&gt;
    fi&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;`&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validates the format of each line in the user file, logging errors and setting flags as necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`&lt;br&gt;
    if id "$username" &amp;amp;&amp;gt;/dev/null; then&lt;br&gt;
        log "User $username already exists."&lt;br&gt;
        continue&lt;br&gt;
    fi&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# User does not exist, so there's work to be done
all_users_exist=false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;`&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
    if ! getent group "$username" &amp;gt; /dev/null; then&lt;br&gt;
        if groupadd "$username"; then&lt;br&gt;
            log "Group $username created."&lt;br&gt;
        else&lt;br&gt;
            log "Failed to create group $username."&lt;br&gt;
            group_creation_failed=true&lt;br&gt;
            continue&lt;br&gt;
        fi&lt;br&gt;
    fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates a personal group for the user if it doesn't already exist, logging the outcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
    IFS=',' read -ra group_list &amp;lt;&amp;lt;&amp;lt; "$groups"&lt;br&gt;
    for group in "${group_list[@]}"; do&lt;br&gt;
        if ! getent group "$group" &amp;gt; /dev/null; then&lt;br&gt;
            if groupadd "$group"; then&lt;br&gt;
                log "Group $group created."&lt;br&gt;
            else&lt;br&gt;
                log "Failed to create group $group."&lt;br&gt;
                group_creation_failed=true&lt;br&gt;
            fi&lt;br&gt;
        fi&lt;br&gt;
    done&lt;br&gt;
    unset IFS&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates any additional groups if they don't already exist, logging the outcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
    if useradd -m -g "$username" -G "$groups" "$username"; then&lt;br&gt;
        log "User $username created and added to groups $groups"&lt;br&gt;
        users_created=true&lt;br&gt;
        any_users_created=true&lt;br&gt;
    else&lt;br&gt;
        log "Failed to create user $username"&lt;br&gt;
        user_creation_failed=true&lt;br&gt;
        continue&lt;br&gt;
    fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creates the user, assigns them to the primary group, and adds them to additional groups, logging the outcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
    password=$(generate_password)&lt;br&gt;
    log "Generated password for $username"&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generates a random password and sets the user's password, storing it securely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;&lt;br&gt;
    # Set the user's password&lt;br&gt;
    if echo "$username:$password" | chpasswd; then&lt;br&gt;
        # Store the password securely in TXT format    &lt;br&gt;
        echo "$username,$password" &amp;gt;&amp;gt; "$PASSWORD_FILE"&lt;br&gt;
        log "Password set for $username and stored securely"&lt;br&gt;
    else&lt;br&gt;
        log "Failed to set password for $username"&lt;br&gt;
        password_setting_failed=true&lt;br&gt;
        continue&lt;br&gt;
    fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
    if chown "$username:$username" "/home/$username" &amp;amp;&amp;amp; chmod 700 "/home/$username"; then&lt;br&gt;
        log "Home directory for $username set up with appropriate permissions."&lt;br&gt;
    else&lt;br&gt;
        log "Failed to set up home directory for $username"&lt;br&gt;
        home_directory_setup_failed=true&lt;br&gt;
    fi&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sets the correct permissions and ownership for the user's home directory, logging the outcome.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
done &amp;lt; "$USER_FILE"&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ends the while loop that processes each line of the user file.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;`&lt;br&gt;
log "User creation script run completed."&lt;/p&gt;

&lt;p&gt;if [ "$any_users_created" = true ]; then&lt;br&gt;
    echo "$(date '+%Y-%m-%d %H:%M:%S') - User creation script completed successfully."&lt;br&gt;
elif [ "$all_users_exist" = true ]; then&lt;br&gt;
    echo "Users already exist. Nothing left to do"&lt;br&gt;
else&lt;br&gt;
    echo "$(date '+%Y-%m-%d %H:%M:%S') - No users were created successfully. Check log file."&lt;br&gt;
    log "No users were created successfully. Please check the input file format: username;group1,group2,group3."&lt;br&gt;
fi&lt;br&gt;
`&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logs a summary of the script's execution and prints appropriate messages to the console based on the success or failure of user creation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
[ "$user_creation_failed" = true ] &amp;amp;&amp;amp; echo "Users creation incomplete." &amp;amp;&amp;amp; log "Some users were not created due to errors. Check file format"&lt;br&gt;
[ "$password_setting_failed" = true ] &amp;amp;&amp;amp; echo "Users' passwords creation incomplete." &amp;amp;&amp;amp; log "Some users' passwords were not set due to errors. Check file format"&lt;br&gt;
[ "$group_creation_failed" = true ] &amp;amp;&amp;amp; echo "Groups creation incomplete." &amp;amp;&amp;amp; log "Some groups were not created due to errors. Check file format"&lt;br&gt;
[ "$home_directory_setup_failed" = true ] &amp;amp;&amp;amp; echo "Home directories creation incomplete." &amp;amp;&amp;amp; log "Some home directories were not set up due to errors."&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks various flags and logs additional error messages if any failures occurred during the script's execution.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;&lt;br&gt;
exit 0&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exits the script with a success status code.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;Here's the general format for calling the script:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
sudo ./create_users.sh &amp;lt;name-of-text-file&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
