usbhotplug

unRAID Automatic USB Hotplugging (Libvirt)

A long time ago, I figured out how to have an unRAID server detect a hotplugged USB device and automatically associate this device with a VM. I wasn’t ever planning on revisiting this issue as unRAID was supposed to implement this as a feature in a future release. Well, after several years and numerous unRAID upgrades, not only am I still waiting for this feature, but the solution I was using stopped working.

There have been a couple of good souls that have written plugins that allow you to manually add a USB device to a running VM, but it requires that you login to the unRAID management GUI everytime you want this done. While this isn’t so bad when you need to plug in an occassional USB thumb drive, it becomes a much bigger hassle when you throw a USB switch and multiple devices into the mix.

For those who are unaware, a USB switch is like a KVM…not the KVM related to VM’s, but the Keyboard/Video/Mouse kind. The only difference is it lacks the video portion. However, like a KVM switch, it allows you to hookup any USB device to multiple PC’s at once, but only one PC can see the device at a time. Anytime you cycle the switch away from a PC, the USB device, like say a keyboard or mouse, will act as if its been unplugged. With the currently available plugins, this means you’d always need a second PC handy to access the unRAID GUI and reassociate your keyboard and mouse. Like I said before, major hassle.

Another complication when using a USB switch, at least in my case, is everytime you cycle the USB switch away from the unRAID server, multiple add and remove events are detected. I’m not quite sure why this is, but while monitoring udev, Linux’s device manager, I would see my mouse and keyboard rapidly attach and detach several times in less than a second. This can wreck havoc on a script that relies on these events to launch itself and it’s one of the reasons why my previous solution stopped working.

Don’t fret, as after much trial and error, I believe I have a working yet, somewhat convulated way to automatically attach and detach your USB devices to your VM’s. I developed this method after stumbling upon this helpful post, Automatic hotplugging of USB devices for libvirt-managed VMs at kicherer.org. If you’re using libvirt on a non-unRAID server while hotplugging a single USB device directly into the VM host, their guide may be better suited to you.

Preparing for the Fix

The first thing we need to do is obtain the ID’s of the device or devices you’d like to have hotplugged to your VM. You can do this by gaining console access to your unRAID server. The easiest way to achieve this is by opening a web browser and logging into the unRAID management GUI.

Identify Your USB Device

After logging in, click on the Terminal icon at the top of the page.

unRAID Terminal Link

*TIP: For those more hardcore or if you’re running an older version of unRAID missing this option, you can ssh to your unRAID server as root with your favorite SSH client.

Inside the terminal window, run the following command to identify your USB device.

lsusb

In the output, locate the USB device and note the Bus & Device ID #’s.

Next run the following, to get the Vendor ID, Model ID & Product ID of your USB device substituting the Bus & Device ID #’s from the last command. For example, if your Bus ID is 001 and your Device ID is 005 your command will be udevadm info /dev/bus/usb/001/005 | egrep “ID_VENDOR=|ID_MODEL=|PRODUCT”.

udevadm info /dev/bus/usb/BusID/DeviceID | egrep "ID_VENDOR=|ID_MODEL=|PRODUCT"

Example USB ID’s

ID_VENDOR = 0000
ID_MODEL = aaaa
PRODUCT = 000/aaa/111

Remember these values as we’ll be needing them in the upcoming sections.

Create the Libvirt Config

We’re now going to create a Libvirt (QEMU Management Library) XML Config file called usb-0000-aaaa.xml in the /etc/libvirt/qemu/USB directory which will define the USB device for the VM. Replace the 0’s with the Vendor ID and the a’s with the Model ID from the last section.

*NOTE: While technically the name of the XML file can be anything, keeping this naming scheme is necessary for the scripts we’ll be creating later on in this guide.

nano /etc/libvirt/qemu/USB/usb-0000-aaaa.xml

Add the following in the XML while making sure to replace the vendor id and the product id values after the “0x” with the 4-digit vendor id and model id values from the last section.

