Build a User Management Tool: Learn Linux User Administration
You’ve added users with useradd. Maybe deleted one with userdel. But have you built a tool that does this safely, with validation, confirmations, and proper error handling?
That’s the difference between running commands and being a sysadmin.
Today, we’re building a User Management Tool - an interactive menu-driven utility for managing Linux users and groups. The kind of tool that prevents the “oops I deleted the wrong user” disasters.
The Problem: Manual User Management is Risky
Here’s how most people manage users:
useradd john # Hope I spelled it right
passwd john # Set a password
usermod -aG sudo john # Add to sudo group
userdel john # Wait, was that the right John?
No confirmation. No validation. No safety checks. One typo and you’ve got problems.
Our tool will:
- Validate usernames before creating
- Prevent deleting root or system users
- Show what’s about to happen before doing it
- Provide a clean, menu-driven interface
- Handle all the edge cases
The Toolkit: User Management Commands
Group 1: Information Gathering
| Command | Purpose |
|---|---|
id username | Check if user exists, show UID/GID |
getent passwd | Query user database |
groups username | List user’s groups |
last username | User’s login history |
who / w | Currently logged-in users |
Group 2: User Modification
| Command | Purpose |
|---|---|
useradd | Create new user |
usermod | Modify existing user |
userdel | Delete user |
passwd | Set/change password |
chage | Password expiry settings |
Group 3: Group Management
| Command | Purpose |
|---|---|
groupadd | Create new group |
groupdel | Delete group |
usermod -aG | Add user to group |
gpasswd -d | Remove user from group |
Important Files
| File | Purpose |
|---|---|
/etc/passwd | User account information |
/etc/shadow | Encrypted passwords |
/etc/group | Group definitions |
/etc/sudoers | Sudo configuration |
The Walkthrough: Building Step by Step
Step 1: Check for Root Privileges
Most user operations require root. Let’s make that check reusable:
check_root() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}Error: This operation requires root privileges${NC}"
return 1
fi
return 0
}
# Usage:
create_user() {
check_root || return 1
# ... rest of function
}
What’s $EUID? It’s the Effective User ID. Root is always 0.
Step 2: Validate Username Format
Before creating a user, validate the input:
validate_username() {
local username="$1"
# Check if empty
if [[ -z "$username" ]]; then
echo "Username cannot be empty"
return 1
fi
# Check format: must start with letter/underscore, contain only valid chars
if ! [[ "$username" =~ ^[a-z_][a-z0-9_-]*$ ]]; then
echo "Invalid username format"
echo "Must start with lowercase letter or underscore"
echo "Can contain: lowercase letters, numbers, underscore, hyphen"
return 1
fi
# Check length
if [[ ${#username} -gt 32 ]]; then
echo "Username too long (max 32 characters)"
return 1
fi
return 0
}
Regex breakdown:
^[a-z_]- Must start with lowercase letter or underscore[a-z0-9_-]*$- Rest can be letters, numbers, underscore, hyphen
Step 3: Build the Interactive Menu
print_header() {
clear
echo -e "${CYAN}"
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ USER MANAGEMENT TOOL v1.0 ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
print_menu() {
echo -e "${BLUE}Select an option:${NC}"
echo ""
echo " 1) List all users"
echo " 2) Show user details"
echo " 3) Create new user"
echo " 4) Delete user"
echo " 5) Modify user"
echo " 6) Lock/Unlock user"
echo " 7) List groups"
echo " 8) Add user to group"
echo " 9) Show logged-in users"
echo " 0) Exit"
echo ""
}
main() {
while true; do
print_header
print_menu
read -p "Enter choice [0-9]: " choice
case $choice in
1) list_users ;;
2) show_user_details ;;
3) create_user ;;
4) delete_user ;;
5) modify_user ;;
6) lock_unlock_user ;;
7) list_groups ;;
8) add_user_to_group ;;
9) show_logged_users ;;
0) echo "Goodbye!"; exit 0 ;;
*) echo "Invalid option" ;;
esac
read -p "Press Enter to continue..."
done
}
Step 4: List Users with Highlighting
list_users() {
echo -e "${CYAN}=== System Users ===${NC}"
echo ""
printf "%-20s %-8s %-8s %-30s %s\n" "USERNAME" "UID" "GID" "HOME" "SHELL"
echo "─────────────────────────────────────────────────────────────────────────────"
while IFS=: read -r username _ uid gid _ home shell; do
# Highlight regular users (UID >= 1000)
if [[ $uid -ge 1000 && $uid -lt 65534 ]]; then
printf "${GREEN}%-20s${NC} %-8s %-8s %-30s %s\n" "$username" "$uid" "$gid" "$home" "$shell"
else
printf "%-20s %-8s %-8s %-30s %s\n" "$username" "$uid" "$gid" "$home" "$shell"
fi
done < /etc/passwd
echo ""
echo -e "${GREEN}Green${NC} = Regular users (UID >= 1000)"
}
What’s happening:
IFS=:sets the field separator to colon (passwd format)read -rreads each line into variables- We skip unused fields with
_ - UIDs 1000+ are typically regular users
Step 5: Create User with Safety Checks
create_user() {
check_root || return 1
echo -e "${CYAN}=== Create New User ===${NC}"
read -p "Enter username: " username
validate_username "$username" || return 1
# Check if user already exists
if id "$username" &>/dev/null; then
echo -e "${RED}Error: User '$username' already exists${NC}"
return 1
fi
read -p "Enter full name (optional): " fullname
read -p "Enter shell [/bin/bash]: " shell
shell="${shell:-/bin/bash}" # Default to bash
# Ask about home directory
local create_home=""
read -p "Create home directory? [y/N]: " response
if [[ "${response,,}" == "y" ]]; then
create_home="-m"
fi
echo ""
echo "Creating user '$username'..."
if useradd $create_home -s "$shell" ${fullname:+-c "$fullname"} "$username"; then
echo -e "${GREEN}User '$username' created successfully${NC}"
read -p "Set password now? [y/N]: " set_pwd
if [[ "${set_pwd,,}" == "y" ]]; then
passwd "$username"
fi
else
echo -e "${RED}Failed to create user${NC}"
return 1
fi
}
Parameter expansion tricks:
${shell:-/bin/bash}- Use/bin/bashif shell is empty${fullname:+-c "$fullname"}- Only add-cflag if fullname is set${response,,}- Convert to lowercase for comparison
Step 6: Delete User with Safety Rails
This is where things can go wrong. Let’s add multiple safety checks:
delete_user() {
check_root || return 1
echo -e "${CYAN}=== Delete User ===${NC}"
read -p "Enter username to delete: " username
# Check if user exists
if ! id "$username" &>/dev/null; then
echo -e "${RED}Error: User '$username' does not exist${NC}"
return 1
fi
# NEVER delete root
if [[ "$username" == "root" ]]; then
echo -e "${RED}ERROR: Cannot delete root user!${NC}"
return 1
fi
# Show what we're about to delete
echo ""
echo "User to be deleted:"
id "$username"
echo ""
# Ask about home directory
local remove_home=""
read -p "Remove home directory and mail spool? [y/N]: " response
if [[ "${response,,}" == "y" ]]; then
remove_home="-r"
fi
# Final confirmation
read -p "Are you SURE you want to delete user '$username'? [y/N]: " confirm
if [[ "${confirm,,}" != "y" ]]; then
echo "Deletion cancelled"
return 0
fi
# Kill any running processes
pkill -u "$username" 2>/dev/null || true
# Delete the user
if userdel $remove_home "$username"; then
echo -e "${GREEN}User '$username' deleted successfully${NC}"
else
echo -e "${RED}Failed to delete user${NC}"
return 1
fi
}
Safety features:
- Can’t delete root
- Shows user info before deletion
- Requires explicit confirmation
- Kills running processes first (prevents “user is logged in” errors)
|| trueprevents script exit if no processes found
Step 7: Lock/Unlock Users
lock_unlock_user() {
check_root || return 1
read -p "Enter username: " username
if ! id "$username" &>/dev/null; then
echo -e "${RED}User '$username' does not exist${NC}"
return 1
fi
# Check current status
local status=$(passwd -S "$username" 2>/dev/null | awk '{print $2}')
if [[ "$status" == "L" || "$status" == "LK" ]]; then
echo "User '$username' is currently LOCKED"
read -p "Unlock user? [y/N]: " response
if [[ "${response,,}" == "y" ]]; then
usermod -U "$username" && echo "User unlocked"
fi
else
echo "User '$username' is currently ACTIVE"
read -p "Lock user? [y/N]: " response
if [[ "${response,,}" == "y" ]]; then
usermod -L "$username" && echo "User locked"
fi
fi
}
Lock status codes:
LorLK= LockedP= Has password (active)NP= No password
Step 8: Group Management
add_user_to_group() {
check_root || return 1
read -p "Enter username: " username
if ! id "$username" &>/dev/null; then
echo -e "${RED}User '$username' does not exist${NC}"
return 1
fi
echo "Current groups for $username:"
groups "$username"
echo ""
read -p "Enter group name to add: " groupname
# Check if group exists, offer to create
if ! getent group "$groupname" &>/dev/null; then
echo "Group '$groupname' does not exist"
read -p "Create group? [y/N]: " create
if [[ "${create,,}" == "y" ]]; then
groupadd "$groupname" && echo "Group created"
else
return 1
fi
fi
# Add user to group
if usermod -aG "$groupname" "$username"; then
echo -e "${GREEN}User '$username' added to group '$groupname'${NC}"
echo "New groups:"
groups "$username"
else
echo -e "${RED}Failed to add user to group${NC}"
fi
}
Key command: usermod -aG
-a= Append (don’t replace existing groups)-G= Supplementary groups
Without -a, you’d replace ALL the user’s groups!
The Complete Script Structure
#!/bin/bash
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
# Helper functions
check_root() { ... }
validate_username() { ... }
confirm() { ... }
# User operations
list_users() { ... }
show_user_details() { ... }
create_user() { ... }
delete_user() { ... }
modify_user() { ... }
lock_unlock_user() { ... }
# Group operations
list_groups() { ... }
add_user_to_group() { ... }
# Menu
print_header() { ... }
print_menu() { ... }
main() {
while true; do
print_header
print_menu
read -p "Enter choice: " choice
case $choice in
# ... handle options
esac
pause
done
}
main
What You Just Learned
You built a professional user management tool. Along the way, you mastered:
- User management commands -
useradd,usermod,userdel,passwd - System files -
/etc/passwd,/etc/shadow,/etc/group - Input validation - Regex, length checks, existence checks
- Safety patterns - Confirmation dialogs, root protection
- Interactive menus -
casestatements, read loops - Error handling - Return codes, graceful failures
Your Challenge
The tool is functional. Now make it production-ready:
- Add bulk user creation - Read usernames from CSV
- Add password expiry management - Use
chagecommand - Export user list - Generate CSV or JSON report
- Add audit logging - Log all changes to a file
- Add backup before delete - Archive home directory first
Remember: With great power comes great responsibility. Test these scripts on a VM first.
Next in the series: Building a Todo CLI with JSON Persistence