Procrastination

I really should be working on this obscure distributed RDF/FRP thing, but for various reasons my head isn't working properly write now. So I did this other stupid thing instead.

Zippy

Once upon a time, M-x yow in emacs would deliver a nice random quote from Zippy the Pinhead. Nowadays, you just get

Yow!  Legally-imposed CULTURE-reduction is CABBAGE-BRAINED!

which has something to do with copyright law. More specifically, the file yow.lines in emacs' data-directory now contains only the opinion expressed above, rather than the original seven-hundred or so precious epigrams, delimited by \000. I have heard dark whispers about so-called "free thinkers," who have managed to procure an original list and thereby restore the functionality of yore. These stories may not be entirely apocryphal, as the internet has a way of not forgetting things.

Of course, you can always create your own yow.lines, with bons mots of your own device, or of incontrovertible public domain provenance, and that probably happens too. All of the above applies, with minor variation, to the unix fortune command as well.

Slack

Slack is a versatile chat system and messaging system with all sorts of clever archiving and search facilities. It's better than anything I've used in the corporate world, including (especially) Lync, and, in truth, I'm finding it hard not to like it better than systems in the same space written by people I know and like personally.

I'd never heard of it until I read this Wired article. Slack is the brainchild of the creator of Flickr, and, like Flickr, you can use it for free, unless you have important business needs, and then you can pay a lot of money for it. All I wanted to do was recreate the more frivolous aspects of a chat room at a company where I once worked. Since neither I nor most of the other participants work there anymore, it would be necessary to set up something independent. I'd considered Google Hangouts, but, even ignoring the increasingly obviously repellent nature of a business model built on invasion of privacy, the whole Google+ ecosystem is a hot mess, with every aspect subject to random deprecation when they realize it doesn't help the bottom line of serving up advertisements to the poor and ignorant. So Slack it was.

Slack also has a lovely API, supporting integration with practically anything with technological sophistication at or above the level of curl. From the perspective of the yowserati, one most interesting features is outgoing webhooks. Basically, they let you define words, the use of which on chat channels will trigger POST requests to a URL of your choice, which can then respond with something appropriate, wrapped up pretty simply in JSON. The POST message includes a secret token, and the URLs may be SSL'd, so the whole protocol is reasonably comforting.

Around 4pm yesterday, I decided to drop everything I was doing and set up a slackbot on a Digital Ocean droplet, powered by a trivial clojure app. I convinced myself that, in addition to the challenge of doing it as quickly as possible, it would give me a chance to solidify my knowledge of a technology stack that, while, not particularly complicated, I've had a tendency to slop my way through, fiddling with this and that until it all works and then stepping slowly away before anything breaks.

Stack

  1. Compojure
  2. Clojure
  3. Nginx
  4. Digital Ocean
  5. Commando

Clojure bits

My "application" differs almost insignificantly from the one you get by typing lein new compojure-app. The vaguely interesting bits have to do with data that I don't necessarily want to commit to github, namely the secret token that Slack will be sending and the database of Shavian gems.

So I have a small module:

(ns slacks.quotes)
(def ^{:private true} quotes (atom nil))
(def ^{:private true} token (atom nil))
(defn init [] 
  (let [y   (slurp "LINES")
        ys  (clojure.string/split y #"\000")
        ys  (drop 1 ys)  ; by convention, documentation
        t   (slurp "TOKEN")]
    (println "Read" (count ys) "lines and token" @token)
    (reset! quotes ys)
    (reset! token t)))
(defn get-quote [] (rand-nth @quotes))
(defn request-ok? [params] (= (:token params) @token))

The quotes and top-secret token are slurped from files that are supposed to be locally available. It's probably overkill to put them in atoms, but old habits die hard.

There's one, very boring route definition:

(defn slacks-json [params]
  (if (slacks.quotes/request-ok? params)
    {:status 200
     :headers {"Content-Type" "application/json"}
     :body (clj-json.core/generate-string {"text" (slacks.quotes/get-quote)})}
    {:status 403
     :body "Buzz off"}))

(defroutes home-routes
  (POST "/slacks" {params :params} (slacks-json params)))

Slack is expecting a response of the form {"text" : "clever quote"}, which is most safely generated via clj-json, since the clever quotes may turn out to contain gruesome punctuation.

And that's about it. You can launch the thing as usual with lein ring server-headless PORT, but it seems a bit icky to run lein in production, or even in "production," made lein ring uberjar instead.

Digital Ocean

I have a droplet for this sort of thing. It's the cheapo 512MB version, running Ubuntu. At some point, I set it up for running and managing JVM apps:

apt-get install git-core maven leiningen nginx

To keep things moderately secure, I set PasswordAuthentication no in /etc/ssh/sshd_config and then maybe kind of subvert that with echo 'pnf ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/pnf.

For today's purposes, I'll start by cloning my yowsabot repo: git clone https://github.com/pnf/slacks.git

nginx

Like many people these days, I'd rather deploy web apps as small programs expose themselves to userland ports on localhost, and then use nginx to proxy external requests to them. There's much more flexibility, in case I ever want to proxy to multiple machines in a private network, and it's nice to have a few, small processes running, rather than one multi-threaded Goliath.

It's also pretty easy to configure SSL with nginx, and it would clearly be irresponsible to allow the Slack token to go out in plaintext, thereby exposing my powerful apothegms to malefactors of all stripes. Digital Ocean has a nice tutorial for creating the needed SSL certificate, and you can blindly follow their instructions up to the point where they say what to put in the nginx config, since their example is for static content.

In our case, we're going to redirect incoming requests to my compojure app, which for some reason or another I decided to have listen on 3001. /etc/nginx/sites-enabled/slacks is a soft link to /etc/nginx/sites-available/slacks, which contains in toto:

server {
        listen 443;
        server_name slacks.whatever.com;
   if ($host !~ ^(slacks.whatever.com)$ ) {
         return 444;
   }
   if ($request_method !~ ^POST$ ) {
         return 444;
   }
   if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
            return 403;
   }
   ssl on;
   ssl_certificate /etc/nginx/ssl/server.crt;
   ssl_certificate_key /etc/nginx/ssl/server.key;
  location / {
     proxy_pass http://localhost:3001;
        #proxy_redirect default;
        proxy_redirect off;
        proxy_buffering off;
        proxy_set_header        Host            slacks.whatever.com;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
   }
}

and /etc/nginx/sites-available/default has been removed.

As pnf, I can sudo /etc/init.d/nginx restart and all is well. There basically nothing you can do other than post to https://slacks.whatever.com/slacks, and you'll get 403'd if you don't provide the right token.

commando.io

commando.io is a probably not necessary in this equation, but I've been playing with it recently and decided to let it play. It's supposed to function as an online control panel for controlling software deployed around the cloud, which it does by letting you define "recipes" and then executing them over a ssh (which obviously requires putting their public key on the servers in question).

My recipe just pulls the latest code down from git, builds an uberjar and then runs it as a daemon:

    DIR=${HOME}/slacks
    ps augxw | grep slacks.jar | grep -v grep | awk '{print "kill",$2}' | sh -v
    sleep 10
    cd ${DIR}
    git pull
    lein ring uberjar
    export PORT=3001
    /bin/rm -f STDERR STDOUT
    daemon -D ${DIR} -O ${DIR}/STDOUT -E ${DIR}/STDERR -- java -jar ${DIR}/target/slacks.jar

Occasionally, I might need to log into the server to mess with something, but most changes to the bot can be administered via git and commando.

So there you are

As noted above, the bot code lives at https://github.com/pnf/slacks.git.

I suppose I have to do something useful now.



Comments

comments powered by Disqus