<hostdev mode='subsystem' type='usb'>
  <source>
    <vendor id='0x0000'/>
    <product id='0xaaaa'/>
  </source>
</hostdev>

udev Rules…Not!

In this section, we’ll be creating a list of rules which udev will execute whenever the USB device is added or removed from the server. In our case, the rules will launch a call script which will in turn execute another script that will attach or detach the USB device to our VM. The reason for this convoluted process is udev doesn’t play nice with complicated, long-running processes. It’s fine running a simple command that starts and ends quickly, but it doesn’t work with more advanced processes.

Create the config file by running the following

nano /boot/config/90-libvirt-usb.rules

*NOTE: Unlike the previous XML file, the name of this file does matter. The “90” at the beginning of the file tells udev the order in which it should apply its rules. The rest of the name, excluding the .rules file extension, can be changed to your preference.

*TIP: For those not using unRAID, you should create this file in /etc/udev/rules.d. The only reason we’re creating the config file in the /boot/config directory is unRAID wipes all user created files from rules.d upon every reboot. To combat this, we’re going to create a script later in this guide which copies back this file during bootup.

Now paste the following text into the 90-libvirt-usb.rules config file.

ACTION=="add", \
	ENV{PRODUCT}=="000/aaa/111", \
	RUN+="/boot/config/callusbhotplug.sh 'add' 'VM-NAME' '0000' 'aaaa'"
ACTION=="remove", \
	ENV{PRODUCT}=="000/aaa/111", \
	RUN+="/boot/config/callusbhotplug.sh 'remove' 'VM-NAME' '0000' 'aaaa'"

*NOTE: udev requires all of its rules to be written on one line. The backslash at the end of each line allows you to circumvent this rule. This method doesn’t work if you include a commented line in the middle of the command, so don’t do that.

Make sure to change the ENV{PRODUCT} value to the Product ID you wrote down earlier.

You’ll also need to modify the two RUN+ lines which executes our call script. Change VM-NAME to the name of the VM you want to attach the USB device to and change all of the 0000’s & aaaa’s to your Vendor & Model ID. This should sound familiar as we’ve already done this at least 50 times already.

*TIP: If you don’t remember the exact name of your VM, you can either check the unRAID management GUI or run the following command within the unRAID console, virsh list –all.

To quickly summarize what’s actually going on here, we’re creating two different udev rules. One for when the USB device is added and one for when it is removed. Both rules launch the same call script and pass along the udev action (add/remove) and the VM name, USB Vendor ID & USB Model ID.

With all of our configs and rules created, we now move on to the scripting portion of the guide which will do all the heavy lifting for us.

Hotplugging Part III: The Scripting

First up we’ll be creating the call script which we’ve referenced in the udev rules at the end of the previous section.

Calling All Scripts

nano /boot/config/callusbhotplug.sh

Copy and paste the following into the script.

#!/bin/bash
/boot/config/usbhotplug.sh $1 $2 $3 $4 & disown

This script passes the arguments that are sent from our udev rules to the script which will handle the USB hotplugging and then disowns the process. As stated previously, udev can’t handle anything more than a quick commmand so it’s important to disavow all knowledge of the mission…I mean process after it is launched from a udev rule.

Now that the call script is in place, we need to create the thing its calling.

Creating the Hotplug Script

Instead of posting the entire script, I created a GitHub repository you can download the script from in case there are any future changes.

Download the script using the large link directly above and move the script to the /boot/config directory.

wget https://raw.githubusercontent.com/labsrc/Libvirt-USB-Hotplugging/master/usbhotplug.sh
mv usbhotplug.sh /boot/config

For those interested in the nitty gritty, the script creates a log of all actions in /root and also creates individual time stamped files anytime it is run for a USB device. This helps to prevent the script from inadvertantly firing multiple times which is what I experienced when using a USB switch to add and remove my keyboard & mouse.

