One of my recent projects involved having a cash register print receipts to a thermal printer. In order to do this, we had a Sinatra server running on boot on the device that would, upon receiving a POST request, print out the ESC/POS data.

The way I did this was by creating a custom service on Ubuntu that automatically starts on boot that runs the following script:

  $ su - <NON_ROOT_USER> -l -c \
    "cd /path/to/code && \
    bundle exec ruby app.rb /dev/usb/lp0"

In this script, we’re passing the file path /dev/usb/lp0 to the server. This is the file descriptor for the thermal printer I wish to print to.

With all this set, what happens when I power on the device and send in a POST request?

Sadly, I get an error saying that I don’t have permission to write to the file /dev/usb/lp0. I can run the following command to fix this, though:

  $ sudo chown <NON_ROOT_USER> /dev/usb/lp0

Once I enter it, I can print to my heart’s (and paper capicity’s) content! However, once the device gets rebooted, the file will get re-created and not have the permissions I assigned. Well, darn.

I did find a solution that could help me, though!

Custom udev rules

This was when I learned about udev, the system for managing devices in Debian systems. With it, I can write specific rules for recognising devices:

udev allows for rules that specify what name is given to a device, regardless of which port it is plugged into. For example, a rule to always mount a hard drive with manufacturer “iRiver” and device code “ABC” as /dev/iriver is possible. This consistent naming of devices guarantees that scripts dependent on a specific device’s existence will not be broken.

This is exactly what I need! I can assign all usb devices plugged into the Linux machine to be owned by NON_ROOT_USER.

By following the instructions, I created a file in the directory /etc/udev/rules.d called 99-perm.rules with the following line:

  SUBSYSTEM=="usb", OWNER="<NON_ROOT_USER>"

With this rule in place, rebooting the machine immediately made it work!

Making it more secure

You might’ve been asking yourself:

Does this mean all USB devices will be owned by NON_ROOT_USER?

And you would be absolutely correct. Maybe we want to only grant permissions for that one device, which we can totally do! We can base it on the serial number of the device. For example, for my thermal printer, I can find this out with the following command:

  $ udevadm -a /dev/usb/lp0

This’ll print out some results, including this line:

    ATTRS{serial}=="L29955839962630040"

Let’s open up /etc/udev/rules.d/99-perm.rules again, and replace the line we added with the following:

  SUBSYSTEM=="usb", ATTRS{serial}=="L29955839962630040", OWNER="<NON_ROOT_USER>"

See with this we’re specifically telling udev that we wanna own this specific thermal printer. A quick reboot and score, it works!

What’s next?

A solid question! There is certainly more we can do to make sure this system works.

For example, if I were to have multiple thermal printers on a device, then it wouldn’t be guaranteed to have the name lp0. For this case we can use the SYMLINK+= attribute to specify a filename it’ll always definitely have. So instead of lp0 or lp1 I could point my script to thermal-printer or something:

  SUBSYSTEM=="usb", ATTRS{serial}=="L72010011070626380", OWNER="<NON_ROOT_USER>", SYMLINK+="usb/thermal-printer"

This will then create a symbolic link called /dev/usb/thermal-printer that I can point my Ruby server to!

Big thank you to Daniel Drake for this helpful guide on how to write rules for udev!

I’m eager to hear your thoughts or ideas for improvements, so hit me up on Twitter!

Buy me a coffee