Automount kindle on nixos

How to configure nixos to automount a kindle with mtp.

Newer kindles are mtp devices meaning we need to use some third-party program to mount them as drives on linux. I thought this would be easy, it took me longer than I would have hoped. If you just want my final answer its down below what follows is a brief explanation as to how I got there1. Also, I do not know what I’m doing. There may be a cleaner way to do this but this is what worked for me.

The big picture is udev lets us react to devices being added or removed to our system. jmtpfs lets us mount an mtp device as a filesystem. However for security reasons udev cannot run arbitrary commands but it can start systemd services. So alls we need to do is:

  1. Create a systemd unit to mount the kindle
  2. Create a udev rule to start this unit when the kindle is plugged in
  3. Create a udev rule to stop this unit when the kindle is unplugged

The unit

This is simple enough, we call jmtpfs with the -f flag to run it in the foreground (or else the mount gets destroyed upon creation) and the -o allow_other to let anyone on the system access this.

systemd.services.mount-kindle = {
	enable = true;
	description = "Mount Kindle when plugged in";
	after = [ "local-fs.target" ];
	serviceConfig = {
		ExecStart = "${pkgs.jmtpfs}/bin/jmtpfs -o allow_other -f /mnt/kindle";
	};
};

The add event

The add event is also fairly straight forward. First we need to get the vendor and product id of our kindle. Plugging in our kindle and running:

nix run nixpkgs#usbutils | grep -i kindle

should produce an entry that looks something like the following.

Bus 003 Device 086: ID 1949:9981 Lab126, Inc. Kindle Paperwhite GR733X0153230GKQ

Where the ID field is in the form {vendor_id}:{product_id}. With this information we can create the following udev rule.

services.udev.extraRules = ''
	ACTION=="add", \
		SUBSYSTEM=="usb", \
		ATTR{idVendor}=="${vendor_id}", \
		ATTR{idProduct}=="${product_id}", \
		TAG+="systemd", \
		ENV{SYSTEMD_WANTS}="mount-kindle.service"
'';

The remove event

You would think this would be basically the same rule but in reverse. I guess, in spirit, it is. There is one minor issue though: the device is not plugged in, we can’t query it’s ATTRs anymore. However, the helpful people over at linuxquestions.org have the answer. The product information gets saved in the udev environment, so we query that instead.

services.udev.extraRules = ''
	ACTION=="remove", \
		SUBSYSTEM=="usb", \
		ENV{PRODUCT}=="${vendor_id}/${product_id}/*", \
		RUN+="${pkgs.systemd}/bin/systemctl --no-block stop mount-kindle.service"
'';

Final Config

Putting it all together I got something which looks like this:

{ pkgs, ... }:
let
  vendor_id = "1949";
  product_id = "9981";
in
{
  systemd.services.mount-kindle = {
    enable = true;
    description = "Mount Kindle when plugged in";
    after = [ "local-fs.target" ];
    serviceConfig = {
      ExecStart = "${pkgs.jmtpfs}/bin/jmtpfs -o allow_other -f /mnt/kindle";
    };
  };

  services.udev.extraRules = ''
    ACTION=="add", \
      SUBSYSTEM=="usb", \
      ATTR{idVendor}=="${vendor_id}", \
      ATTR{idProduct}=="${product_id}", \
      TAG+="systemd", \
      ENV{SYSTEMD_WANTS}="mount-kindle.service"
    ACTION=="remove", \
      SUBSYSTEM=="usb", \
      ENV{PRODUCT}=="${vendor_id}/${product_id}/*", \
      RUN+="${pkgs.systemd}/bin/systemctl --no-block stop mount-kindle.service"
  '';
}

  1. With all the deadends and out of date guides removed↩︎