macOS Catalina: execute bash script on boot with launchctl
/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:
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.