Slide 1

Slide 1 text

Intro into Daemons & Agents on macOS

Slide 2

Slide 2 text

Daemon: runs under root user Agent: runs under any other user Difference between Daemon & Agent

Slide 3

Slide 3 text

Single daemon for root tasks + multiple per-user agents Typical scheme in application

Slide 4

Slide 4 text

How to add Daemon using API (from code) using command-line tools deprecated deprecated current current SMJobBless() AuthorizationExecute WithPrivileges() launchctl load / unload launchctl bootstrap / bootout

Slide 5

Slide 5 text

How to add Daemon using API (from code) using command-line tools deprecated deprecated current current SMJobBless() AuthorizationExecute WithPrivileges() launchctl load / unload launchctl bootstrap / bootout

Slide 6

Slide 6 text

How to add Agent using API (from code) using command-line tools deprecated current current SMLoginItemSetEnabled() load / unload bootstrap / bootout

Slide 7

Slide 7 text

How to add Agent using API (from code) using command-line tools deprecated current current SMLoginItemSetEnabled() load / unload bootstrap / bootout

Slide 8

Slide 8 text

How to add Daemon/Agent using command-line tool 1. Add .plist with Daemon/Agent configuration to specified location in system* 2. If adding Daemon or Agent for all users, set plist's owner to root (sudo chown root) 3. Load this .plist to launchd (this step can be skipped: in that case launchd will load .plist from listed locations during the next login. But if you want your job to start now, you need to load .plist) */Library/LaunchAgents | ~/Library/LaunchAgents for agent * /Library/LaunchDaemons for daemon for more details on plist configuration files see next slides of this presentation and man launchd.plist

Slide 9

Slide 9 text

How to add Daemon/Agent using system API 1. Use SMJobBless() to load Daemon or SMLoginItemSetEnabled() to load Agent. 2. Remember to follow additional requirements for given API to work properly. for more details see next slides of this presentation

Slide 10

Slide 10 text

Using command-line tools

Slide 11

Slide 11 text

How to add Daemon/Agent Loading configuration .plist to launchd launchd is waaaaaaaaaaaaay too cool, so you don't speak to him directly

Slide 12

Slide 12 text

How to add Daemon/Agent Loading configuration .plist to launchd but he has a secretary — Launch Control, launchctl for short launchd is waaaaaaaaaaaaay too cool, so you don't speak to him directly

Slide 13

Slide 13 text

Add: * Daemon: sudo launchctl load /Library/LaunchDaemons/com.mycompany.mydaemon.plist * Agents for all users: sudo launchctl load /Library/LaunchAgents/com.mycompany.myagent.plist * Agent for one user: launchctl load ~/Library/LaunchAgents/com.mycompany.myagent.plist Remove: * Daemon: sudo launchctl unload /Library/LaunchDaemons/com.mycompany.mydaemon.plist * Agents for all users: sudo launchctl unload /Library/LaunchAgents/com.mycompany.myagent.plist * Agent for one user: launchctl unload ~/Library/LaunchAgents/com.mycompany.myagent.plist How to load Daemon/Agent .plist to launchd Command-line API before 10.11

Slide 14

Slide 14 text

How to load Daemon/Agent .plist to launchd Command-line API before 10.11 Add: * Daemon: sudo launchctl load /Library/LaunchDaemons/com.mycompany.mydaemon.plist * Agents for all users: sudo launchctl load /Library/LaunchAgents/com.mycompany.myagent.plist * Agent for one user: launchctl load ~/Library/LaunchAgents/com.mycompany.myagent.plist Remove: * Daemon: sudo launchctl unload /Library/LaunchDaemons/com.mycompany.mydaemon.plist * Agents for all users: sudo launchctl unload /Library/LaunchAgents/com.mycompany.myagent.plist * Agent for one user: launchctl unload ~/Library/LaunchAgents/com.mycompany.myagent.plist

Slide 15

Slide 15 text

List all Daemons/Agents, submitted to launchd sudo launchctl list ← print jobs, running as root launchctl list ← print jobs, running as other user

Slide 16

Slide 16 text

How to load Daemon/Agent .plist to launchd Command-line API since 10.11 Add: * Daemon: sudo launchctl bootstrap system /Library/LaunchDaemons/com.mycompany.mydaemon.plist 'system' is the 'domain' of the submitted executable. See man launchctl for details about domains

Slide 17

Slide 17 text

How to load Daemon/Agent .plist to launchd Command-line API since 10.11 Add: * Daemon: sudo launchctl bootstrap system /Library/LaunchDaemons/com.mycompany.mydaemon.plist * Agents for all users: sudo launchctl bootstrap gui/501 /Library/LaunchAgents/com.mycompany.myagent.plist Domain for user agent is 'gui/[user id]'. To register agent for all users you should call this command with every user id as given user (not as root).

Slide 18

Slide 18 text

