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
- Compojure
- Clojure
- Nginx
- Digital Ocean
- 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 slurp
ed 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