I'm trying to manually install a daemon (Oracle Grid Engine) on my machine, and I would like it to run under an isolated account. What is the preferred way, using Directory Services, to add a "system" account to the local machine on OS X? Plenty of them exist in /etc/passwd (_www, _dovecot, etc.), but comments at the top of that file say that it isn't used except in single-user mode.

I'm running on 10.6, and do not require any special networked account management. I'm hoping for something simple--the equivalent of useradd on nearly every other Unix-like OS.


dscl is the command you are looking for.


I tried the script from par, and found a few issues. So I modified it for one specific userid and for OS X Mavericks (10.9).

I found that there was a couple extraneous records added to the User account under Mavericks -- a PasswordPolicyOptions and an AuthenticationAuthority record -- that needed to be removed to correctly mimic other builtin service user accounts (like _www).

I also added the Password and RealName records to the Group account.

I created a custom, one off, script just for a WSGI service account. Here's the updated script.

#! /bin/bash
#

# Check that we are superuser (i.e. $(id -u) is zero)
if (( $(id -u) ))
then
    echo "This script needs to run as root"
    exit 1
fi

username_=wsgi
uid_=240
realname_="WSGI Daemon"

dscl . -create /Groups/_$username_
dscl . -create /Groups/_$username_ PrimaryGroupID $uid_
dscl . -create /Groups/_$username_ RecordName _$username_ $username_
dscl . -create /Groups/_$username_ RealName $realname_
dscl . -create /Groups/_$username_ Password \*

dscl . -create /Users/_$username_
dscl . -create /Users/_$username_ NFSHomeDirectory /xpt/local/apache2/wsgi/api
dscl . -create /Users/_$username_ Password \*
dscl . -create /Users/_$username_ PrimaryGroupID $uid_
dscl . -create /Users/_$username_ RealName $realname_
dscl . -create /Users/_$username_ RecordName _$username_ $username_
dscl . -create /Users/_$username_ UniqueID $uid_
dscl . -create /Users/_$username_ UserShell /usr/bin/false
dscl . -delete /Users/_$username_ PasswordPolicyOptions
dscl . -delete /Users/_$username_ AuthenticationAuthority

Note that after running this script the /etc/passwd and /etc/groups files are not updated. I believe they are updated on reboot.


EDIT: Updated Jan 9, 2014 for OS X Mavericks (suggestions from Dave, thanks!)

I wrote a bash script to do this. It will use the first unused uid which is less than or equal to 500 (daemon account uids on Mac OS X) that also has an identical unused gid.

Save the script to a file named add_system_user.sh and set it executable with chmod 755 add_system_user.sh.

Then let's say you want to add a daemon/system user called par. You would run this script like so:

sudo add_system_user.sh par

And you will get a system user called _par which is aliased to par (the name you requested) and has a matching uid and gid (e.g. 499 or whatever it found).

Here's the script:

#!/bin/bash

if (( $(id -u) )) ; then
    echo "This script needs to run as root"
    exit 1
fi

if [[ -z "$1" ]] ; then
    echo "Usage: $(basename $0) [username] [realname (optional)]"
    exit 1
fi

username=$1
realname="${2:-$username}"

echo "Adding daemon user $username with real name \"$realname\""

for (( uid = 500;; --uid )) ; do
    if ! id -u $uid &>/dev/null; then
        if ! dscl /Local/Default -ls Groups gid | grep -q [^0-9]$uid\$ ; then
          dscl /Local/Default -create Groups/_$username
          dscl /Local/Default -create Groups/_$username Password \*
          dscl /Local/Default -create Groups/_$username PrimaryGroupID $uid
          dscl /Local/Default -create Groups/_$username RealName "$realname"
          dscl /Local/Default -create Groups/_$username RecordName _$username $username

          dscl /Local/Default -create Users/_$username
          dscl /Local/Default -create Users/_$username NFSHomeDirectory /var/empty
          dscl /Local/Default -create Users/_$username Password \*
          dscl /Local/Default -create Users/_$username PrimaryGroupID $uid
          dscl /Local/Default -create Users/_$username RealName "$realname"
          dscl /Local/Default -create Users/_$username RecordName _$username $username
          dscl /Local/Default -create Users/_$username UniqueID $uid
          dscl /Local/Default -create Users/_$username UserShell /usr/bin/false

          dscl /Local/Default -delete /Users/_$username AuthenticationAuthority
          dscl /Local/Default -delete /Users/_$username PasswordPolicyOptions
          break
        fi
    fi
done

echo -e "Created system user $username (uid/gid $uid):\n"

dscl /Local/Default -read Users/_$username

echo -e "\nYou can undo the creation of this user by issuing the following commands:\n"
echo "sudo dscl /Local/Default -delete Users/_$username"
echo "sudo dscl /Local/Default -delete Groups/_$username"

Here's an article that explains how to use dscl to create a user account.

osxdaily.com article


Here's a version of Dave's script, which also checks if the user/group exists before creating it:

#!/bin/sh
# creates service account user similar to Linux adduser command
# to view existing users and ids try:
# dscl . -readall /Users UniqueID | sort -nk 2

die () {
    echo >&2 "$@"
    exit 1
}

echo "Usage: sudo $0 username uid realname"
echo "NOTES: username shouldn't start with the underscore (it will be appended by the script)"
echo "       check that the user does not exist and get the free ID number in 1000 range"
echo "       e.g. with dscl . -readall /Users UniqueID | sort -nk 2"
echo ""

# Check that we are superuser (i.e. $(id -u) is zero)
[ `id -u` -eq 0 ] || die "This script needs to run as root"

[ "$#" -eq 3 ] || die "Error: 3 arguments required: username, uid and realname"

username_=$1
uid_=$2
realname_=$3
nfs_homedir="/var/tmp"
user_shell="/usr/bin/false"

echo "Checking if the user/group exists: \c"

check_uuid=`dscl . -search /Users UniqueID $uid_`
check_upgid=`dscl . -search /Users PrimaryGroupID $uid_`
check_urn=`dscl . -search /Users RecordName _$username_`
check_grn=`dscl . -search /Groups RecordName _$username_`


[ ${#check_uuid} = 0 ] || die "failed!\nERROR: Non-unique User UniqueID:\n\n`dscl . -read /Users/_$username_ RecordName PrimaryGroupID RealName` \n\nTo view existing users/ids run: dscl . -readall /Users UniqueID | sort -nk 2"
[ ${#check_upgid} = 0 ] || die "failed!\nERROR: Non-unique User PrimaryGroupID\n\n`dscl . -read /Users/_$username_ RecordName PrimaryGroupID RealName` \n\nTo view existing users/ids run: dscl . -readall /Users UniqueID | sort -nk 2"
[ ${#check_urn} = 0 ] || die "failed!\nERROR: Non-unique User RecordName\n\n`dscl . -read /Users/_$username_ RecordName PrimaryGroupID RealName` \n\nTo view existing users/ids run: dscl . -readall /Users UniqueID | sort -nk 2"
[ ${#check_grn} = 0 ] || die "failed!\nERROR: Non-unique Group RecordName\n\n`dscl . -read /Groups/_$username_ RecordName PrimaryGroupID RealName` \n\nTo view existing users/ids run: dscl . -readall /Users UniqueID | sort -nk 2"

echo "we're good to go!"

# echo "Continue (y/n) ? "
# read input_
# [ "$input_" = "y" ] || die "as you wish..." 

echo "Creating User: \c"

dscl . -create /Groups/_$username_
dscl . -create /Groups/_$username_ PrimaryGroupID $uid_
dscl . -create /Groups/_$username_ RecordName _$username_ $username_
dscl . -create /Groups/_$username_ RealName "$realname_"
dscl . -create /Groups/_$username_ Password \*

dscl . -create /Users/_$username_
dscl . -create /Users/_$username_ NFSHomeDirectory $nfs_homedir
dscl . -create /Users/_$username_ Password \*
dscl . -create /Users/_$username_ PrimaryGroupID $uid_
dscl . -create /Users/_$username_ RealName "$realname_"
dscl . -create /Users/_$username_ RecordName _$username_ $username_
dscl . -create /Users/_$username_ UniqueID $uid_
dscl . -create /Users/_$username_ UserShell $user_shell
dscl . -delete /Users/_$username_ PasswordPolicyOptions
dscl . -delete /Users/_$username_ AuthenticationAuthority

echo "done!"

and a script to delete user:

#!/bin/sh
# delete service user similar to Linux userdel command, but leaving the files intact
# to view existing users and ids try:
# dscl . -readall /Users UniqueID | sort -nk 2

die () {
    echo >&2 "$@"
    exit 1
}

# Check that we are superuser (i.e. $(id -u) is zero)
[ `id -u` -eq 0 ] || die "This script needs to run as root"
[ "$#" -eq 1 ] || die "Error: username arguments is required!"

username_=$1

dscl . -delete /Users/$username_ 
dscl . -delete /Groups/$username_ 

echo "done!"