5

I'm trying to get a script to run every day at the same time to restart a Mac. I cannot use the built in schedule feature in the energy saving preference panel because I have apps running that prevent a normal restart. If I initiate the restart via the shutdown command in terminal it forces the restart regardless of which apps might be preventing it.

I'm really new to shell scripting and launchd so I'm really as a novice level so please keep that in mind. This is my first post on here but I have periodically visited over the years and normally found the answer I was looking for.

Having tried various things I've kind of reached a bit of a road block. Where I'm at is that I have a .plist file in my /Library/LaunchDaemons folder that points to a script that should run at a certain time. I initially tried writing the .plist file in bbedit using code found on an online tutorial. It always failed to load into launchctl (either manually or via a restart). I then tried using the plist editor in Xcode and that seems to have worked better.

Having done that, the plist loads into launchctl. It also seems to try to run the script at the specified time. I've looked at the console and it seemsthat the Mac does try to run the script but it fails with this error:

com.apple.xpc.launchd[1] (com.apple.restart.sh[940]): Service exited with abnormal code: 1

I've tried running the script directly from the terminal eg sh scriptname and the Mac restarts.

This is the script:

#!/bin/bash
shutdown -r now

I have the script saved in the /Library/Scripts folder.

This is the contents of the .plist:

<?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>com.apple.restart.sh</string>
    <key>Program</key>
    <string>/Library/Scripts/com.apple.restart.sh</string>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>9</integer>
        <key>Minute</key>
        <integer>50</integer>
    </dict>
</dict>
</plist>

I'm now really at a loss as to what is going wrong because the .plist seems to successfully load into launchctl and the script tries to run at the specified time but fails. If the script is run manually it works.

My best guess is this is something to do with permissions or ownership but that's just my hunch. I've tried running chown root:wheel on the files. That changed the error to:

abnormal code 78

Any help would be gratefully received.

Edit following requests from https://stackoverflow.com/users/2836621/mark-setchell

Latest plist code here:

<?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>com.erithacus.restart</string>
    <key>Program</key>
    <string>/Library/Scripts/com.erithacus.restart.sh</string>
    <key>StandardOutPath</key>
    <string>/tmp/com.erithacus.restart.stdout</string>
    <key>StandardErrorPath</key>
    <string>/tmp/com.erithacus.restart.stderr</string>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>11</integer>
        <key>Minute</key>
        <integer>10</integer>
    </dict>
</dict>
</plist>

Latest script contents:

#!/bin/bash

/sbin/shutdown -r now >>/tmp/shutdown.log 2&>1

The contents of the .stdout file is empty.

The contents of the .stderr is as follows: /Library/Scripts/com.erithacus.restart.sh: line 3: 1: Read-only file system

Could this be to do with macOS Catalina having the OS on a read only partition?

EDIT 2. A solution.

Final plist file (com.erithacus.restart.sh) contents (had to be made using Xcode though):

<?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>com.erithacus.restart</string>
    <key>Program</key>
    <string>/Library/Scripts/com.erithacus.restart.sh</string>
    <key>StandardOutPath</key>
    <string>/tmp/com.erithacus.restart.stdout</string>
    <key>StandardErrorPath</key>
    <string>/tmp/com.erithacus.restart.stderr</string>
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>10</integer>
    </dict>
</dict>
</plist>

(obviously change the time under StartCalendarInterval to suit)

This plist is saved in /Library/LaunchDaemons

Final script (com.erithacus.restart.sh):

#!/bin/bash

date +"%F %T Shutting down"

/sbin/shutdown -r now

Script is saved in /Library/Scripts

The key, and I think, fundamental thing that changed from the original is that I changed the ownership of the plist file to be root using sudo chown root [plist file name].

Then I had to load it into launchctl as root, ie sudo launchctl load [plist file name]. The weird thing is that when loaded this way, it doesn't show up with the command launchctl list but does if sudo launchctl list.

This resulted in a successful timed restart of the machine by the system which overrides any apps that might otherwise prevent a restart. This is helpful for a server that I want to restart each night at 5am (so obviously the times in the plist file will be changed now to run at that time).

Particular thanks to @marksetchell who's error logging suggestions helped me to figure this out.

Erithacus
  • 51
  • 1
  • 1
  • 5
  • Which account does the script run under? Can you add debugging? (Like `shutdown -r now >>/tmp/shutdown.log` and make sure the user who runs the script has write access to that file.) – tripleee Jul 30 '20 at 09:36
  • Also, probably your own scripts should not look like they belong to `com.apple` – tripleee Jul 30 '20 at 09:36
  • Thanks, I'll give that a go. I've tried various names and somewhere I read someone saying to try that. Agree, it is weird. Will try changing it to something else again as well. My understanding of launchd is that they run by the system as root so I'm not sure the user is relevant? At least, I'm trying to arrive at an arrangement where the user isn't important and it just runs regardless. – Erithacus Jul 30 '20 at 09:41
  • I would imagine that it runs as root but then there should not be an error so that's my line of speculation at this point. I _think_ there's a way with `launchd` to specify which user to run as. – tripleee Jul 30 '20 at 10:32
  • Oh and of course redirect standard error as well, otherwise the log will likely be empty. `shutdown -r now >>/tmp/shutdown.log 2&>1` ... sorry for failing to mention that. – tripleee Jul 30 '20 at 10:33
  • Edit: just read your most recent post after sending this. Will try (ignore rest of this comment). I added the log but the log file produced was completely empty. This is the full error that appears in the system.log: Jul 30 11:30:00 [machine name] com.apple.xpc.launchd[1] (com.erithacus.restart[1658]): Service exited with abnormal code: 1 – Erithacus Jul 30 '20 at 10:33
  • Could these issues be connected to macOS Catalina security features? – Erithacus Jul 30 '20 at 10:38
  • I tried the adjusted script for the log but the log file remains empty and wasn't edited with this most recent running of the script. – Erithacus Jul 30 '20 at 10:40
  • 1
    This is a warning to all future Googlers: redirecting `STDOUT` or `STDERR` from inside a shell script launched from `launchd` is a route to madness. Do not do it unless you already understand what `3>` does. Hopefully don't do it at all. As mentioned, `launchd` can redirect them for you. It's much happier if you go that way. – joshfindit Mar 30 '22 at 19:37

1 Answers1

3

A few things to check.


Firstly, make sure your script is executable with:

chmod +x /Library/Scripts/com.apple.restart.sh

Secondly, put the full path to shutdown in your script to make sure it can be found:

#!/bin/bash
/sbin/shutdown -r now

I found that by running:

which shutdown

Output

/sbin/shutdown

Third, make sure your script is straight ASCII text and not an RTF or Pages or MS-Word document:

file /Library/Scripts/com.apple.restart.sh

Output

/Library/Scripts/com.apple.restart.sh: Bourne-Again shell script text executable, ASCII text

Fourth, test your script outside of launchctl to make sure it runs stand-alone first:

/Library/Scripts/com.apple.restart.sh

Fifth, redirect the output like this in your plist file:

<key>StandardOutPath</key>
<string>/tmp/com.apple.restart.stdout</string>
<key>StandardErrorPath</key>
<string>/tmp/com.apple.restart.stderr</string>
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432
  • Thank you for such a thorough reply. I have been able to successfully run the script file from the terminal. Would that exclude some of your suggestions? Anyway, I checked the ASCII and the output is correct. – Erithacus Jul 31 '20 at 09:58
  • The steps are in order, so if you get to Step 4 you don't need to worry about anything in Steps 1..3. I presume it is still not working, so can you click `edit` under your question and add in your latest, greatest `plist` file, and also the contents of `/tmp/com.apple.restart.stderr` and corresponding `stdout` after you reboot your computer and load the launchctl job please? – Mark Setchell Jul 31 '20 at 10:03
  • You could also maybe a line to your shutdown script, just before you run `/sbin/shutdown` that does `date +"%F %T Shutting down"` – Mark Setchell Jul 31 '20 at 10:07
  • Edit: your last two comments appeared after I posted this. Will do as you've said. I've followed everything you've noted and this is the error from the .stderr log: /Library/Scripts/com.erithacus.restart.sh: line 3: 1: Read-only file system – Erithacus Jul 31 '20 at 10:12
  • One question, what does: date +"%F %T Shutting down" do? – Erithacus Jul 31 '20 at 10:14
  • Try it in Terminal. It just writes a message with the date and time. – Mark Setchell Jul 31 '20 at 10:15
  • Your `plist` file still uses `com.apple....` whereas your script is called `com.erithacus.restart.sh` – Mark Setchell Jul 31 '20 at 10:16
  • Please click `edit` under your question and make sure that you have the latest, greatest script and the latest, greatest plist and the latest, greatest `stdout/stderr`. – Mark Setchell Jul 31 '20 at 10:18
  • I changed them all... I think. Hopefully it all correlates with erithacus. Just editing the original question now. – Erithacus Jul 31 '20 at 10:18
  • I have a feeling given the error message, that it is to do with macOS Catalina security. Do you think I should just try having the script located somewhere else? – Erithacus Jul 31 '20 at 10:25
  • You could try that, but I would try removing the `>>/tmp/shutdown.log 2&>1` from your script first because the output is already redirected by `launchctl` to `StandardOutPath` – Mark Setchell Jul 31 '20 at 10:28
  • That was added as a suggestion by triplee in the comments under the original question. It didn't seem to work so no harm it removing it. – Erithacus Jul 31 '20 at 10:30
  • OK, updated contents of stderr and stdout:... was just posting this and the machine restarted!!! It was about 3 mins late though which is strange. Logs were lost as a result. Perhaps I should direct them to save somewhere not in the tmp folder? Anyway, the error came up saying 'NOT SUPER USER' but then the restart happened – Erithacus Jul 31 '20 at 10:37
  • And the script? – Mark Setchell Jul 31 '20 at 10:40
  • #!/bin/bash date +"%F %T Shutting down" /sbin/shutdown -r now – Erithacus Jul 31 '20 at 10:44
  • I'm just testing it again because it seemed surprising that it worked but 3 mins delayed – Erithacus Jul 31 '20 at 10:45
  • Really strange but nothing happening this time. I've waited 5 mins and no restart. Contents of the stderr file: `shutdown: NOT super-user` and of the stdout: `2020-07-31 11:45:00 Shutting down` – Erithacus Jul 31 '20 at 10:51
  • OK, I think I've cracked it. Although the script would run from the terminal, I found that all the other .plist files in `/LaunchDaemons` were owned by root. I changed the ownership of it using `sudo chown root [script name]`. I then loaded it using `sudo launchctl load [script name]`. Strangely when loaded like this it isn't listed when doing `launchctl list` unless that command is run as `sudo launchctl list`. Thank you for all your help @marksetchell – Erithacus Jul 31 '20 at 11:16