sudo
is very useful when working interactively in the shell. However, when you are scripting workflows then you don’t want to encounter the interactive prompt for authentication.
- Part 1: Demystifying
root
- Part 2: The
sudo
Command - Part 3:
sudo
and Scripting (this post) - Part 4: The Authorization Database
sudo
in Scripts
You will write scripts that require root
privileges to perform their tasks. Many novice scripters will simply add the sudo
command to their scripts. For example:
#!/bin/bash
timeserver="time.apple.com"
echo "Setting Time Server to: $timeserver"
# note: sudo is superfluous
sudo systemsetup -setnetworktimeserver "$timeserver"
sudo systemsetup -setusingnetworktime on
When you invoke this script from the command line, it will actually work as expected, since the first sudo
will prompt the password and the second sudo
will use the cached credentials.
In many cases where the script already has root
privileges, it will work also, because sudo
when run as root
will not prompt for authorization again.
However, when this script is sent with Remote Desktop or run in a different context without user interaction to authorize sudo
, the script will stall and eventually fail.
In most contexts, the scripts should already be running with root
privileges so sudo
in the script is not necessary.
#!/bin/bash
timeserver="time.apple.com"
echo "Setting Time Server to: $timeserver"
systemsetup -setnetworktimeserver "$timeserver"
systemsetup -setusingnetworktime on
You should not use sudo
in your management scripts. When commands inside a script require root
privileges, you should invoke the entire script with sudo
:
$ sudo ./settimeserver.sh
or you deploy and execute the script through other methods that provide root
privileges. (Remote Desktop: run as user root, installation scripts, LaunchDaemons, management systems, etc.)
Testing for root
Privileges in Scripts
When you write scripts that require root
privileges, you may want to test whether it is actually running as root
early in the script or exit gracefully with an error or warning. As usual with Unix, there are many ways to achieve this, but the recommended one is to check the EUID
(effective user id) environment variable. For the root
user the EUID
is 0
.
#!/bin/bash
# test if root
if [[ $EUID -ne 0 ]]; then
>&2 echo "script requires super user privileges, exiting..."
exit 1
fi
# continue with important things here
echo "I am root"
Running a Process as Another User
Most administrator scripts are run in a context where they run with super user privileges. Often they require root privileges.
On the other hand, when you need to affect settings or processes in a user’s context or login session, you may need to run commands as a specific user from a script running as root
. It’s the opposite problem of gaining root
privileges.
Update: 2020-08-25 macOS has changed and I had a few things to add. Rather than keep modifying this post, I decided to make a new post with some updated code.
You can use sudo -u user command
or su user -c command
to run a command as a different user. However, the launchd
man page warns us:
On Darwin platforms, a user environment includes a specific Mach boot strap subset, audit session and other characteristics not recognized by POSIX. Therefore, making the appropriate setuid(2) and setgid(2) system calls is not sufficient to completely assume the identity for a given user. Running a service as a launchd agent or a per-user XPC service is the only way to run a process with a complete identity of that user.
So it is safest use the Darwin/macOS native mechanism to launch a process as another user. Enter launchctl
. You can use the the launchctl asuser
verb to execute a script or command as a different user.
uid=$(id -u "$username")
launchctl asuser "$uid" /path/to/command arguments
The launchctl
command takes a user’s numerical ID or UID rather than the short name as an argument. You can get a user’s UID with the id -u username
command.
Note that launchctl asuser
does not launch the command in the context of a new shell environment. You cannot rely on environment variables being set in that context. This is especially relevant for the PATH
.
Note also that the asuser
verb of launchctl
is listed among the deprecated functions of launchctl
. However, there is no replacement for the functionality yet.
Getting the Current User
Most of the time you will not know which user you need to run as, when you write a script, but need to run as the ‘currently logged in user’. There are many unix-y ways of determining the current user. However, once again there are edge cases in macOS where some of the traditional methods fail. (Mainly concerning Fast User Switching)
Update 2019-09-04: I have changed the command to get the current user from the
python
based solution to thescutil
based solution. You can get more details why I now recommendscutil
in this post.
The ‘official’ method is to use the SystemConfiguration framework, and from a script this is easiest with scutil
:
loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ && ! /loginwindow/ { print $3 }' )
This will return the currently active user, even when multiple users are logged in with Fast User Switching. When no user is logged and the system is logging in the value returned will be ""
. Your scripts have to cover this case as well.
You can use this code snippet as a template:
# get the current user
loggedInUser=$( echo "show State:/Users/ConsoleUser" | scutil | awk '/Name :/ && ! /loginwindow/ { print $3 }' )
# test if a user is logged in
if [ -n "$loggedInUser" ]; then
# get the uid
uid=$(id -u "$loggedInUser")
# do what you need to do
launchctl asuser "$uid" /path/to/command arguments
fi
AppleScript
AppleScript has a special function when invoking shell commands for gaining super user privileges. When you run a shell command from AppleScript with the do shell script
command you can add with administrator privileges
to have the script prompt for admin user authentication and run the command as root
:
do shell script "whoami"
do shell script "whoami" with administrator privileges
Will run like this:
do shell script "whoami"
--> "armin"
do shell script "whoami" with administrator privileges
--> "root"
Like with sudo
the credentials for the first command run with administrator privileges will also be cached, so you will not get mulitple prompts, unless the script runs for a long time.
This is useful for providing tools and workflows from within AppleScript for interactive use. However, as with sudo
you have to keep in mind that some contexts in which a script may be run does not allow for user interaction and then you have to find other means of elevating privileges.
AppleScript is often used to communicate with other applications and/or the user interface. However, when run with root
privileges AppleScripts are often prohibited from connecting to other process or present user interface, such as dialogs. When you really need to do this, you have to use launchctl asuser
to change the user the script is run as:
launchctl asuser "$uid" /usr/bin/osascript -e 'display dialog "Do you really want to do this?"
Beyond the Shell
sudo
allows you to gain super user privileges from the interactive shell. LaunchDaemons, installer Packages, management systems and other tools will run scripts as root
when required. This covers many cases where administrators need to influence the system on macOS.
However, for most users, macOS is exclusively the graphical interface. Users can authorize to perform certain tasks when they are administrator users, like unlocking a Preference Pane or running an installer package.
These tasks are not controlled by sudo
but by a separate mechanism. The data for that mechanism is stored in the authorization database, which we will cover in the next post.
- Part 1: Demystifying
root
- Part 2: The
sudo
Command - Part 3:
sudo
and Scripting (this post) - Part 4: The Authorization Database