#!/usr/bin/env bash # # Author: Georg Voell - georg.voell@standby.cloud # Version: @(#)group-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 groups within Linux. #@ #@Usage: group-management [options] [action] groupname #@ Options: #@ -h, --help : Displays helptext. #@ -v, --version : Displays the version of the script. #@ -a, --all : Display all groups. #@ -g, --gid : Group ID. #@ -n, --new-name : New groupname. #@ #@ Action: #@ show : Show groups. #@ check : Check if group exists and has the given attributes - otherwise create or change. #@ delete: Delete group. #@ #@Examples: #@ group-management check dba -g 1005 #@ Create group 'dba' (if it doesn't exist) with group id '1005'. #@ group-management delete dba #@ Delete group 'dba'. # # 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 groupname 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 23.05.2021 New version # 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 gpasswd # 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" gid_min=$(grep "^GID_MIN " "$scratchfile" | cut -d' ' -f2) gid_max=$(grep "^GID_MAX " "$scratchfile" | cut -d' ' -f2) fi } # Display all regular groups function DisplayGroups() { local groupname="" local passstr="" local pass="" local gid=0 local member="" local user="" local agroups="" if [[ -r "$groupfile" ]]; then printf "Please wait a few seconds." printf "groupname\tpassword\tgid\tmember\n" > "$scratchfile" while IFS=':' read -r groupname pass gid member; do if [[ "$showall" = true || $gid -ge $gid_min && $gid -le $gid_max ]]; then if [[ -r "$gshadowfile" ]]; then passstr=$(grep "^${groupname}:" $gshadowfile) else passstr="" fi if [[ "$passstr" != "" ]]; then pass=$(echo "$passstr" | cut -d':' -f2) case "$pass" in '') pass="NoPass" ;; '!') pass="Restricted" ;; '!*') pass="Locked" ;; *) pass="PassSet" esac fi user=$(grep "^.*:.*:.*:$gid:" $passwdfile | cut -d':' -f1) user=$(printf "$user" | tr '\n' ',') if [[ "$member" = "" ]]; then if [[ "$user" = "" ]]; then agroups="" else agroups="($user)" fi else if [[ "$user" = "" ]]; then agroups="$member" else agroups="(${user}),$member" fi fi # echo "groupname: '$groupname' - user: '$user' - member: '$member'." printf "%s\t%s\t%s\t%s\n" "$groupname" "$pass" "$gid" "$agroups" >> "$scratchfile" fi done < <(StripComment "$groupfile" | grep .) if [[ "$showall" = true ]]; then printf "All groups:\n\n" > "${scratchfile}.pp" else printf "Regular groups:\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 CreateGroup() { local groupname=${1} local stat=0 if [[ "$groupname" != "" ]]; then # Create the group groupadd "$groupname" 2>/dev/null stat=$? if (( stat > 0 )); then errormsg 0 "Unable to create group '$groupname'." else # Modify if [[ "$gid" != "" ]]; then groupmod -g "$gid" "$groupname" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to set GID to '$gid' for group '$groupname'. Using default." fi fi if [[ "$newname" != "" ]]; then if [[ "$newname" != "$groupname" ]]; then groupmod -n "$newname" "$groupname" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change name to '$newname' for group '$groupname'." fi fi fi fi else errormsg 0 "Unexpected: Empty groupname." fi } function DeleteGroup() { local groupname=${1} local groupstr="" local stat=0 if [[ -r "$groupfile" && "$groupname" != "" ]]; then groupstr=$(StripComment "$groupfile" | grep "^${groupname}:") if [[ "$groupstr" != "" ]]; then groupdel -f "$groupname" 2>/dev/null stat=$? if (( stat > 0 )); then errormsg 0 "Unable to delete group '$groupname'." fi else errormsg 0 "Group '$groupname' does not exist." fi fi } function CheckGroup() { local groupname=${1} local groupstr="" local oldgid="" local stat=0 if [[ -r "$groupfile" && "$groupname" != "" ]]; then groupstr=$(StripComment "$groupfile" | grep "^${groupname}:") if [[ "$groupstr" != "" ]]; then # group exists # groupname pass gid member oldgid=$(echo "$groupstr" | cut -d':' -f3) if [[ "$gid" != "" ]]; then if [[ "$oldgid" != "$gid" ]]; then groupmod -g "$gid" "$groupname" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change GID from '$oldgid' to '$gid' for group '$groupname'." fi fi fi if [[ "$newname" != "" ]]; then if [[ "$newname" != "$groupname" ]]; then groupmod -n "$newname" "$groupname" >/dev/null 2>&1 stat=$? if (( stat > 0 )); then errormsg 0 "Unable to change name to '$newname' for group '$groupname'." fi fi fi else # group does not exist yet # Create group CreateGroup "$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 (( $# > 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="" gid="" newname="" showall=false gid_min=1000 gid_max=60000 # Check parameters: Loop until all parameters are used up while (( $# > 0 )); do pname=${1} case "$pname" in -v | --version) shift showversion=true ;; -h | --help) shift showhelp=true ;; -a | --all) shift showall=true ;; -g | --gid) shift if [[ "$1" != "" ]]; then gid=${1} result=$(echo "$gid" | grep '^[0123456789]*$') if [[ "$result" = "" ]]; then errstr="Id has to be a number." fi shift else errstr="Please specify an id after parameter '$pname'." fi ;; -n | --new-name) shift if [[ "$1" != "" ]]; then newname=${1} shift else errstr="Please specify a name 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" 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) DisplayGroups ;; check) if [[ "$param1" = "" ]]; then exitcode=3 errormsg $exitcode "No groupname specified." else CheckGroup "$param1" fi ;; delete) if [[ "$param1" = "" ]]; then exitcode=3 errormsg $exitcode "No groupname specified." else DeleteGroup "$param1" fi ;; *) exitcode=2 errormsg $exitcode "Unknown action: '$action'." esac fi # Cleanup and exit Cleanup exit $exitcode