A short time ago, I worked on a web application that need both Gunicorn (behind a proxy server, like nginx) and some external scripts executed in a tmux session.
The web application is deployed on a university lab's server, which was often down, mostly due to the overloading created by the A/C operating full-time in this season. As a consequence there was the need to login and start the services manually everytime.
Sick of this situation, I've spent some time to automate the entire process. It was not easy as expected because I've never used Upstart before.
Automatically start Gunicorn was sufficiently easy. First of all, in my case, Gunicorn must start after mongoDB. As regards the stop part, I chose the standard runlevels. Moreover, I chose to use the respawn stanza, which just means that the service will be restarted if ended unexpectedly.
Second, I need to specify the PYTHONPATH environment variable because (from the cookbook):
When Upstart runs a job, it provides it with a very restrictive environment which contains just two system variables: TERM and PATH.
Last, I included in a script stanza a couple of commands. The first just change the directory in which to launch Gunicorn, while the second actually starts the service.
The resulting script is the following:
start on (started mongodb) stop on runlevel  respawn env PYTHONPATH=/path/one:/path/two/:/path/three script cd /my/wonderful/webapp/path exec /usr/bin/gunicorn --user myuser --group mygroup --log-file /var/log/gunicorn/myConfig.log --bind=127.0.0.1:4000 app:app end script
While this first version perfectly works, I think, however, that some small improvements can be applied to make the script adhere to Upstart standards:
- a finer grained stop on stanza: the best practice to stop the service should be just before mongoDB stops;
- the use of setuid and setgid stanzas in place of Gunicorn options;
- the use of chdir.
This can be a better and cleaner version, but I still need to test it (and I don't know if I'll ever do it!):
start on (started mongodb) stop on (stopping mongodb) respawn env PYTHONPATH=/path/one:/path/two/:/path/three setuid myuser setgid mygroup chdir /my/wonderful/webapp/path exec /usr/bin/gunicorn --log-file /var/log/gunicorn/myConfig.log --bind=127.0.0.1:4000 app:app
Starting tmux was trickier. First of all, I decided to use tmuxinator which allows to create and manage different sessions of tmux very easily. If you are curious, just follow the link and check its README.
First, the script that will run inside tmux need mongoDB too, therefore our start on condition is the same as Gunicorn. Same considerations are valid for the stop on.
The tricky part comes now. Tmuxinator need some enviroment variables to properly work and the documentation mentions only the EDITOR one (which probably is useless here, but I inserted it anyway). The other two variables needed are:
- SHELL: tipically initialized with user's standard shell, /bin/bash in my case;
- HOME: the home of the user that will have access to the terminals (it needs this because it uses ~ to access the right directory).
Finally, we need to set the right permissions. If you don't specify which user will use the session, then only root will have access to it. Here's the working script:
start on (started mongodb) stop on runlevel  #could be also stopping mongoDB env SHELL=/bin/bash env EDITOR=nano env HOME=/my/home setuid myuser setgid mygroup exec tmuxinator start myProjectName
Unfortunately, there's a drawback. Upstart can't track every fork that tmux does because they depend on the number of windows opened and processes started inside it. There are two stanzas (expect fork and expect daemon) that define how the service is going to fork and make Upstart behave consequently, but none of them work with tmux(inator) and make the stop command to hang. If you don't use the mentioned stanzas, then the stop command will not hang, but you will get the following message (and you'll need to kill the process by hand, if needed):
stop: Unknown job: myProjectName
So, if you need to stop regularly the service, I'm afraid that there's no solution AFAIK (if you find it, just let me know!), but if you need tmux always up, then this solution is perfect.