Installing a Launchd.plist


Sandor Szatmari
 

I have an application that presents a window to the user which floats above all other windows. It must be running at all times when any user is logged in.

I achieve this with a launchd.plist installed in /Library/LaunchDaemons which simply relaunches the app if the window is ever closed (closing the window terminates the app).

For development I currently install the plist manually. What is the preferred way to install this file for a distributable version of my app? Do I create an installer? This seems to be a thing of the past, especially considering the MAS. Do I use SMJobBless? If so I can't find good documentation, and it seems this is all about a helper tool. I have no helper tool, I just need to install a plist which relaunches my app? And it's unclear if the SMJobBless permanently alter system behavior?

Sandor


 


On Jul 20, 2017, at 5:45 AM, Sandor Szatmari <admin.szatmari.net@...> wrote:

I achieve this with a launchd.plist installed in /Library/LaunchDaemons which simply relaunches the app if the window is ever closed (closing the window terminates the app). 

LaunchDaemons is the wrong directory; that’s for system daemons that run outside of any user context. (I’m surprised you actually got this to work, because it’s difficult to present any UI from a daemon.) LaunchAgents is better, as those are launched into the current login context.

Also, if you want the window to be visible at all times, just don’t make it closable (turn off that checkbox in IB.)

What is the preferred way to install this file for a distributable version of my app?  Do I create an installer?  

That’s one way, or use the authorization API to run a helper shellscript that installs the file.

This seems to be a thing of the past, especially considering the MAS.

You’ll never be able to distribute this via the MAS, because that would require the app to be sandboxed, which prevents it from being able to install the plist. (It’s pretty obvious that altering files in /Library has tremendous potential for malware, and this is the kind of thing sandboxing is designed to protect the user from.)

—Jens


Jack Brindle
 

SMJobBless is a royal pain to get going. By far the easiest (and perhaps coolest) way of running a privileged app (usually for an installer) is to do it through AppleScript.
Something like: “do shell script xxx with administrator privileges” does the job of setting up authentication quite nicely and gets around all the SMJobBless mess.
This is set up in a top-level app, one that runs when the installer is launched. It then launches a lower-level app (with privileges) where the actual work is done.
Once thing to watch for - the low level program does not inherent the same settings as a normal application. It will probably run in English with a US locale, as
opposed to whatever language and locale setting the user has set. It is easy to capture these in the top level app and send them over to the main installer,
usually through command line arguments.

I doubt it is usable in an MAS program, mostly because it requires a two-program approach. But then so does SMJobBless, so it might be worth a try.

- Jack


On Jul 20, 2017, at 12:07 PM, Jens Alfke <jens@...> wrote:


On Jul 20, 2017, at 5:45 AM, Sandor Szatmari <admin.szatmari.net@...> wrote:

I achieve this with a launchd.plist installed in /Library/LaunchDaemons which simply relaunches the app if the window is ever closed (closing the window terminates the app). 

LaunchDaemons is the wrong directory; that’s for system daemons that run outside of any user context. (I’m surprised you actually got this to work, because it’s difficult to present any UI from a daemon.) LaunchAgents is better, as those are launched into the current login context.

Also, if you want the window to be visible at all times, just don’t make it closable (turn off that checkbox in IB.)

What is the preferred way to install this file for a distributable version of my app?  Do I create an installer?  

That’s one way, or use the authorization API to run a helper shellscript that installs the file.

This seems to be a thing of the past, especially considering the MAS.

You’ll never be able to distribute this via the MAS, because that would require the app to be sandboxed, which prevents it from being able to install the plist. (It’s pretty obvious that altering files in /Library has tremendous potential for malware, and this is the kind of thing sandboxing is designed to protect the user from.)

—Jens


Sandor Szatmari
 

Jens,

Thanks for your thoughts...

On Jul 20, 2017, at 15:07, Jens Alfke <jens@...> wrote:


On Jul 20, 2017, at 5:45 AM, Sandor Szatmari <admin.szatmari.net@...> wrote:

I achieve this with a launchd.plist installed in /Library/LaunchDaemons which simply relaunches the app if the window is ever closed (closing the window terminates the app). 

