Creating users and groups from a file with bash.
Tags: hng, sysadmin, bash, devops
Published: 2024-07-01 22:07:27

As a devops engineer creating users and managing them is one of the primary responsibilities as a devops engineer or sysadmin.


Today we will be creating a bash script that :

1. Reads a text file containing the employee’s usernames and group names, where each line is formatted as user;groups.

2.  create users and groups as specified

3. set up home directories with appropriate permissions and ownership

4. generate random passwords for the users

5. log all actions to /var/log/user_management.log

6. Additionally, store the generated passwords securely in /var/secure/user_passwords.txt

Without further ado let's get started.

Firstly lets create the file create_user.sh using the `touch` command

`touch create_user.sh`
we want to first add our shebang `#!/bin/bash`.

Now we an start ticking off some of the requirements.

requirement 5 specifically, let us create the user_management.log file
#### Creating the required log and txt files
```
if [[ ! -e /var/log/user_management.log ]]; then
	sudo mkdir -p /var/log/
	sudo touch /var/log/user_management.log
fi

```

analyzing the above code: we check if the fire /var/log/user_management.log exist, if it does not we 

create the directory /var/log/ with mkdir and the -p flag for persistence, then we create the file user_management.log using `touch`  and then end the if statement using fi.

lets do the same for the file that all the passwords will be saved in

```
if [[ ! -e /var/secure/user_passwords.txt ]]; then
	sudo mkdir -p /var/secure/
	sudo touch /var/secure/user_passwords.txt
fi
```
/var/secure/user_passwords.txt and /var/log/user_management.log is difficult to type and a mouthful to say so we'll assign the to variables 

```
log_file="/var/log/user_management.log"
pass_file="/var/secure/user_passwords.txt"

```
and now we can start sending outputs of all that is going on to our logfile 

```
echo "logfile created..." >> $log_file

echo "checking if text_file exists" >> $log_file

```
checking if a text file containing the users and groups have been passed
 
#### Confirming a file was passed as an argument
```
if [[ -z "$1" ]]; then
    echo "Usage: $0 filename"
    echo "Text file does not exist. ...exiting" >> $log_file
    exit 1
fi
```
The above code snippet checks if there is user_input from the terminal
and if there is not it sends a text to the log file and the program ends.

However assuming there is a file passed/user_input made in the terminal

#### Reading the file
```
echo "Reading file" >> $log_file

file_path="$1"
if [[ -f "$file_path" ]]; then
	echo "fetching the usernames and groups" >> $log_file
	while IFS= read -r lines; do
              user_name=$(echo "$lines" | awk -F'; ' '{print $1}')
		groups=$(echo "$lines" | awk -F'; ' '{print $2}')


```
The above code opens up the file and starts to read from it,

#### Fetching the usernames and groups
```
While IFS = read -r lines; do
    user_name=$(echo "$lines" | awk -F'; ' '{print $1}')
    groups=$(echo "$lines" | awk -F'; ' '{print $2}')
```
The above snippet basically says while there are lines in the file i.e the code has not traveled to the end of the file, we then assign user_name to the $lines being piped through awk and the delimiter '; ' to separate the line

basically,
we have a line vale; sudo, vale, data
when we pipe it into  `awk -F'; ' '{print $1}'` it splits the line into tow parts which are `vale` and  `sudo, vale, data` and {print $1} let's us access the first part of the line before the delimiter. while the line below does the same thing but {print $2} gives us access to the second part of the line.

```
file_path="$1"
if [[ -f "$file_path" ]]; then
	echo "fetching the usernames and groups" >> $log_file
	while IFS= read -r lines; do
		user_name=$(echo "$lines" | awk -F'; ' '{print $1}')
		groups=$(echo "$lines" | awk -F'; ' '{print $2}')

		if id -u "$user_name">/dev/null 2>&1; then
			echo "user $user_name already exists"
			echo "user $user_name already exists" >> $log_file
		else
			IFS=',' read -ra group_array <<< "$groups" #explain in the post
```
now that we have access to the users we can check if a user already exist, to do that we simply check if a user has an id, if the name already has an id it exist and we simply make it known to the user that the user_name has been created already 
```
		if id -u "$user_name">/dev/null 2>&1; then
			echo "user $user_name already exists"
			echo "user $user_name already exists" >> $log_file
```
### checking if user already exists
`id -u "$user_name"` is used to get the id of a user and `>dev/null 2>&1` is used to send the output to a null file so no output is displayed

`else
			IFS=',' read -ra group_array <<< "$groups" ` is used to read the elements in groups and put them into an array group_array where the elements are split by the ',' delimiter.

```
file_path="$1"
if [[ -f "$file_path" ]]; then
	echo "fetching the usernames and groups" >> $log_file
	while IFS= read -r lines; do
		user_name=$(echo "$lines" | awk -F'; ' '{print $1}')
		groups=$(echo "$lines" | awk -F'; ' '{print $2}')

		if id -u "$user_name">/dev/null 2>&1; then
			echo "user $user_name already exists"
			echo "user $user_name already exists" >> $log_file
		else
			IFS=',' read -ra group_array <<< "$groups" #explain in the post
			for group in "${group_array[@]}"; do
				if ! getent group "$group" >/dev/null 2>&1; then
					sudo groupadd "$group"
					echo "Group $group created"
					
					echo "Group $group created" >> $log_file
				fi
			done


```
### Group creation
```
for group in "${group_array[@]}"; do
				if ! getent group "$group" >/dev/null 2>&1; then
					sudo groupadd "$group"
					echo "Group $group created"
					
					echo "Group $group created" >> $log_file
				fi
			done
```

