Dennis Kaarsemaker

Vim trick: detect filetype after typing #!shbang line

Last update on June 22, 2014.

Vim can detect syntax based on filename or on shbang lines, but if you're creating for example a new perl script and you don't want to use the .pl extension, vim will not automatically detect that after you type #!/usr/bin/perl. Instead, you manyally have to type :filetype detect. There must be a better way!

But of course there is a better way, there's vimscript!

function! RedetectFiletype()
    if getpos(".")[1] == 2 && getline(1) =~ '^#!'
        filetype detect
inoremap <CR> <CR>:call RedetectFiletype()<CR>i<BS><CR>

So if you write a new shbang and hit enter, vim will take the hint and try to guess what kind of syntax you will be using.

Puppet & Yum, sitting in a tree...

Last update on Nov. 13, 2013.

Let's start with a nice flamebait: the way puppet uses yum sucks! And now that I have your attention, I'll defend this opinion with irrefutable facts. And then present a solution, because whining is useless. Onwards!

Puppet is great for configuration management, and yum is a pretty decent package manager. But together they've been making my life difficult for quite a while, and that's mostly Puppet's fault (though yum isn't completely innocent here). The biggest problem by far is that when puppet evaluates its catalog, it will process each package one by one. When you use lots of packages (we have a repository with 1000+ perl packages, the average server uses over half of them), this is not only slow, it also leads to some really undesirable behaviour. Undesirable as in puppet throws no errors, but the system becomes unusable.

The problem we suffered most from is version flapping. Puppet would keep on up- and downgrading packages on alternate runs, and removing/installing other packages. The cause of this is a really bad interaction between puppet dependencies and rpm dependencies. Add to that the one-at-a-time behaviour and yum being completely unaware of what puppet wants, and your system quickly becomes unstable, or even unusable. Here's an example:

        ensure => "1.0-1.el6";
        ensure => "2.0-1.el6";

Looks simple enough right? But what if the foo rpm has an rpm dependency on baz 1.0 and bar an rpm dependency on baz 2.0? That's right! A puppet run will see foo is installed, so puppet is happy. But bar s not installed, so puppet makes yum install bar. Yum then installs baz 2.0, causing foo to be removed. And the next run foo is installed, with baz 1.0. And bar is removed.

And this goes on until someone spots that this system is really unusable...

Now grow this to a list of 500 CPAN modules, wrapped in RPMs. Version flapping happens. A lot. So we wrote a yum2 package provider for puppet that disables downgrading. Which sucks, because not everything thats newer is better. We do frequently downgrade packages and a hack in the yum2 provider allows a hardcoded set of packages to be downgraded. Far from ideal, but workable.

And downgrades go wrong in other places too: when downgrading interdependent packages. For instance the uwsgi / uwsgi-plugin-common / uwsgi-plugin-perl trifecta. We do test with newer versions on a set of boxes and then downgrade when things fail. But becaue puppet only tries to downgrade one package at a time, yum will always complain that it cannot downgrade any of them, because the other two are complaining. Aaaaaaaaaaaaargh.... ok. A puppet exec{...} can work around this, but again puppets use of yum is frustrating.

And then a question on ServerFault gave me some inspiration.

First I solved 0A0D's question on serverfault, which made me look real hard at the yum plugin API. I realized quickly that yum has hooks in all the right places to actually solve all my puppet problems, it now became a simple matter of programming (and swearing) to get this all just right. The first trick is making yum aware of puppet. So I made yum 'parse' the puppet catalog, so it would simply ignore undesired versions of packages. Goodbye package flapping, hello robust up and downgrades.

Oh, and yum also updates its own config from the catalog, this works around the need for really hairy dependencies in puppetland (or waiting 2 or 3 puppet runs before yum can do its thing properly, costing another hour if unattended). There's also some guards against human error, so you now can no longer remove packages required by puppet (another part of the version flap problem) and yum will warn you when installing a package that overwrites a puppet-managed file.

Time to tackle the puppet side of things... with yum! A package provider can provide a 'prefetch' method, that's called before anything in the catalog is evaluated. This is an excellent place to do more magic. The new yum3 package provider will happly take ths catalog, find all packages that have no dependencies (other than other packages that have no dependencies other than... you get the gist) and will install/remove/upgrade/downgrade all of them in one call to a new yum function, provided by the yum plugin. Yes, this makes an initial puppet run on our application servers not only 300% more reliable, it also makes it 1000% faster by not needing to call yum 500+ times.