LaunchDaemons is the wrong directory; that’s for system daemons that run outside of any user context. (I’m surprised you actually got this to work, because it’s difficult to present any UI from a daemon.)
It's not really a Daemon, it's an application that is persisted by Launchd.
LaunchAgents is better, as those are launched into the current login context.

Yea my instinct said this wasn't right, but it just worked better in LaunchDaemons.  Should always listen to my instinct.  There must be something wrong with my plist.  I'll post its contents when I'm at my machine.


Also, if you want the window to be visible at all times, just don’t make it closable (turn off that checkbox in IB.)
Yes, the window is not really closable.  I was being terse or expedient, you can pick which is more forgivable :)  

Maybe my approach is just all wrong. Here's the details of what I'm doing/trying to accomplish.

The application has a transparent window that floats above all windows.  The user can drag it around if they want to move it out of their way.  There is no title bar, there's not even a menu bar for the application, it's just a window.  It doesn't show up in the dock, or application switcher.  It just displays static information about the computer so it's easily accessible for users and administrators.  CPU, RAM, ... etc.

The point is that if someone force quits it, it should be relaunched.  Administrators can do this intentionally, and by holding down a 'secret' key, the option key, access a panel to input preferences before the window is displayed on application relaunch.  Should have just said all this in the first place... sorry.

What is the preferred way to install this file for a distributable version of my app?  Do I create an installer?  

That’s one way, or use the authorization API to run a helper shellscript that installs the file.

Authorization API may be the way to go.  Thanks 

This seems to be a thing of the past, especially considering the MAS.

You’ll never be able to distribute this via the MAS,
because that would require the app to be sandboxed, which prevents it from being able to install the plist.

Good point.  Although, I don't really intend to so I didn't really consider that; it's an in house app.  But, staying in the past is depressing.  If there's a modern way to do something I should learn it, I guess.  

(It’s pretty obvious that altering files in /Library has tremendous potential for malware, and this is the kind of thing sandboxing is designed to protect the user from.)
Yes, curses to the malware!

Thanks again, I appreciate your feedback,
Sandor


—Jens


John Brownie
 



21 July 2017 at 05:41
SMJobBless is a royal pain to get going. By far the easiest (and perhaps coolest) way of running a privileged app (usually for an installer) is to do it through AppleScript.
Something like: “do shell script xxx with administrator privileges” does the job of setting up authentication quite nicely and gets around all the SMJobBless mess.
This is set up in a top-level app, one that runs when the installer is launched. It then launches a lower-level app (with privileges) where the actual work is done.
Once thing to watch for - the low level program does not inherent the same settings as a normal application. It will probably run in English with a US locale, as
opposed to whatever language and locale setting the user has set. It is easy to capture these in the top level app and send them over to the main installer,
usually through command line arguments.

For my own purposes, this looks like a good solution to a problem. I've never been much of a user of AppleScript, though, so I'm not sure how one creates such a lower-level app to be called from the main app. Any pointers on where to learn how to do this?

John
--
John Brownie
SIL-PNG, Ukarumpa, Eastern Highlands, Papua New Guinea
Mussau-Emira language, New Ireland Province, Papua New Guinea


Jack Brindle
 

That’s one of the cool things about it. Nothing special about the lower level (main) app. It is simply a standard app. You can either do the installation yourself there, have the top level installer run the command line installer as the privileged app, or a combination where the lower level app calls the command line installer. Remember, once the applescript command is executed, the application it runs is running at elevated level (root), so you can do a lot of both good and damage.

We used this method to replace the standard installer user interface with one of our own, and invoke the command-line interface to do the actual installation job. In another product, we do the actual file copies, privilege setting and other installation work in the lower-level app itself. There are some gotchas there, like handling file download quarantines, but with some effort they can be handled. Both apps have a lot to install, so there is a lot of work happening here. There is a big advantage to being root in this situation...

For the top-level installer app, we use a user-agent (so as not to show icons, menus, etc). It does some checking, then calls the lower-level (main) installer using the applescript. That is not too difficult to set up. I wonder if it would be easier to just use an Applescript app to do that job. The top-level app usually does not have any UI - the exception is if something goes wrong, we tell the user.

If you need some help with the Applescript setup, just ask and I’ll dig up that info!