How to load Daemon/Agent .plist to launchd Command-line API since 10.11 Add: * Daemon: sudo launchctl bootstrap system /Library/LaunchDaemons/com.mycompany.mydaemon.plist * Agents for all users: sudo launchctl bootstrap gui/501 /Library/LaunchAgents/com.mycompany.myagent.plist * Agent for one user: launchctl bootstrap gui/501 ~/Library/LaunchAgents/com.mycompany.myagent.plist Remove: * Daemon: sudo launchctl bootout system /Library/LaunchDaemons/com.mycompany.mydaemon.plist * Agents for all users: sudo launchctl bootout gui/501 /Library/LaunchAgents/com.mycompany.myagent.plist * Agent for one user: launchctl bootout gui/501 ~/Library/LaunchAgents/com.mycompany.myagent.plist

Slide 19

Slide 19 text

sudo launchctl print system launchctl print gui/501 List all Daemons/Agents, that launchd knows about List all agents, registered for user with given id (replace 501 with relevant id) List all daemons

Slide 20

Slide 20 text

Command-line API since 10.11 sudo launchctl disable system/com.mycompany.mydaemon sudo launchctl disable gui/501/com.myagent launchctl disable gui/501/com.myagent Disable Enable sudo launchctl enable system/com.mycompany.mydaemon sudo launchctl enable gui/501/com.myagent launchctl enable gui/501/com.myagent Switch on/off your Daemon/Agent

Slide 21

Slide 21 text

Using system API

Slide 22

Slide 22 text

How to add Daemon/Agent Framework: Security AuthorizationExecuteWithPrivileges(...) from code before 10.7

Slide 23

Slide 23 text

How to add Daemon/Agent Framework: Security AuthorizationExecuteWithPrivileges(...) from code before 10.7

Slide 24

Slide 24 text

Framework: Service Management Enable a user agent application, located in the main application bundle’s “Contents/Library/LoginItems” directory. Compatible with AppStore Enable a daemon. Replaces AuthorizationExecuteWithPrivileges() NOT compatible with AppStore How to add Daemon/Agent from code after 10.7

Slide 25

Slide 25 text

Requirements to adding Daemon via SMJobBless() 1. The calling application and target executable tool must both be signed. 2. The calling application's Info.plist must include a "SMPrivilegedExecutables" dictionary of strings. Each string is a textual representation of a code signing requirement used to determine whether the application owns the privileged tool once installed (i.e. in order for subsequent versions to update the installed version). See example on next slide. 3. The helper tool must have an embedded Info.plist containing an "SMAuthorizedClients" array of strings. Each string is a textual representation of a code signing requirement describing a client which is allowed to add and remove the tool. How to embed: see futher. 4. The helper tool must have an embedded launchd plist. The only required key in this plist is the Label key. When the launchd plist is extracted and written to disk, the key for ProgramArguments will be set to an array of 1 element pointing to a standard location. You cannot specify your own program arguments, so do not rely on custom command line arguments being passed to your tool. Pass any parameters via IPC. 5. The helper tool must reside in the Contents/Library/LaunchServices directory inside the application bundle, and its name must be its launchd job label. So if your launchd job label is "com.apple.Mail.helper", this must be the name of the tool in your application bundle. Details: https://developer.apple.com/documentation/servicemanagement/1431078-smjobbless

Slide 26

Slide 26 text

How to add Daemon SMPrivilegedExecutables key in client app's Info.plist key signing requirement to owned tool (see example on next slide); there can be multiple tools owned by same app

Slide 27

Slide 27 text

How to add Daemon Signing requirements example See Code Signing Requirement Language for syntax

Slide 28

Slide 28 text

How to add Daemon SMAuthorizedClients key in Info.plist of the Daemon key signing requirements to client applications Note: you can additionally use here other keys, listed in man launchd.plist

Slide 29

Slide 29 text

How to embed plists in Daemon for SMJobBless() path to .plist path to .plist Add 'Other linker flags', shown on the screenshot:

Slide 30

Slide 30 text

Using SMJobBless(): Flow 1. Launch the client application (that owns the Daemon) 2. Obtain SFAutorization object (for kAuthorizationRightExecute) 3. Call SMJobBless with required arguments: — domain kSMDomainSystemLaunchd — label of the Daemon — AuthorizationRef from obtained authorization — reference to NSError object That's it: now you can talk to your Daemon via XPC to ask it to do its job.

Slide 31

Slide 31 text

How to add Agent via SMLoginItemSetEnabled() 1. The helper tool must reside in the Contents/Library/LoginItems directory inside the application bundle. Details: https://developer.apple.com/documentation/servicemanagement/1501557-smloginitemsetenabled

Slide 32

Slide 32 text

Requirements to adding Agent via SMLoginItemSetEnabled() 1. The helper tool must reside in the Contents/Library/LoginItems directory inside the application bundle. Details: https://developer.apple.com/documentation/servicemanagement/1501557-smloginitemsetenabled Yes, that's it.

Slide 33

Slide 33 text

Using SMLoginItemSetEnabled(): Flow 1. Launch the client application (that owns the Agent) 2. Call SMLoginItemSetEnabled() with required arguments: — agent's bundle identifier — `true` or `false` depending if you want to run it right now

Slide 34

Slide 34 text

Thank you! Happy to help via [email protected]