TL;DR;
If you want to spin up a fetchmail process per /etc/fetchmailrc.d/*.config
entry, build and install fetchmail-conf-d package.
The problem
The problem which I wanted to solve was fetching my email from multiple servers with relatively low latency. You might ask why fetch your email in the first place, but that’s out of scope of this post – I just want to use it as an excuse for expressing my admiration for systemd anyway. For years the go-to solution for fetching email was fetchmail.
Design decisions
fetchmail allows you to list multiple accounts and run as a daemon to scrape them every once in a while. This polling-style solution is not a pretty one. Fortunately, IMAP has a feature called IDLE, which basically makes an email client keep a connection open and the server notify clients as soon as it gets a new message. fetchmail supports it, however, only for one account. The obvious solution is to spin up a separate fetchmail for every account and this is what I decided to use systemd generators for.
Overview
systemd generators are basically templated services. They comprise a unit file template and its instantiations. The template is specified just like any other systemd unit file, except with a ‘@’ suffix. Instantiations are generated by a script or program, which you provide.
We’ll just put per-account configuration in /etc/fetchmailrc.d/some_config.config
and the script will instantiate the unit file with some_config
or something analogous for every file in /etc/fetchmailrc.d
with the suffix .config
.
Service template
In ubuntu 18.04 fetchmail comes with an old school init.d script, so I had to start from scratch. This is what I came up with:
[Unit] Description=Fetchmail for %i After=network-online.target [Service] User=root ExecStart=/usr/bin/fetchmail \ -d180 \ -l104857699 \ -f /etc/fetchmailrc.d/%i.config \ -N \ --syslog \ --sslcertck \ --pidfile /var/run/fetchmail/%i.pid Restart=always RuntimeDirectory=fetchmail RuntimeDirectoryMode=0750 [Install] WantedBy=multi-user.target
Notice the use of %i
. This is the instantiation – it will be substituted by whatever our script generates. Based on this, /etc/fetchmailrc.d/%i.config
will be used for configuration and /var/run/fetchmail/%i.pid
will be used to store the PID. Other than this, it’s a standard unit file.
Putting this file in a proper place is already enough to run systemctl start fetchmail@myconfig
and it will create a service which will try to use /etc/fetchmailrc.d/myconfig.config
.
Instantiating the template
Our script has to make sure thatmulti-user.target
depends on fetchmail@something
for every file we have in /etc/fetchmailrc.d
. That way the fetchmails will be started at boot time. The systemd generator infrastructure allows you to write scripts for exactly that. Here is what I came up with:
#!/bin/bash set -e normal=${1?} early=${2?} late=${3?} log() { echo "$@" > /dev/stderr } wantdir="$normal/multi-user.target.wants" mkdir -p "$wantdir" for config in /etc/fetchmailrc.d/*.config ; do [ -f "$config" ] || continue basename="$(basename "$config")" conf_name="${basename%.config}" ln -s "/lib/systemd/system/fetchmail@.service" \ "${wantdir}/fetchmail@${conf_name}.service" done
The script basically creates symlinks of fetchmail@.service
unit file to a directory subdirectory of a directory provided by an argument. That’s how you fit in systemd’s generator infrastructure.
Putting it all together
All you have to do is put the unit file and the script to where they live. I went for /lib/systemd/system/fetchmail@.service
for the unit file because I have put it in a debian package. If you want to just copy the file then /etc/systemd/user/
might be a better choice, but remember to update the generator script to resemble that.
Similarly for the generator script: I have put it in /lib/systemd/system-generators/system-fetchmail-generator
but you mind find it more convenient to put it in /etc/systemd/user-generators/
directory.
In order to regenerate the symlinks just run systemctl daemon-reload
.
You can even run systemctl status fetchmail@*
to see the status of all the running instances.
If you want a ready solution, you can get the debian package source which I prepared, build it by running debuild -us -uc -F
and install by running dpkg -i
.