So far it's been a great succes, so I decided it would be wrong to keep this for ourselves. You can find all the relevant bits on GitHub. If you use yum and puppet, give it a whirl!

Everybody is in customer service

Last update on Oct. 21, 2013.

An old mantra in business is that everybody is in sales. As Todd Cohen puts it: "Here is the point I am going to make-Everyone in every profession is somehow “selling” what they do. Period.". I've never liked this idea, mostly because I utterly suck at selling things. Of course most people believing this are good salespeople and will be able sell this idea to many non-believers :)

I've never been able to put the finger on why I didn't like that idea, just that it somehow felt wrong. An article by mailchimp's Ben Chestnut I read the other day made it clearer to me: the mantra is reasonable correct, they just picked the wrong department.

The key part of Ben's article is "Deliver awesome customer service. Delight them. Empower them.". At first that seemed awfully logical to me, because I'm used to that attitude at work. But especially in this day and age where your customers have more power than ever, delighting your customers is more important than ever. Everybody in your company represents that company and everybody is responsible for delivering great service to your customers.

And that applies to 'internal selling' too: the only way I can convince my peers and management that an idea I have is a good idea is by showing that it improves the experience of the customers or colleagues using what I want to change or build. So instead of selling an idea, I am proving that I delight the customer.

Automatically pushing local git repositories to other sources

Last update on Oct. 17, 2013.

One of the many things I do at, is maintaining the infrastructure behind, the main git repository for Perl 5. The perl 5 committers moved to git a few years ago and we have been hosting this ever since. However, that repository not being on GitHub was causing some confusion with quite a few copies being pushed to GitHub anyway by individual contributors, and even a /mirrors/perl that we did not control. We couldn't even switch of pull requests, so possible contributions kept getting lost.

So now there's an official mirror at Perl/perl5.git! Instead of doing pushes from cron, I've set the master repository up to do pushes from the post-receive hook. Though because actually pushing will slow pushes to our master repository down, and could even cause errors, this is done via a beanstalk tube. It's a bit fiddly to set up, but works like a charm.

First the git post-receive hook. We already have a post-receive hook, so we need to do a little trick to run more than one hook:

/usr/local/bin/pee /srv/gitcommon/contrib/hooks/post-receive-email \
                   /srv/gitcommon/contrib/hooks/submit-jenkins-job \

Pee is a tee-like utility to send input to multiple commands, making it really easy to have multiple hooks. So what does this hook do?


remotes=$(git config hooks.push_to)
tube=$(git config hooks.push_tube)

push_to_github() {
    oldrev=$(git rev-parse $1)
    newrev=$(git rev-parse $2)

    if expr "$oldrev" : '0*$' >/dev/null
        if expr "$newrev" : '0*$' >/dev/null

    # --- Get the revision types
    newrev_type=$(git cat-file -t $newrev 2> /dev/null)
    oldrev_type=$(git cat-file -t "$oldrev" 2> /dev/null)
    case "$change_type" in

    # The revision type tells us what type the commit is, combined with
    # the location of the ref we can decide between
    #  - working branch
    #  - tracking branch
    #  - unannoted tag
    #  - annotated tag
    case "$refname","$rev_type" in
            # un-annotated tag
            # annotated tag
            refname_type="annotated tag"
            # change recipients
            if [ -n "$announcerecipients" ]; then
            # branch
            # tracking branch
            refname_type="tracking branch"
            echo >&2 "*** Push-update of tracking branch, $refname"
            echo >&2 "***  - no email generated."
            exit 0
            # Anything else (is there anything else?)
            echo >&2 "*** Unknown type of update to $refname ($rev_type)"
            echo >&2 "***  - no email generated"
            exit 1

    if [ $change_type = "delete" ]; then
        beanstalk-submit "$tube" repo="$(pwd)" push_to="$remote" ref=:$refname
        beanstalk-submit "$tube" repo="$(pwd)" push_to="$remote" ref=$refname:$refname

while read oldrev newrev refname
    push_to_remote $oldrev $newrev $refname

Those familiar with git may recognize parts of the post-receive-email hook there :) This hook interprets the input from git, and schedules pushes or deletions by submitting them to beanstalk. The repository configuration determins which tube will be used and which repository. For perl.git, this is

	remotes =
        tube = github-push