- Jack


On Jul 22, 2017, at 9:48 PM, John Brownie <john_brownie@...> wrote:



21 July 2017 at 05:41
SMJobBless is a royal pain to get going. By far the easiest (and perhaps coolest) way of running a privileged app (usually for an installer) is to do it through AppleScript.
Something like: “do shell script xxx with administrator privileges” does the job of setting up authentication quite nicely and gets around all the SMJobBless mess.
This is set up in a top-level app, one that runs when the installer is launched. It then launches a lower-level app (with privileges) where the actual work is done.
Once thing to watch for - the low level program does not inherent the same settings as a normal application. It will probably run in English with a US locale, as
opposed to whatever language and locale setting the user has set. It is easy to capture these in the top level app and send them over to the main installer,
usually through command line arguments.

For my own purposes, this looks like a good solution to a problem. I've never been much of a user of AppleScript, though, so I'm not sure how one creates such a lower-level app to be called from the main app. Any pointers on where to learn how to do this?

John
--
John Brownie
SIL-PNG, Ukarumpa, Eastern Highlands, Papua New Guinea
Mussau-Emira language, New Ireland Province, Papua New Guinea


John Brownie
 

OK, that is useful. What I'm trying to implement is a system to move files around into correct folders without troubling the user about finding the correct places in the file system. So the AppleScript part is going to look something like

set sourceFile to "/path/to/source"
set destinationFolder to "/path/to/destination"
set theScript to "mv \"" & sourceFile & "\" \"" & destinationFolder & "\""
do shell script theScript with administrator privileges

So I'll need to pass the file and destination paths to the app.

But building the lower-level app is new territory for me. How does one create an app that calls such a script? I've only ever done it from Script Editor...

John

Jack Brindle wrote:

That’s one of the cool things about it. Nothing special about the lower level (main) app. It is simply a standard app. You can either do the installation yourself there, have the top level installer run the command line installer as the privileged app, or a combination where the lower level app calls the command line installer. Remember, once the applescript command is executed, the application it runs is running at elevated level (root), so you can do a lot of both good and damage.

We used this method to replace the standard installer user interface with one of our own, and invoke the command-line interface to do the actual installation job. In another product, we do the actual file copies, privilege setting and other installation work in the lower-level app itself. There are some gotchas there, like handling file download quarantines, but with some effort they can be handled. Both apps have a lot to install, so there is a lot of work happening here. There is a big advantage to being root in this situation...

For the top-level installer app, we use a user-agent (so as not to show icons, menus, etc). It does some checking, then calls the lower-level (main) installer using the applescript. That is not too difficult to set up. I wonder if it would be easier to just use an Applescript app to do that job. The top-level app usually does not have any UI - the exception is if something goes wrong, we tell the user.

If you need some help with the Applescript setup, just ask and I’ll dig up that info!

--
John Brownie
SIL-PNG, Ukarumpa, Eastern Highlands, Papua New Guinea
Mussau-Emira language, New Ireland Province, Papua New Guinea


Jack Brindle
 

John;

I suspect you are putting more into the scripting idea than is really needed. The only thing I use AppleScript for is to launch the installer application. That (standard Mac) application is written in Objective-C/Cocoa (OK, someday I’ll move to Swift). It _does_ take arguments on the command line. Those are picked up with the NSProcessInfo arguments method, which returns an array, just like you would get with main(arg, arg), except the strings are NSStrings. You can directly use those in the installer application after validating them. Everything you need to do, such as moving files, setting their privilege level and so on can be done with Cocoa calls here. Let me emphasize this a bit more - there really is nothing special to this app other than the fact that it runs at an elevated privilege level.

The magic lines that make this work run in the top-level program. They are also incredibly simple, just create the arguments you want to pass, then craft the AppleScript string, set up the environment to run the AppleScript, and do it.

// At this point the argument string is already set up. It can contain something like —from /Wherever/the/file/comes/from…
// likewise the path argument is a pointer to the main installer. It usually is placed inside the top level app’s bundle to make packaging much easier. You would get it from NSBundle calls...
NSString *script = [NSString stringWithFormat: @"do shell script quoted form of \"%@\" & \" %@\" with administrator privileges", path, arguments];

