Irssi notifications on iOS and Android

Or really anywhere that https://pushover.net supports.

In light of the earlier article, I thought I'd might as well supercharge my IRC setup. So, now we're gonna get some notifications for our mobile devices via pushover.

Setting up pushover

There really isn't much to do here. First, go create an account and download the app on the device you want notifications on.

Then, register a pushover application. You can do this from the dashboard, or directly from https://pushover.net/apps/build. It will look something like this,

Pushover Application Registration

Setting up the script

So, like the article mentioned before, I'm assuming you're using irssi and the fnotify plugin.

Create a file called something like irssi_pushover.sh with the contents below,

#!/bin/bash

# Replace `YOUR_APP_TOKEN` and `YOUR_USER_TOKEN` with your values from pushover
tail -f /home/user/.irssi/fnotify | while read heading message; do
    url=`echo "${message}" | grep -Eo 'https?://[^ >]+' | head -1`;
    if [ ! "$url" ]; then
        curl -s \
            --form-string "token=YOUR_APP_TOKEN" \
            --form-string "user=YOUR_USER_TOKEN" \
            --form-string "message=${message}" \
            --form-string "title=${heading}" \
            https://api.pushover.net/1/messages.json
    else
        curl -s \
            --form-string "token=YOUR_APP_TOKEN" \
            --form-string "user=YOUR_USER_TOKEN" \
            --form-string "message=${message}" \
            --form-string "title=${heading}" \
            --form-string "url=${url}" \
            https://api.pushover.net/1/messages.json
    fi;
done;

This script will read from /home/user/.irssi/fnotify and send a POST request with the message and title via CURL using the pushover API.

You need to change the destination to the correct location of your fnotify file, and of course also replace YOUR_APP_TOKEN and YOUR_USER_TOKEN with your values from pushover (the user token is on the dashboard and the app token is under the app).

Conclusion

Now you've got notifications on your phone too! :) Never miss a moment of IRC again. A tip: you can make pushover open links directly to the browser under settings in the app.

Local notifications from irssi on a remote server

As of writing, the versions used are

  • OS X 10.9 Mavericks (should work on all, unless they change launchtl plists)
  • irssi v0.8.15
  • autossh v1.4c
  • terminal-notifier v1.6.1

Update: Added checks for existing processes in the script.

So, if you're like me and like to have your IRC client (in this instance irssi) running on a server in a tmux or screen session to never miss out on the conversation, you might feel like you're missing some of the benefits of running a local IRC client.

In particular, I was missing local notifications when someone would highlight my nickname. I could of course use a bouncer, but hey! it's no fun running irssi locally and having to close it for a reboot just as you've gotten it precisely the way you like it :)...

So, how does one solve this problem?

The setup

The final setup will look something like this,

autossh -> irssi + fnotify -> terminal-notifier

You can replace terminal-notifier with whatever notification program will work on your system. The rest is pretty much OS agnostic, except for the automatic startup of the script.

What you need

First off, I'm assuming you're running the fnotify plugin in irssi, and that you have setup private keys between you and the server. These are the only things you need to do server-side.

On your local setup you'll need,

  • autossh
    • sudo port install autossh
  • terminal-notifier
    • sudo port install terminal-notifier or
    • sudo gem install terminal-notifier

