article

A Weekend Learning macOS Development

octopus working underwarter

This project started with a noble cause. I wanted to shutdown my remote machine (a DGX Spark) any time I shut down my local machine (my MacBook Pro). Simple enough, right? What followed was a weekend-long deep dive into macOS internals that taught me more about the operating system than I ever expected.

The Problem

The setup was straightforward: I have a DGX Spark on my local network that I SSH into for ML workloads. When I'm done for the day and shutdown my Mac, I want the Spark to shutdown too. No need to remember to SSH in and run sudo shutdown -h now separately.

Attempt 1: Go Application with LogoutHook

My first attempt was a Go application that would listen for the shutdown signal and then SSH to the remote machine to send the shutdown command. I learned about the deprecated LogoutHook mechanism that macOS provides through com.apple.loginwindow.

The LogoutHook is set via:

sudo defaults write com.apple.loginwindow LogoutHook /path/to/script.sh

But here's the thing about LogoutHook: it's deprecated. Apple doesn't recommend it for new development. And when I tested it, it simply didn't trigger during shutdown. The hook is supposed to run when the user logs out, but the behavior during system shutdown was unreliable.

Attempt 2: LaunchAgents and LaunchDaemons

This is where I went down the rabbit hole. I learned about launchd, Apple's replacement for the traditional Unix init system. The concept is elegant:

I created a plist file that would start a shell script at boot. The script would run tail -f /dev/nullto stay alive (since macOS's sleep command doesn't support infinity like Linux does), and use trap to catch SIGTERM:

trap shutdown_remote SIGTERM

# Keep the script running
tail -f /dev/null &
wait $!

I discovered a helpful plist key from Apple's own system daemons:

<key>AlwaysSIGTERMOnShutdown</key>
<true/>

This guarantees that launchd sends SIGTERM to your daemon during shutdown. Combined withExitTimeOut, you get a window to perform cleanup before SIGKILL.

The Real Problem: Shutdown Order

Here's where everything fell apart. My daemon was receiving SIGTERM correctly, but by the time it tried to SSH to the remote machine, the network was already down. I saw errors like:

ssh: Could not resolve hostname spark-45ca.local: nodename nor servname provided

The mDNS/Bonjour service (which resolves .local hostnames) had already been terminated. Okay, I thought, I'll just use the IP address directly. But then:

ssh: connect to host 192.168.2.17 port 22: No route to host

The network interface itself was already down. macOS was tearing down services in an order that made my use case impossible. My daemon was getting SIGTERM, but it was too late. The dependencies I needed (networking, DNS) were already gone.

What I Learned

After a weekend of experimentation, I gained a deep appreciation for macOS's shutdown sequence:

The Unsolved Problem

If there were a way to prioritize my daemon so that it executes first in the shutdown order, this would probably work. I haven't found a reliable way to ensure that my script runs before the network stack is torn down.

Some ideas I didn't fully explore:

Conclusion

This project is dead at this point. But the time wasn't wasted. I learned more about macOS development in one weekend than I had in years of using the platform. Sometimes the journey is the destination.

The code is still on my GitHub if anyone wants to pick up where I left off. Maybe someone smarter than me will figure out how to win the race against macOS's shutdown sequence.

**This article was principally written by an AI agent, using context from a coding session with small additions, written portions by me (Naeem).

macOS
LaunchDaemon
LaunchAgent
launchd
SIGTERM
Shell Scripting
SSH
DGX Spark
Software Engineering
Systems Programming