Systemd – How to Start a Service When USB Device is Plugged In

arch linuxethernetsystemdusb

I have an USB ethernet dongle I use for ethernet access, but I'd like to automatically start netctl-ifplugd@... when plugging it in. (Not to be confused with plugging in the ethernet cable – that's handled by netctl-ifplugd.) How can I make this happen?

I've seen How to write a systemd service that depends on a device being present? but that doesn't seem to be about plugging it in, just about it being present.

Best Answer

I've seen "How to write a systemd service that depends on a device being present?" but that doesn't seem to be about plugging it in, just about it being present.

It's very similar, only you need the opposite dependency: instead of (or perhaps in addition to) your service depending on the device, you want the virtual device unit to depend on your service.

In fact, despite the linked question's title seemingly asking the opposite thing, the accepted answer in the thread already documents one of the exact methods that would make the device start a service (the udev-rule method).

But if you have an exact .device unit name, it's simpler to use systemd dependencies directly:

If the exact sysfs path is known:

For example, you have determined that the device is sys-subsystem-net-devices-usb0.device. You can use various ways of expanding that unit (despite it being virtual), such as foo.device.d/*.conf drop-ins or foo.device.wants/ symlinks (which work the same way as e.g. multi-user.target.wants/).

  • You can add an [Install] configuration in your service, then systemctl enable it:

    [Install]
    WantedBy=sys-subsystem-net-devices-%i.device
    
  • Or you can get the same result directly with systemctl add-wants:

    # systemctl add-wants sys-subsystem-net-devices-usb0.device netctl-ifplugd@usb0.service
    
  • Both methods just result in a symlink that can be made by hand via ln -s.

This generally works the same for user units.

If only partial name (or some combination of other properties) is known:

Write a udev rule that matches your device (the exact udev rule syntax is out of scope here) and have it set the SYSTEMD_WANTS udev property (aka "device environment variable") indicating your unit:

ACTION=="add", SUBSYSTEM=="net", KERNEL=="usb*", ENV{SYSTEMD_WANTS}+="netctl-ifplugd@%k.service"

As all *.rules files are processed in alphabetical order, make sure your rule is placed later than systemd's own "persistent network interfaces" rules.

This works with user units if you use ENV{SYSTEMD_USER_WANTS}.


You might be tempted to use RUN+="/bin/systemctl start foo" via udev. Don't do that. But if you do, then at least use the systemctl --no-block option so that you won't create a possible deadlock between the two components.