NSDictionary *errorDict = [NSDictionary dictionary];
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
[appleScript executeAndReturnError:&errorDict];

That is the entire applescript usage. As I said, amazingly simple.

- Jack


On Jul 22, 2017, at 10:16 PM, John Brownie <john_brownie@...> wrote:

OK, that is useful. What I'm trying to implement is a system to move files around into correct folders without troubling the user about finding the correct places in the file system. So the AppleScript part is going to look something like

set sourceFile to "/path/to/source"
set destinationFolder to "/path/to/destination"
set theScript to "mv \"" & sourceFile & "\" \"" & destinationFolder & "\""
do shell script theScript with administrator privileges

So I'll need to pass the file and destination paths to the app.

But building the lower-level app is new territory for me. How does one create an app that calls such a script? I've only ever done it from Script Editor...

John

Jack Brindle wrote:
That’s one of the cool things about it. Nothing special about the lower level (main) app. It is simply a standard app. You can either do the installation yourself there, have the top level installer run the command line installer as the privileged app, or a combination where the lower level app calls the command line installer. Remember, once the applescript command is executed, the application it runs is running at elevated level (root), so you can do a lot of both good and damage.

We used this method to replace the standard installer user interface with one of our own, and invoke the command-line interface to do the actual installation job. In another product, we do the actual file copies, privilege setting and other installation work in the lower-level app itself. There are some gotchas there, like handling file download quarantines, but with some effort they can be handled. Both apps have a lot to install, so there is a lot of work happening here. There is a big advantage to being root in this situation...

For the top-level installer app, we use a user-agent (so as not to show icons, menus, etc). It does some checking, then calls the lower-level (main) installer using the applescript. That is not too difficult to set up. I wonder if it would be easier to just use an Applescript app to do that job. The top-level app usually does not have any UI - the exception is if something goes wrong, we tell the user.

If you need some help with the Applescript setup, just ask and I’ll dig up that info!

--
John Brownie
SIL-PNG, Ukarumpa, Eastern Highlands, Papua New Guinea
Mussau-Emira language, New Ireland Province, Papua New Guinea


John Brownie
 

Thanks, that's very helpful. I will try it out when I get the time (my day job is getting in the way of my programming these days).

John

Jack Brindle wrote:

I suspect you are putting more into the scripting idea than is really needed. The only thing I use AppleScript for is to launch the installer application. That (standard Mac) application is written in Objective-C/Cocoa (OK, someday I’ll move to Swift). It _does_ take arguments on the command line. Those are picked up with the NSProcessInfo arguments method, which returns an array, just like you would get with main(arg, arg), except the strings are NSStrings. You can directly use those in the installer application after validating them. Everything you need to do, such as moving files, setting their privilege level and so on can be done with Cocoa calls here. Let me emphasize this a bit more - there really is nothing special to this app other than the fact that it runs at an elevated privilege level.

The magic lines that make this work run in the top-level program. They are also incredibly simple, just create the arguments you want to pass, then craft the AppleScript string, set up the environment to run the AppleScript, and do it.

// At this point the argument string is already set up. It can contain something like —from /Wherever/the/file/comes/from…
// likewise the path argument is a pointer to the main installer. It usually is placed inside the top level app’s bundle to make packaging much easier. You would get it from NSBundle calls...
NSString *script = [NSString stringWithFormat: @"do shell script quoted form of \"%@\" & \" %@\" with administrator privileges", path, arguments];

NSDictionary *errorDict = [NSDictionary dictionary];
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
[appleScript executeAndReturnError:&errorDict];

That is the entire applescript usage. As I said, amazingly simple.

--
John Brownie
SIL-PNG, Ukarumpa, Eastern Highlands, Papua New Guinea
Mussau-Emira language, New Ireland Province, Papua New Guinea


Sandor Szatmari
 

Sounds like this would be a good approach for me.  Sounds like I could build a helper app, make it available in my main app's bundle, and call it with arguments to install/uninstall and load/unload the Launchd script.  

I'm assuming when you run the AppleScript to launch the privileged helper you are prompted for an administrative password...

Sandor

On Jul 23, 2017, at 02:51, John Brownie <john_brownie@...> wrote:

