As I considered the requirements of moving an existing Grails app from a Cloud-Foundry-based host to a VPS (virtual private service), there was one thought nagging in my mind. It wasn't choosing between the Oracle and OpenJRE, nor configuring PostgreSQL, nor minimizing downtime to be caused by the database restore and DNS updates, nor... It was deployment. That thing I'd have to do as part of developing and supporting the application. I had become accustomed to pushing the app and having it copy over to the host and start up all on its own.

1
2
3
4
cd magical_directory_with_latest_war_file
af login
...
af push myapp

Poof! Validation was one Chrome tab away. Then --most of the time-- I'd find my updates happily humming along. So I set about replicating this work-flow on a naked VPS.

Nobody's home

After logging in to the Debian Linux VPS through SSH, I began to implement my infrastructure plan. Installing and configuring as if I had a gun to my head. There was a hard deadline and I wasn't about to miss it.

In the end, I got it done waaaay ahead of time. Hell, I didn't know that was going to happen!

I found the app a comfortable home in /opt/myapp/ and while I was at it, dumped the boring ol' Tomcat and replaced her with a new exiting pal to play footsies with: Jetty. Actually, I replaced Tomcat with Jetty to simply the deployment process I was cooking up; sometimes I loose myself in hyperbole.

With everything in place, and my app running, the only thing missing was the deployment process. How to get new versions of the app copied over and have the app restart with the new code. With an idea in mind, I paid a visit to the mistress. I mean, the other mistress: Mercurial.

One Mercurial hook a day, keeps the project lead away

The idea was simple:

Deployment sequence diagram

The developer (me) would check in the latest WAR file into a Mercurial repository. The repository would be a clone of a repo on the VPS. So a push would copy over the changesets and then run a Mercurial hook on the VPS. The hook would first stop the app, then check out the latest WAR file, and finally start the app.

Right away, my first question was: How do I go about stopping and starting the app? And just as quickly a dormant corner of my brain responded: By making the app an ordinary Operating System service. Duh!

I looked about at various ways to wrap my app in something that would make it function like an OS service, a daemon, but the solutions gave me indigestion. Besides, I preferred to avoid modifying the code to make this happen. With the app being executed by Jetty, a simple SIGTERM/SIGKILL was enough to shut it down. So I played with the idea of coding a wrapper which could be executed by the OS's init system. The wrapper would be able start/stop the app, and the Mercurial hook could then simply call the wrapper. Being a Gentoo Linux user, I'm familiar with OpenRC. Debian uses systemd, (which I quickly learned is quite nice), so off to creating a systemd service.

Here's a fun fact: Due to Gentoo Linux's rolling-release, my current Gentoo installation is the same one --though up-to-date-- installed about 14 years ago.

The systemd service for my app looks like this:

/etc/systemd/system/myapp.service
1
2
3
4
5
6
7
8
9
10
11
[Unit]
Description=My App
After=network.target system-postgresql.slice

[Service]
User=myapp
EnvironmentFile=/etc/myapp.conf
ExecStart=/usr/bin/java $JAVA_OPTS -jar $JETTY --out $LOG $WAR

[Install]
WantedBy=multi-user.target

That was easy. There was no need to manage a PID file! Next, the configuration file for the service.

/etc/myapp.conf
1
2
3
4
JAVA_OPTS=-Xms512M -Xmx512M -XX:MaxPermSize=256M -XX:PermSize=128M 
JETTY=/opt/myapp/jetty-runner-9.2.9.v20150224.jar
LOG=/var/log/myapp/yyyy_mm_dd-myapp.log
WAR=/opt/myapp/webapp/myapp.war

Then it was a matter of enabling the service to run on boot and... giving it a go.

1
2
3
# systemctl enable myapp
# systemctl start myapp
# systemctl stop myapp

It worked beautifully.

Now, I didn't want to log in as root when pushing to the repo, so I granted my developer account access to the necessary systemctl commands via sudo. Then I was ready for the final piece. The Mercurial hook:

/opt/myapp/webapp/.hg/deployhook.sh
1
2
3
4
5
6
7
#!/bin/bash

# NAME: deployhook.sh
# AUTHOR: Emmanuel Rosa
# PURPOSE: Deploy myapp WAR file on the tip of the default branch

sudo /bin/systemctl stop myapp && hg update default && sudo /bin/systemctl start myapp

I configured the hook to run on changegroup, which means after the entire push completes.

/opt/myapp/webapp/.hg/hgrc
1
2
[hooks]
changegroup = /opt/myapp/webapp/.hg/deployhook.sh

Happy as a clam

Having just confirmed the meaning of the expression happy as a clam, I can honestly say that's me. Deployment of my app is still a simple push away, and I had a good time set it all up.