Printer swarm-worker daemon implementation for PreConnect.
This tiny worker is just a stateless SSE/TcpStream hybrid under the hood, which constantly listens for incoming jobs from the printer SSE endpoint of the PreConnect API, and dispatches them over to the on-campus LPR server via a simple TcpStream.
Requires Rust (2024 edition or later) to be installed.
Run the traditional release command:
cargo build --releaseYou can also directly install the preprintd binary globally using cargo:
cargo install preprintdNote
The release binary is optimized for the smallest-possible size, although you can change this behavior by disabling the optimizations specified in the [profile.release] section of Cargo.toml.
See the GitHub Releases for a prebuilt binary for either Windows, Linux (built via CI workers running Ubuntu), or macOS. The Linux builds are done using the x86_64-unknown-linux-musl target in Rust (see musl.cc).
Create a new systemd service which you can enable later:
sudo touch /etc/systemd/system/preprintd.service
# or, if you want to run it as a user unit:
# touch ~/.config/systemd/user/preprintd.serviceWrite this INI configuration in your preprintd.service file. Make sure to replace the following fields/values:
- Under
Environment=:
WORKER_KEY: Your worker key credential (from the PreConnect API).DEF_HOST: The default printer host to use in case the API cannot provide one.DEF_QUEUE: The default queue name to send printable data to.- (Optional)
ALIAS: The name which determines the program's identity on the system and in TCP requests.
- Replace
/usr/bin/preprintdwith the appropriate path to the daemon binary. - Replace
usernamewith your appropriate username on the machine. Note that this step is important if you want to use--inhibitlater on (see below).
Warning
If you prefer to use the service as a user unit (or, in other words, by passing in systemctl --user), please make sure to omit the User field from [Service], otherwise it may cause errors during service startup.
Once you are done, enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable --now preprintd
# or, for running as a user unit:
# systemctl --user daemon-reload
# systemctl --user enable --now preprintd
# now check status:
systemctl status preprintdTo check the logs in real-time, run:
journalctl -u preprintd -fYou may pass in the --inhibit flag while running preprintd to acquire an inhibitor FD (or "file descriptor") for the lifecycle of the program. This will prevent your Linux machine from sleeping. This may be crucial if you computer auto-suspends, and suspension may kill outward connections such as the running HTTP SSE from the program.
The file descriptor is derived from the org.freedesktop.login1 service. While running the daemon with --inhibit, you can test out the functionality by running systemd-inhibit --list and checking if preprintd pops up anywhere.
Note
Using inhibitor locks may not prevent manual suspension/sleep.
Some things you might come across while navigating this project are noted down below:
- The standard LPR/LPD sequence is present for sending requests to the LPR server. Note that the connections are one-time, meaning that once a job has been dispatched successfully, the socket connection is dropped. It is recreated once another job has been received.
- Lots of
LazyLockusage is present. Although this is not optimal for a program that's supposed to be tiny, this pattern has been used to reuse as much data as physically possible to (slightly) prevent hardcoding and messing things up. - The HTTP requests are made using a
reqwest::blocking::Clientinstance, and in general there is zero async I/O inside the codebase. Some tasks are just offloaded to separate threads (i.e. decoupling aJobusinghandle()). - The
clientmodule provides aready_tls_config()function which creates a custom client configuration for thereqwest::blocking::Clientinstance to enable TLS 1.3 and ECH (Encrypted Client Hello) support.
More specific parts of the codebase that you may be more curious about are described below:
Although most of the instructions above are primarily made for Linux (and can be migrated over to Unix/macOS), some built-in features are not available on the Windows operating system by default. For example, the STATE_DIRECTORY environment variable set via systemd during runtime never shows up there. Moreover, some Windows-specific features might be missing from this implementation entirely, for which it is encouraged that you give the Reference Implementation a try.
While claiming a job, each worker identifies itself with an X-Worker-Ident header which has a pattern of <UUID>;<ARCH>;<IDENTITY_TYPE> (e.g. 03780793-e7af-49c1-b55d-92ff57be8c6e;aarch64-apple-darwin;static).
Visible from the pattern mentioned above, the worker identity can be broken down into three parts:
<UUID>: A randomly-generated UUID (v4) string literal, which is used to give the worker a unique identity to be correlated with.<ARCH>: The architecture of the compiled binary of the worker.<IDENTITY_TYPE>: Another string literal representing whether the identity is static (when it successfully retrieves a previous identity or creates a new one under$STATE_DIRECTORY/.identand then retrieves it), or dynamic (due to issues withstd::fsoperations or just the$STATE_DIRECTORYpath being unavailable).
See: https://github.com/sabbirba/preconnect/blob/main/printer.py (courtesy: @sabbirba)
Licensed under the GNU General Public License v3.