4

Before updating to Catalina (10.15.1) my agent based on a bash script was working perfectly.

I had set the agent in order to execute the bash script on boot. Here is the plist file:

<?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>mark.battery.info</string>
        <key>Program</key>
        <string>/Users/mark/Dropbox/Exec/BatteryInfoOnBoot.sh</string>
        <key>RunAtLoad</key>
        <true/>
    </dict>
</plist>

After the last update I noticed that the script (BatteryInfoOnBoot.sh) was no longer loaded at boot and if I execute the command:

launchctl list | grep mark
-   1   mark.battery.info

I read that the status 1 means:

PID - Status 1 means that the process doesn't have enough privileges to access some files

ref

The script BatteryInfoOnBoot is very simple:

#!/bin/bash

echo "~~~~~~~~~"  >> /Users/mark/Documents/battery-log.txt
date  >> /Users/mark/Documents/battery-log.txt
/Users/mark/Dropbox/Exec/BatteryInfo.sh >> /Users/mark/Documents/battery-log.txt

instead BatteryInfo.sh is used only to get some useful info:

#!/bin/bash

echo "Current battery percentage:"
pmset -g batt | grep -Eo "\d+%" | cut -d% -f1
echo ""

echo "Cycle count:"
system_profiler SPPowerDataType | grep "Cycle Count"
echo ""

echo "Capacity stats:"
ioreg -brc AppleSmartBattery | grep -i capacity | grep -v Legacy | grep -v BatteryData | grep -v AppleRaw
echo ""

PS: the script is still working if I exec it manually:

/Users/mark/Dropbox/Exec/BatteryInfoOnBoot.sh

Any suggestions?

2 Answers 2

6

/Users/mark/Documents/ is a protected user folder (as Downloads and Desktop).

Usually you can add applications to System Preferences > Security & Privacy > Privacy > Full Disk Access to enable access to these folders. Terminal is already added probably - the reason why no error is shown executing the script manually.

Change the destination of the log file in the shell script BatteryInfoOnBoot.sh to /Users/mark/Library/Logs/ and it will work.

Proof (Virtual machine - no battery inside ;-)):

user@host ~ % cat Library/Logs/battery-log.txt 
~~~~~~~~~
Tue Dec  3 01:23:55 CET 2019
Current battery percentage:

Cycle count:

Capacity stats:

As an unwanted alternative (because even a-typical log files belong to ~/Library/Logs/ or /Library/Logs/) you can keep your original file as it is but you have to add /bin/launchctl to System Preferences > Security & Privacy > Privacy > Full Disk Access then.

Proof (still no battery inside):

user@host ~ % cat Documents/battery-log.txt 
~~~~~~~~~
Tue Dec  3 01:32:30 CET 2019
Current battery percentage:

Cycle count:

Capacity stats:
2
  • thanks thanks thanks everything is working fine now! Recap: System Preferences > Privacy & Security > Security > Full Disk Access to enable Terminal app + file plist in ~/Library/LaunchAgents + output file in ~/Library/Logs, while bash scripts can remain in Documents (sub)folder :-)
    – suikoy
    Commented Dec 3, 2019 at 20:54
  • @suikoy I tested it with the plist in /Library/LaunchAgents.
    – klanomath
    Commented Dec 3, 2019 at 20:57
1

For me, what worked--

I didn't have much luck with the process recommended here by others (giving full access)- I wanted to call an rsync periodically to backup a critical working folder to another (non-mac) server.

Even when I gave cron and launchctl and launchd and rsync (I tried all) to the 'full access', I was getting errors indicating that the process (rsync) didn't have access to the appropriate file path. (there is probably some other process that I'm missing) -- I also don't like the idea of giving things like 'cron' full access in case something malicious gets installed into my crontab.

What finally worked for me was somewhat insane, but I wrote a quick Mac Application (Or really, a swift "command line tool" as Xcode calls it), which just executed the necessary shell command I wanted anyway- - and modified my user agent to call the compiled swift app -- then the OS would prompt me to grant access to ~/Documents for the swift app, which then kicked off the shell script (Which then called rsync) -- whew.

My swift App is about 5 lines:

//localrsync
//main.swift

import Foundation

let task = Process()
task.launchPath = "~/bin/backDocsRsync.sh"
task.launch()
task.waitUntilExit()
print(task.terminationStatus)

and then I just put the compiled product from Xcode into my ~/bin folder, and pointed my UserAgent at that:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>local.backdocs.rsync</string>
    <key>ProgramArguments</key>    
    <array>
        <string>~/bin/localrsync</string>
    </array>
    <key>StartInterval</key> 
    <integer>7200</integer>
    <key>StandardErrorPath</key>
    <string>~/Library/Logs/rsync.log</string>
    <key>StandardOutPath</key>
    <string>~/Library/Logs/rsync.log</string>
</dict>
</plist>

(and yes- I know there are 'better' ways than rsync, but this works well for the combination of different hardware and OSs I happen to have available)

To do all of this, you need Xcode installed, or at least available, which requires at least a free Apple Developer account, and maybe some other things. I'm not sure whether you can compile self-signed Mac apps without paying for a developer provisioning profile or not, so if you really want to do this, I'm not sure what to recommend.

1
  • Thanks! This worked great for me. I ended up writing a simple C app rather than Swift that just fork/exec'd the bash script, but same concept. I also granted it Full Disk Access rather than waiting for pop ups. But same concept, and means I can limit the permission grant to just this app.
    – c-had
    Commented Dec 15, 2021 at 19:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .