defaults write help (Mac) - macos

I am trying to set a preference for Firefox to open in 32bit mode on 130 macs via Apple Remote Desktop. The easiest way to do this usually is with defaults write unfortunately I havent found any documention on this, and I cant figure out the syntax for writing a string thats not at the root level.
The file is ~/Library/Preferences/com.apple.LaunchServices.plist
I need to change the string x86_64 to i386
<dict>
<key>LSArchitecturesForX86_64v2</key>
<dict>
<key>org.mozilla.firefox</key>
<array>
<data>
Ym9va2QCAAAAAAQQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAYAEAAAwAAAABAQAAQXBwbGljYXRpb25zCwAAAAEB
AABGaXJlZm94LmFwcAAIAAAAAQYAAAQAAAAYAAAACAAAAAQDAABq
ix4BAAAAAAgAAAAEAwAAWYNjAQAAAAAIAAAAAQYAADwAAABMAAAA
CAAAAAAEAABBtfgZbgAAABgAAAABAgAAAgAAAAAAAAAPAAAAAAAA
AAAAAAAAAAAAAAAAAAEFAAAMAAAAAQEAAE1hY2ludG9zaCBIRAgA
AAAEAwAAAICcregAAAAIAAAAAAQAAEG0QBQYAAAAJAAAAAEBAAA0
NkFGNUYyOC1DNTExLTM0MEMtQkU1RS1DREYzMTgyQThFOTIYAAAA
AQIAAIEAAAABAAgA7z8AAAEACAAAAAAAAQAAAAEAAAABAQAALwAA
AAQAAAADAwAAAAAAIBkAAAABAQAAL0FwcGxpY2F0aW9ucy9GaXJl
Zm94LmFwcAAAAMwAAAD+////AQAAAAAAAAAQAAAABBAAACwAAAAA
AAAABRAAAFwAAAAAAAAAEBAAAHwAAAAAAAAAIBAAABgAAAAAAAAA
MBAAAEwAAAAAAAAAQBAAAGwAAAAAAAAAAiAAACQBAAAAAAAAECAA
AKQAAAAAAAAAESAAANgAAAAAAAAAEiAAALgAAAAAAAAAEyAAAMgA
AAAAAAAAICAAAAQBAAAAAAAAMCAAAJwAAAAAAAAAAdAAAJwAAAAA
AAAAENAAADABAAAAAAAAAP8AADwBAAAAAAAA
</data>
<string>x86_64</string>
</array>
</dict>

I don't think there's a way to do this with defaults (well, there is, but it involves dumping the entire LSArchitecturesForX86_64v2 dictionary, editing it, then reimporting). But PlistBuddy can do the job:
/usr/libexec/PlistBuddy -c "set :LSArchitecturesForX86_64v2:org.mozilla.firefox:1 i386" ~/Library/Preferences/com.apple.LaunchServices.plist
And the usual caveats before deploying anything to 130 computers: test this first (I tried it, once...), and have it back up the relevant file on each computer so if something does go sideways you can roll it back.

Related

macOS: Notarize in Script?

Because the codesigning and archiving by Xcode is time-consuming, boring and problematic, I've always codesigned, archived and shipped my Developer ID signed macOS app using the command-line tools xcodebuild, codesign, etc. via my own script. Notarizing looks like it is going to be a major pain. Is it possible to add notarizing to my script?
Yes. Unfortunately, the official answer leaves some loose ends, for example this important tidbit from Quinn "the Eskimo". Here is how to do it:
One-Time Setups
Get an App-Specific Password
Decide on a name for your "app" of notarizing apps. I use the name of my product-shipping script, SSYShipProduct.pl because this is the "app" which will use this password. We shall refer to whatever name you compose as your-notarizing-name.
Browse to https://appleid.apple.com/account/manage, scroll to Security > App-Specific Password, and generate an App-Specific password for an app named your-notarizing-name. Copy the password that it gives you. We shall call that app-specific-password.
Add the App-Specific password to your macOS Keychain
Run this command to add the password you just created to your keychain:
security add-generic-password -a "your-apple-ID-email" -w "app-specific-password" -s "your-notarizing-name"
The -s parameter is the name that this item will have in your Keychain. I think you could actually use a different name, but in my mind it makes sense to use your-notarizing-name here too.
You can verify that it worked by searching in the Keychain Access application. However, be aware that new items are not listed in Keychain Access until after you quit and relaunch it.
Maybe, get the relevant itc-provider
If your Apple ID is associated with more than one Apple Developer Connection team (such as if you do contract work), you will need the itc_provider of the team for which this app should be notarized.
To find the itc_provider of your team, execute this command:
/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/itms/bin/iTMSTransporter -m provider -u "your-apple-ID-email" -p "app-specific-password"
Scroll to the end of the output printed by this command and look at the Provider listing table. Copy the Short Name of the desired team. We shall call this "developer-team-itc-provider".
For each shipment (Scriptable!)
If you sign components of your app using the /usr/bin/codesign command line tool, each invocation of codesign must have the following new argument parameter , which tells codesign to sign with the so-called hardened runtime:
`--options runtime`
Conversely if your app is signed in Xcode, you must set the Build Setting Hardened runtime, available in Xcode 10 or later, to Yes in all executable component targets.
Other than that, your script should create a build of your app in Release configuration and codesign it, same as in pre-notarization days.
Upload to Apple Notary service
Your script should then archive your app to a .zip or .dmg. Note that this is an interim file which will only be uploaded to the Apple Notary service, not shipped.
Then, your script should compose a primary bundle ID value, which will be your app's bundle identifier with .zip or .dmg appended. Example: your-pbid-value = com.mycompany.YourApp.zip.
In what follows, your script will use altool, which is Apple's name for Application Loader Tool.
Your script should then run this command to get your .zip or .dmg notarized:
/usr/bin/xcrun altool --notarize-app --primary-bundle-id "your-pbid-value" --username "your-apple-id-email" --password "#keychain:your-notarizing-name" -itc_provider "developer-team-itc-provider" --file /path/to/YourApp.zip/or/YourApp.dmg --output-format "xml"
(Note that, in the above command, oddly, all argument names are preceded by two dashes except -itc_provider is preceded by only one dash. Also, if the scripting language you are using interpolates # characters in strings, code it to prevent interpolation of #keychain).
After a minute or so, xcrun will exit and print to stdout some XML which, if your submission was accepted (note: not approved yet), will look like this example:
<?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>notarization-upload</key>
<dict>
<key>RequestUUID</key>
<string>2ab59b26-19ec-4a30-84cf-6d2cb8d3c97e</string>
</dict>
<key>os-version</key>
<string>10.15.0</string>
<key>success-message</key>
<string>No errors uploading 'path/to/YourApp.zip'.</string>
<key>tool-path</key>
<string>/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework</string>
<key>tool-version</key>
<string>1.1.1138</string>
</dict>
</plist>
All you really need out of there is that RequestUUID value. However, since I ship four apps often, and because it ruins my day when my shipping script fails without providing helpful error information, and because (see below) you are going to make another call which also returns interesting XML, I invested some time in adding to my script a subroutine which takes two parameters, XML and a key path, and returns the value of the XML at a given key path. In the case above, I call this subroutine to get the RequestUUID, and then again to get the success-message.
(My script is in Perl. Although there is available in CPAN a module named XML::Simple which can do this parsing in a line or two, it is marked by the maintainer as not for use in new designs. So, to avoid needing to install and wrangle with a real XML parser, I opted instead to use PlistBuddy as suggested in the comment by #khuttun. This was slightly painful also because, unfortunately, altool does not have an option to write its output to a file, and PlistBuddy is not documented to accept stdin. So my subroutine writes the stdout from altool to a temporary file, and then passes that temporary file's path to PlistBuddy. Kind of disgusting, but it works.)
Delete the Un-stapled zip package
At this point, I recommend that your script delete the .zip or .dmg file which it uploaded. Reason: That file was archived from a product which does not yet have your notarization ticket stapled to it. At the end of your script, you will create a new .zip or .dmg from a modified app which has the ticket. Deleting the file immediately prevents you from shipping an un-stapled app by mistake.
Wait in a Loop for Apple's Response
Your script can then start pestering Apple's server for your final results, by running this command in a loop along with with some sleep:
`/usr/bin/xcrun altool --notarization-info --username "your-apple-id-email" --password "#keychain:your-notarizing-name" --output-format "xml"
If your script runs this command immediately, it will get returned in stdout some xml which will look something like this example:
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>notarization-info</key>
<dict>
<key>Date</key>
<date>2019-08-07T01:17:37Z</date>
<key>RequestUUID</key>
<string>4ba71353-9d99-4b52-b579-37f384717130</string>
<key>Status</key>
<string>in progress</string>
</dict>
<key>os-version</key>
<string>10.15.0</string>
<key>success-message</key>
<string>No errors getting notarization info.</string>
<key>tool-path</key>
<string>/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework</string>
<key>tool-version</key>
<string>1.1.1138</string>
</dict>
</plist>
The significant key path in there is notarization-info:Status, whose value in progress means that Apple is still working on your submission. After a few minutes usually (Apple says "should be less than an hour", but I experienced times of up to three and a half hours on the USA holiday afternoon of 2019-Jul-04), altool will retturn to your script a different xml in stdout, something like this:
<?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>notarization-info</key>
<dict>
<key>Date</key>
<date>2019-08-06T23:28:25Z</date>
<key>LogFileURL</key>
<string>https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma113/v4/f6/09/be/f609bee3-b031-323a-0987-d1f620a78758/developer_log.json?accessKey=1565410613_1722173034418364591_TvycjBAzd6FRTYGKZEFU6EwDfsws8Wa1MV%2FYnTiJ1zyOZamc%2FoeO5RMeIzZN669ZQJgO2Q4W48ipKNFO%2BQGuq%2FITXN8MQAetbNe90w9ogzqXbrzTHg%2FgYK89yvEFmiiRxhaVlZqLI93NBpY0hwBqXv2bvvlg%2FRCc%2BVaCNRJ%2BrnE%3D</string>
<key>RequestUUID</key>
<string>07fc3745-b0ff-4d1a-9b15-37f384717130</string>
<key>Status</key>
<string>success</string>
<key>Status Code</key>
<integer>0</integer>
<key>Status Message</key>
<string>Package Approved</string>
</dict>
<key>os-version</key>
<string>10.15.0</string>
<key>success-message</key>
<string>No errors getting notarization info.</string>
<key>tool-path</key>
<string>/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework</string>
<key>tool-version</key>
<string>1.1.1138</string>
</dict>
</plist>
After some reverse-engineering, you see that, in each loop iteration, your script should parse the XML and break out of the loop whenever the value of Status is something other than in progress, or if you prefer, when LogFileURL is defined. Or if you prefer email triggers, your script can look for an email from Apple with subject line You can now distribute your Mac software..
UPDATE 2019-11-02
After having trouble with this step in my last couple shipments, and again today, I have now confirmed a bug in Apple's Notary Service. The bug is that the altool --notarization-info command will fail for 1-5 hours, returning nonzero exit codes, and in stdout an error code 1519 "Could not find the RequestUUID", as in the following example stdout:
<?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>os-version</key>
<string>10.15.1</string>
<key>product-errors</key>
<array>
<dict>
<key>code</key>
<integer>1519</integer>
<key>message</key>
<string>Could not find the RequestUUID.</string>
<key>userInfo</key>
<dict>
<key>NSLocalizedDescription</key>
<string>Could not find the RequestUUID.</string>
<key>NSLocalizedFailureReason</key>
<string>Apple Services operation failed.</string>
<key>NSLocalizedRecoverySuggestion</key>
<string>Could not find the RequestUUID.</string>
</dict>
</dict>
</array>
<key>tool-path</key>
<string>/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/Frameworks/AppStoreService.framework</string>
<key>tool-version</key>
<string>4.00.1181</string>
</dict>
</plist>
This is a bug because, of course my script did submit the Request UUID which it just received from Apple Notary Service, Apple should be able to find it and, furthermore, when I kept sending the command manually, after about 2 hours, suddenly, the command returned Success and continued to return Success with subsequent commands, and I got the Success email from Apple. This delay happened today with 7 different good Request UUIDs, the longest was 5 hours. Possibly, at this time, there is a 1-5 hour delay between Apple Notary Service creating and sending you a Request UUID, and it appearing in the database which Apple Notary Service uses to respond to notarization-info requests, so you get this false error. Very sad.
Since I have no control over when Apple assigns people to fix bugs, I have modified this stage of my script to parse the response from Apple and die only if the command returns nonzero exit status and the code of the first (index=0) product-errors array entry is not 1519. If you are using PlistBuddy to parse XML as I am, the key path for that is code should be product-errors:0:code. The loop in my script prints each time Error 1519 is received, so I can see what is going on, and of course, I've modified its while condition to not exit if the error code is 1519.
After so fixing my script, I had several apps to ship. Apple Notary Service treated the first one nicely: No Errors 1519, and Success after about two minutes. The next one, however, needed this new feature of my script. At time 09:54 (HH:mm) my script received the Request UUID from Apple. 20 seconds later, it sent the first altool --notarization-info query. The response was a false Error 1519. Subsequent queries also returned false Errors 1519, for almost 3 hours, through 12:44. Then, at 12:45, all of a sudden it received an in progress response. After 5 more in progress responses, at 12:47, finally, Success.
One more thing before leaving this topic: An hour after that request succeeded with no Errors 1519, a prior request from an hour ago suddenly started returning in progress and then a few minutes later, Success. Conclusion: Request UUIDs which get detoured into the Error 1519 morass are not queued FIFO with later Request UUIDs which might, by chance, avoid the Error 1519 detour. So, a better workaround might be to abandon a Request UUID after receiving one more Error 1519 responses and start over by re-uploading the app to Apple Notary Service and getting another Request UUID which you hope will work better. Of course, you'll get many emails during the next few hours as all of the Request UUIDs which you abandoned eventually succeed.
At any rate, now, on to the next step in the script…
Check Apple's Log file
Your script should parse out the value of the LogFileURL so it can check the log, because because even if notarization succeeds the Log file created by Apple might contain warnings. To get the Log file your script should, of course,
curl <LogFileURL-Value>
The Log file is apparently JSON. Warnings or Errors are presented as an array, which is the value of key issues . So your script should parse that curl output with a JSON parser and if the value of key issues is a JSON null or an empty array, continue shipping.
Staple the Ticket to your App
This step is pretty easy…
xcrun stapler staple /path/to/YourApp.app
Running this command will add to your app's package a new file: YourApp.app/Contents/CodeResources. This is apparently your notarization ticket. Note that this file is in addition to the file YourApp.app/Contents/_CodeSignature/CodeResources which is still there, and contains the code signature, the same as in pre-notarization days.
Verify the Ticket Stapling
But there is a better way to verify that your app now has a good ticket. Your script should now run (or re-run) a Gatekeeper check:
spctl -a -v /path/to/YourApp.app
The result, in stderr, should be,
/path/to/YourApp.app: accepted
source=Notarized Developer ID
which is the same result as pre-notarization, except for the insertion of Notarized. Astute scripts will parse that stderr and abort shipping if the above words are not detected.
Zip and Ship
Now that the ticket has been added, your script can zip or dmg your .app again, but this time, ship it.
Here's a reusable and freely-licensed notarize & staple script for automated builds:
https://github.com/rednoah/notarize-app/blob/master/notarize-app
It'll run and wait and only exit once everything is done:
Run altool --notarize-app
Run altool --notarization-info periodically until notarization is complete
Run stapler staple
Here's an example auto-notarizer I wrote for RawTherapee. First we notarize the app:
https://github.com/Beep6581/RawTherapee/blob/6fa533c40b34dec527f1176d47cc6c683422a73f/tools/osx/macosx_bundle.sh#L225-L250
Then we notarize the dmg:
https://github.com/Beep6581/RawTherapee/blob/6fa533c40b34dec527f1176d47cc6c683422a73f/tools/osx/macosx_bundle.sh#L283-L307
The notary credentials are passed thru the cmake command directive
-DNOTARY="--username user#mail.com --password abcd-efgh-ijkl-mnop"

How can I remap my OSX keyboard without using third-party programs?

I currently am using Seil to remap caps-lock to escape on OSX 10.11. Whenever I check htop just to see what is running when the system isn't doing much of anything (programs open but not doing much processing), Seil is always up there and fluctuating in its cpu and memory use. It probably isn't a big deal, but it feels crazy to have to always run this thing considering I only use vim for occasional flat text files. I don't want to have to start Seil every time I use vim. Is my best option to just disable caps lock and physically attach a bridge up to the escape key? Vim comes standard on OSX and is unusable without remapping escape to something, so I would think this would come up instead of relying on someone else to make an app for it.
Remap Caps Lock to Control
You can remap the caps lock key to the control key in the keyboard settings (click on the Modifier Keys button).
In vim, an alias for esc is control-[, which is much more accessible than reaching for the esc key.
Remap Caps Lock to Escape in Vim
Navigate to the Library/Preferences/ByHost directory under the home folder
cd ~/Library/Preferences/ByHost
Identify the hidden property list file
ls -a | grep Global
Assign it to a shell variable for easy referencing later
pfile=$(ls -a | grep Global)
Make sure the newly created variable pfile has the right value. It should look something like this: .GlobalPreferences.7D7C488E-0E83-5562-B58B-011C540890F3.plist
echo $pfile
Make a backup just in case something goes horribly wrong
cp $pfile{,.bak}
Copy the pfile to the home directory and convert it to xml
cp $pfile ~/keys-binary.plist
plutil -convert xml1 -o ~/keys-xml.plist ~/keys-binary.plist
Open the file and search for the text HIDKeyboardModifierMappingSrc and HIDKeyboardModifierMappingDst. Set the value in between the integer tags under HIDKeyboardModifierMappingDst to 5 and under HIDKeyboardModifierMappingSrc to 0, as follows.
<key>com.apple.keyboard.modifiermapping.1452-588-0</key>
<array>
<dict>
<key>HIDKeyboardModifierMappingDst</key>
<integer>5</integer>
<key>HIDKeyboardModifierMappingSrc</key>
<integer>0</integer>
</dict>
</array>
Save, then convert back to a binary file
plutil -convert binary1 -o ~/keys-binary.plist ~/keys-xml.plist
Finally, copy this file back to the ByHost directory, replacing the original file.
You'll need to log out and back in before the changes take effect. Once you have logged back in, add the following line to your .vimrc:
noremap <Esc>Op <Esc>
noremap! <Esc>Op <Esc>
This is going to work if you open vim in the terminal. If you are using gvim, try out Solution 2 by eelco which I referenced to write this post with a couple of different mappings to make it work in the terminal.
Note also that, for this to work, the Allow VT100 application keypad mode option in your Terminal Preferences must be checked.

Launch shell script on login in Mac OS (OS X)

I have this shell script Test.sh:
#! /bin/bash
FILE_TO_CHECK="/Users/test/start.txt"
EXIT=0
while [ $EXIT -eq 0 ]; do
if [ -f "$FILE_TO_CHECK" ]
then
/usr/bin/java -jar myapp.jar
EXIT=1
else
sleep 30
fi
done
I need to start this script automatically after login.
So I put it inside a folder Test in /System/Library/StartupItems/
When I reboot the Mac, nothing happens after I log in.
Any clue?
I also tried Automator, but with the same result: the java program is not running.
Ivan Kovacevic's pointers, especially the superuser.com link, are helpful; since at least OS X 10.9.2, your options for creating run-at-login scripts are:
Note: The methods are annotated with respect to whether they are:
specific to a given user ("[user-SPECIFIC]"); i.e., the installation must be performed for each user, if desired; scripts are typically stored in a user-specific location, and root (administrative) privileges are NOT required for installation.
effective for ALL users ("[ALL users]"); i.e., the installation takes effect for ALL users; scripts are typically stored in a shared location and root (administrative) privileges ARE required for installation.
The scripts themselves will run invisibly, but - with the exception of the com.apple.loginwindow login-hook method - you can open applications visibly from them; things to note:
There is no guarantee that any such application will be frontmost, so it may be obscured by other windows opened during login.
If you want to run another shell script visibly, simply use open /path/to/your-script, which will open it in Terminal.app; however, the Terminal window will automatically close when your script terminates.
Automator [user-SPECIFIC]:
File > New, type Application
Add a Run Shell Script action, which adds an embedded bash script, and either paste your script code there or add a command that invokes an existing script from there.
Save the *.app bundle and add it to the Login Items list in System Preferences > User & Groups > Login Items.
Note:
The embedded script runs with the default "C" locale.
$PATH is fixed to /usr/bin:/bin:/usr/sbin:/sbin, which notably does NOT include /usr/local/bin
The working dir. is the current user's home directory.
com.apple.loginwindowlogin hook [ALL users - DEPRECATED, but still works]:
If you have admin privileges, this is the easiest method, but it is DEPRECATED, for a variety of reasons (security, limited to a single, shared script, synchronous execution); Apple especially cautions against use of this mechanism as part of a software product.
Place your script, e.g., Test.sh, in a shared location - e.g., /Users/Shared - and make sure it is executable (chmod +x /Users/Shared/Test.sh).
From Terminal.app, run the following:
sudo defaults write com.apple.loginwindow LoginHook /Users/Shared/Test.sh
Note:
The script will run as the root user, so exercise due caution.
Among the methods listed here, this is the only way to run a script as root.
There's only one system-wide login hook.
Note that there's also a log-OUT hook, LogoutHook, which provides run-at-logout functionality - unlike the other approaches.
The login-hook script runs synchronously before other login actions, and should therefore be kept short.
Notably, it runs before the desktop is displayed; you cannot launch applications from the script, but you can create simple interactions via osascript and AppleScript snippets (e.g., osascript -e 'display dialog "Proceed?"'); however, any interactions block the login process.
The script runs in the context of the root user and he username of the user logging on is passed as the 1st argument to the script.
The script runs with the default "C" locale.
$PATH is fixed to /usr/bin:/bin:/usr/sbin:/sbin, which notably does NOT include /usr/local/bin
The working dir. is /.
launchd agents:
launchd-agent-executed scripts can be installed for a SPECIFIC user OR for ALL users - the latter requires administrative privileges.
While using launchd is Apple's preferred method, it's also the most cumbersome, as it requires creating a separate *.plist configuration file.
On the upside, you can install multiple scripts independently.
Note:
No specific timing or sequencing of launchd scripts is guaranteed; loosely speaking, they "run at the same time at login"; there is even no guaranteed timing between the user-specific and the all-user tasks.
The script runs with the default "C" locale.
$PATH is fixed to /usr/bin:/bin:/usr/sbin:/sbin, which notably does NOT include /usr/local/bin
The working dir. is / by default, but you can configure it via the .plist file - see below.
The script-file path must be specified as a full, literal path (e.g., /Users/jdoe/script.sh; notably , ~-prefixed paths do not work.
For a description of all keys that can be used in *.plist configuration files, see man launchd.plist.
Both user-specific and all-users tasks run as the current user (the user logging on).
launchd [user-SPECIFIC]:
Note: Lingon 3 ($5 as of early 2014) is a GUI application that facilitates the process below, but only for user-specific scripts.
Place your script, e.g., Test.sh, in your home folder, e.g., /Users/jdoe
Create a file with extension .plist in ~/Library/LaunchAgents, e.g., ~/Library/LaunchAgents/LoginScripts.Test.plist, by running the following in Terminal.app:
touch ~/Library/LaunchAgents/LoginScripts.Test.plist
Open the file and save it with the following content:
<?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>
<!-- YOUR SELF-CHOSEN *UNIQUE* LABEL (TASK ID) HERE -->
<string>LoginScripts.Test.sh</string>
<key>ProgramArguments</key>
<array>
<!-- YOUR *FULL, LITERAL* SCRIPT PATH HERE -->
<string>/Users/jdoe/Test.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
The <!-- ... --> comments indicate the places to customize; you're free to choose a label, but it should be unique - ditto for the .plist filename; for simplicity, keep the label and the filename root the same.
From Terminal.app, run the following:
launchctl load ~/Library/LaunchAgents/LoginScripts.Test.plist
Note that, as a side effect, the script will execute right away. From that point on, the script will execute whenever the CURRENT user logs on.
It is not strictly necessary to run launchctl load -- since, by virtue of the file's location, it will be picked up automatically on next login -- but it's helpful for verifying that the file loads correctly.
launchd [ALL users]
Place your script, e.g., Test.sh, in a SHARED location, e.g., /Users/Shared
Create a file with extension .plist in /Library/LaunchAgents (requires admin privileges), e.g., /Library/LaunchAgents/LoginScripts.Test.plist, by running the following in Terminal.app:
sudo touch /Library/LaunchAgents/LoginScripts.Test.plist
Open the file and save it with the following content (make sure your text editor prompts for admin privileges on demand; alternatively, use sudo nano /Library/LaunchAgents/LoginScripts.Test.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>
<!-- YOUR SELF-CHOSEN *UNIQUE* LABEL (TASK ID) HERE -->
<string>LoginScripts.Test.sh</string>
<key>ProgramArguments</key>
<array>
<!-- YOUR *FULL, LITERAL* SCRIPT PATH HERE -->
<string>/Users/Shared/Test.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
The <!-- ... --> comments indicate the places to customize; you're free to choose a label, but it should be unique - ditto for the .plist filename; for simplicity, keep the label and the filename root the same.
From Terminal.app, run the following:
sudo chown root /Library/LaunchAgents/LoginScripts.Test.plist
sudo launchctl load /Library/LaunchAgents/LoginScripts.Test.plist
Note that, as a side effect, the script will execute right away. From that point on, the script will execute whenever ANY user logs on.
It is not strictly necessary to run launchctl load -- since, by virtue of the file's location, it will be picked up automatically on next login -- but it's helpful for verifying that the file loads correctly.
You can't just place plain scripts in that folder. You need a "specialized bundle" how Apple calls it, basically a folder with your executable, and a .plist configuration. And you should put it in /Library/StartupItems since /System/Library/StartupItems/ is reserved for the operating system. Read all about it here:
https://developer.apple.com/library/mac/documentation/macosx/conceptual/bpsystemstartup/chapters/StartupItems.html
Also note that the whole stuff is marked as deprecated technology. And that Apple is suggesting the use of launchd. There is an example how to set it up here:
https://superuser.com/questions/229773/run-command-on-startup-login-mac-os-x
launchd-oneshot is used to install script as a launchd job to run on login, with
brew install cybertk/formulae/launchd-oneshot
sudo launchd-oneshot Test.sh --on-login
Disclosure: I am the author of this package.

Setting environment variables (specifically the PYTHON PATH) on a mac

Because it was such a mission to get this simple bit of info I've decided to post it for others:
In terminal execute: mkdir ~/.MacOSX
In terminal execute: touch ~/.MacOSX/environment.plist
Browse to the file and open.
Paste:
<key>DISPLAY</key>
<string>:0.0</string>
<key>PYTHONPATH</key>
<string>/full/path/ofyour/favorite/script/dir:/full/path/of/another/script/dir:
Edit to what you need and save.
Here is the documented way to do it with Property List Editor.
Note that you should use caution when setting environment variables this way as they apply to launched GUI applications which might not be expecting them. For setting default environment variables when working in a terminal shell, the conventional UNIX way of using shell profile commands, like .profile or .bash_profile is preferred and less likely to break things.
In terminal execute: mkdir ~/.MacOSX
In terminal execute: touch ~/.MacOSX/environment.plist
Browse to the file and open.
Paste:
<?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>DISPLAY</key>
<string>:0.0</string>
<key>PYTHONPATH</key>
<string>/full/path/ofyour/favorite/script/dir:/full/path/of/another/script/dir:</string>
</dict>
</plist>
Edit to what you need and save.
(This was written by the original poster as part of the question. Reposted as community wiki because I didn't write it, just cleaned up the formatting.)

What configuration file sets $DISPLAY in Leopard?

According to this, for best results in Leopard my $DISPLAY variable should start with /tmp/launchd. Alas, my $DISPLAY variable is /tmp/launch-aLhnOW/:0
I do not set $DISPLAY in any of these ...
/private/etc/profile
/private/etc/bashrc
~/.bash_profile
~/.profile
... so I suspect there is some other configuration file that X11 is reading. Any suggestions?
I specifically need to ensure that it is not set anywhere. Per the linked article:
Starting X11.app from the dock will get you two icons, setting it to auto-launch is also a no-no, and if you have set DISPLAY in any of your configuration files you get a disaster.
I think your DISPLAY variable looks all right. I don't think it is being set by a configuration file.
Normally you have a launchd configuration file such as /System/Library/LaunchAgents/org.x.startx.plist. This contains a section:
<key>Sockets</key>
<dict>
<key>:0</key>
<dict>
<key>SecureSocketWithKey</key>
<string>DISPLAY</string>
</dict>
</dict>
I believe this causes launchd to open a socket and set the DISPLAY variable to its path. When a program contacts this socket, startx is invoked by launchd.
You should be able to set it in the ~/.MacOSX/environment.plist file. Details can be found here.

Resources