MacOS – Configure Yosemite Server WebApp with LaunchD

apachelaunchdmacososx-server

Question

Has anyone successfully been able to start a webapp via a webappctl configuration which specifies a launchd configuration in their webapp.plist launchKeys key?

Background

Specifically, with Server.app installed, one can, in theory, configure custom web applications which would show up in the Server.app GUI under the "Advanced Settings" of a Website configuration.

If you take a look at the ReadMe.txt in /Library/Server/Web/Config/apache2 there's a section which states:

webapps/

This directory contains the webapp.plist files for all defined webapps.
See the man page for webapp.plist(8) and webappctl(8).
(Server app man pages are present in /Applications/Server.app/Contents/ServerRoot/usr/share/man/.)

Administrators are strongly encouraged to use the webapp mechanism
instead of modifying virtual host config files directly.
In general, you can place Apache configuration directives in an "Include" file,
and create a webapp.plist file that references that Include file.
You can then activate or de-activate that Include file for the default
sites, or for specific custom sites, by using webappctl(8).

You can also read the man entries, as suggested in the ReadMe.txt by these terminal commands:

man -M /Applications/Server.app/Contents/ServerRoot/usr/share/man/ webapp.plist
man -M /Applications/Server.app/Contents/ServerRoot/usr/share/man/ webappctl

There's also a sample webapp configuration /Library/Server/Web/Config/apache2/com.example.mywebapp.plist which has some comments in it about how this plist could be configured.

Setup

In my case I have added a webapp.plist to the /Library/Server/Web/Config/apache2/ directory which looks 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">

<!-- See man pages for webapp.plist(5) and webappctl(8) for information about webapp.plist -->

<plist version="1.0">
<dict>
  <key>includeFiles</key>
    <array/> <!-- Include files are activated in virtual host when webapp is started -->
  <key>launchKeys</key>
    <array> <!-- Launchd plists in /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons are loaded when webapp is started -->
      <string>org.levi.foo</string>
    </array>
  <key>name</key>
    <string>org.apache.tomcat</string>
  <key>displayName</key> <!-- Name shown in Server app -->
    <string>Tomcat</string>
  <key>proxies</key> <!-- ProxyPass/ProxyPassReverse directives are activated when webapp is started -->
    <dict/>
  <key>requiredModuleNames</key>
    <array/>
  <key>installationIndicatorFilePath</key> <!-- The presence of this file indicates web app is installed -->
    <string>/Library/Tomcat/Home/bin/catalina.sh</string>
  <key>sslPolicy</key> <!-- Determines webapp SSL behavior -->
    <integer>0</integer> <!-- 0: default, UseSSLWhenEnabled -->
  <!-- 1: UseSSLAlways -->
  <!-- 2: UseSSLOnlyWhenCertificateIsTrustable -->
  <!-- 3: UseSSLNever -->
  <!-- 4: UseSSLAndNonSSL -->
</dict>
</plist>

Paying specific attention to:

<key>launchKeys</key>
  <array>
  <string>org.levi.foo</string>
</array>

which specifies my launchd.plist which is located at /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/org.levi.foo.plist and looks like:

<?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>org.levi.foo</string>
      <key>RunAtLoad</key>
        <true/>
<key>Program</key>
    <string>/Library/Tomcat/start.sh</string>
</dict>
</plist>

which should simply call the script /Library/Tomcat/start.sh which is simply:

#!/bin/bash

BASENAME=`basename $0`
LOGGER_B="/usr/bin/logger"

function log {
  MESSAGE="$1"
  ${LOGGER_B} -i -p daemon.notice -t ${BASENAME} ${MESSAGE}
}

log "Here I am!"

Issue

If I use launchctl to manually load the org.levi.foo.plist:

$ sudo launchctl load org.levi.foo.plist

I get the expected message in /var/log/system.log:

Apr  2 16:12:01 host sudo[85791]:     levi : TTY=ttys000 ; PWD=/Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons ; USER=root ; COMMAND=/bin/launchctl load org.levi.foo.plist
Apr  2 16:12:01 host start.sh[85795]: Here I am!

However, if I use webappctl to start my webapp configuration (above):

$ sudo webappctl start org.apache.tomcat

I get an error in /var/log/system.log:

Apr  2 16:18:46 host sudo[85904]:     levi : TTY=ttys000 ; PWD=/Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons ; USER=root ; COMMAND=/Applications/Server.app/Contents/ServerRoot/usr/sbin/webappctl start org.apache.tomcat
Apr  2 16:18:47 host serverctl[85914]: ERROR: The operation couldn’t be completed. Operation not permitted
Apr  2 16:18:49 host serverctl[85915]: ERROR: The operation couldn’t be completed. Operation not permitted
Apr  2 16:18:49 host servermgr_web[85908]: XSWebConfig:ERROR: Failed on second attempt to load /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/org.levi.foo.plist

and I don't see my "Here I am!" message.

This seems to indicate that my launchd.plist is not making it as far as running the script when trying to start via webappctl, but I have no idea what operation couldn't be completed, or why not. All of the files I've mentioned are owned by root:wheel with rw-r--r-- permissions, except for the script, which is rwx-r-xr-x.

So, what magic must be performed to get my webapp to load/unload my launchd configuration on start/stop?

Side note:
Obviously these configurations and scripts are simple examples I've used for debugging purposes and to simplify the details here. In reality I'm trying to stand up an instance of Tomcat as a "webapp" to run behind Apache 2 on my server such that I can deploy true web apps through Tomcat and have them served to the web.)

Side note 2:
I know I can install my launchd.plist in /Library/LaunchDaemons, and omit the launchKeys from my webapp.plist as a workaround, but this does not allow me to start/stop the webapp via Server.app (or the command line equivalent), and, I mean, it should work, right?

Many thanks,

Levi

(crosspost from https://discussions.apple.com/message/27946624 )

Best Answer

It turns out the setup I posed in my question is correct, and works as expected. This was confirmed by @klanomath, who was able to reproduce my configuration and see things working as expected. Specifically:

After issuing:

$ sudo webappctl start org.apache.tomcat

I get the expected "Here I am!" message in /var/log/system.log.

The solution for me was to simply restart my machine.

Take note, seemingly, the launchd configurations in /Applications/Server.app/Contents/ServerRoot/System/Library/LaunchDaemons/ appear to be cached somehow, and changes made there are not picked up by webappctl until something* is restarted.

Hopefully this will save someone the day it took me to have a gentle "have you tried restarting?" suggestion solve the issue.

Levi

*I'm not sure what needs to be restarted... I tried starting and stopping the webserver and Server.app, enabling and disabling the web app from within Settings.app GUI, with no effect. The only thing which picked up the changes was a full system restart.