We use autossh to keep our ssh connection from dropping, and killing the script (so you don't have to constantly restart it whenever you close the lid on your laptop).

To display the notifications, we use terminal-notifier, which creates native notifications on OS X. You can change this out with whatever program your OS needs.

Get it going

So, to the fun part. First off, create a file called something like irssi_notifier.sh in a location you don't mind it being in forever, but can still remember (at least until the end of this article).

In this script, you'll need the following code. I'll explain what it does below,

Replace the two user@server instances with your details!

#!/bin/bash
irssi_notifier() {
  (ssh user@server -o PermitLocalCommand=no \
    ": > .irssi/fnotify ; tail -f .irssi/fnotify " |  \
  while read heading message; do                      \
    url=`echo "${message}" | grep -Eo 'https?://[^ >]+' | head -1`; \
    if [ ! "$url" ]; then terminal-notifier -sender com.apple.Terminal -message "${message}" -title "${heading}" -activate com.apple.Terminal; \
    else terminal-notifier -sender com.apple.Terminal -message "${message}" -title "${heading}" -open "${url}"; \
    fi; \
  done)
}
# Make sure we don't make a new autossh connection
if ! ps aux | grep -q 'autoss[h]'; then
     /opt/local/bin/autossh -M 0 -f -N -p 22 -g -c 3des -D 1080 user@server;
fi;
# Avoid relaunching the notifier function if it's already running
if ! ps aux | grep -q 'tail -f .irssi/fnotif[y]'; then
     irssi_notifier
fi;

So, what happens here?

  • Our she-bang line (the line with #) makes sure the script gets run as a bash script without the need to specify bash on launch.
  • We create a function for the notification part of the script
    • Inside the irssi_notifier function, we connect to the server with irssi on, and read via tail from the fnotify file.
    • When somethings comes in, we divide it into a heading and a message, and check for an URL in the message part.
    • If no URL is found, we make construct the notification via terminal-notifier to just activate our terminal upon click on the notification.
    • If there is a URL, we open the URL in a browser upon click.
  • Just after the function definition, we create a connection to the server via and monitor it with autossh.
  • We call the previously defined irssi_notifier function, and start the notification service.

In the last two calls, we check if the processes exist, and only call them if they don't. This avoids spamming a zillion autossh connections etc.

Testing

If you just want to test it, you can just run the script (remember to chmod +x it) without the autossh part (just comment it out). If you use autossh, you'll need to kill the process if you relaunch the script, else it'll keep spawning more and more autossh processes.

Launch on startup

NOTE: This is specific for OS X, but I'm sure there are ways for other OSs

If you're lazy like me, you probably aren't satisfied quite yet. There is still the part about starting the script yourself whenever you boot up your system.

On OS X, this is quite simple (once you know how to do it, that is). We will use launchctl, which manages programs that launch upon startup.

To launch the script, you need to create a file called com.irssi.notifier.plist in ~/Library/LaunchAgents/. The contents will be,

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.irssi.notifier</string>
  <key>ProgramArguments</key>
  <array>
    <string>/absolute/path/to/irssi_notifier.sh</string>
  </array>
  <key>KeepAlive</key>
  <true/>
</dict>
</plist>

The only thing you need to change is under ProgramArguments, where you substitute /absolute/path/to/irssi_notifier.sh with the actual path to the script file.

Finally, you need to do launchctl load ~/Library/LaunchAgents/com.irssi.notifier.plist.

Conclusion

After this, you should be good to go! Now you can enjoy getting IRC highlights whenever you have a working connection.

Try and reboot, and check if it works :)

Why not use mosh instead of autossh?

The reason I didn't use mosh to do what I accomplished with autossh, is because of running into trouble executing commands directly after a connection initiation (mosh user@server 'echo 1' fails instantly, to give an example).

That said, I do recommend using mosh instead of ssh normally, since it works excellent :)

Deploying with Vagrant

As of writing, the newest version of Vagrant is 1.4.3.

I recently started looking into ways that I could improve my current deployment workflow. Since my server doesn't have much RAM, I currently build the binaries locally in a Virtual Machine (VM from here on out) and then send them to the server using scp.

Although I can't do much about the server part (except buying a bigger server), I can do something about what I do locally. I set out to check what possibilities I had, and ended up looking at Vagrant.

While it doesn't cut hugely from the local part of my deployment hassles, it could atleast streamline it a bit, and help remove the cabal hell that I'd sometimes run into with my current setup (having multiple applications that I deploy from the same VM).

A quick overview of the article:

What is Vagrant?

Vagrant basically manages your VMs in a manner that is local to each project you use it in (although you can use it in a more general way). As standard it uses VirtualBox, but you can also use it with other providers, such as VMWare.

That said, for the purpose of being convenient to most people, I'm going to assume VirtualBox throughout this article, since that is the free alternative of the two I mentioned.

Getting started with Vagrant

