#!/usr/bin/env bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)user-management 3.3.0 19.01.2026 (c)2026 Standby.cloud # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/ # # This script can be used free of charge. Use it as is or customize as needed. It is not guaranteed to be # error free and you will not be reimbursed for any damage it may cause. # #@ Manages users within Linux. #@ #@Usage: user-management [options] [action] username [groupname] #@ Options: #@ -h, --help : Displays helptext. #@ -v, --version : Displays the version of the script. #@ -a, --all : Display all users. #@ -u, --uid : User ID. #@ -c, --comment : Comment. #@ -s, --shell : Login Shell. #@ -d, --home : Home directory. #@ -g, --groups : Additional groups (comma separated) or empty string to remove groups. #@ #@ Action: #@ show : Show users. #@ check : Check if user exists and has the given attributes - otherwise create or change. #@ lock : Lock user. #@ unlock: Unlock user. #@ delete: Delete user. #@ #@Examples: #@ user-management check oracle dba -c "Oracle user" #@ Create user 'oracle' (if it doesn't exist) and assign group 'dba'. #@ user-management delete oracle #@ Delete user 'oracle'. # # Exit codes: # 01: Unknown or wrong parameter and only LINUX supported and script needs to be executed with ROOT privileges. # 02: Unknown action. # 03: No username specified. # 99: User interrupt. # # See also: # **bootstrap**(1), **disk-management**(1), **key-management**(1), **install-scripts**(1) # # ToDo: # # Known bugs: # # Update history: # # V 3.0.0 15.07.2020 New version # V 3.0.1 16.05.2021 List all users # V 3.0.2 22.05.2021 More actions # V 3.0.3 04.06.2023 Minor bugfixes (create new user with specified home directory) # V 3.1.0 05.06.2023 New copyright # V 3.2.0 12.08.2024 New minor version # V 3.2.1 02.09.2025 Get status from passwd # V 3.3.0 19.01.2026 Revised with support of Claude Code # # Find executable bash library and source it lib=$(command -v lib.bash 2>/dev/null) if [[ -n "$lib" ]]; then source "$lib" else progdir=$(dirname "$0") if [[ -r "${progdir}/lib.bash" ]]; then source "${progdir}/lib.bash" else echo "Unexpected error: Unable to locate bash library 'lib.bash'." exit 1 fi fi # Do extra cleanup function ExtraCleanup { filecheck -rm "${scratchfile}.pp" } # Read configuration for users function ReadConfig() { local config="/etc/login.defs" if [[ -r "$config" ]]; then cat $config | tr -s ' ' ' ' | tr -s ' ' ' ' > "$scratchfile" uid_min=$(grep "^UID_MIN " "$scratchfile" | cut -d' ' -f2) uid_max=$(grep "^UID_MAX " "$scratchfile" | cut -d' ' -f2) create_home=$(grep "^CREATE_HOME " "$scratchfile" | cut -d' ' -f2 | ToLower) fi } # Display all regular users function DisplayUsers() { local username="" local passstr="" local pass="" local uid=0 local gid=0 local comment="" local home="" local shell="" local result="" local mgroup="" local ogroup="" local agroups="" local keys="" local token="" local key="" local name="" local result="" local getauthkeys="" local sshdconfig="/etc/ssh/sshd_config" if [[ -r "$passwdfile" ]]; then printf "Please wait a few seconds." printf "username\tpassword\tuid\tgid\tgroups\tcomment\thome\tshell\tkeys\n" > "$scratchfile" if [[ -s "$sshdconfig" ]]; then result=$(grep "^AuthorizedKeysCommand " "$sshdconfig") if [[ "$result" != "" ]]; then getauthkeys=$(echo "$result" | awk '{print $2}') fi fi while IFS=':' read -r username pass uid gid comment home shell; do if [[ "$showall" = true || $uid -ge $uid_min && $uid -le $uid_max ]]; then passstr=$(passwd "$username" -S) if [[ "$passstr" != "" ]]; then pass=$(echo "$passstr" | cut -d' ' -f2) case "$pass" in NP) pass="NoPass" ;; P | PS) pass=$(echo "$passstr" | cut -d' ' -f5) if [[ "$pass" = "-1" ]]; then pass="Expired" else pass="PassSet" fi ;; L | LK) pass="Locked" ;; *) pass="Unknown" esac fi if [[ "$comment" = "" ]]; then comment="null" fi mgroup=$(grep "^.*:.*:$gid:" $groupfile | cut -d':' -f1) ogroup=$(grep "$username" $groupfile | grep -v "^${mgroup}:" | cut -d':' -f1) if [[ "$ogroup" = "" ]]; then if [[ "$mgroup" = "" ]]; then agroups="" else agroups="($mgroup)" fi else if [[ "$mgroup" = "" ]]; then agroups=$(printf "$ogroup" | tr '\n' ',') else agroups=$(printf "(${mgroup}),$ogroup" | tr '\n' ',') fi fi keys="" printf "" > "${scratchfile}.pp" if [[ -s "${home}/$keyfile" ]]; then grep "^ssh-rsa" "${home}/$keyfile" >> "${scratchfile}.pp" fi if [[ "$getauthkeys" != "" ]]; then $getauthkeys $username >> "${scratchfile}.pp" fi if [[ ! -s "${scratchfile}.pp" ]]; then keys="null" else while IFS=' ' read -r token key name; do if [[ "$token" = "ssh-rsa" ]]; then if [[ "$name" = "" ]]; then name="Nameless" fi if [[ "$keys" = "" ]]; then keys="$name" else keys="${keys},$name" fi fi done < "${scratchfile}.pp" if [[ "$keys" = "" ]]; then keys="null" fi fi printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "$username" "$pass" "$uid" "$gid" "$agroups" "$comment" "$home" "$shell" "$keys" >> "$scratchfile" fi done < <(StripComment "$passwdfile" | grep .) if [[ "$showall" = true ]]; then printf "All users:\n\n" > "${scratchfile}.pp" else printf "Regular users:\n\n" > "${scratchfile}.pp" fi print-table --import "$scratchfile" >> "${scratchfile}.pp" printf "\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b \n" cat "${scratchfile}.pp" filecheck -rm "${scratchfile}.pp" fi } function CreateUser() { local username=${1} local groupname=${2} local newhome="" local stat=0 if [[ "$username" != "" && "$groupname" != "" ]]; then # If we don't have a specific homedir - use a default if [[ "$home" = "" ]]; then newhome="/home/$username" else newhome="$home" fi # Create the user if [[ "$create_home" = "yes" ]]; then useradd "$username" -g "$groupname" -m -d "$newhome" 2>/dev/null stat=$? else useradd "$username" -g "$groupname" -d "$newhome" 2>/dev/null stat=$? fi if (( stat > 0 )); then errormsg 0 "Unable to create user '$username'." else # Modify if [[ "$uid" != "" ]]; then usermod -u "$uid" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to set UID to '$uid' for user '$username'. Using default." fi fi if [[ "$comment" != "" ]]; then usermod -c "$comment" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to set comment to '$comment' for user '$username'." fi fi if [[ "$shell" != "" ]]; then usermod -s "$shell" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to set login shell to '$shell' for user '$username'." fi fi if [[ "$addgroups" != "NoneSpecified" ]]; then usermod -G "$addgroups" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change secondary groups '$addgroups' for user '$username'." fi fi fi else errormsg 0 "Unexpected: Empty username or groupname." fi } function LockUser() { local username=${1} local userstr="" local result="" local pass="" local stat=0 if [[ -r "$passwdfile" && "$username" != "" ]]; then userstr=$(StripComment "$passwdfile" | grep "^${username}:") if [[ "$userstr" != "" ]]; then if [[ -r "$shadowfile" ]]; then result=$(grep "^${username}:" $shadowfile) if [[ "$result" != "" ]]; then pass=$(echo "$result" | cut -d':' -f2) fi fi if [[ "$pass" = "!!" ]]; then errormsg 0 "Password not set for user '$username'." else usermod -L "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to lock user '$username'." fi fi else errormsg 0 "User '$username' does not exist." fi fi } function UnlockUser() { local username=${1} local userstr="" local stat=0 if [[ -r "$passwdfile" && "$username" != "" ]]; then userstr=$(StripComment "$passwdfile" | grep "^${username}:") if [[ "$userstr" != "" ]]; then usermod -U "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to unlock user '$username'." fi else errormsg 0 "User '$username' does not exist." fi fi } function DeleteUser() { local username=${1} local userstr="" local stat=0 if [[ -r "$passwdfile" && "$username" != "" ]]; then userstr=$(StripComment "$passwdfile" | grep "^${username}:") if [[ "$userstr" != "" ]]; then userdel -f -r -Z "$username" 2>/dev/null stat=$? if (( stat > 0 )); then errormsg 0 "Unable to delete user '$username'." fi else errormsg 0 "User '$username' does not exist." fi fi } function CheckUser() { local username=${1} local groupname=${2} local userstr="" local groupstr="" local olduid="" local oldgid="" local oldcomment="" local oldhome="" local oldshell="" local gid=100 local stat=0 if [[ -r "$passwdfile" && -r "$groupfile" && "$username" != "" ]]; then userstr=$(StripComment "$passwdfile" | grep "^${username}:") if [[ "$userstr" != "" ]]; then # User exists # username pass uid gid comment home shell olduid=$(echo "$userstr" | cut -d':' -f3) oldgid=$(echo "$userstr" | cut -d':' -f4) oldcomment=$(echo "$userstr" | cut -d':' -f5) oldhome=$(echo "$userstr" | cut -d':' -f6) oldshell=$(echo "$userstr" | cut -d':' -f7) if [[ "$uid" != "" ]]; then if [[ "$olduid" != "$uid" ]]; then usermod -u "$uid" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change UID from '$olduid' to '$uid' for user '$username'." fi fi fi if [[ "$groupname" != "" ]]; then groupstr=$(StripComment "$groupfile" | grep "^${groupname}:") if [[ "$groupstr" = "" ]]; then # Group does not exists - create it first groupadd "$groupname" stat=$? if (( stat > 0 )); then errormsg 0 "Unable to create group '$groupname'. Using default group 'users'." groupname="users" fi groupstr=$(StripComment "$groupfile" | grep "^${groupname}:") fi if [[ "$groupstr" != "" ]]; then # groupname pass gid member gid=$(echo "$groupstr" | cut -d':' -f3) if [[ "$oldgid" != "$gid" ]]; then usermod -g "$gid" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change group '$groupname' from '$oldgid' to '$gid' for user '$username'." fi fi fi fi if [[ "$comment" != "" ]]; then if [[ "$oldcomment" != "$comment" ]]; then usermod -c "$comment" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change comment from '$oldcomment' to '$comment' for user '$username'." fi fi fi if [[ "$home" != "" ]]; then if [[ "$oldhome" != "$home" ]]; then if [[ "$create_home" = "yes" ]]; then usermod -m -d "$home" "$username" >/dev/null 2>&1 stat=$? else usermod -d "$home" "$username" >/dev/null 2>&1 stat=$? fi if (( stat > 0 )); then errormsg 0 "Unable to change home dir from '$oldhome' to '$home' for user '$username'." fi fi fi if [[ "$shell" != "" ]]; then if [[ "$oldshell" != "$shell" ]]; then usermod -s "$shell" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change login shell from '$oldshell' to '$shell' for user '$username'." fi fi fi if [[ "$addgroups" != "NoneSpecified" ]]; then usermod -G "$addgroups" "$username" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change secondary groups '$addgroups' for user '$username'." fi fi else # User does not exist yet if [[ "$groupname" = "" ]]; then # Default group for users groupname="users" fi groupstr=$(StripComment "$groupfile" | grep "^${groupname}:") if [[ "$groupstr" = "" ]]; then # Group does not exists - create it first groupadd "$groupname" stat=$? if (( stat > 0 )); then errormsg 0 "Unable to create group '$groupname'. Using default group 'users'." groupname="users" fi fi # Create user CreateUser "$username" "$groupname" fi fi } # Check if we are running on Linux and have root privileges. if [[ "$OS" != "Linux" ]]; then errstr="This script only supports LINUX." elif [[ "$USER" != "root" ]]; then # Try sudo check-sudo stat=$? if (( stat == 0 )); then printf 'sudo -n "%s"' "$script" > "$scratchfile" while [[ $# -gt 0 ]]; do pname=${1} printf ' "%s"' "$pname" >> "$scratchfile" shift done # Execute script chmod 700 "$scratchfile" "$scratchfile" stat=$? # Cleanup rm -f "$scratchfile" exit $stat else # Need root privileges errstr="Root privileges needed to continue." fi fi # Preset action="" param1="" param2="" comment="" shell="" home="" uid="" addgroups="NoneSpecified" showall=false uid_min=1000 uid_max=60000 create_home="yes" # Check parameters: Loop until all parameters are used up while [[ $# -gt 0 ]]; do pname=${1} case "$pname" in -v | --version) shift showversion=true ;; -h | --help) shift showhelp=true ;; -a | --all) shift showall=true ;; -u | --uid) shift if [[ "$1" != "" ]]; then uid=${1} result=$(echo "$uid" | grep '^[0123456789]*$') if [[ "$result" = "" ]]; then errstr="Id has to be a number." fi shift else errstr="Please specify an id after parameter '$pname'." fi ;; -c | --comment) shift if [[ "$1" != "" ]]; then comment=${1} shift else errstr="Please specify a comment after parameter '$pname'." fi ;; -s | --shell) shift if [[ "$1" != "" ]]; then shell=${1} result=$(filecheck -x "$shell") if [[ "$result" = "" ]]; then errstr="Login shell '$shell' does not exist or is not executable." fi shift else errstr="Please specify a comment after parameter '$pname'." fi ;; -d | --home) shift if [[ "$1" != "" ]]; then home=${1} shift else errstr="Please specify a directory after parameter '$pname'." fi ;; -g | --groups) shift paramck=$(echo "$1" | grep '^-') if [[ "$paramck" = "" ]]; then addgroups=${1} shift else errstr="Please specify none (empty string), one or more groups (comma separated) after parameter '$pname'." fi ;; *) shift if [[ "$pname" == -* ]]; then # Options begin with '-' errstr="Unknown option '$pname'." else if [[ "$action" = "" ]]; then action="$(ToLower "$pname")" elif [[ "$param1" = "" ]]; then param1="$pname" elif [[ "$param2" = "" ]]; then param2="$pname" else errstr="Unknown additional parameter: '$pname'." fi fi esac done # Display help or error message DisplayHelp # Plausibilty check if [[ "$action" = "" ]]; then action="show" fi # Main passwdfile="/etc/passwd" shadowfile="/etc/shadow" gshadowfile="/etc/gshadow" groupfile="/etc/group" keyfile=".ssh/authorized_keys" if [[ "$USER" = "root" ]]; then ReadConfig case "$action" in show) DisplayUsers ;; check) if [[ "$param1" = "" ]]; then exitcode=3 errormsg $exitcode "No username specified." else CheckUser "$param1" "$param2" fi ;; lock) if [[ "$param1" = "" ]]; then exitcode=3 errormsg $exitcode "No username specified." else LockUser "$param1" fi ;; unlock) if [[ "$param1" = "" ]]; then exitcode=3 errormsg $exitcode "No username specified." else UnlockUser "$param1" fi ;; delete) if [[ "$param1" = "" ]]; then exitcode=3 errormsg $exitcode "No username specified." else DeleteUser "$param1" fi ;; *) exitcode=2 errormsg $exitcode "Unknown action: '$action'." esac fi # Cleanup and exit Cleanup exit $exitcode