Problem Statement
Your company hng 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:
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
-
#!/usr/bin/bash
- The shebang line tells the system that this script should run with the bash shell.
-
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 byLOG_FILE
.
- Defines a function
-
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.
-
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.
-
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
-
- Define Paths:
-
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.
-
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.
-
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.
-
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.
-
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.
-
`
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
andvalidate_groups
to ensure usernames and groups are valid, using regex checks.
- Defines functions
-
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
andgroups
variables, and trims any whitespace.
- Reads the user file line by line, splitting each line into
-
`
if [ -z "$username" ] || [ -z "$groups" ]; then
log "Invalid line format in user file: '$username;$groups'"
user_creation_failed=true
continue
fiif ! 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.
-
`
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.
-
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.
-
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.
-
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.
-
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
-
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.
-
done < "$USER_FILE"
- Ends the while loop that processes each line of the user file.
-
`
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.
-
[ "$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.
-
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>
Top comments (0)