Vagrant has a nice getting started series that you should follow if you want to understand Vagrant better (I highly recommend it, it's not that long). I'm going to try and give a quick explanation of how it works though.

Vagrant uses something called a box. These serve as the basis for the Vagrant setups you create, and are in essence just VMs with the bare minimum installed. To add a box you do,

vagrant box add precise64 http://files.vagrantup.com/precise64.box

which will add a VM with a 64-bit version of Ubuntu 12.04 (Precise Pangolin). It will from then on be under the name precise64, stored on your computer. You can change the url with any other url or a local filepath, and the name with what you want your box to be refferred to as.

You can try and search around if you can find a box that matches your needs, but if you want to customize it completely, I recommend creating your own.

Replicating your server

Before we start creating our Vagrant box, we need to know what our server looks like, so we can get the correct distro image.

On Debian, and most likely others, you can check out what version you're using with, cat /etc/*-release. This will output something like,

PRETTY_NAME="Debian GNU/Linux jessie/sid"
NAME="Debian GNU/Linux"
ID=debian
ANSI_COLOR="1;31"
HOME_URL="http://www.debian.org/"
SUPPORT_URL="http://www.debian.org/support/"
BUG_REPORT_URL="http://bugs.debian.org/"

which tells us we're dealing with Debian Jessie (testing).

Next up, is the architecture, and we get that by running, uname -r, which may look like,

3.2.0-4-amd64

with amd64 telling us it's 64 bit and built for amd. You should also try and match the kernel version as best you can though (the 3.2.0).

Rolling your own Vagrant box

First off, you need to install VirtualBox, and then go and download the installer image for the distro you want. Since my server uses the amd64 version of Debian Jessie, I'm going to go ahead and download the amd64 netinst CD image.

After downloading it you launch it in VirtualBox and install it as you normally would a Virtual Machine. Following Vagrant's guidelines for creating a base box, I set the following properties (with some altercations of my own):

  • 40GB dynamically resizing drive
  • 2GB RAM (they recommend 360MB, but compiling is quite memory intensive, and my laptop has 8GB of memory already)
  • Audio disabled (we will only ssh into it)
  • USB disabled
  • NAT networking

And as recommended, I set the following values:

  • hostname: vagrant-debian-jessie
  • domain: vagrantup.com
  • Root password: vagrant
  • Main account login: vagrant
  • Main account password: vagrant
  • Uncheck all software, to get the bare minimum

Now that you have the VM up and running, it's time to set it up as a Vagrant box.

Setting up the VM

Vagrant assumes sudo access without password, since that allows Vagrant to do a lot of neat thigns without our intervention (installing packages, setting up network folders etc).

Sudo access

First we change to root, and then,

apt-get install sudo

followed by editing the sudoers file with visudo. Here we add,

# /etc/sudoers
Defaults    env_keep="SSH_AUTH_SOCK"
%admin ALL=(ALL:ALL) ALL
%admin ALL=NOPASSWD: ALL

which allows users in the admin group to use sudo without a password.

After that, we need to add the vagrant user to the admin group,

groupadd admin
usermod -a -G admin vagrant

Installing VirtualBox's Guest Additions

While still logged in as root, we need to uninstall VirtualBox packages and install linux headers so we can install the tools VirtualBox uses,

apt-get autoremove virtualbox-ose-guest-dkms virtualbox-ose-guest-utils virtualbox-ose-guest-x11
apt-get install linux-headers-$(uname -r) build-essential

note that there might not be able to find any of the packages we tried to remove, and that is fine, but sometimes they get installed automatically depending on the distro etc.

After that, we mount the Guest Additions CD, which is located in the Devices menu and then something like Insert Guest Additions CD Image.... We then run,

mount /dev/cdrom /media/cdrom
sh /media/cdrom/VBoxLinuxAdditions.run

Installing packages

The last thing we need to do as root is installing the packages that we need,

apt-get install ruby rubygems puppet openssh-server openssh-client

depending on what distro you use, rubygems might fail to install. On debian Jessie, gems was included in the ruby package. After this, we need to set SSH to not use DNS. This will give a small speedup when SSH'ing to the VM,

echo 'UseDNS no' >> /etc/ssh/sshd_config

I intentionally didn't include chef here, since I don't personally need it, and the install process is also a tad more bothersome than puppet's. You would just add it here if you need it though.

Custom packages

This step is a bit more personalized, whereas the rest is for creating a general Vagrant box. Since I intend to use this box for deploying and testing my Haskell packages, it's relevant for me to install the haskell-platform and an updated version of cabal to save some time when I create the VM with vagrant up later on.

You can also do these steps automatically on the creation of a box, by adding a shell script with the comamnds inside the VagrantFile in your project, sorta like the example with automatically setting up Apache.

That said, I did the following,

apt-get install haskell-platform
cabal update
cabal install cabal-install

This will save me a bit of time compiling things (especially cabal-install) over and over again.

SSH keys

Finally, you can now jump back to the vagrant user with exit, since the last bit needs to be done in the home folder of the vagrant user. We need to setup the authorized SSH keys for the vagrant user so vagrant ssh works easily using their standard insecure key pair,

mkdir .ssh
wget --no-check-certificate -O .ssh/authorized_keys https://raw.github.com/mitchellh/vagrant/master/keys/vagrant.pub
chmod 700 .ssh
chmod 600 .ssh/authorized_keys

Now we just need to cleanup the VM and shut it down,

sudo apt-get clean
sudo shutdown -h now

Exporting the VM box

The final step is to export the VM as a vagrant box and add it. Vagrant neatly picks up the VMs that VirtualBox manages (assuming you use the default location), so you can simply do,

vagrant package --base Jessie64

where Jessie64 is the name I gave the VM inside VirtualBox. It will create a file named package.box at the location you ran the command.

Now you can add the box to Vagrant just like any other box,

vagrant box add jessie64 ~/package.box

But how do we use it?

I'm going to give a little example of how I would set up and use Vagrant in one of my projects.

First I create the VagrantFile inside my project, using the box I just created,

vagrant init jessie64

After this, I create a small script that serves to setup my project with the packages that it needs. The content of the script should be self-explanatory. I name this bootstrap.sh and it contains the following,

#!/usr/bin/env bash

cd /vagrant

/home/vagrant/.cabal/bin/cabal update && \
/home/vagrant/.cabal/bin/cabal sandbox init && \
/home/vagrant/.cabal/bin/cabal install

The /vagrant folder is a folder that Vagrant keeps in sync with the host filesystem. It is the folder that Vagrant is running in, which in this case is my project folder.

I then add a line,

config.vm.provision :shell, :path => "bootstrap.sh"

in my VagrantFile inside the Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| section.

After this I can do, vagrant up and my project will get completely bootstrapped and ready for me to compile it and deploy. This usually means doing vagrant ssh and then two simple commands,

cd /vagrant
yesod keter

with this, I've now build my binary, packaged it up and sent it to my server.

If there is some trouble with some packages I can simply do, vagrant destroy. This will destroy the VM in my project (it still keeps the box we created before, don't worry).

You can change destroy with halt to shut down the VM or suspend to simply suspend it. There is some information about the pros and cons to the three options in the Vagrant documentation.

Concluding thoughts

Vagrant has greatly made me more comfortable messing around with my local VMs, since it's so easy to just destroy one and spin up an exact replica.

It also allows me to easily sandbox my projects completely by having VagrantFile's in each of them, with exactly what they need. Since Vagrant syncs the project folder, I can make changes using my preferred tools and the VM keeps the folders in sync.

As to what comes after this; I'm looking into puppet at the moment, since I would like to make setting up my server completely automatic. This will be a future article for itself though.

What is this about?

This blog will mostly serve as a personal memory pad. I often discover a process, learn something new or just am plain forgetful. Therefore, I'll try to write down when I learn various things.

That said, I'm striving to make my content as general as possible, so it won't only be me that benefits from it :)

What will be on here? Mostly things related to programming and technology. What I learn is almost always related to some hobby project I got going on.

Another thing I'll try to do, is post upfront whatever version of tools or libraries I might be using throughout that post. Far too often has it bothered me that I didn't know what version an article or tutorial applied to, or had in mind, and because of that, one might be doing something that was solved in later versions of said library or tool.

A little background on me

I'm mainly a self-taught programmer, and started my venture out with PHP and the web dev stack (JS, HTML and CSS). This also got me my first programming related jobs, and I've been grateful for that. Later on, meanwhile working, I started playing around with Python and a small plethora of various languages. All in the imperative end of things though.

I then started studying at university, which I still do, and around the time I started I got more interested in declarative programming. Namely Haskell. This has since been my focus, although the progress is slow (my imperative background is slowing me a bit), it's still quite a joy to work with GHC, and the level of assurance the compiler gives me with my programs.

I'll be trying to update this blog as much as time allows. Hopefully there'll be something interesting on here from time to time.

A closing note; this blog itself is written in Haskell using the Yesod framework. I'm working on making it as general as possible so people can fetch it and turn it into their own. You can find it at GitHub.com/Tehnix/HsCMS