The script also removes any previous instances of the USB device from the VM before attaching it. This is to prevent the VM from continually adding multiple instances of the same USB device and eventually hitting the VM’s limit if your udev rule fails to trigger when the USB device is removed. Keep in mind, this will cause issues if you were planning on hotplugging multiple copies of the exact same USB device into your unRAID server.

Surviving the Reboot

For those of you who have already ventured beyond the constraints of unRAID’s WebGUI, you probably know that just about everything you create via command-line magically disappears when the server reboots. Fortunately, the creators of unRAID provided a way to make sure everything you customize can be automatically re-added during startup including the udev rules we’ve created. The key to our files having everlasting life is unRAID’s Go Script.

Go Go Gadget Go Script!

The Go Script, located at /boot/config/go, is really just a bash script that runs after unRAID mounts its drives. We’ll be using this script to copy the udev rules we made earlier in this guide to its proper location.

nano /boot/config/go

Now add the following into the Go script and save.

#!/bin/bash
cp /boot/config/90-libvirt-usb.rules /etc/udev/rules.d
chmod 644 /etc/udev/rules.d/90-libvirt-usb.rules

All this does is copy our udev rules to /etc/udev/rules.d and change the permissions on the file.

Now with everything in place, go ahead and reboot your unRAID server.

Testing Phase

With your unRAID server now rebooted, make sure your VM is powered on. Now go ahead and plug in your USB device into the unRAID server or, as in my case, toggle your USB switch to the unRAID server. If everything worked as it should, your USB device should now be attached to your VM. If this is the first time attaching the USB device to the VM, you may have to load device drivers just like you would for a physical PC.

You can check if the USB device successfully attached to the VM by inputting the following into the unRAID console.

virsh dumpxml VM-Name | grep vendorID -A1

Change VM-Name to the name of your VM and make sure any letters in the vendorID are typed in as lowercase. If the output is blank, make a sad face and skip to the next section.

If that worked, wait a few seconds, unplug the USB device, wait a few more seconds and plug it back in. If my script did it’s job, you should see the USB device attached to the VM again.

If any part of the test failed, it’s time to put on your troubleshooting caps.

What To Do If You Failed

The first thing you should do is stop blaming me. I’m just some random lifeform on the internet giving out free advice. The next thing you should do is check the /root directory. I coded the script to create log files in case of different types of failures. If you don’t see any newly created files in the /root directory, that means the script never ran and we most likely have a udev rules issue. Start off by doublechecking all the files we’ve created for syntax errors.

*TIP: If you make changes to your udev rules file, you can skip the reboot and still have your changes take effect immediately by running the following command, udevadm control –reload.

If you’ve already doublechecked for any typos, it’s time to make sure your server is actually generating udev events whenever the USB device is attached or removed from the server.

Inside the unRAID console, you can monitor all udev events by running the following.

udevadm monitor

While this command is running, physically plug the USB device into the server and you should see a bunch of output within the console if the USB device is recognized. If the console remains blank, you most likely have a hardware issue.

If, however, you do see output, confirm there are lines that contain the USB device’s Vendor & Model ID’s. Check this against the ID’s you’ve been using in your configs to make sure they match.

If that checks out, you can try manually attaching the USB device to the VM.

virsh detach-device VM-Name /etc/libvirt/qemu/USB/usb-vendorID-productID.xml

Change VM-Name to the name of your VM and change vendorID & productID to the corresponding ID’s of your USB device.

Now check to see if the VM sees the device attached.

virsh dumpxml VM-Name | grep vendorID -A1

If this still outputs nothing, there’s probably a typo within the usb xml file we created in /etc/libvirt/qemu/USB/ or you’re using the wrong USB ID.

Hopefully these troubleshooting steps should get things up and running for you. If not, feel free to comment down below and I’ll be happy to hand out more free interweb advice.


Share on Facebook
Facebook
Tweet about this on Twitter
Twitter
Share on LinkedIn
Linkedin
Share on Reddit
Reddit
Email this to someone
email
Print this page
Print

Please Share Me...I'm Lonely