Skip to content

Instantly share code, notes, and snippets.

@billchurch
Last active February 1, 2025 02:53
Show Gist options
  • Save billchurch/8ce87fdf248a01d0d8c78ecb369938ab to your computer and use it in GitHub Desktop.
Save billchurch/8ce87fdf248a01d0d8c78ecb369938ab to your computer and use it in GitHub Desktop.
rtl_433 proxmox lxc and docker

rtl_433 proxmox lxc and docker

These are my notes setting up rtl_433 docker container on a Proxmox host inside an LXC guest...all the inceptions...

proxmox host setup

background on rtl_433 on lxc:

Docker LXC install script

Some decent helper scripts here, this one installs docker in LXC pretty effortlessly

Proxmox Host Steps

We need to prevent autoload of proxmox kernel modules from seizing the RTL device:

create /etc/modprobe.d/blacklist-rtlsdr.conf:

# Blacklist host from loading modules for RTL-SDRs to ensure they
# are left available for the Docker guest.

blacklist dvb_core
blacklist dvb_usb_rtl2832u
blacklist dvb_usb_rtl28xxu
blacklist dvb_usb_v2
blacklist r820t
blacklist rtl2830
blacklist rtl2832
blacklist rtl2832_sdr
blacklist rtl2838

# This alone will not prevent a module being loaded if it is a
# required or an optional dependency of another module. Some kernel
# modules will attempt to load optional modules on demand, which we
# mitigate here by causing /bin/false to be run instead of the module.
#
# The next time the loading of the module is attempted, the /bin/false
# will be executed instead. This will prevent the module from being
# loaded on-demand. Source: https://access.redhat.com/solutions/41278

install dvb_core /bin/false
install dvb_usb_rtl2832u /bin/false
install dvb_usb_rtl28xxu /bin/false
install dvb_usb_v2 /bin/false
install r820t /bin/false
install rtl2830 /bin/false
install rtl2832 /bin/false
install rtl2832_sdr /bin/false
install rtl2838 /bin/false

run this once to unload modules if already loaded on host

modprobe -r dvb_core
modprobe -r dvb_usb_rtl2832u
modprobe -r dvb_usb_rtl28xxu
modprobe -r dvb_usb_v2
modprobe -r r820t
modprobe -r rtl2830
modprobe -r rtl2832
modprobe -r rtl2832_sdr
modprobe -r rtl2838

update modules and initramfs on host

depmod -a
update-initramfs -u

find path of rtl device we want to export to lxc/docker

# lsusb
Bus 002 Device 002: ID 0bda:8153 Realtek Semiconductor Corp. RTL8153 Gigabit Ethernet Adapter
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 003: ID 8087:0a2b Intel Corp. Bluetooth wireless interface
Bus 001 Device 009: ID 0bda:2838 Realtek Semiconductor Corp. RTL2838 DVB-T
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

In this case we want the device on Bus 001 and Device 009

check permissions of /dev/bus/usb/001/009

# ls -l /dev/bus/usb/001/009
crw-rw-r-- 1 root root 189, 8 May 15 09:59 /dev/bus/usb/001/009

set permissions of /dev/bus/usb/001/009 to 0666:

# chmod 0666 /dev/bus/usb/001/009
# ls -l /dev/bus/usb/001/009
crw-rw-rw- 1 root root 189, 8 May 15 09:59 /dev/bus/usb/001/009

a more permament setup is to use udev rules:

/etc/udev/rules.d/50-usb.rules:

SUBSYSTEM=="usb", ATTR{idVendor}=="0bda", ATTR{idProduct}=="2838", GROUP="root", MODE="0666"

And reload them with:

udevadm control --reload-rules && udevadm trigger

Dynamic USB device update

I've found that, even without phsycially changing the USB devices the paths can move around, which is weird to me but whatever. I devised this method to dynamically update the lxc configuration on boot. Could probably also do this with a udev rule but, let's not get crazy...