The beanstalk-submit utility is a really simple python script that grabs arguments and turns them into a json document that gets submitted.


import beanstalkc
import json
import sys

tube = sys.argv[1]
args = dict([x.split('=', 1) for x in sys.argv[2:]])

bs = beanstalkc.Connection('localhost', 11300)

So now the pushes are scheduled. What's next? Actually pushing them of course! I have another script running in a screen session (yeah, I should turn this into a proper daemon, but I have bigger plans for that). In an infinite loop, it fetches jobs from the github-push queue and pushes code to the configured remote or remotes. It also ignores malformatted jobs and automatically buries failed jobs so they won't get retried.


import json
import beanstalkc
import traceback
import os
import sys
from whelk import Shell

shell = Shell(redirect=False, raise_on_error=True)

bs = beanstalkc.Connection('localhost', 11300)'github-push')
while True:
    print "Waiting for job"
        job = bs.reserve()
    except KeyboardInterrupt:
    except Exception:
        print "Reconnecting"
        bs = beanstalkc.Connection('localhost', 11300)'github-push')
        data = json.loads(job.body)
        if not data or not isinstance(data, dict):
        if 'repo' not in data or not data['repo'] or 'ref' not in data or not data['ref'] or 'push_to' not in data or not data['push_to']:
        for remote in data['push_to'].split():
            shell.git('push', remote, data['ref'])
    except Exception:

So there you go, now we have a near-instantly mirrored perl.git on github while still having our own main archive!

CDATA support in ElementTree

Last update on Oct. 10, 2013.

Oh the joys of writing software...

The otherwise excellent ElementTree python library for dealing with the horror that is XML is lacking a feature that I need in python-hpilo: <![CDATA[ ]]> support. In its continuing quest for maximum inconcistency, HP has decided that one XML API function now requires that the data be in a CDATA element.

So, let's see how we can add that. Fortunately, Stack Overflow had a few recipes. And a warning that the internals changed in an incompatible way over the years. Oh, and I need to support all python versions from 2.4 (Hello RHEL 5) to 3.3. So here's the monstrosity of monkeypatching and backwards compatibility that I came up with:

    import xml.etree.ElementTree as etree
except ImportError:
    import elementtree.ElementTree as etree

# Oh the joys of monkeypatching...
# We need a CDATA element in set_security_msg, but ElementTree doesn't support it
def CDATA(text=None):
    element = etree.Element('![CDATA[')
    element.text = text
    return element

# Python 2.7 and 3
if hasattr(etree, '_serialize_xml'):
    etree._original_serialize_xml = etree._serialize_xml
    def _serialize_xml(write, elem, *args):
        if elem.tag == '![CDATA[':
            write("\n\n" % (elem.tag, elem.text))
        return etree._original_serialize_xml(write, elem, *args)
    etree._serialize_xml = etree._serialize['xml'] = _serialize_xml
# Python 2.5-2.6, and non-stdlib ElementTree
elif hasattr(etree.ElementTree, '_write'):
    etree.ElementTree._orig_write = etree.ElementTree._write
    def _write(self, file, node, encoding, namespaces):
        if node.tag == '![CDATA[':
            file.write("\n<![CDATA[%s]]>\n" % node.text.encode(encoding))
            self._orig_write(file, node, encoding, namespaces)
    etree.ElementTree._write = _write
    raise RuntimeError("Don't know how to monkeypatch CDATA support. Please report a bug at")

Irssi for X-chat refugees

Last update on March 25, 2013.

I'm a pretty horrible terminal junkie. As a large scale sysadmin, my life is spent mostly in a terminal, either fixing problems or writing puppet recipes to prevent them. I also manage mail with mutt (though I still mostly read it with evolution) and my favourite editor is vim.

As such, it's rather odd that I use X-chat for IRC, with a bouncer on my VPS, instead of using irssi in screen. Truth is that I've tried to switch from X-chat to irssi many times, but never could get used to it. A week ago I tried again. Determined to make it work this time, I started off with searching a theme that resembles X-Chat. A quick search uncovered Anton Fagerberg's irssi setup, including an X-chat theme.

