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"?>
<project>
<node name="ot-make-cen7"
description="Centos7 builder"
tags=""
hostname="ot-make-cen7"
osArch="x86_64"
osFamily="unix"
osName="centos"
osVersion="7.3"
username="jpm"/>
</project>
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:
#!/usr/bin/expect
## Usage: ./rpmsign.exp *.rpm
# requires: $GPGPASSPHRASE in environment
# hacked together by JPM from http://aaronhawley.livejournal.com/10615.html and
# from @roidela_____ ;-)
global env
spawn rpmsign "--addsign" {*}$argv
expect "Enter pass phrase: "
send $env(GPGPASSPHRASE)
send \r
expect eof
Anyway, where to put the passphrase? Rundeck uses something they call “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
nodefilters:
dispatch:
excludePrecedence: true
keepgoing: true
rankOrder: ascending
threadcount: 1
filter: ot-make-cen7 ot-make-deb8 ot-raspi
nodesSelectedByDefault: true
scheduleEnabled: true
sequence:
commands:
- description: pullmake
script: |
rm -rf recorder.git
git clone https://github.com/owntracks/recorder.git recorder.git
cd recorder.git
cp etc/@node.os-name@/config.mk.in config.mk
make
sh etc/@node.os-name@/fpm-make.sh
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.
So, my workflow now goes like this: when I want to build packages, I kick my local Rundeck into action via its API:
#!/bin/sh
token=QeAJ32IWc1qYb0uuMzuEdOErCarm7Mqo
jobid=ec5bcb05-fe01-43bd-9bf9-240f15d18875
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.
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?