Occasional blog posts from a random systems engineer

Moving back to physical tasks

· Read in about 4 min · (683 Words)

In my work life, I’ve moved away from working in a team and into an IC role. As a result, not only do I have a much longer list of tasks, but the scope and variety of those tasks has expanded significantly—more context switching, more stakeholders, more blockers, and more background work to keep track of.

I’ve tried using various tools to manage this—Microsoft To Do, Jira boards, etc.—but they’re either slow and painful to update or just don’t feel like a true “go-to” system.

After reading a great post by Laurie, I wanted to replicate the setup. Bringing a tactile, physical element to tasks felt like a great idea. Plus, without a clear end-of-sprint review or celebration, my ongoing list of tasks rarely felt satisfying or complete.

In this post, I’m not going to focus on the workflow itself, but instead on a couple of interesting challenges I encountered while building and deploying the application:

Authentication

The ability to print tasks shouldn’t be limited to just my personal laptop or phone—I also want to create them easily from my work laptop. However, for my homelab systems, I only expose endpoints that are publicly accessible. As a result, my usual authentication tooling (e.g. OIDC, SAML) isn’t available.

For this project, I did need authentication, but I was open to something more lightweight and custom.

My goals were:

  • Easy authentication
  • Avoid exposing personal information to my work laptop
  • Avoid exposing anything work-related to my personal infrastructure

I settled on SSH key authentication.

SSH public keys are, by design, shareable—so there’s no issue using them from a work device. The underlying cryptography is well established, and there are plenty of libraries to support it.

The approach:

  • Generate the request body (including the URL)
  • Sign the request using the private key and include the signature as a header
  • Include the public key in the request headers

On the API side, I maintain a list of allowed public keys. The server validates:

  • The signature against the request body
  • That the signature matches the provided public key
  • That the public key is in the allowed list

Passing hardware to the application

The second challenge was exposing the receipt printer to the application.

The printer I purchased was USB (unfortunately not a network printer), and the application runs in Docker, which itself runs on a QEMU VM (configured by libvirt).

To both automate passing through the device and allow me to power the printer on/off at will, I configured the following:

Host -> VM

Libvirt has a simple configuration to pass devices to VMs at runtime, using virsh attach-device printer "printer.xml" --live, with an XML definition like:

<hostdev mode='subsystem' type='usb' managed='yes'>
    <source>
        <vendor id='0x17ef'/>
        <product id='0x480f'/>
    </source>
</hostdev>

To automatically attach/detach the device when it’s powered on/off, I used udev rules to trigger scripts:

ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="17ef", ATTR{idProduct}=="480f", RUN+="/opt/printer/attach_printer.sh"
ACTION=="remove", SUBSYSTEM=="usb", ATTR{idVendor}=="17ef", ATTR{idProduct}=="480f", RUN+="/opt/printer/detach_printer.sh"

VM -> Container

Similarly, inside the VM, another udev rule detects when the USB device is attached or removed.

To expose the device to the container, I used Docker’s device flag, which maps a host device into the container. The downside is that if the device isn’t present, the container fails to start. While my orchestrator can handle restarts indefinitely, I didn’t want it thrashing when the printer is intentionally off (e.g. overnight).

Instead, I had udev run a script that creates a stable symlink in /dev. When the printer is connected, it points to the real device; when disconnected, it points to /dev/null.

The script then restarts the container so it picks up the change.

The small caveat is that the orchestrator currently takes ~30 seconds to reschedule the task, but this isn’t a major issue and can be tuned.

Fin

As the original post suggested, this has been a really effective way to keep track of tasks—both for work and personal use. The physical nature of printing tasks adds a small but meaningful sense of progress and completion.

The addition of an iOS app has also been a nice extension. Building that was an interesting experience in itself—but probably something for another post.