/set theme xchat
/set indent 25
/set autolog yes
/set join_auto_chans_on_invite no
/set show_names_on_join off
/set channel_sync off

That made a world of difference! I felt a lot more at home already, so was quite motivated to really dig into what was left that annoyed me and fix all those issues. Step one was the nicklist: I really wanted it back. Even in larger channels, where it is less than useful, I like to see and browse through the list of nicks. And on our corporate jabber rooms (yay bitlbee) I find it essential. Fortunately there is a script available that can abuse screen to do this. Problem sorted.

/script load
/nicklist screen
/set nicklist_automode SCREEN
/set nicklist width 24
/bind mup command nicklist scroll -1
/bind mdown command nicklist scroll 1

Next up was the list of windows. In X-chat I always use the channel tree view to get a quick overview of activity. In irssi I've always found this utterly impossible, the constantly changing window numbers always made me abandon it. A bit of searching, and I found that irssi can assign static numbers to windows. Problem solved! Or so I thought... irssi would now use the gaps in numbering to assign new windows to. This being the last thing to hold me back from really switching to irssi, I went ahead and dove into the code to find a solution. None existed, so I patched irssi myself. The patch adds an option to create new windows at the end of the list, or even at a much higher starting number. New windows now are numbered 200 and up, making it trivial to identify private messages.

/set windows_auto_renumber off
[... renumber all windows to my liking ...]
/set create_windows_at_end on
/set autocreate_window_min_refnum 200

My irssi setup was now usable! I didn't even want to switch back to X-chat, so victory could be declared there and then. But irssi is much more flexible, so why stop here? Another neat trick from Anton's page is the script. /go ubu<tab> would bring me to #ubuntu. Except it didn't, so I patched the script to make that work. As a bonus /go off now works for taking me to #ubuntu-offtopic. I find that I don't use it much for channels, as the numbers are now static and I can remember them, so it's /win 10 instead of /go ubu. But for PM windows, is indispensible. Especially at work, where I have dozens of jabber conversations open at any time.

Another well known script is, automatically setting you to away when screen is detached. I don't like that, but the idea inspired me to write, which forwards messages to my android phone when screen is detached. I also use the hilight window as detailed by Anton. I had something similar in X-chat and in irssi it's even better as it's always there!

/script load
/script load
/window new split /window name hilight /window size 3
/script load
/statusbar window visible active

So there's my current setup. 1 irssi in screen, connected via znc still to 2 different networks (I'll take znc out at some point) and via 2 bitlbee instances to twitter, google talk and corporate jabber. All my instant messaging needs running in screen on my server, my terminal addiction satisfied for now :)

Adventures in lazyness

Last update on Feb. 27, 2013.

My laptop is sitting a few meters away from me. I'm behind a desktop in the same /24. I'd like to SSH to this laptop, but don't know its IP address. On this network there are quite a few machines, mostly macs. How do I find the IP address?

Arp and nc to the rescue! First we arp-scan the network, then we find SSH versions.

$for h in $(arp-scan --localnet | grep 10.15 | cut -f1); do echo -ne "$h\t"; (echo "" | nc -w1 $h 22 || echo)|head -n1; done | grep ubuntu    SSH-2.0-OpenSSH_5.9p1 Debian-5ubuntu1    SSH-2.0-OpenSSH_6.0p1 Debian-3ubuntu1    SSH-2.0-OpenSSH_5.8p1 Debian-7ubuntu1    SSH-2.0-OpenSSH_6.0p1 Debian-3ubuntu1

Of course it was the last one I needed :)

Don't let your SSL certificates expire

Last update on Feb. 23, 2013.

It looks like Microsoft made a rather classical beginner mistake and forgot to renew an SSL certificate, taking down quite some Azure services doing so. I will not comment on what I think of that, but here is a tip to make sure this doesn't happen to you: monitor it!

It is really easy to forget a task that only needs to be done once every few years. Staff comes and goes, or gets reassigned. Reports get forgotten and putting it on your todo list only serves to make sure it doesn't get done. So instead of trying to remember when it is time to buy a new certificate, let your monitoring system check the age of your certificate and warn you if it's time to get a new one.

If you use nagios, this is really easy to set up, the check_http plugin already has this functionality! If your webservers are all in the https hostgroup, the following service and command definition will make nagios check certificate age every two hours so this type of embarrasment doesn't happen to you:

