Automated deploy from travis-ci via ssh

The other day I wanted to have an automated deployment for a sideproject ( of mine. I wanted to be able to merge a PullRequest on github and have Travis-CI deploy the merged files via rsync after all tests succeed. Easy thing I thought, I can’t be the first one attempting that. Looked like I was at least the first one writing about it…

The Requirements:

So I have a Server where I am able to use scp or rsync on. I do not want to have git, bower, npm or any other fancy stuff on the server that is not needed for serving the content. I am able to log into the server without a password via ssh. My Code is on Github (might be bitbucket or gitlab as well) and my tests are run on Travis-CI (could be any other CI-Server, certain steps will then be different). I am deploying a PHP-Project.

The Workflow:

I open a PullRequest(PR) on github (or bitbucket or gitlab…) which is then automatically tested by Travis-CI. When the tests pass, I can merge the PR and then the tests are rerun. When everything works fine, the relevant files are copied into a build-folder and the necessary build-steps (fetching requirements, parsing SASS-files, merging and minifying JS and CSS-Files and so forth) are perforemd there by Travis-CI. When everything is prepared the final package is pushed to the live-server using rsync (or scp). Before and after pushing a respective script can be executed on the server to do database-migrations, set a maintenance-page or whatever I want to do.

The steps:

The single steps are pretty straightforward and I can do them manually any time. And even automation is not that complicated using a build-tool like phing. The main thing was the login on the server as it needs to be passwordless using public/private key authentication in ssh. And for that the person loging into the server needs a private key and has the corresponding public key installed on the server. But wait… That means I have to put the private key into the repository.
Because Travis-CI will need to use it to log into the server. But can’t then everyone else do that? Yes! So how does one solve that? Well, there are actually two ways:

  1. Encrypt the private key using the travis-cli! There is a CLI to travis that has a command encrypt-file which uses a key only known to Travis-CI which is used to encrypt the private key. And then you can add that encrypted key to the repo as it is useless without the key from Travis-CI (which I hope will never get into the wrong hands!) Make sure to only add the encrypted key to the repo!!

  2. And I didn’t test that yet, there is a possibility to add environment-variables to your build in Travis-CI. So you can add your private key as content of an environment-variable and put that during the build-process into a file and use it then to authenticate against the server.

So during the build-process I have to either decrypt the encrypted file or put the content of the env-variable into a file and then I can use that file to authenticate to the webserver during the scp or rsync-process.

So how’s that done? Let’s show some code:

gem install travis # when you didn't already install the travis-cli
travis login
travis encrypt-file id_rsa --add

This last command will do two things: It will encrypt the file id_rsa to id_rsa.enc and it will add a command to decrypt the file id_rsa.enc during the travis-build to your .travis.yml. You can now safely remove the ir_rsa from the folder to wherever you need it. Add the newly created id_rsa.enc and the changed .travis.yml to your repo.

Cool stuff so far!

But you’ll have to be carefull! The key to decrypt the file will only available on master builds and not on pull-request builds or branch builds. So unless you are running a build on the master branch decryption will fail and therefore your whole build will fail. Therefore I enclosed the decryption in an if statement like so:

- if [[ $TRAVIS_PULL_REQUEST == "false" && $TRAVIS_BRANCH == "master" ]] ; then
 openssl aes-256-cbc -K $encrypted_944d199e46d0_key -iv $encrypted_944d199e46d0_iv
 -in id_rsa.enc -out id_rsa -d && chmod 600 id_rsa; ./vendor/bin/phing deploy; fi

The $encrypted_944d199e46d0_key will be different in your case. And also note that I added a chmod 600 because I had to do a lot of debugging to find out that the key has to be read/write only for the user for authentication to work!

So now that the key-decrypting part is solved I’ll show you how to get the key into the rsync.

For that I wrote this build.xml-file that depends on this properties-file that contains beneath other important informations – like the IP-Adress of your server you want to deploy to – the name of the key-file in the parameter deploy.identity_file. Add id_rsa here and now deployment should work.

And calling the deploy target then copies your file to a freshly created build-folder, run composer install as well as bower install, calls a pre-deploy script on the server (if existent), moves your files to the server and calls an after-deploy-script on the server. The pre- and post-deploy scripts are run via ssh using the passwordless authentication and moving the files to the server is using either rsync via SSH or directly scp also with the passwordless authentication.

Questions? Ideas? Recommendations? Feel free to add a comment.