1. Create a Script to Update the LXC Configuration

Create a script, for example, /usr/local/bin/update_lxc_usb_path.sh, that will find the correct USB device path and update the LXC configuration file.

#!/bin/bash
# /usr/local/bin/update_lxc_usb_path.sh

# Configuration
CONTAINER_ID=111
CONFIG_FILE="/etc/pve/lxc/$CONTAINER_ID.conf"
DEVICE_ID="0bda:2838" # Change to your USB device ID

# Find the USB device path
device_info=$(lsusb | grep $DEVICE_ID)
bus=$(echo $device_info | awk '{print $2}')
device=$(echo $device_info | awk '{print $4}' | sed 's/://')
device_path="/dev/bus/usb/$bus/$device"

# Update the LXC configuration
if [ -n "$device_info" ]; then
    sed -i "/^lxc.mount.entry:.*dev\/bus\/usb\/.* none bind,create=file/c\lxc.mount.entry: $device_path dev/bus/usb/$bus/$device none bind,create=file" $CONFIG_FILE

    # Update snapshot configurations
    sed -i "/^\[.*\]/,/\[.*\]/ s/^lxc.mount.entry:.*dev\/bus\/usb\/.* none bind,create=file/lxc.mount.entry: $device_path dev/bus/usb\/$bus\/$device none bind,create=file/" $CONFIG_FILE

    echo "Updated LXC config with new device path: $device_path"
else
    echo "Device with ID $DEVICE_ID not found"
fi

Make the script executable:

chmod +x /usr/local/bin/update_lxc_usb_path.sh

2. Create a Systemd Service to Run the Script at Boot

Create a systemd service file, for example, /etc/systemd/system/update_lxc_usb_path.service, to run the script at boot.

[Unit]
Description=Update LXC USB Path
After=pve-cluster.service

[Service]
Type=oneshot
ExecStart=/bin/bash /usr/local/bin/update_lxc_usb_path.sh

[Install]
WantedBy=multi-user.target

3. Enable and Start the Systemd Service

Enable the service to run at boot and run it for testing (--now):

systemctl enable --now update_lxc_usb_path.service

LXC guest setup

Setup guest image

I use the docker LXC setup script from tteck's proxmox script and I used the default debian install from bash -c "$(wget -qLO - https://github.com/tteck/Proxmox/raw/main/ct/docker.sh)"

After the install completes I needed to shut down the container and modify the config file /etc/pve/lxc/111.conf to passthrough / allow the USB device:

lxc.cgroup2.devices.allow: c 189:* rwm
lxc.mount.entry: /dev/bus/usb/001/009 dev/bus/usb/001/009 none bind,optional,create=file

Then I restart the container and log-in (console is auto-login by default, we modify this later). I also setup my ssh keys for root in /root/.ssh/authorized_keys to login remotely later.

Test intial container setup

I created a user called rtl_433 and dropped my config file in /home/rtl_433/rtl_433.conf on the LXC guest and I exported that volume to docker.

adduser --system rtl_433 --home /home/rtl_433

