15

Is there any way to run a script only at shutdown?

I mean, only when the computer is really shutting down to off state. This script should not run when doing just a log off or restart.

Bach
  • 5,661
  • 6
  • 25
  • 57
Audio01
  • 153
  • 1
  • 1
  • 5

4 Answers4

25

Few days ago I published on github a configuration/script able to be executed at boot/shutdown.

Basically on Mac OS X you could/should use a System wide and per-user daemon/agent configuration file (plist) in conjunction with a bash script file. This is a sample of the plist file you could use:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>boot.shutdown.script.name</string>

<key>ProgramArguments</key>
<array>
  <string>SCRIPT_PATH/boot-shutdown.sh</string>
</array>

<key>RunAtLoad</key>
<true/>

<key>StandardOutPath</key>
<string>LOG_PATH/boot-shutdown.log</string>

<key>StandardErrorPath</key>
<string>LOG_PATH/boot-shutdown.err</string>

</dict>
</plist>

You can place this file into /Library/LaunchDaemons. There are many directories where the plist file could be placed, it depends from what you need, the rights of the process and so on.

~/Library/LaunchAgents         Per-user agents provided by the user.
/Library/LaunchAgents          Per-user agents provided by the administrator.
/Library/LaunchDaemons         System wide daemons provided by the administrator.
/System/Library/LaunchAgents   Mac OS X Per-user agents.
/System/Library/LaunchDaemons  Mac OS X System wide daemons.

This script boot-shutdown.sh will be loaded and executed at every boot/shutdown.

#!/bin/bash
function shutdown()
{

  # INSERT HERE THE COMMAND YOU WANT EXECUTE AT SHUTDOWN OR SERVICE UNLOAD

  exit 0
}

function startup()
{

  # INSERT HERE THE COMMAND YOU WANT EXECUTE AT STARTUP OR SERVICE LOAD

  tail -f /dev/null &
  wait $!
}

trap shutdown SIGTERM
trap shutdown SIGKILL

startup;

Then call launchctl command which load and unload daemons/agents.

sudo launchctl load -w /Library/LaunchDaemons/boot-shutdown-script.plist
freedev
  • 17,230
  • 4
  • 83
  • 98
  • It works well on el capitan. But my xcode7 crash on el capitan then i go back to yosemite, the script not work on yosemite. I have to use LogoutHook to run shutdown function. – Anh Bảy Oct 10 '15 at 03:42
  • it is not working for shutdown on el-capitan it work only for startup, is i am missed something – Sangram Shivankar May 25 '17 at 06:03
  • Try to post your issue here https://github.com/freedev/macosx-script-boot-shutdown – freedev May 25 '17 at 07:46
  • You might rather do this instead of tail -f /dev/null : mkfifo /tmp/wait-fifo; read < /tmp/wait-fifo I think tail -f does an active polling which is kind of wasteful. – user914330 Mar 29 '18 at 14:18
3

It looks like the most straightforward way would be to write a small C++ application that would run as a daemon with launchctl, catch the shutdown notification but ignore the reboot notification (see below) and then call whatever is given to it as arguments, e.g. a shell script. It does not look like Apple provides libraries to catch those notifications in any other language.

From the "Kernel Programming" manual https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/KernelProgramming/KernelProgramming.pdf from Apple, page 150:

"Although OS X does not have traditional BSD-style shutdown hooks, the I/O Kit provides equivalent functionality in recent versions. Since the I/O Kit provides this functionality, you must call it from C++ code."

"To register for notification, you call registerSleepWakeInterest (described in IOKit/RootDomain.h) and register for sleep notification. If the system is about to be shut down, your handler is called with the message type kIOMessageSystemWillPowerOff. If the system is about to reboot, your handler gets the message type kIOMessageSystemWillRestart. If the system is about to reboot, your handler gets the message type kIOMessageSystemWillSleep."

As you can see there is a different message for reboot, so you can handle the shutdown case exclusively.

Oleg Sklyar
  • 8,393
  • 3
  • 35
  • 53
  • 1
    This would be the clean way of accomplishing it, but much more complex. – Audio01 Jun 14 '14 at 00:57
  • Two years from the date of my answer I would suggest go in place of C++ to make matters simpler... – Oleg Sklyar Oct 19 '16 at 19:21
  • @olegsklyar, Hi, I've tried to use this registration from the driver but it seems like my callback function doesn't called in ShotDown and Restart events, perhaps you also encountered with similar issue ? – Irad K Nov 30 '17 at 13:35
2

Here is a way that does work (I just tested it) but it is quite technical and not for inexperienced people... I put a wrapper around /sbin/shutdown. This will work even if you shutdown your Mac from the Apple menu in the GUI.

Basically, you need to su to root, like this, and rename the existing, Apple-supplied shutdown binary to shutdown.orig.

su -
cd /sbin
mv shutdown shutdown.orig

Then you create a bash script called shutdown that does what you want first, then execs the original Apple-supplied shutdown binary.

#!/bin/bash
Do something you want done before shutdown
exec /sbin/shutdown.orig "$@"

There are three things to watch out for...

1. Make all the permissions the same on shutdown as shutdown.orig
2. Parse the parameters to the originl shutdown and see if `-r` is one of them as this means it is a `restart` shutdown. You will also have to pass through the other parameters that Apple calls the script with - if any.
3. Apple may feel at liberty to overwrite your lovely, shiny, new `shutdown` script when updating OSX, so maybe abstract out the bulk of your personal shutdown script into another place so that you can easily re-insert a single-line call to it if/when Apple overwrites it at some point.

Be careful! And make a backup first!

Mark Setchell
  • 146,975
  • 21
  • 182
  • 306
  • 1
    This is the dirty way for accomplishing it, in a closed "controlled" enviroment (i.e. no updates) it should work ok. – Audio01 Jun 14 '14 at 00:56
  • 3
    @Audi01 Dirty? Dirty? I prefer "slightly unwashed but effective" :-) – Mark Setchell Jun 14 '14 at 08:18
  • 1
    When El Capitan is released, this will no longer work. `shutdown` is in `/sbin` which is protected by rootless. – Martijn Bleeker Jul 02 '15 at 11:19
  • @TheBleacher Aha - so point #3 has come true! Thank you for the update. Can you supply any links for folks to refer to and understand please? – Mark Setchell Jul 02 '15 at 11:22
  • I'm afraid the documentation is lacking at the moment. The only thing I could find: System Integrity Protection, also called "rootless," is a new system security feature that prevents the user or any process from writing in system-protected folders. This list includes /System, /bin, /usr (but not /usr/local), and /sbin. (from: http://arstechnica.com/apple/2015/06/preview-os-x-el-capitans-first-beta-is-a-promising-heap-of-refinements/4/) – Martijn Bleeker Jul 02 '15 at 11:43
  • @MarkSetchell : Will thos solution wok for other OS like linux /Windows as well ? – rose Oct 18 '16 at 04:09
0

In case someone comes to this answer like me googling for similar but somewhat different problem.

Due to CopyQ crash bug that I found https://github.com/hluk/CopyQ/issues/1301 I had to write a cron script which will reload if it crashes. But I did not want to reload copyq, if copyq exits during shutdown or restart

I ended up using

if ps aux | grep "Finder.app" | grep -v grep
then
    echo "I am still running"
else
    echo "I am in shutdown"
fi

To detect if system is in shutdown, (you can use Chrome.app or any other always running app depending on your need)

not exactly the solution, but helped me solve a similar problem that I was having

zainengineer
  • 10,877
  • 4
  • 35
  • 26