Issue with shutdown command in script (MacOS High Sierra)

Hello,

I have a backup script that runs an rsync backup to an external drive. I use the script frequently on Windows and Linux and have installed it on a Mac. The script has an option to run shutdown after the backup has completed. Since backup can take hours to run, this is an option that is set when the script starts and is executed when the script is finished (usually long after I have gone to bed).

The script looks more or less like this,

#!/bin/bash

# read input to shutdown after script or not
echo " "
echo "do you want to shutdown after the backup is finished? (y/n)  "
read shutdown_response

...
backup stuff
...

# shutdown if asked to   
if [ "$shutdown_response" = "y" ]; then
   echo " "
   echo "shutting down"
   shutdown -s now
fi

# prompt to exit script if not shutting down
echo " "
echo "press enter to exit script"
read exit_response
sleep 1
exit

This works well on Linux and Windows cygwin. In MacOS, the backup works correctly, but I get an error from the shutdown command that I am not super user. This does not happen under Windows or Linux, I guess because I am running from an account with administrator. My user account on MacOS is administrator, but sudo appears frequently and apparently shutdown is one of the tasks that requires password authentication.

I can add a command to switch to super user, sudo su , so that when the user specifies that they want to shutdown they can enter their password (you have to use sudo to switch to su in MacOS, more or less).

That version looks like,

#!/bin/bash

# read input to shutdown after script or not
echo " "
echo "do you want to shutdown after the backup is finished? (y/n)  "
read shutdown_response

# shutdown if asked to   
if [ "$shutdown_response" = "y" ]; then
   echo "enter your password for shutdown"
   sudo su
fi

...
backup stuff
...

# shutdown if asked to   
if [ "$shutdown_response" = "y" ]; then
   echo " "
   echo "shutting down"
   shutdown -s now
fi
# prompt to exit script if not shutting down
echo " "
echo "press enter to exit script"
read exit_response
sleep 1
exit

It looks like sudo su opens a new subshell and so the script does not continue to run. I really can't see an easy way around this. Maybe I could have a calling script that would run two different versions of the backup script, one called with sudo and the other not, but I'm not sure about that.

Is there a standard way to handle this?

LMHmedchem

You don't need to sudo , just run it from the admin account on the Mac and move on to other tasks.

That way the script stays the same and you don't need to be bothered using sudo to run a simple, basic MacOS admin backup task.

1 Like

First off, i can only emphasize what Neo already said. This is the best solution from a practical POV. But since you may want to understand your system better i will try to explain what happened anyway:

More or less - but not quite.

First, yes, the shutdown command can only be issued by the root user. UNIX is a multiuser system at its core so the "default use case" it was built with in mind is some dozens of users using the same system at the same time. You don't want any of them to shut down the system whenever they feel like it in such a setting. This is why only root (or sometimes a special user group "wheel") is allowed to do it.

Now, everybody can - in principle - use the command su to switch the user. What su does is to open a subshell (you are correct in your assumption that it does) with the new rights and you are that new user as long as you do not leave this shell again. Notice that there are two (three) ways to run su :

su newuser
su - newuser
su - newuser -c command

Let us first talk about the difference between the first and the second: without the dash ("-") in between the environment of your own user is preserved. Variables like "PATH" and everything else you have set remains even though the user credentials change. With the dash, though, your environment is completely overwritten by the environment of the new user, effectively it is like doing a login as the new user - complete with executing the profile and whatever else is done during a normal login. In both cases, once you leave the subshell this is reverted back because all the changes are made within that subshell.

The last variant (which i have written only once but in fact it can be done in the with-dash- or the without-dash-fashion) does the same but within the resulting subshell only the specified command is executed and the subshell is left immediately afterwards. This way you can do single commands*) as a different user.

When you try a su command you are asked for the password of the user you try to switch to, just like you are asked for the password when you log in as that user.

Finally, what does sudo do? Well, certain commands (like su , but also others, shutdown , halt , ...) are deemed so security-relevant that one doesn't want to rely on all users being prevented to use them by just not knowing the relevant password. So all these commands would be made unavailable for all (normal) users at all. Nobody could use su any more. No, a select few users should be able to do it, though, and for this there is sudo . It allows certain users to run certain commands under different rights.

So, basically it goes like this: su is forbidden to use for everybody. But userX can use sudo and it will executed su for him as root (which can't be prevented from using it) so userX can still - with the help of sudo - execute su . UserY, for which this rule does not exist, can't execute su even though knowing the root password, because su is not allowed for anybody - he would not even be asked for that password. Which user is allowed to do what using sudo is laid down in a list of rules in the file /etc/sudoers .

Since many UNIX systems are effectively single-user these days (perhaps only you use your Mac and nobody else) it is common to add a sudo-rule for that user that allows for any program to be called via sudo and in turn being executed as root. So, this is where sudo shutdown comes from. sudo does not ask for the password of the user it runs the command as but the password of the user who invokes it - to make sure it is the user for whom to check if there is a rule allowing that.

No, you don't - it is just common. If you know the root password you most probably can do a

su - root

(notice that the "root" is implied if no user is specified so su - is the same) and you will be asked for the password of root and then become root. When entering

sudo su - root

you will be asked for your own password instead (by sudo , not by su ) and then sudo wil call su as root (no password question from su therefore) to do an su - root for you.

This results - as a side effect - in nobody having to know the root password any longer and that is a second reason why it is done that way. Usually the root password is generated by a software and then thrown away. So nobody can become root save for the "backdoor" by having a rule in sudo that does it for him. Since sudo rules can be broken and sudo will refuse to work if /etc/sudoers contains syntactical errors it is still a good idea to know the root password just in case. Simply switch to root using sudo and change the password to some value you know by using the passwd command. In big companies this is done too, usually by someone not from the administrator team. The password is then sealed and kept in a safe where you can obtain it should the need arise.

Finally, how should you do the backup: you should run the whole backup process as user root because this way you avoid the possibility that your user may not be allowed to access some data and hence cannot backup them. When you do it like this you can shutdown at the end or not without any additional password. Furthermore, instead of asking interactively, i would use an option to select an optional shutdown at the end:

mybackup -s   # shutdown at the end of the backup
mybackup      # no shutdown at the end of the backup

Interactive parts in script prevent them from being called by other scripts. If you use an option instead you could build some other script which as one step calls your backup script. You couldn't do that (actually you could but it would defy the reason for a script, namely to make things automatic) if it has interactive parts.

I hope this helps.

bakunin
________________
*) note that "single command" here includes scripts including several commands as well as lists of commands - to be precise this should read "list" instead, like i.e.:

su - newuser -c "command1; command2; command3"
1 Like

Thank you for taking the time to put together this informative post. I'm sure that others will read it as well.

Some of my issue was how to go about running from the admin account. The account I am in is already administrator , but I get permission denied when I try to run shutdown from a script. To authorize shutdown, I can run the script as, sudo scriptname , or I can su and then call the script.The default root account is disabled on many Mac installations, so you are not supposed to be able to change the user to root without activating the root account first. Please let me know if I am not correct about any of this.

One thing I was trying to accomplish was to have this all run without having to launch a terminal, navigate to the script location, and run the script. I can do that, but there are others who may use this machine who cannot. I wanted to set it up so that an Alias to the script could be double clicked on, the script launched in Terminal, and the user prompted for their password.

Apparently there is an issue in MacOS when you just try su. If you just do su you get an error even though you have entered your administrator password. If you do su root and enter your password, everything works. This is odd since the root account is supposedly not active.

This is an important point, especially in that the version of rsync that ship with MacOS is fairly old and seems to have some issues with metadata. If you run rsync with -E (preserve file execution permissions) and don't run as root, you get a whole long list of errors in your log file. If you are backing up your entire profile directory, there are allot of hidden system files there that will be skipped if you don't run as root.

This again goes back to not wanting to have to call the script from a Terminal. There are always choices to be made and so I guess there is no perfect solution.

This is one method I found that works. This is a little script that adds a username to /etc/sudoers and sets the user to be able to run sudo rsync without a password. You double click on the script, or script alias, enter the username, and enter your password. The proper entry is added to the sudoers list.

#!/bin/bash

# check if user exists in /etc/sudoers
# if not, add user and recheck
function add_user_and_check () {
   # check if the user entry already exists
   if sudo grep -q $user_name" ALL= NOPASSWD:/usr/bin/rsync" /etc/sudoers; then
      echo $user_name "already has the correct privileges"
   else
   # add the user
   sudo bash -c "echo \"$user_name ALL= NOPASSWD:/usr/bin/rsync\" >> /etc/sudoers"
      # recheck
      if sudo grep -q $user_name" ALL= NOPASSWD:/usr/bin/rsync" /etc/sudoers; then
         echo "success: the user was given rsync sudo privileges
      else
          echo "failure: the user was NOT given rsync sudo privileges
      fi
   fi
}

# get the username to add
echo "enter the username you want to add"
echo ""
read user_name

# get the password and call the function with sudo
echo "enter the administrator password"
echo ""
# call the function in a subshell as root
sudo su root -c "$(declare -f add_user_and_check)"; add_user_and_check
echo ""
echo "key \"Enter\" to exit script"
read exit_now
sleep 1
exit

My understanding is that because the function is declared and then called as root to run in a subshell, the instructions in the function can all run under root privileges. It is necessary to call the function instructions like grep and echo with sudo or you still get permission denied errors. My guess it that you could run the backup script as root using the same method, namely having all of the backup code in a function that is declared and called in the same way. You would start the script by double clicking on the script and then enter your password. The script function would then run as root. I don't think your function call other functions using this method, which is a drawback if true.

I opted for now to add the user to the sudoers list with $user_name ALL= NOPASSWD:/usr/bin/rsync so that rsync can run as root without a password. This has worked in tests so far in my tests.

LMHmedchem

Why are you feeding a function which calls sudo, into sudo? And then you don't even use it, just call it outside sudo afterwards.

Further, you really shouldn't be editing /etc/sudoers like that. sudo on most systems will refuse to operate after /etc/sudoers has been edited by anything but visudo.

Hi LMHmedchem...

Take a look at post #2 here, it might be of use:

This is mostly an exercise in ways to run a script with root privileges without a user needing to open a terminal, navigate to a directory, and either call the script with sudo or su root from the terminal and then call the script. These are actions that are beyond the abilities of a sizable number of users who still may need to perform tasks like running a backup as root. I am trying to create a script that can be run by double clicking on a desktop icon, entering a password when prompted, and have the script do the rest with the privileges it needs. I don't have any problem running a script as root, but that is not the case for every user.

The method I used in the script above prompts the user for a password when sudo is invoked for the function call, but the password is not requested for any of the commands in the function. The password is required once at the beginning and not again. This is the behavior I am looking for. The sudo command still needs to be in the commands in the function, or you get errors. This doesn't make sense to me because the intent was to run the function in a subshell as root. It does, however, run without error and gives the expected output.

I have confirmed with visudo that running the sudoers script does add the the correct line to the file. I don't notice any issues running sudo with other commands, but I need to dig a bit deeper to make sure that the backup script is actually running as root (directories like /Library are being copied) after making the sudoers edit the way I did above. I know that the script was running as root when I made the $user_name ALL= NOPASSWD:/usr/bin/rsync entry to sudoers with visudo but I haven't checked it carefully with the other method. The edit of the sudoers list is important in order to be able to run a scheduled unattended backup as root.

LMHmedchem

No, you are not really correct. I have 4 Macs which I work on extensively, and I do not use MacOS as you describe. When I login as "Neo", which is a user account with MacOS Admin rights, I do not use my "Neo" account to run scripts which need to run as root. I simply do this:

sudo -i

...then I am into a terminal window and if I need a cron file to run (for example) with superuser privs, I install a root crontab. I don't create a "Neo" crontab and then write a bunch of "goofy" (sorry, but that's the first word that comes to mind, it is not directed at you or your work) sudo wrappers around a "Neo crontab script" to force the "Neo" account do accomplish what I can easily do running the script as root in the root account.

As a side note, I think it was also you who, in a earlier post (LMHmedchem, but I might be mistaken as I've busy coding sorry and don't have time to go back and review your older posts, my bad) , was working "as an exercise" to figure out how to close the MacOS terminal window in a script when the shell exits, and I read (in reply to that post) a lot of people replying to help you "in your exercise" which was confusing to me because this is simply a " 10 second preferences" selection in the MacOS terminal config dialog box.

You seemingly are asking questions, "as exercises" to solve problems that do not exist and that's OK "as exercises" and I encourage you to do so in these forums and I encourage everyone to reply who has time to reply; but honestly, you seem to have the luxury of an abundance of free time on your hands if you need to create problems in order to solve them. That's OK too. However, let me be clear since you asked me:

I write a lot of small task-related scripts on MacOS and I work in a quite robust development environment on the Mac and my "main development box" is a 12 core Mac Pro, and if I need to run a script to do "root things", like a crontab script which needs root privs , I run this script as root, configured as the root user from the root account. Boom. Task done. Move on to the next task. I never run scripts as "Neo" wrapped in a lot of "sudo" code to accomplish tasks as the superuser which I can do as the root user directly. Sorry, but why would I waste my time doing that? I have a lot to do and never enough time to do it and if I worked 12 hours a day everyday, I would still not have enough time to so all the things I need to get done in development, so I need to get things done quickly and not turn 30 second tasks into days of exploring MacOS. Hey, that's just me and more power to you if you have a lot of free time to do these "turn a one minute routine task into days of Q&A" experience.

The same is true for the earlier post about "how to close a MacOS terminal window when the shell exits"... I have shells exiting in MacOS terminal windows all the time, and if I wanted or needed those terminal windows to close when the shell exits, I would configure the terminal preference to "close window on shell exit..."

MacOS is great and I would not develop code on any other platform. If you or anyone has the luxury of a lot of time to play around with MacOS and do things in a more "difficult" way than the "10 second routine way" then by all means, go for it; but for me (and you asked me directly), when I read your posts my "inner thoughts" are "wow, this guy has a lot more free time than I do because he spends days on tasks which I can do one minute" and you seem to enjoy it; so go for it!

I used to have free time like that many years ago. I'm kinda envious to be honest :slight_smile:

1 Like

Shutdown is a system-wide action, and requires root privileges in some UNIX systems (macOS included).

This means, if you want to have shutdown as an option in a script, you will need to give the script root privileges, sooner or later.

Assuming the script filename is "backupscript", you can run it like this:

sudo ./backupscript

If you place it in /usr/local/bin, you can run it like this:

 sudo backupscript

The following version of your script, is POSIX shell compliant, and it works with Bash too.

#!/bin/sh

# POSIX shell compliant.

# read input to shutdown after script, or not.
echo " "
printf "do you want to shutdown after the backup is finished? (y/n):  "
read -r shutdown_response


# ...
# backup stuff
# ...


# shutdown if asked to.
if [ "$shutdown_response" = "y" ] || [ "$shutdown_response" = "Y" ]
then

  shutdown -h now


else
  echo " "
  echo "exiting script"

  exit 0

fi

Now, if you want to double click on a script on your Desktop, you can do the following:

  1. Place backupscript in /usr/local/bin

  2. Create a script on your Desktop, named something like "runbackupscript", that contains:

#!/bin/sh
# POSIX shell compliant.

sudo backupscript

exit 0
1 Like

These scripts have always been run by double clicking on a launcher, shortcut, or alias. Command line arguments like sudo -i scriptname can generally be executed from a launcher or shortcut, but I don't know how to do that with an alias on MacOS.For myself, I can certainly run such a script from the terminal, but there are others who use this particular computer who wouldn't be very comfortable doing that. For that matter, I'm not sure how comfortable I would be having them fire up a terminal and start running commands under sudo. I have tried to make such scripts interactive so they just have to double click on the script and enter the information requested. What I was looking for was to be able to open the script by double click, enter the password, and then have the rest of the script run as root (shutdown was the first issue I ran into but there were others requiring root). I just wasn't sure how to do that. I suggested having a launcher script that would call the backup script as root, thus prompting for the password to execute the call. That didn't seem to generate any support, so I worked through my goofy code for calling a script function as root (I don't disagree with your assessment though it didn't really take very long). In his last post, johnprogrammer also suggested a launcher script, so maybe that is a good option to to this for users who aren't very good with a terminal.

This is another important issue in that I also have such scripts that are scheduled to run unattended. In MacOS, I use launchd to do that by,

-> create an appropriate plist for the task
-> copy the task.plist file to /Library/LaunchDaemons
-> change owner with sudo chown root:wheel task.plist
-> change permissions with sudo chmod 600 task.plist
-> add the task to the launchd queue sudo launchctl load /Library/LaunchDaemons/task.plist

Is this the launchd equivalent of a root crontab, meaning does the called script run as root in this scenario? Does the called script file also have to be owned by root or be in a specific location? Does the plist file need to have root specified as the user?

Well the simple answer there is that I didn't know that (about the terminal preferences). There are many tasks that are simple, or at least straightforward, after you know what to do. I have never seen a terminal that didn't close on the exit command and I have never seen that option in the terminal preferences before. None of the other posters in that thread seemed to know that off the top of their head either, thought I found it eventually. Most of the help I received in that thread was for the issue of the script not running in terminal on double click. This was also a minor issue but others seemed interested in why it wasn't happening when there was a proper shebang in place and the script was permissioned as executable.

This is all well and good if you happen to know the 10 second solution. If you don't, then you have to learn it like everyone else. Just because it takes me a long time to do some things doesn't mean that I prefer it that way.

I would like to be clear again about how much I appreciate the effort made by the member of this group. There are a great many things that I can do now that were well out of reach not that long ago. Those acquired skills have been very beneficial to my research even if they are not directly in line with what I do most days. I am afraid that I am not able to make much of a contribution here myself in that there are not many questions I can provide a competent answer to. I do give advice on many other forums where I have greater expertise and experience since I think it is important for users to contribute where they can do so effectively.

LMHmedchem

Some idea that flashed through my mind: If there are several (admin?) tasks that you'd like to offer to several users that have to be done in terminal: why not create a (restricted) shell with a small menu script offering the tasks? You could centralize common code into functions, run certain code snippets with elevated privileges, and so on...