Thanks, that's very helpful. I will try it out when I get the time (my day job is getting in the way of my programming these days).

John

Jack Brindle wrote:
I suspect you are putting more into the scripting idea than is really needed. The only thing I use AppleScript for is to launch the installer application. That (standard Mac) application is written in Objective-C/Cocoa (OK, someday I’ll move to Swift). It _does_ take arguments on the command line. Those are picked up with the NSProcessInfo arguments method, which returns an array, just like you would get with main(arg, arg), except the strings are NSStrings. You can directly use those in the installer application after validating them. Everything you need to do, such as moving files, setting their privilege level and so on can be done with Cocoa calls here. Let me emphasize this a bit more - there really is nothing special to this app other than the fact that it runs at an elevated privilege level.

The magic lines that make this work run in the top-level program. They are also incredibly simple, just create the arguments you want to pass, then craft the AppleScript string, set up the environment to run the AppleScript, and do it.

// At this point the argument string is already set up. It can contain something like —from /Wherever/the/file/comes/from…
// likewise the path argument is a pointer to the main installer. It usually is placed inside the top level app’s bundle to make packaging much easier. You would get it from NSBundle calls...
NSString *script = [NSString stringWithFormat: @"do shell script quoted form of \"%@\" & \" %@\" with administrator privileges", path, arguments];

NSDictionary *errorDict = [NSDictionary dictionary];
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
[appleScript executeAndReturnError:&errorDict];

That is the entire applescript usage. As I said, amazingly simple.

--
John Brownie
SIL-PNG, Ukarumpa, Eastern Highlands, Papua New Guinea
Mussau-Emira language, New Ireland Province, Papua New Guinea


John Brownie
 

Just to follow up, it all works nicely. I've done it in Swift rather than Objective-C. The string handling is a little messy, as I need to get the quoted form of each argument (they're all paths).

let scriptString = "do shell script quoted form of \"\(toolPath)\" & \" \" & quoted form of \"\(sourcePath)\" & \" \" & quoted form of \"\(destPath)\" with administrator privileges"
let appleScript = NSAppleScript.init(source: scriptString)
var errorDict: NSDictionary? = NSDictionary.init()
appleScript?.executeAndReturnError(&errorDict)

Thanks once again!

John
23 July 2017 at 16:20
John;

I suspect you are putting more into the scripting idea than is really needed. The only thing I use AppleScript for is to launch the installer application. That (standard Mac) application is written in Objective-C/Cocoa (OK, someday I’ll move to Swift). It _does_ take arguments on the command line. Those are picked up with the NSProcessInfo arguments method, which returns an array, just like you would get with main(arg, arg), except the strings are NSStrings. You can directly use those in the installer application after validating them. Everything you need to do, such as moving files, setting their privilege level and so on can be done with Cocoa calls here. Let me emphasize this a bit more - there really is nothing special to this app other than the fact that it runs at an elevated privilege level.

The magic lines that make this work run in the top-level program. They are also incredibly simple, just create the arguments you want to pass, then craft the AppleScript string, set up the environment to run the AppleScript, and do it.

// At this point the argument string is already set up. It can contain something like —from /Wherever/the/file/comes/from…
// likewise the path argument is a pointer to the main installer. It usually is placed inside the top level app’s bundle to make packaging much easier. You would get it from NSBundle calls...
NSString *script = [NSString stringWithFormat: @"do shell script quoted form of \"%@\" & \" %@\" with administrator privileges", path, arguments];

NSDictionary *errorDict = [NSDictionary dictionary];
NSAppleScript *appleScript = [[NSAppleScript alloc] initWithSource:script];
[appleScript executeAndReturnError:&errorDict];

That is the entire applescript usage. As I said, amazingly simple.

- Jack



23 July 2017 at 14:48


For my own purposes, this looks like a good solution to a problem. I've never been much of a user of AppleScript, though, so I'm not sure how one creates such a lower-level app to be called from the main app. Any pointers on where to learn how to do this?

John

--
John Brownie
SIL-PNG, Ukarumpa, Eastern Highlands, Papua New Guinea
Mussau-Emira language, New Ireland Province, Papua New Guinea