```bash
docker run --rm --name rtl_433 --device /dev/bus/usb/001/009 -v /home/rtl_433:/home/rtl_433:rw hertzg/rtl_433:latest-alpine -c /home/rtl_433/rtl_433.conf

because the USB device path might change (happened to me a couple of times without phsyically changing the device) I decided to make this part dynamic in the container, so that it only needs to be changed in one place (in the lxc config file)

/usr/local/bin/rtl_dev.sh:

#!/bin/bash
# /usr/local/bin/rtl_dev.sh

device_info=$(lsusb | grep '0bda:2838')
bus=$(echo $device_info | awk '{print $2}')
device=$(echo $device_info | awk '{print $4}' | sed 's/://')
device_path="/dev/bus/usb/$bus/$device"

echo "USB_DEVICE_PATH=$device_path" > /etc/sysconfig/rtl_433

The /etc/sysconfig directory didn't exist for me, but it felt like the right place so i made it anyway, also chmod the script we just made:

mkdir /etc/sysconfig
chmod 755 /usr/local/bin/rtl_dev.sh

created a service to start the container at boot: /etc/systemd/system/rtl_433.service:

[Unit]
Description=rtl_433 Container
After=network.target docker.service
Requires=docker.service
Wants=network-online.target

[Service]
Restart=always
RestartSec=5s
EnvironmentFile=/etc/sysconfig/rtl_433
ExecStartPre=/bin/bash /usr/local/bin/rtl_dev.sh
ExecStartPre=-/usr/bin/docker stop rtl_433
ExecStartPre=-/usr/bin/docker rm rtl_433
ExecStart=/usr/bin/docker run --name rtl_433 --device ${USB_DEVICE_PATH} -v /home/rtl_433:/home/rtl_433:rw --restart unless-stopped hertzg/rtl_433:latest-alpine -c /home/rtl_433/rtl_433.conf
ExecStop=/usr/bin/docker stop rtl_433

[Install]
WantedBy=multi-user.target

reload units and start it

# systemctl daemon-reload
# systemctl enable --now rtl_433
# systemctl status rtl_433
● rtl_433.service - rtl_433 Container
     Loaded: loaded (/etc/systemd/system/rtl_433.service; enabled; preset: enabled)
     Active: active (running) since Sat 2024-05-11 12:36:40 EDT; 6min ago
    Process: 460 ExecStartPre=/usr/bin/docker stop rtl_433 (code=exited, status=0/SUCCESS)
    Process: 465 ExecStartPre=/usr/bin/docker rm rtl_433 (code=exited, status=0/SUCCESS)
   Main PID: 470 (docker)
      Tasks: 8 (limit: 38334)
     Memory: 8.0M
        CPU: 69ms
     CGroup: /system.slice/rtl_433.service
             └─470 /usr/bin/docker run --name rtl_433 --device /dev/bus/usb/001/009 -v /home/rtl_433:/home/rtl_433:rw --restart unless-stopped hertzg/rtl_433:latest-alpine -c /home/rtl_433/rtl_433.conf

May 11 12:43:17 rtl-433 docker[470]: Wind speed: 0.0 m/s
May 11 12:43:17 rtl-433 docker[470]: Gust speed: 0.0 m/s
May 11 12:43:17 rtl-433 docker[470]: UVI       : 0.0
May 11 12:43:17 rtl-433 docker[470]: Light     : 30.0 lux
May 11 12:43:17 rtl-433 docker[470]: Flags     : 82
May 11 12:43:17 rtl-433 docker[470]: Total Rain: 86.8 mm
May 11 12:43:17 rtl-433 docker[470]: Supercap Voltage: 2.5 V
May 11 12:43:17 rtl-433 docker[470]: Firmware Version: 133
May 11 12:43:17 rtl-433 docker[470]: Extra Data: 3fff67b2bc------1492ffbffb0000
May 11 12:43:17 rtl-433 docker[470]: Integrity : CRC

modified the auto-login from ttech boi to tail the logs of the container instead /etc/systemd/system/[email protected]/override.conf:

  [Service]
  ExecStart=
  ExecStart=/usr/local/bin/rtl_433_journal.sh
  StandardInput=tty
  StandardOutput=tty
  TTYPath=/dev/tty1
  Restart=always

  # old contents
  # [Service]
  # ExecStart=
  # ExecStart=-/sbin/agetty --autologin root --noclear --keep-baud tty%I 115200,38400,9600 $TERM

and created this to support above: /usr/local/bin/rtl_433_journal.sh:

#!/bin/bash
# Follow the journal of rtl-433 in color
exec journalctl -f -u rtl_433 --output=short-iso

reloaded and restarted:

systemctl daemon-reload
systemctl restart container-getty@1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment