kyk's blog

Launchd Auto

Example:

Automatically renames Jamf Connect.app to Jamf Connect 2.app the instant it appears in /Applications, using macOS-native launchd with kernel-level FSEvents (WatchPaths). Zero polling, zero lag.


Overview

TriggerAction
Jamf Connect.app appears in /Applications⚡ Fires immediately via WatchPaths
Mac boots / wakes from sleep🔄 Runs once via RunAtLoad
Every day at 12:00 noon🕛 Runs as final safety net

Step 1: Create the Rename Script

sudo nano /usr/local/bin/rename_jamf.sh

Paste the following:

#!/bin/bash

SRC="/Applications/Jamf Connect.app"
DST="/Applications/Jamf Connect 2.app"

if [ -d "$SRC" ]; then
    # Kill the app if it's running
    pkill -x "Jamf Connect" 2>/dev/null
    sleep 1

    # Remove destination if it already exists
    [ -d "$DST" ] && rm -rf "$DST"

    mv "$SRC" "$DST"
    echo "$(date): Renamed 'Jamf Connect.app' → 'Jamf Connect 2.app'" >> /var/log/rename_jamf.log
else
    echo "$(date): Source not found, skipping." >> /var/log/rename_jamf.log
fi

Make it executable:

sudo chmod +x /usr/local/bin/rename_jamf.sh

Step 2: Create the launchd Plist

sudo nano /Library/LaunchDaemons/com.local.rename-jamf.plist

Paste the following:

<?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.local.rename-jamf</string>

    <key>ProgramArguments</key>
    <array>
        <string>/bin/bash</string>
        <string>/usr/local/bin/rename_jamf.sh</string>
    </array>

    <!-- Trigger immediately when Jamf Connect.app appears -->
    <key>WatchPaths</key>
    <array>
        <string>/Applications/Jamf Connect.app</string>
    </array>

    <!-- Also run at boot as a safety net -->
    <key>RunAtLoad</key>
    <true/>

    <!-- Also run daily at 12:00 noon as a safety net -->
    <key>StartCalendarInterval</key>
    <dict>
        <key>Hour</key>
        <integer>12</integer>
        <key>Minute</key>
        <integer>0</integer>
    </dict>

    <key>StandardOutPath</key>
    <string>/var/log/rename_jamf.log</string>

    <key>StandardErrorPath</key>
    <string>/var/log/rename_jamf_error.log</string>

    <key>UserName</key>
    <string>root</string>

</dict>
</plist>

Step 3: Set Permissions & Load

# Set correct ownership and permissions
sudo chown root:wheel /Library/LaunchDaemons/com.local.rename-jamf.plist
sudo chmod 644 /Library/LaunchDaemons/com.local.rename-jamf.plist

# Load the daemon
sudo launchctl load /Library/LaunchDaemons/com.local.rename-jamf.plist

# Trigger immediately to test
sudo launchctl start com.local.rename-jamf

Step 4: Verify It Is Running

Check it’s loaded

sudo launchctl list | grep rename-jamf

Expected output:

-   0   com.local.rename-jamf
ColumnValueMeaning
PID-Idle (normal when not actively running)
Exit Code0✅ Last run succeeded
Exit Code1❌ Last run failed — check error log
Not listed❌ Not loaded — reload it

Validate the plist syntax

plutil -lint /Library/LaunchDaemons/com.local.rename-jamf.plist

Expected: com.local.rename-jamf.plist: OK

Check the plist is in place

ls -la /Library/LaunchDaemons/com.local.rename-jamf.plist

Check logs

tail -20 /var/log/rename_jamf.log
tail -20 /var/log/rename_jamf_error.log

Management Commands

ActionCommand
Load / enablesudo launchctl load /Library/LaunchDaemons/com.local.rename-jamf.plist
Unload / disablesudo launchctl unload /Library/LaunchDaemons/com.local.rename-jamf.plist
Reload after editssudo launchctl unload ... && sudo launchctl load ...
Trigger manuallysudo launchctl start com.local.rename-jamf
Check statussudo launchctl list | grep rename-jamf
Watch logs livetail -f /var/log/rename_jamf.log

Why WatchPaths? — Performance Impact

MethodHow it worksCPU/RAM impact
WatchPaths (launchd)Kernel FSEvents — sleeps until file event fires~0% — event-driven
StartInterval pollingWakes every N seconds to checkVery low but wasteful
while true; do sleep 1 loopConstant process running⚠️ Unnecessary overhead

WatchPaths is completely event-driven — the daemon is dormant until a filesystem change occurs at the watched path. It uses the same FSEvents mechanism as Spotlight and Time Machine. There is no noticeable performance impact on the Mac.


Why /Library/LaunchDaemons/?

LocationRuns asWhen
/Library/LaunchDaemons/rootAt boot, before login ✅
/Library/LaunchAgents/rootAfter login
~/Library/LaunchAgents/current userAfter user login

Renaming apps in /Applications requires root privileges, making LaunchDaemons the correct choice.


Does It Run When the MacBook Lid Is Closed?

No — both cron and launchd are paused when the Mac is asleep. However:

  • WatchPaths will fire immediately on wake if Jamf Connect.app appeared while asleep
  • RunAtLoad ensures it runs on every boot and wake
  • StartCalendarInterval catches up and runs on the next wake if the Mac was asleep at noon

This combination makes the setup resilient without needing the Mac to be awake 24/7.


File Summary

FilePurpose
/usr/local/bin/rename_jamf.shThe rename script
/Library/LaunchDaemons/com.local.rename-jamf.plistlaunchd daemon configuration
/var/log/rename_jamf.logstdout log
/var/log/rename_jamf_error.logstderr / error log