define service {
    use                   generic-service
    hostgroup_name        https,https-auth
    service_description   HTTPS SSL Cert Age
    check_command         check_cert_age!14!443
    normal_check_interval 120
    notification_interval 360
    notification_period   workhours
define command {
    command_name    check_cert_age
    command_line    $USER1$/check_http -S -C $ARG1$ -I $HOSTADDRESS$ -p $ARG2

The python trademark is in danger in Europe

Last update on Feb. 16, 2013.

Python trademark at risk in Europe: The python foundation need your help!

There is a company in the UK that is trying to trademark the use of the term "Python" for all software, services, servers... pretty much anything having to do with a computer.

In my not so humble opinion this is rather ridiculous. Python is the programming language originally created by Guido van Rossum and now maintained by a large community. Not some currently not-existing product by a british company.

According to our London counsel, some of the best pieces of evidence we can submit to the European trademark office are official letters from well-known companies "using PYTHON branded software in various member states of the EU" so that we can "obtain independent witness statements from them attesting to the trade origin significance of the PYTHON mark in connection with the software and related goods/services." We also need evidence of use throughout the EU.

So it's incredibly easy for any company using Python to help out and you should feel ashamed for yourself if you don't do it. At we use python quite a bit (internal django apps, yum, mockbuild, mailman, func and a host of scripts for example) so I just sent our letter to the PSF. Hope it helps!

CV writing, or the art of selling yourself

Last update on Feb. 4, 2013.

Note for american readers: I'm european. In europe, CV and resume are synonyms and in the Netherlands, where I live, we call this thing a CV. An american reader of mine pointed out that what I'm describing is what americans call a resume, not a CV. In his words: "Here a resume generally only contains professional accomplishments. When someone asks for a CV, which usually only happens in education, they want more details like published works, community involvement if you're going for a Dean or Chair, etc etc".

Over the last few months, a few friends have asked me for help in writing a CV and applying for a job. As a person who has interviewed dozens of candidates and reviewed hundreds of CV's, I think I can safely say I know what I'm looking for and I know what makes a great CV for me. Unfortunately, most people suck at writing CV's. If you want to make it easier for a hiring manager to think he should interview you, make sure you understand the importance of the CV. There are no rules for writing one, though the following rules guidelines should help you write a great CV.

You are writing an ad. Not a CV.

Your CV is the only thing a hiring manager knows about you. After reading your CV, he needs to be willing to invite you in for an interview, so make it easy for him to decide to do so. Advertise yourself! This means:

  • Tell the hiring manager who you are
  • Give good, detailed information about you
  • Don't waste his time

I like CV's that start with an objective. What do you want in your career? This tells me a lot about whether you will fit in my team or not and whether I can give you what you want.

When reviewing CV's The worst thing there is, is a 14 page CV. Seriously. As a hiring manager I do not have enough time to waste it on 14 pages of a detailed description of your life, there are 30 other CV's waiting for me. On the flip side, I do want to know all the relevant things about you. So tell me what's relevant! For each education or experience item, tell me what you did. And tell me who you are.

Give facts about what you did, and start with important ones. Something like "Built a puppet environment for application X, allowing me and my team of 3 to install 200 servers in 3 days" is an excellent item. "Contributed to projects" (Yes, this is a quote from an actual CV) is not.

Also horrible is the common trap us techies fall into: mentioning everything we worked with. This is utterly useless. I "read" A CV the other day, which was a list 7 pages list each and every technology the person used in all of his jobs. This tells me absolutely nothing about this person, and it's really easy to make a decision: no interview. Remember: you're writing an ad for yourself. Have you ever seen an ad that was just a list of ingredients?

You don't have `a CV`

You should tailor your resume for the job you're applying for. Every job is slightly different, so you will want to present yourself slightly different, putting emphasis on what's more important for this job. For instance, if I were to apply for a sysadmin team leader position, I would start the section of my CV with:, unix team leader (2010-2012) and systems architect (2012-2013)

  • I led a team of 5 sysadmins/developers, developing tools to make system administration easier, such as a central infrastructure database, linked to our kickstart, puppet, nagios and dns infrastructures
  • Designed a database loadbalancing scheme and implemented the prototype of it, guiding other engineers in implementing it further

But if I were to apply for a python development position, I would start with:, unix admin and team leader (2006-2012) and systems architect (2012-2013)

  • Developed our central infrastructure database and management system, a python/django application, and integrated it with every service that needs server information, such as kickstart, puppet, nagios and DNS
  • Introduced Func as remote execution framework and built various deployment and monitoring tools using it.

Both are an accurate description of things I did, but emphasis is on what's relevant for the job I am applying for.

A natural follow-up of this customization per job, is that you shouldn't be sending your CV to a bajillion places. Do you really want to interview for any company that may want to hire you? Before applying, I'd rather know the company. Your personality, past results and future potential are things a hiring manager thinks about when reading about you, so why aren't the company culture, past results and future prospects important to you? If you don't know anything about the company before applying, you're only setting yourself up for failure.

Following on even further from there, I would be very careful with applying via a recruitment agency. They tend to blast your CV around to any company that has an opening that may be relevant, without doing this customization. As a hiring manager, I find the hit rate for most recruitment companies far too low. Some also insist on "fixing" your resume, turning "played with mysql once" into "advanced mysql DBA" (yes, this actually happens), or removing your contact info and replacing it with a large ad for them.

Know your CV very well

A surprising amount of people do not know their own CV, which I find mindboggling. A good hiring manager will base many of his questions on your CV, making you explain what you did, trying to find your thought patterns, your reasoning and deduction skills. If your CV starts with "designed a database for an e-commerce app", you'd better be able to explain your design. If you list an MSc. in computer science as one of your degrees, you should be able to tell me what you learned there.

So read your CV. Twice. Three times. Put yourself in the hiring managers shoes and ask yourself questions about what you did, for every job and education you list. For every hobby you list. And if you find it difficult to talk about a part of your CV: leave it out or reduce the importance of it.

Cover letter

Not many people include a cover letter with their CV, which I find a shame. Your CV is an overview of your education and experience, but equally important for the hiring manager is your motivation. Why do you want to work for me? If I can't answer that question, do I know you really want to work for me? Or are you just machinegunning your CV to all places you can find? Your cover letter should tell me you care about landing a job you want, why this job would be good for you and why you would be good for the company. The CV is the evidence to back up those statements.

And a tip I really should add here: your e-mail to HR when you apply is your cover letter. But those e-mails don't generally reach the hiring manager, especially when organizations grow and have a recruitment system. So prepend your cover letter to your CV document to make sure the hiring manager gets it too.

The dreaded soft skills part

It's a common issue for people applying for technical positions, forgetting that their awesome technical skills are not all that matters. For me it's only about a third of what I'm looking at in a resume, and especially during interviews. And it's the easy part, as most answers are verifiable and it's playing to your strengths. But there are four more questions that I need to see an answer to, and your CV should start giving those answers

  • Can I work with you?
  • Can my team work with you?
  • Can the company work with you and for you?
  • Do you understand out business?

The first three are all about your personality. How driven are you? Are you a creative or process-oriented person? What's your communication style? Do you lead or follow? All in all these questions can be summed up as "Do you fit the company and does the company fit you?". Your technical skills may be excellent, but if your personality would not fit with the rest of the team, you would not be happy at this job.

The last question I find really important and most candidates utterly fail to prepare for this. The company does not revolve around you or the position you are applying for. Do you understand how our industry works? Show it on your CV! Can you translate your contributions to improvements for customers? Make sure I know that!

It depends

I said before that there are no hard rules for the CV. So questions such as "Should I add a photo?" "Should I list hobbies?" or "How about wife & kids?" don't have an answer other than "It depends". Do they add anything to your CV? Does it tell the hiring manager more about who you are or are you wasting his time with useless trivia?

One pet peeve of mine is people who add the text "Curriculum Vitae" to their CV. Yes, I know it's one and you just look like a prick spelling out a word you had to google in the first place. Why not put something useful there, such as a one line summary of you? Instead of "Curriculum Vitae of I.M. Weasel", why not say "I.M. Weasel - Actor, Deep sea diver and Astronaut".


  • Research the company whose job opening you are responding to
  • Sell yourself! Make sure your future employer knows how awesome you are
  • Customize your CV per job, emphasize the things that are most relevant for the job
  • Know your CV and prepare to answer questions about all parts of it
  • Page 1 of 3
  • 1
  • 2
  • 3
  • 3

Yearly archives


December 2015
May 2014