The back-end program we’ve created for the OwnTracks apps is finding a modest amount of appreciation, but in spite of relatively simple building instructions, I’ve been wanting to provide ready-to-run packages for a while now. (Not to say that many people have requested such packages.) I bit the bullet, and I’m happy to say I think I’m doing this like all the cool kids do it. Sort of. I hope.

A while back I created a Homebrew formula for the Recorder. A client suggested a Docker image, so I did that too. Whenever I create a release, I update both and push them into their appropriate locations. About a week ago, I read that Docker hub allows automatic building, so I switched to that and had a builder kicked by a Github notification thing. And now packages. Sigh.

Without Jordan Sissel’s fpm I wouldn’t have done this. My copy of Maximum RPM (I think that was the name) collects dust somewhere in the basement; I read it many many years ago and can’t be bothered to study that again. Furthermore, I knew I’d have to dig into the Debian way of packaging and flatly refuse to learn how to build a .deb the way it probably should be done. fpm to the rescue, wrapped in a little shell script. Life is good.

Then of course I need hosts on which to build said packages. I wanted to offer Debian 8, Raspbian, and Centos 7. A short while later, I could ssh into these systems, launch make and, ta-da!, had three package files. Which need to be signed. Sigh. Another few shell scripts wrapped rpm --sign and reprepro, pushed the files here and there, and an rsync then finally dropped them in our repository. It worked, and I took copious notes on which machine to do what after which other step. You know how it is. (Ansible wouldn’t have helped me much I decided.)


After several years of hearing how people automate things, I thought: no, this can’t be it. Let me try and do this properly; it’ll hurt, but the effort should be worth it. I re-read the notes I took about Jenkins and read most carefully what Mark Maas had to say about Rundeck. In for a penny, in for a pound, why not? Moments later, (java -jar rundeck-launcher.jar) I had Rundeck’s Web interface up and running and looked around a bit. It appeared simple, but how to add hosts (nodes)?

<?xml version="1.0" encoding="UTF-8"?>
  <node name="ot-make-cen7"
    description="Centos7 builder"

Aha: an editor on the enterprisey-looking XML file resources.xml fixed that, and few minutes after downloading Rundeck I was launching remote tasks. Very nice indeed. The Web interface is good and snappy, and it has an API. Of course it has an API.


So, I created a job to pull our code, run make and fpm, and drop the resulting package file on shared storage. Hey, this is very productive! Rundeck talks to my servers over SSH (and I recommend you allow its variables to pass through. I then quickly write another job to sign the packages, but this job never stops running … What’s the problem? Ah, the Rundeck debug log shows "Enter passphrase:". Signing means GnuPG keys, and these have a passphrase. Duh. OK, I’m quite sure the world has solved this problem before I have to. It seems that lots (most?) people “solve” this by running expect as a wrapper around the signing program to have it “key in” the clear-text passphrase. I’m aghast, and it seems rpm has trouble with gpg-agent… So, if the cool kids do it, I must also:


## Usage: ./rpmsign.exp *.rpm
#  requires: $GPGPASSPHRASE in environment
#  hacked together by JPM from and
#  from @roidela_____ ;-)

global env

spawn rpmsign "--addsign" {*}$argv
expect "Enter pass phrase: "
send \r
expect eof

Anyway, where to put the passphrase? Rundeck uses something they call “key storage”.

key storage

Basically these are files on the Rundeck server’s file system which contain my clear-text secrets which Rundeck can pass into a job via, say, an environment variable. This is not what I’d call “safe storage”, but I’ll let it pass for the moment. (The reason I’ll let is pass is that all this is happening locally on my Mac and not in the cloud, so I feel relatively safe – famous last words…)

I now have two Rundeck jobs: one to get and compile code, and the other to sign packages. (I typically learn things in small steps to get to know how things work.) What I could now do is to merge those two jobs into one, but Rundeck can do that for me with a “workflow” job, in which I tell it to run this job, then that. Perfect. Rundeck also exports and imports job descriptions in XML or YAML:

- description: Pull git repo and make
  executionEnabled: true
  id: d5c0f354-5c85-4181-9791-ab170f1a6612
  loglevel: INFO
  name: make Recorder packages
      excludePrecedence: true
      keepgoing: true
      rankOrder: ascending
      threadcount: 1
    filter: ot-make-cen7 ot-make-deb8 ot-raspi
  nodesSelectedByDefault: true
  scheduleEnabled: true
    - description: pullmake
      script: |
        rm -rf recorder.git
        git clone recorder.git
        cd recorder.git
        cp etc/@node.os-name@/
        sh etc/@node.os-name@/
    keepgoing: false
    strategy: node-first
  uuid: d5c0f354-5c85-4181-9791-ab170f1a6612

This is looking very good, even though I’m still unhappy with the GPG passphrase thing. What I’m probably going to do is to have a passphrase-less key: whether I have that or the passphrase in clear text is probably six of one or half-a-dozen of the other.

Hey, can I have Rundeck tell my colleagues when a new package is ready? Yes, with notifications directly into our Slack channel.

notify slack

So, my workflow now goes like this: when I want to build packages, I kick my local Rundeck into action via its API:



curl --header "X-Rundeck-Auth-Token:$token" http://localhost:4440/api/1/job/$jobid/run

Rundeck then launches the build on the (currently) three machines, takes the packages, signs them, and pushes the result out to our public repository.

job started via API

As soon as that’s done, the last Rundeck job is to kick the Docker hub builder into action so that it builds our OwnTracks Recorder Docker container. (I’m aware a git push to Github could trigger Rundeck, but recall my installation is not publically reachable which is why I kick it into life manually.)

I knew it would be a lot of work, and I guessed the investment would amortize itself quickly; I was right. Automation is always a good investment.

May I now, finally, consider myself grown up?

automation :: 30 Jan 2016 :: e-mail