• Monitoring Processes with Monit and Slack

    (This post was originally posted on Cubicle Rebel’s engineering blog. Check it out!)

    Our clients rely on us to provide rock-solid long-term stability for their projects, and one critical aspect of maintaining uptime is providing realtime logging and monitoring of long-running services (also called daemons) on our servers.

    We use Slack exclusively for communication and notifications regarding technical matters. Because of Slack’s great webhook support, we decided to hook it up to Monit and have Monit automatically notify us and attempt to restart the process if anything goes down. Here’s how we went about it.

    For the purposes of demonstration, let’s assume that we have an application called Beerist (a social network for beer drinkers). It happens to be a Rails app, and we want to use Unicorn as our Rack server so that we can serve requests concurrently with forked OS processes.

    1 - Set Slack up

    The first thing you want to do is to get the Webhook URL from Slack by visiting this page and signing in. You will be prompted to choose a channel to post to, but it doesn’t really matter which one you pick because you will be able to override the (default) channel in your payload. After you get the URL, don’t close the page just yet - it contains useful information we need for Step 3.

    2 - Install Monit on your server

    Monit is available on most Unix distributions. If you’re running Ubuntu, the usual sudo apt-get update and sudo apt-get install monit will suffice. You can verify that Monit is working by starting it: sudo service monit start. monit status -v will print out Monit’s status in verbose mode, which will come in handy.

    3 - Familiarize yourself with Slack’s incoming webhooks endpoint

    Go back to the page with the Webhook URL, or visit the equivalent documentation page here, and documentation on message attachments here. Read through them, but I’ll give a brief overview here anyway, as well as the options we went with.

    We’ll be sending serialized JSON in the request body. The JSON payload structure looks like this:

    {
      "text":        String,
      "channel":     String,
      "username":    String,
      "icon_emoji":  String,
      "attachments": [
        "fields": [
        { 
          "title": String,
          "short": Boolean,
          "value": String
        },
        { 
          "title": String,
          "short": Boolean,
          "value": String
        },
        // ... add more fields here if you wish
        ]
      ]
    }
    

    4 - Configuring Monit

    The hardest part of this tutorial is this step. Monit can monitor all sorts of things, and depending on what you want to monitor, this can either be very painful or very easy.

    In the case of processes, Monit will require a pidfile as well as the shell command for starting (and stopping) the process, if you want Monit to be able to automatically restart the process for you.

    Let’s say our app happens to be located at /apps/beerist, and our unicorn pidfile is located at /apps/beerist/current/tmp/pids/unicorn.pid.

    Open the Monit configuration file at /etc/monit/monitc. It should be well commented with some sensible defaults already in place. Monit has its own internal DSL, which explains the weird, terse syntax. The only thing you really want to look at and change is the set daemon setting near the top of the file. Monit runs as a daemon which wakes itself up periodically to monitor whatever its supposed to monitor. This setting allows you to set how frequently you want Monit to wake up, and is specified in seconds. In this case, we have it set to just 5 seconds.

    Under the Services section, start by adding the following line:

    check process unicorn with pidfile /apps/beerist/current/tmp/pids/unicorn.pid
    

    Remember that we want Monit to notify us AND restart the server if Unicorn is down. Let’s add the following test:

    check process unicorn with pidfile /apps/beerist/current/tmp/pids/unicorn.pid
      if does not exist for 1 cycle
        do something
        else if succeeded for 3 cycles then do something else
    

    if does not exist in this case refers to the process indicated by the process ID in the pidfile. We will come back to do something in just a bit. Meanwhile, let’s figure out how we can get Monit to send POST requests.

    5 - Write a script to send POST requests

    Out of the box, Monit’s alert system only supports email-based notifications. Seriously though, this is 2015 - who has time for email?

    Thankfully, we don’t have to use Monit’s built-in alert system, because Monit allows us to run arbitrary scripts with the exec action, which means we can do something like:

    check process unicorn with pidfile /apps/beerist/current/tmp/pids/unicorn.pid
      if does not exist for 1 cycle
        then exec "/usr/bin/ruby /home/cooluser/post_to_slack.rb --service Unicorn --message 'Unicorn is down!' --alert danger"
        else if succeeded for 3 cycles then exec "/usr/bin/ruby /home/cooluser/post_to_slack.rb --service Unicorn --message Unicorn is back up! --alert good"
    

    So, we can write a script to do the POST-ing to Slack for us.

    To speed things up a little, I’m going to just link to the script we wrote for this purpose, which you are free to use. It’s quite long but if you want to roll your own you can definitely make it shorter.

    Assuming you use our Ruby script, please ensure that you have some version of Ruby installed on your system, and take note of the location of the ruby executable in the current path (whereis ruby) because it is good practice to indicate the full path to the executable.

    In our case, the ruby executable is located at /usr/bin/ruby (as you saw earlier) and our script is in the home directory of cooluser.

    At this point in time, it is possible to test that this setup works, but we’ll wait until the end of the next step. We can do a monit status -v to make sure that we’re on the right track:

    cooluser@cubiclerebels:/home/cooluser# monit status -v
    Runtime constants:
     Control file       = /etc/monit/monitrc
     Log file           = /var/log/monit.log
     Pid file           = /var/run/monit.pid
     Id file            = /var/lib/monit/id
     Debug              = True
     Log                = True
     Use syslog         = False
     Is Daemon          = True
     Use process engine = True
     Poll time          = 5 seconds with start delay 0 seconds
     Expect buffer      = 256 bytes
     Event queue        = base directory /var/lib/monit/events with 100 slots
     Mail from          = cooluser@cubiclerebels.com
     Mail subject       = $SERVICE $EVENT at $DATE
     Mail message       = message: Monit $ACTI..(truncated)
     Start monit httpd  = True
     httpd bind address = localhost
     httpd portnumber   = 9999
     httpd signature    = True
     Use ssl encryption = False
     httpd auth. style  = Host/Net allow list
    
    The service list contains the following entries:
    
    Process Name          = unicorn
     Pid file             = /apps/beerist/tmp/pids/unicorn.pid
     Monitoring mode      = active
     Existence            = if does not exist 1 times within 1 cycle(s) then exec '/home/cooluser/unicorn_down.sh' timeout 0 cycle(s) else if succeeded 3 times within 3 cycle(s) then exec '/usr/bin/ruby /home/cooluser/post_to_slack.rb --service Unicorn --message Unicorn is back up! --alert good' timeout 0 cycle(s)
     Pid                  = if changed 1 times within 1 cycle(s) then alert
     Ppid                 = if changed 1 times within 1 cycle(s) then alert
    
    System Name           = awesomepossums.cubiclerebels.cluster
     Monitoring mode      = active
    

    6 - Write a wrapper script

    Due to Monit’s DSL limitations, each test can only be associated with one action. If an action is not specified for a monitored process, the default action is to restart the process when the process associated with the pidfile is no longer running.

    However, once we indicate that we want to run exec if something happens, then that becomes the action, and Monit will no longer attempt to restart the process. And you can’t do something like:

    if does not exist for 1 cycle
      then exec "/usr/bin/ruby /home/cooluser/post_to_slack.rb --service Unicorn --message 'Unicorn is down!' --alert danger" and restart
    

    because the Monit DSL is painfully limited.

    Thankfully, there is a way to sidestep this limitation, and that is by writing a wrapper script that will execute the (multiple) commands that we need to be executed.

    Open a new file called unicorn_wrapper.sh or something and place the following lines inside:

    #!/bin/bash
    
    /usr/bin/ruby /home/cooluser/post_to_slack.rb --service Unicorn --message 'Unicorn is down!' --alert danger
    
    cd /apps/beerist/ && RAILS_ENV=production BUNDLE_GEMFILE=/apps/beerist/Gemfile bundle exec unicorn -c /apps/beerist/config/unicorn.rb -E deployment -D;
    

    That second ugly-looking line is our shell command for starting Unicorn with the correct context. For other processes like Apache, the command usually looks a lot shorter, like /etc/init.d/apache start.

    With this script, we can replace

    check process unicorn with pidfile /apps/beerist/current/tmp/pids/unicorn.pid
      if does not exist for 1 cycle
        then exec "/usr/bin/ruby /home/cooluser/post_to_slack.rb 'Unicorn is down!'"
        else if succeeded for 3 cycles then exec "/usr/bin/ruby /home/cooluser/post_to_slack.rb --service Unicorn --message Unicorn is back up! --alert good"
    

    with

    check process unicorn with pidfile /apps/beerist/current/tmp/pids/unicorn.pid
      if does not exist for 1 cycle
        then exec "/home/cooluser/unicorn_wrapper.sh"
        else if succeeded for 3 cycles then exec "/usr/bin/ruby /home/cooluser/post_to_slack.rb --service Unicorn --message Unicorn is back up! --alert good"
    

    And this will correctly run the script and attempt to restart the Unicorn process. Beer drinkers rejoice!

    7 - Testing everything

    At this point, do monit reload to reload the configuration and monit validate to make sure you haven’t made any syntax errors, and monit status -v to make sure that the process is being monitored.

    We can manually test if it works by killing the process and seeing if it revives. Something like ps auwx | grep unicorn will reveal the master Unicorn process, which you can kill with kill XXXX. If everything works correctly, Slack should receive a notification in the channel you indicated, and Unicorn should be restarted (under a different pid), after which you should receive another notification.

    cooluser@cubiclerebels:/home/cooluser# ps auwx | grep unicorn
    cooluser 5155  2.6  7.6 324864 129944 ?       Sl   12:25   0:09 unicorn master -c /apps/beerist/config/unicorn.rb -E deployment -D
    cooluser 5403  0.0  7.4 325020 125944 ?       Sl   12:26   0:00 unicorn worker[0] -c /apps/beerist/config/unicorn.rb -E deployment -D
    cooluser 5406  0.0  7.4 325020 125752 ?       Sl   12:26   0:00 unicorn worker[1] -c /apps/beerist/config/unicorn.rb -E deployment -D
    

    If this works for you on the first try, congratulations!

    8 - Extensions

    We’ve tried to be as general as possible when describing the steps, so you can adapt this technique to monitor arbitrary processes, or even anything at all. Take a look at Monit’s documentation for a exhaustive list of what kind of things Monit can monitor, and what kind of tests you can set up on those monitors, including CPU/memory usage, space usage, uid/gid/pid changes, and even network tests (ICMP pings, bandwidth, socket connection, etc). The possibilities are endless, but the syntax remains very similar, if not exactly the same.

    9 - Conclusion

    As you can probably tell, we take reliability very seriously here at Cubicle Rebels, and this is just one of the things we do to make sure everything goes up and stays up. There’s a lot more to do with this technique, such as ensuring that the script is automatically uploaded when servers are provisioned.

    Stay tuned for more articles and tutorials!

  • Classic Rock Primer

    Top lists are old, especially top lists of old songs. Who wants to hear another person’s opinion on what he or she thinks are the best songs of all time?

    Nevertheless, these are some songs I think are a good introduction for a newcomer to the genre. I’ve limited the list to one song per band/musician, and it was sweet agony for me to assemble the playlist. You can find the Spotify playlist at the end of the post.

    Born to Run

    Bruce Springsteen - Born to Run (1975)

    The Boss kicks the list off with his mainstream breakout.

    Don’t Stop Believin’

    Journey - Escape (1981)

    A staple of classic rock radio stations, and for good reason.

    Rosanna

    Toto - Toto IV (1982)

    Everyone knows Africa, but Rosanna is pretty good too. Give it a spin.

    Foreplay/Long Time

    Boston - Boston (1976)

    From Boston’s landmark eponymous album, and written by frontman Tom Scholz shortly after graduating from MIT.

    Sympathy for the Devil

    The Rolling Stones - Beggar’s Banquet (1968)

    As iconic as it gets. I think what really made it stick was the juxtaposition of its lyrical content and its dancey bongo samba rhythm. According to Jagger, the song had:

    … an undercurrent of being primitive - because it is a primitive African, South American, Afro-whatever-you-call-that rhythm. So to white people, it has a very sinister thing about it.

    Livin’ On A Prayer

    Bon Jovi - Slippery When Wet (1986)

    This will forever be my shower song.

    Uptown Girl

    Billy Joel - An Innocent Man (1983)

    Yes, I know it should have been Piano Man, but I listened to way too much Westlife when I was young and the tune stuck.

    Romeo and Juliet

    Dire Straits - Making Movies (1980)

    Dire Straits has many great songs, but Romeo and Juliet trumps them all. Mark Knopfler pours his heart out on this number, and it shows.

    A lovestruck Romeo sings the streets of serenade
    Laying everybody low with a love song that he made
    Finds a streetlight, steps out of the shade
    Says something like, you and me babe, how ‘bout it?

    Heart Of Gold

    Neil Young - Harvest (1972)

    Originally a rocker, Neil Young turned to writing soft acoustic pieces after he injured his back and could play only his acoustic guitar sitting down. Funnily enough, according to Wikipedia, Bob Dylan disliked the song:

    The only time it bothered me that someone sounded like me was when I was living in Phoenix, Arizona, in about ‘72 and the big song at the time was “Heart of Gold”. I used to hate it when it came on the radio. I always liked Neil Young, but it bothered me every time I listened to “Heart of Gold.” I think it was up at number one for a long time, and I’d say, “Shit, that’s me. If it sounds like me, it should as well be me.”

    Free Bird

    Lynyrd Skynyrd - (Pronounced ‘Lĕh-‘nérd ‘Skin-‘nérd) (1973)

    The longest song on this playlist, the definitive version for me is their live version performed at The Fox Theater, which can be found on The Essential Lynyrd Skynyrd (clocking in at 14 minutes). Don’t be put off by the length though, it gets better towards the end. A LOT better.

    Go Your Own Way

    Fleetwood Mac - Rumours (1977)

    A banger of a song from Fleetwood Mac’s best album, and the album that saved them from certain irrelevance.

    Mr. Blue Sky

    Electric Light Orchestra - Out of the Blue (1977)

    Awesome pick-me-up for those deary Monday mornings. The cryptic ending of the song is actually “Please turn me over”, instructing the listener to flip the vinyl over to the next side.

    Runnin’ down the avenue
    See how the sun shines brightly
    In the city on the streets where once was pity
    Mr. Blue Sky is living here today, hey!

    Sunshine of Your Love

    Cream - Disraeli Gears (1968)

    Eric Clapton makes his first appearance on this list with Cream. This song also accompanies one of my favourite scenes in Goodfellas.

    Black Magic Woman

    Santana - Abraxas (1970)

    Who can forget Santana?

    Behind Blue Eyes

    The Who - Who’s Next (1971)

    Some of you will recognize it from the Limp Bizkit cover (which isn’t half bad, to be honest).

    Comfortably Numb

    Pink Floyd - The Wall (1979)

    Another one for the road.

    Carry On Wayward Son

    Kansas - Leftoverture (1976)

    What more can I say? This belongs in the annals of epic rock songs. Also, watch this 10-year-old Japanese girl own it on the electone.

    While My Guitar Gently Weeps

    The Beatles - The Beatles (The White Album) (1968)

    The Beatles is sadly, conspicuously absent from Spotify. Clapton makes his second appearance on this playlist here, playing the famous guitar solo with “Lucy”, Harrison’s Les Paul (which was in fact gifted to him by Clapton). McCartney also plays his Fender Jazz bass instead of his usual Rickenbacker basses.

    Dream On

    Aerosmith - Aerosmith (1973)

    Aerosmith’s first major hit of many hits to come. Also sampled heavily by Eminem on Sing for the Moment.

    Hurricane

    Bob Dylan - Desire (1976)

    A protest song written by Dylan on the conviction of boxer Rubin Carter for murder during a robbery. Initially convicted in 1967, Carter was later released via habeas corpus in November 1985. Particularly poignant given recent developments.

    Under Pressure

    Queen, David Bowie - Hot Space (1982)

    A great single from an transitional album. Legend has it that Mercury and Bowie took turns improvising the lyrics as they jammed in the studio.

    La Grange

    ZZ Top - Tres Hombres (1973)

    A haw haw haw, a hmm hmm hmmm.

    Bold As Love

    The Jimi Hendrix Experience - Axis: Bold as Love (1967)

    My red is so confident he flashes trophies of war
    And ribbons of euphoria
    Orange is young, full of daring
    But very unsteady for the first go round
    My yellow in this case is not so mellow
    In fact I’m trying to say it’s frightened like me
    And all these emotions of mine keep holding me from, eh
    Giving my life to a rainbow like you

    John Mayer also does an excellent cover on his album Continuum.

    Ramble On

    Led Zeppelin - II (1969)

    This was so difficult for me to pick (but who can resist Led Zeppelin singing about The Lord of the Rings?) I will do a Led Zeppelin-only list in due time.

    Layla

    Derek & The Dominos - Layla and Other Assorted Love Songs (1970)

    Clapton makes his third and final appearance here in one of rock’s most iconic love songs. Thoroughly inspired by The Story of Layla and Majnun, a anecdotal love story of ancient Arabic origins popularized by Persian poet Nizami Ganjavi in 1192.

    Hotel California

    Eagles - Desperado (1973)

    And no other song than Hotel California to round off the playlist. By far my favourite version of the song is on MTV Live and Unplugged in 1994, which tragically I can’t find on Spotify. I can’t even find the video on Youtube! Luckily, Vimeo saves the day. Like most other MTV Live and Unplugged live concerts, this show is remarkably well-recorded, and you can often find it being played as an demonstration at high-end audio trade shows.

    Here it is:

    Hope you enjoy!

  • Updating EOL Ubuntu Releases

    I was handed an EC2 instance running 12.10 (Quantal Quetzal), which had reached EOL. Due to some reasons, upgrading wasn’t an option. When I tried to apt-get update, apt snapped back at me with multiple 404s along the lines of:

    W: Failed to fetch http://<blah blah blah>.ubuntu.com/ubuntu/dists/main/universe/binary-amd64/Packages  404  Not Found [IP: 91.189.92.200 80]
    

    It turns out Ubuntu moves sources for EOL releases to a separate subdomain old-releases, so all I had to do was change the subdomain of all the sources in /etc/apt/sources.list, like so (note that you need sudo for it, and its also a good idea to backup your original sources.list file before making any changes):

    deb http://old-releases.ubuntu.com/ubuntu/ quantal main
    deb-src http://old-releases.ubuntu.com/ubuntu/ quantal main
    

    Digital Ocean also hosts a mirror of these:

    deb http://nyc2.mirrors.digitalocean.com/ubuntu-old/ quantal main
    deb-src http://nyc2.mirrors.digitalocean.com/ubuntu-old/ quantal main
    

    If you have a EOL Ubuntu distro that you need to maintain for any reason, you can head over to http://old-releases.ubuntu.com/ubuntu/dists to see if your distro is covered.

  • Blogging Workflow

    In this post, I want to talk about some of the technical details behind this site and the workflow that I have for writing.

    This site is built with Jekyll and hosted on a private Github repository, with two branches. The master branch is where the published site lives (basically, the contents of the _site folder), while the source branch is where the working files are kept, under version control.

    “Doesn’t Github Pages build your site for you automatically?”

    Yes, it’s supposed to, but not if you use custom plugins that are not supported by Github Pages. I have a custom Ruby plugin that generates category pages, so there’s that.

    I use fish, so I wrote a function sy that acts on the surface like a command line binary.

    For the purposes of this post, I’ve renamed the directories. ~/website is the directory where my website source files live, and ~/website.publish is the directory where the published files live. There is also a ~/website.publish.backup folder which contains a backup of the previous version of the published files. (Yes, everything is managed under VC, but still.)

    This is how sy looks like, in its entirety:

    function sy
    
        switch (echo $argv[1])
            case "--serve"
                subl ~/website
                cd ~/website
                rvm use 2.1.1
                jekyll serve -w
                cd
            case "--publish"
                rm -rf ~/website.publish.backup
                rvm use 2.1.1
                cd ~/website | jekyll build 
                cp -fR ~/website.publish ~/website.publish.backup
                cd ~/website.publish/
                rm -r *
                cp -fR ~/website/_site/  ~/website.publish/
                cd
            case "--new"
                set newdate (date +%Y-%m-%d)
                set categories "[$argv[2]"
                for x in $argv[3..-2]
                    set categories $categories","$x
                end
                set categories $categories"]"
                echo -e "---\ntitle:\nlayout: post\ncategories: $categories\ndate: $newdate\n---" > ~/website/_posts/$argv[2]/$newdate-$argv[-1].markdown
                subl ~/website/_posts/$argv[2]/$newdate-$argv[-1].markdown
            case "--gallery"
                set python_arguments ""
                for x in $argv[3..-2]
                    set python_arguments $python_arguments $x
                end
                set path ""
                for x in $argv[3..-2]
                    set path $path"/"$x
                end
                python ~/website/_plugins/gallery.py $argv[2] $python_arguments $argv[-1] >> ~/website/_posts/$argv[3]/$argv[5].markdown
                mv $argv[2] ~/website$path
            case ""
                echo -e "\nUsage:\n"
                echo -e "--serve\nOpens the website folder in Sublime Text, and opens a local server at localhost:4000\n"
                echo -e "--publish\nBuilds the Jekyll files from source and copies it to the publish folder. The existing publish folder is backed up in the same directory.\n"
                echo -e "--new [main-category] [nested-categories] [post-name]\nCreates a new post in the [main-category] folder titled [post-name]. The date defaults to today's date, and the categories tag is populated with the [main-category] followed by [nested-categories] if any.\n"
                echo -e "--gallery [folder] [main-category] [nested-categories] [post-name]\nRuns gallery.py on [folder] to generate HTML markup and append to [post-name] (which must exist). Specified folder is then moved to appropriate location.\n"
            case "*"
                echo "Invalid command."
        end
    end
    

    Let’s go through each option one by one.

    case "--serve"
        subl ~/website
        cd ~/website
        rvm use 2.1.1
        jekyll serve -w
        cd
    

    sy, when used with the --serve flag, opens the website directory in Sublime Text and runs jekyll serve, which opens a local copy for development preview. Straightforward enough.

    case "--publish"
        rm -rf ~/website.publish.backup
        rvm use 2.1.1
        cd ~/website | jekyll build 
        cp -fR ~/website.publish ~/website.publish.backup
        cd ~/website.publish/
        rm -r *
        cp -fR ~/website/_site/  ~/website.publish/
        cd
    

    The --publish option builds the source files in ~/website and copies it to ~/website.publish, but not before it makes a copy of original published files in ~/website.publish/backup. There’s a lot of potentially dangerous rm -rf-ing here, and I’ll take another crack at refactoring it when I have the time.

    case "--new"
        set newdate (date +%Y-%m-%d)
        set categories "[$argv[2]"
        for x in $argv[3..-2]
            set categories $categories","$x
        end
        set categories $categories"]"
        echo -e "---\ntitle:\nlayout: post\ncategories: $categories\ndate: $newdate\n---" > ~/website/_posts/$argv[2]/$newdate-$argv[-1].markdown
        subl ~/website/_posts/$argv[2]/$newdate-$argv[-1].markdown
    

    The --new option is where it starts to get interesting. In the description, it says:

    The date defaults to today's date, and the categories tag is populated with the [main-category] followed by [nested-categories] if any.
    

    --new takes three (or more) options: main-category, nested-categories, and post-name. It takes these options and creates a new Markdown file in the appropriate location in the _post directory, with the necessary YAML front matter appended. For example, if the following command is run (today):

    sy --new coding ruby i-made-a-new-plugin
    

    A new post will be created in ~/website/_posts/coding/2014-12-02-i-made-a-new-plugin.markdown with the following YAML front matter:

    ---
    title: 
    layout: post
    categories: [coding,ruby]
    date: 2014-12-02
    ---
    

    Finally, this file is opened in Sublime Text for immediate editing.

    case "--gallery"
        set python_arguments ""
        for x in $argv[3..-2]
            set python_arguments $python_arguments $x
        end
        set path ""
        for x in $argv[3..-2]
            set path $path"/"$x
        end
        python ~/website/_plugins/gallery.py $argv[2] $python_arguments $argv[-1] >> ~/website/_posts/$argv[3]/$argv[5].markdown
        mv $argv[2] ~/website$path
    

    --gallery is by far the most complex bit of sy. Its arguments are similar to --new’s, except it takes in an additional [folder] argument. Its main purpose is to generate HTML markup for the photo galleries on the site, by acting as a wrapper for a Python script that I wrote, gallery.py.

    Its first argument is the full location of a folder, located anywhere, containing the images to be used in the post. The images in the folder must be arranged in a specific manner:

    • Inline images live in the root of the folder.
    • Gallery images should be placed in a subfolder called gallery.
    • Thumbnails for the gallery above should be placed in a subfolder called thumbs. gallery.py will check and match all the images in gallery with those in thumbs and shout if there’s a mismatch.
    • The cover image should be placed in a subfolder called cover.

    The subsequent arguments are the main and nested categories, as well as the name of the post (which must already exist). gallery.py then generates the HTML markup for all of the different types of images, which sy appends to the post according to the arguments supplied. Lastly, it moves the folder to the correct place in the source directory.

    I’ll spare you the details of gallery.py, but the last thing I’ll like to mention in the section is that it leverages on the pyexiv2 library to extract the title metadata from the images if it can find any.

    Why do I do this?

    Lightroom 5 is my photo management tool of choice, and it allows me to enter the title and caption of each image. When exported, this information continues to live in the metadata of the image. When gallery.py is generating the markup, it looks to see if each image’s title field is populated, and if so, enters it as the title attribute in the img tag. This title attribute is recognized by Magnific Popup and will then be displayed beneath the lightbox.

    So yeah, that’s the gist of my blogging workflow. There’re a couple more details I didn’t go into (such as my Lightroom workflow, and how it segues into the Jekyll workflow), and I’ll append to this post if I feel like documenting the rest of it.

  • OK Go - I Won't Let You Down

    OK Go is back with their usual antics, and by antics I mean, mind-blowingly awesome music videos. Watch the video. I promise you it’s the most amazing music video you’ll watch this year. When you’re done, the obligatory behind-the-scenes video is here.

    The song itself is pretty good, and the rest of the album, Hungry Ghosts, is worth a listen as well.

    Check out Turn Up The Radio, Obsession, and of course, official single The Writing’s on the Wall, which has a pretty awesome music video too.