lets break down the above block of code :
`for group in "{$group_array[@]};` this creates a variable group that is equal to the current item in the group array that was created before and it keeps on changing according to the number of elements in that group 
`do
				if ! getent group "$group" >/dev/null 2>&1;` remember when we wanted to find out if a user exists or not? This is basically the same thing but instead of a user we use the getent function to get entities that belong to a particular group and if no entity belongs to a particular group the group most likely does not exists so we pipe the output to the null folder and create the group.

### password creation
we can generate a random password using openssl

`password=$(openssl rand -base64 12)`

This creates a variable password which would have a 12 digit password in base 64
#### creating users with their assigned group and passwords

luckily someone asked this question on stackoverflow and got a response 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lrtsrf7veedr5z5rriyx.png)

Thank you very much Netzego and Damien
sudo useradd -m -G "$groups" -p "$(openssl passwd -1 "$password")" "$user_name"

echo "adding groups :$groups for user: $user_name and password in $pass_file " >> $log_file
group_fold="/home/$user_name"

so far we have:
 ```
file_path="$1"
if [[ -f "$file_path" ]]; then
	echo "fetching the usernames and groups" >> $log_file
	while IFS= read -r lines; do
		user_name=$(echo "$lines" | awk -F'; ' '{print $1}') #explain in the post
		groups=$(echo "$lines" | awk -F'; ' '{print $2}')

		if id -u "$user_name">/dev/null 2>&1; then #explain in the post
			echo "user $user_name already exists"
			echo "user $user_name already exists" >> $log_file
		else
			IFS=',' read -ra group_array <<< "$groups" #explain in the post
			for group in "${group_array[@]}"; do
				if ! getent group "$group" >/dev/null 2>&1; then #explain in the post
					sudo groupadd "$group"
					echo "Group $group created"
					
					echo "Group $group created" >> $log_file
				fi
			done
			
			password=$(openssl rand -base64 12)


			sudo useradd -m -G "$groups" -p "$(openssl passwd -1 "$password")" "$user_name" #explain in post
			echo "adding groups :$groups for user: $user_name and password in $pass_file " >> $log_file
			group_fold="/home/$user_name"
```
now we just have to create the users home folders with the appropriate permissions and groups 

```
sudo chmod 700 $group_fold > $log_file 2>&1
			sudo chown $user_name:$user_name $group_fold > $log_file 2>&1
			
			echo "Home directory for $user set up with appropriate permissions and ownership" >> $log_file

			echo "$user_name,$password" >> "$pass_file"
		fi
	done < "$file_path"
	sudo chmod 600 "$password_file"
    	sudo chown "$(id -u):$(id -g)" "$password_file"
    	echo "File permissions for $password_file set to owner-only read" >> $log_file
else
	echo "File not found: $file_path" >> "$log_file"
	exit 1
```

### The  complete code:
```
#!/bin/bash

if [[ ! -e /var/log/user_management.log ]]; then
	sudo mkdir -p /var/log/
	sudo touch /var/log/user_management.log
fi

if [[ ! -e /var/secure/user_passwords.txt ]]; then
	sudo mkdir -p /var/secure/
	sudo touch /var/secure/user_passwords.txt
fi

log_file="/var/log/user_management.log"
pass_file="/var/secure/user_passwords.txt"

echo "logfile created..." >> $log_file
echo "checking if text_file exists" >> $log_file

if [[ -z "$1" ]]; then
    echo "Usage: $0 filename"
    echo "Text file does not exist. ...exiting" >> $log_file
    exit 1
fi

echo "Reading file" >> $log_file

file_path="$1"
if [[ -f "$file_path" ]]; then
	echo "fetching the usernames and groups" >> $log_file
	while IFS= read -r lines; do
		user_name=$(echo "$lines" | awk -F'; ' '{print $1}') #explain in the post
		groups=$(echo "$lines" | awk -F'; ' '{print $2}')

		if id -u "$user_name">/dev/null 2>&1; then #explain in the post
			echo "user $user_name already exists"
			echo "user $user_name already exists" >> $log_file
		else
			IFS=',' read -ra group_array <<< "$groups" #explain in the post
			for group in "${group_array[@]}"; do
				if ! getent group "$group" >/dev/null 2>&1; then #explain in the post
					sudo groupadd "$group"
					echo "Group $group created"
					
					echo "Group $group created" >> $log_file
				fi
			done
			
			password=$(openssl rand -base64 12)


			sudo useradd -m -G "$groups" -p "$(openssl passwd -1 "$password")" "$user_name" #explain in post
			echo "adding groups :$groups for user: $user_name and password in $pass_file " >> $log_file
			group_fold="/home/$user_name"
			
			sudo chmod 700 $group_fold > $log_file 2>&1
			sudo chown $user_name:$user_name $group_fold > $log_file 2>&1
			
			echo "Home directory for $user set up with appropriate permissions and ownership" >> $log_file

			echo "$user_name,$password" >> "$pass_file"
		fi
	done < "$file_path"
	sudo chmod 600 "$password_file"
    	sudo chown "$(id -u):$(id -g)" "$password_file"
    	echo "File permissions for $password_file set to owner-only read" >> $log_file
else
	echo "File not found: $file_path" >> "$log_file"
	exit 1
```
This task was assigned to me during my [HNG](https://hng.tech/internship) internship devops track