Skip to content

Instantly share code, notes, and snippets.

@jordanlambrecht
Last active February 25, 2024 03:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jordanlambrecht/7edce0914e5ae2785e0fce0880adf94c to your computer and use it in GitHub Desktop.
Save jordanlambrecht/7edce0914e5ae2785e0fce0880adf94c to your computer and use it in GitHub Desktop.
MacOS: Create New System User
#!/bin/bash
## This code is based off of https://raw.githubusercontent.com/Servarr/Wiki/master/servarr/servarr-install-script.sh and re-worked for MacOS users
### Boilerplate Warning
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
#NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
#LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
#OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
#WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## Remixed by Jordan Lambrecht (https://github.com/jordanlambrecht)
scriptversion="3.0.11"
scriptdate="2024-2-24"
set -euo pipefail
printf "\n"
echo "Running Servarr Install Script - Version [$scriptversion] as of [$scriptdate]"
printf "\n"
bold=$(tput bold)
normal=$(tput sgr0)
# Ensure the script is run with root privileges
if [ "$EUID" -ne 0 ]; then
echo "Please run as root."
exit
fi
echo "${bold}🚨 DANGER 🚨 This script is hella powerful and may cause permanent damage to your file system. Use at your own risk${normal}"
printf "\n"
echo "📋 Listing all user-defined groups (excluding system and specific groups):"
# Initialize counter for formatting output
counter=0
echo "---------------------------------------------------------------------------------------------------------------------"
# Retrieve, sort, and filter groups, then process each line
dscl . -list /Groups PrimaryGroupID | sort | while IFS= read -r line; do
group_name=$(echo "$line" | awk '{print $1}')
group_id=$(echo "$line" | awk '{print $2}')
# Exclude groups starting with '_', containing 'com.apple.', or matching specific names
if ! [[ $group_name =~ ^_ || $group_name =~ com.apple. || $group_name =~ ^(wheel|sys|tty|utmp|procview|procmod|kmem)$ ]]; then
# Print group info with formatting, three names per row
printf "%-1s %8s %-30s" "|" "[$group_id]" "${bold}$group_name${normal}"
((counter++))
if (( counter % 3 == 0 )); then
printf "\n"
fi
fi
done
# Ensure there's a newline at the end of the output if the last line wasn't complete
if (( counter % 3 != 0 )); then
printf "\n"
fi
printf "\n"
echo "---------------------------------------------------------------------------------------------------------------------"
read -r -p "${bold}✌️ Hi there.${normal} Enter the name of the group you would like to use or create (Default: staff): " app_group_name
app_group_name=$(echo "${app_group_name:-staff}" | tr -d ' ')
if dscl . -read /Groups/"$app_group_name" &>/dev/null; then
app_group_id=$(dscl . -read /Groups/"$app_group_name" PrimaryGroupID | awk '{print $2}')
echo "⚠️ Group name already exists as group number ${bold}$app_group_id${normal}. Using this group."
else
echo "✅ Group does not exist yet. We'll create a new one."
while true; do
read -r -p "Enter a group ID to use (or hit enter to auto-assign): " app_group_id
if [ -z "$app_group_id" ]; then
# Auto-assign logic
highest_group_id=$(dscl . -list /Groups PrimaryGroupID | awk '{print $2}' | sort -n | tail -1)
app_group_id=$((highest_group_id + 1))
echo "🤖 Auto-assigned group ID is $app_group_id."
sudo dscl . -create /Groups/"$app_group_name"
sudo dscl . -create /Groups/"$app_group_name" PrimaryGroupID "$app_group_id"
echo "✅ Created Group [$app_group_name] with auto-assigned ID [$app_group_id]."
break
elif ! dscl . -read /Groups gid "$app_group_id" &>/dev/null; then
echo "✅ Group ID $app_group_id is available."
sudo dscl . -create /Groups/"$app_group_name"
sudo dscl . -create /Groups/"$app_group_name" PrimaryGroupID "$app_group_id"
echo "✅ Created Group [$app_group_name] with specified ID [$app_group_id]."
break
else
existing_group_name=$(dscl . -search /Groups PrimaryGroupID "$app_group_id" | awk '{print $1}')
echo "❗️ Group ID $app_group_id already exists for group $existing_group_name."
read -r -p "Use this group? (y/n): " decision
if [[ "$decision" =~ ^[yY]$ ]]; then
app_group_name="$existing_group_name"
echo "✅ Using existing group $app_group_name with ID $app_group_id."
break
else
echo "❌ Please enter a new group ID."
fi
fi
done
fi
# Prompt for the username
read -r -p "✍️ Enter the name of the user you would like to create: " app_username
app_username=$(echo "$app_username" | tr -d ' ')
# Prompt for a unique identifier for the user, though macOS generally handles this automatically
read -r -p "✍️ Enter a unique ID for the user (leave blank to auto-generate): " app_uid
echo "This will create user ${bold}$app_username${normal} with UID ${bold}$app_uid${normal}, and add to group ${bold}$app_group_name${normal}."
# Wait for user confirmation
read -n 1 -r -s -p $'Press enter to continue or ctrl+c to exit...\n'
# Now, determine if the user exists
if ! dscl . -read /Users/"$app_username" &>/dev/null; then
# User does not exist, create the user and set primary group
if [ -z "$app_uid" ]; then
sudo sysadminctl -addUser "$app_username" -fullName "$app_username" -password "" -home "" -admin
else
sudo sysadminctl -addUser "$app_username" -UID "$app_uid" -fullName "$app_username" -password "" -home "" -admin
fi
echo "✅Created User ${bold}$app_username${normal} successfully."
# Set the primary group ID for the new user
sudo dscl . -create /Users/"$app_username" PrimaryGroupID "$app_group_id"
echo "✅ I set primary group of User ${bold}$app_username${normal} to ${bold}$app_group_id${normal}"
else
echo "❗️ User ${bold}$app_username${normal} already exists."
# For an existing user, only add them to the group without changing their primary group ID
# Check if the group exists or was created during this script execution
if dscl . -read /Groups/"$app_group_name" &>/dev/null || [ ! -z "$app_group_id" ]; then
# Add the existing user to the specified group as a supplementary group
sudo dseditgroup -o edit -a "$app_username" -t user "$app_group_name"
echo "Added User [$app_username] to Group [$app_group_name] as a supplementary group."
else
echo "Specified group does not exist and was not created. No changes made to User [$app_username]."
fi
fi
read -r -p "❓ Would you like to see a list of all users/groups on the system? (y/n): " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
# Define the format string for the header and the data rows
format="%-2s %-5s %-2s %-18s %-2s %-10s %-2s %-18s \n"
headerFormat="%-2s %-5s %-2s %-18s %-2s %-10s %-2s %-18s \n"
# Print the header
printf "\n"
printf "$headerFormat" "| " "ID" "| " "Name" "| " "GUID" "| " "Group Name"
echo "-------------------------------------------------------------"
# Fetch and print user information excluding system accounts
dscacheutil -q user | awk -v OFS='\t' '
/uid:/ {uid=$2}
/name:/ {name=$2}
/gid:/ {gid=$2; if (uid >= 500) print uid, name, gid, "Fetching..."}' | sort -n | while IFS=$'\t' read -r uid name gid fetching; do
# Use dscl to directly query the group name from the GID
gname=$(dscl . -search /Groups gid "$gid" | awk 'NR==1 {print $1}')
# Check if gname is not empty before printing. Safely handle potential leading dashes in variables.
if [ ! -z "$gname" ]; then
printf "$format" "| " "$uid" "| " "$name" "| " "$gid" "| " "$gname"
else
printf "$format" "| " "$uid" "| " "$name" "| " "$gid" "Unknown"
fi
done
echo "-------------------------------------------------------------"
fi
printf "\n"
echo "👋 It's been a pleasure working with you, hit me up if you ever need additional assistance."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment