How to Add a User to a Mac OS X System using script

you saw the basic steps involved in adding a new user to a typical Unix or Linux system. The Mac OS X version is fundamentally quite similar. In essence, you prompt for an account name and login shell, append the appropriate information to the /etc/passwd and /etc/shadow files, create the new user's home directory, and set an initial password of some sort. With Mac OS X it's not quite this simple, because appending information to /etc/passwd will not create a new Aqua account. Instead, the information must be injected into the NetInfo system using the niutil command.

The Code
#!/bin/sh

# addmacuser - Adds a new user to the system, including building the
# home directory, copying in default config data, etc.
# You can choose to have every user in his or her own group (which requires
# a few tweaks) or use the default behavior of having everyone put
# into the same group. Tweak dgroup and dgid to match your own config.

dgroup="guest"; dgid=31 # default group and groupid
hmdir="/Users"
shell="uninitialized"

if [ "$(/usr/bin/whoami)" != "root" ] ; then
echo "$(basename $0): You must be root to run this command." >&2
exit 1
fi

echo "Add new user account to $(hostname)"
echo -n "login: " ; read login

if nireport . /users name | sed 's/[^[:alnum:]]//g' | grep "^$login$" ; then
echo "$0: You already have an account with name $login" >&2
exit 1
fi

uid1="$(nireport . /users uid | sort -n | tail -1)"
uid="$(( $uid1 + 1 ))"

homedir=$hmdir/$login

echo -n "full name: " ; read fullname

until [ -z "$shell" -o -x "$shell" ] ; do
echo -n "shell: " ; read shell
done

echo "Setting up account $login for $fullname..."
echo "uid=$uid gid=$dgid shell=$shell home=$homedir"

niutil -create . /users/$login
niutil -createprop . /users/$login passwd
niutil -createprop . /users/$login uid $uid
niutil -createprop . /users/$login gid $dgid
niutil -createprop . /users/$login realname "$fullname"
niutil -createprop . /users/$login shell $shell
niutil -createprop . /users/$login home $homedir

niutil -createprop . /users/$login _shadow_passwd ""

# adding them to the $dgroup group
niutil -appendprop . /groups/$dgroup users $login

if ! mkdir -m 755 $homedir ; then
echo "$0: Failed making home directory $homedir" >&2
echo "(created account in NetInfo database, though. Continue by hand)" >&2
exit 1
fi

if [ -d /etc/skel ] ; then
ditto /etc/skel/.[a-zA-Z]* $homedir
else
ditto "/System/Library/User Template/English.lproj" $homedir
fi
chown -R ${login}:$dgroup $homedir

echo "Please enter an initial password for $login:"
passwd $login

echo "Done. Account set up and ready to use."
exit 0

How It Works
This script checks to ensure that it's being run by root (a non-root user would generate permission errors with each call to niutil and the mkdir calls, and so on) and then uses the following test to check whether the specified account name is already present in the system:

nireport . /users name | sed 's/[^[:alnum:]]//g' | grep "^$login$"

You've already seen in Script #93 that nireport is the easy way to interface with the NetInfo system, so it should be straightforward that this call generates a list of account names. It uses sed to strip all spaces and tabs and then uses grep to search for the specified login name, left rooted (^ is the beginning of the line) and right rooted ($ is the end of the line). If this test succeeds, the script outputs an error and quits.

The script also uses nireport to extract the highest user ID value in the NetInfo database and then increments it by 1 to generate the new account ID value:

uid1="$(nireport . /users uid | sort -n | tail -1)"
uid="$(( $uid1 + 1 ))"

Notice the use of the -n flag with sort to ensure that sort organizes its results from lowest to highest (you can reverse it with -nr instead, but that wouldn't work in this context), and then the use of tail -1 to pull off just the highest uid on the list.

The user is then prompted to enter a login shell over and over until either it's matched to an executable program or it's ascertained to be an empty string (empty strings default to /bin/sh as the login shell):

until [ -z "$shell" -o -x "$shell" ] ; do
echo -n "shell: " ; read shell
done


And finally we're ready to create the actual account in the NetInfo database with niutil. The first line creates an entry for the account in NetInfo, using -create, and the subsequent account attributes are added with -createprop. Notice that a special _shadow_passwd field is created, though its value is left as null. This is actually a placeholder for the future: NetInfo doesn't store the encrypted password in a secret place. Yet.

Instead of using cp -R to install user files and directories into the new account, the script uses a Mac OS X-specific utility called ditto. The ditto command ensures that any files that might have special resource forks (an Aqua-ism) are copied intact.

Finally, to force the password to be set, the script simply calls passwd with the special notation that only the root user can utilize: passwd account, which sets the password for the specified account.

Running the Script
This script prompts for input, so no command flags or command-line arguments are necessary.

The Results
$ addmacuser
addmacuser: You must be root to run this command.

Like any administrative command, this one must be run as root rather than as a regular user. This is easily solved with the sudo command:

$ sudo addmacuser
Add new user account to TheBox.local.
login: gareth
full name: Gareth Taylor
shell: /bin/bash
Setting up account gareth for Gareth Taylor...
uid=508 gid=31 shell=/bin/bash home=/Users/gareth
Please enter an initial password for gareth:
Changing password for gareth.
New password:
Retype new password:
Done. Account set up and ready to use.

That's all there is to it. Figure 11-1 shows the login window with account gareth as one of the choices.


Figure 11-1: Login window with Gareth's account included
Hacking the Script
Probably the greatest adjustment that might be required for this script is to change the group membership model. Currently the script is built to add all new users to the guest group, with the group ID specified as dgid at the beginning of the script. While many installations might work fine with this setup, other Mac OS X sites emulate the Linux trick of having every user in his or her own group. To accomplish that, you'd want to add a block of new code that auto-generates a group ID one value higher than the largest group ID currently in the NetInfo database and then instantiates the new group using the niutils command:

niutil -create . /groups/$login


Another nice hack might be to automatically email new users a welcome message, so that when they first open up their mailer there are some basic instructions on how to work with the system, what the default printer is, and any usage and network access policies.