Automatic mounting of known removable media with systemd in non-graphical environments

In a Unix/Linux context, the term “automount” is often interpreted as the automatic on-demand mounting of devices or remote filesystems as soon as a controlled path is accessed: the actual mounting is deferred to the moment the path is first accessed. Or in other words: only when it is needed. But what if we want a different kind of automount?

In the old days classical on-demand mounting has been implemented with autofs, where the automount daemon would consider the /etc/auto.master file for mounts points to set up and then keep running in the background to do all (un)mounting as needed. Nowadays systemd is not only dominant in all major Linux distributions but also supports this functionality by augmenting its mount units, which replace fstab entries, with automount units, which encode the additionally needed information such as the automount point and optionally an idle timeout.

However this does not help in a different scenario: when we want known removable media to be automatically mounted as soon as it becomes available. Example use cases include USB sticks plugged into physical systems but also virtual storage volumes getting attached or removed from virtual machines.

“But wait!”, you say, “What you’re describing is exactly what happens in my Linux distribution! When I plug in an USB stick, it will get mounted below /run/$USER/$VOLNAME and a window will automatically open with its contents!” That may be the case, but usually only when you’re in a graphical environment and not in text-mode server setups. There the only thing that will happen is that a block device will appear as /dev/sdX (or /dev/vdX with virtio setups). This wasn’t always this way and may be different for some Linux distributions but if not and you’re curious how we achieve this with systemd and a little udev magic, read on!

The mount unit

First we need a systemd mount unit for the device to be mounted. We could also work with a fstab entry but it’s cleaner to stick to the usual systemd conventions and will be advantageous in an upcoming follow-up blog post, so we’ll follow that road. Like all custom systemd units we write it goes into /etc/systemd/system to keep it separate from units provided through system packages. It’s named after the mountpoint in question plus a .mount suffix, except that it follows the standard systemd escaping logic described in the systemd.unit(5) manpage: in short, the leading slash gets stripped away and other slashes get turned into dashes. Here’s an example /etc/systemd/system/media-mydata.mount for a USB stick to be mounted at mountpoint /media/mydata:

[Unit]
Description=Mount /dev/disk/by-label/mydatafs at /media/mydata

[Mount]
What=/dev/disk/by-label/mydatafs
Where=/media/mydata
Type=auto
Options=noauto,nofail

Note how we do not refer to a particular /dev/sdX node: whether the plugged-in stick appears as /dev/sdb or /dev/sdc or even some other name can not be reliably predicted. That is why we instead refer to a known unique property of the stick, the technique will thus only work with known media. We could of course use its serial number if exposed through sysfs, but then you’d have to adapt the mount unit whenever you need to replace a broken stick with another one. Therefore we instead refer to something that can be transferred to a replacement stick: the label of the filesystem on it, here mydatafs. You should obviously pick something sufficiently distinct here.

The other specified options equal those in a fstab entry: Type=auto tells the kernel to auto-detect the filesystem on the device, Options=noauto,nofail tells mount not to try mounting the volume when it’s called with the -a option and in general not to report errors if the device is not present. For more mount unit options, refer to the systemd.mount manpage.

You should test the mount point at this point: after a systemctl daemon-reload so systemd knows the new mount unit, with the stick plugged in and the /media/mydata directory created, a systemctl start media-mydata.mount should succeed and render the stick’s contents visible at /media/mydata.

The udev rule

Next, create /etc/udev/rules.d/99-automounts.rules with the following contents (the line breaks here are only for better readability — udev rules must always be written on a single line!):

ACTION=="add",
SUBSYSTEM=="block",
ENV{DEVLINKS}=="*/dev/disk/by-label/mydatafs*", 
ENV{SYSTEMD_WANTS}="media-mydata.mount"

This extends the system’s set of udev rules with a custom action that reacts to the udev event created when the stick is plugged in — you might have seen something like this back in your old Linux days. Every udev event brings with it a set of properties and here we use the fact that among these are the symlinks created by other udev rules, including our already known /dev/disk/by-label/mydatafs link. But the second part is the real magic: udev rules can also create systemd dependencies! The one we use here is a Wants dependency on our mount unit. Meaning: the udev rule connects appearance of the block device to a start of the mount unit, which, as seen above, will take care of the mounting. A final udevadm control --reload-rules will be necessary for the udev rule to become active.

And that’s it! If you now attach the stick, it should be mounted automatically.

What about umounting?

Chances are that you’ve been not familiar with systemd mount units so far, so you may wonder if you now always need to systemctl stop a mount point or can just umount it yourself. The answer is: it depends. For the context of this blog post alone it won’t make much difference but as we will see in an upcoming follow-up blog post you should always use the former.