Passthrough of a Z-Wave USB Stick to a Proxmox LXC Container
The goal of this guide is to make a Z-Wave USB Stick that is connected to a Proxmox 8.2.2 host available to an lxc container. To accomplish this, we'll passthrough the serial device from the host to the container.
First, we need to identify which serial device to passthrough:
$ ls -l /dev/serial/by-id/
total 0
lrwxrwxrwx 1 root root 13 May 27 09:51 usb-Silicon_Labs_Zooz_ZST10_700_Z-Wave_Stick_0001-if00-port0 -> ../../ttyUSB0
The device we're looking for is a Zooz ZST10, which we can easily identify here among the many serial devices.
Now we need the major/minor numbers for the device. Follow the symlink to /dev/ttyUSB0
and run stat
:
$ stat -c '%Hr:%Lr' /dev/ttyUSB0
188:0
This gives us the major number of 188
and the minor number of 0
. The major number should always remain the same, but the minor number may change if another USB serial device is attached.
Now we open the configuration of our container, in this case at /dev/pve/lxc/103.conf
and add the following lines (replacing major/minor numbers and device path as needed):
lxc.cgroup.devices.allow: c 188:0 rwm
lxc.mount.entry: /dev/serial/by-id/usb-Silicon_Labs_Zooz_ZST10_700_Z-Wave_Stick_0001-if00-port0 dev/ttyUSB0 none bind,optional,create=file
Now the device is successfully passed through. However, if you're using an unprivileged container, then nothing in the container can access it! You'll need both read and write access to the device to use it. We'll look at two ways to accomplish this: the easy way, and the hard way.
The Easy Way
Simply give everyone read/write permissions on the device: chmod o+rw /dev/ttyUSB0
. Quick and easy, but it does mean that every user has full access to the Z-Wave stick. Good for troubleshooting, but not recommended for long-term use.
The Hard Way
For The Hard Way™, we'll map a group into the container that can use the Z-Wave stick. First, we'll create a user on the host, and get the new uid and gid.
$ useradd zwave
$ id zwave
uid=1002(zwave) gid=1002(zwave) groups=1002(zwave)
In the container, create a group with the same gid (same gid isn't strictly necessary but makes things much easier).
groupadd -g 1002 zwave
Any user that is a member of the zwave
group will be able to access the Z-Wave stick.
Now we have to map both the gid and uid into the container. The exact values will differ depending on the gid and uid that you end up with.
# Map 1002 ids from 0-1001 (container) to 100000-101001 (host)
lxc.idmap: u 0 100000 1002
lxc.idmap: g 0 100000 1002
# Map 1 id from 1002-1002 (container) to 1002-1002 (host)
lxc.idmap: u 1002 1002 1
lxc.idmap: g 1002 1002 1
# Map 64533 ids from 1003-65535 (container) to 101003-165535 (host)
lxc.idmap: u 1003 101003 64533
lxc.idmap: g 1003 101003 64533
More information on ID mapping can be found on the Proxmox wiki.
We need to allow the uid and gid to be remapped. In both /etc/subgid
and /etc/subuid
add the line root:1002:1
. They should look something like
root:1002:1
root:100000:65536
Lastly, group of the device needs to be changed to zwave
:
chgrp zwave /dev/ttyUSB0
Now we can restart the container, and the device's group will be zwave
with 660 permissions:
$ ls -l /dev/ttyUSB0
crw-rw---- 1 nobody zwave 188, 0 May 26 12:00 /dev/ttyUSB0
Reboot Persistence
Right now, once the host reboots, the new permissions on the device will disappear, and will need to be re-applied. To avoid this, we can create a udev rule to match the Z-Wave stick and change the group or file mode.
Identify the vendor ID and product ID using lsusb -v
. Find the device, and note the idVendor
and idProduct
values.
Bus 003 Device 003: ID 10c4:ea60 Silicon Labs CP210x UART Bridge
Device Descriptor:
<snip />
idVendor 0x10c4 Silicon Labs
idProduct 0xea60 CP210x UART Bridge
bcdDevice 1.00
iManufacturer 1 Silicon Labs
iProduct 2 Zooz_ZST10_700_Z-Wave_Stick
<snip />
In this case, the vendor id is 10c4
and the product id is ea60
.
Create a new file under /etc/udev/rules.d/
called 90-zwave.rules
, and add
SUBSYSTEMS=="usb", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", GROUP="zwave", MODE="0660"
Now any device that rule will be assigned to the group zwave
and set to the file mode 0660
.
If you're doing The Easy Way, you'll instead want to ignore the group, and use the mode 0666
.
SUBSYSTEMS=="usb", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", MODE="0666"
Below is the full lxc configuration file for my container:
# Passthrough configuration
lxc.cgroup.devices.allow: c 188:0 rwm
lxc.mount.entry: /dev/serial/by-id/usb-Silicon_Labs_Zooz_ZST10_700_Z-Wave_Stick_0001-if00-port0 dev/ttyUSB0 none bind,optional,create=file
lxc.idmap: u 0 100000 1002
lxc.idmap: g 0 100000 1002
lxc.idmap: u 1002 1002 1
lxc.idmap: g 1002 1002 1
lxc.idmap: u 1003 101003 64533
lxc.idmap: g 1003 101003 64533
# Not relevant for passthrough but included for completeness
arch: amd64
cores: 1
features: nesting=1
hostname: zwavejs
memory: 512
net0: name=eth0,bridge=vmbr0,firewall=1,hwaddr=BC:24:11:D6:81:29,ip=dhcp,type=veth
ostype: debian
rootfs: vmstore:subvol-103-disk-0,size=8G
swap: 512
unprivileged: 1
lxc.cap.drop: sys_rawio