preprintd
Printer swarm-worker daemon implementation for PreConnect.
Overview
This tiny worker is just a TcpStream under the hood, constantly listening for jobs and claiming one if open. It works by constantly listening for incoming data from the api.preconnect.app endpoint (which uses Mercure under the hood for streaming real-time data), and then initiating the claiming procedure.
Compiling
Requires Rust (2024 edition or later) to be installed.
Run the traditional release command:
[!NOTE] 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.
Prebuilt Binaries
See the GitHub Releases for a prebuilt binary for either Windows, Linux (built via CI workers running Ubuntu), or macOS.
Daemon Usage
Create a new systemd service which you can enable later:
Write the following INI configuration in your preprintd.service file. Make sure to replace the following things as well:
- Under
Environment=:
WORKER_KEY: Your worker key credential (from the PreConnect API).AGENT: Agent name to use for outbound requests.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.
- Under
User, replaceusernamewith the username you're logged in with on your local machine.
[Unit]
Description=PreConnect Printer Worker Daemon
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/preprintd --debug
Restart=always
Environment="WORKER_KEY=yourworkerkeyhere" "AGENT=preprintd" "DEF_HOST=192.168.0.102" "DEF_QUEUE=queuename"
User=username
[Install]
WantedBy=multi-user.target
Enable and start it once you're done:
# now check status:
To check the logs in real-time, run:
Code Inspection
When you're going through the code, you'll see these:
- Some
decode_b64_string()calls - those are primarily for obfuscation needs but since the inner value is Base64-encoded, you can easily use a decoder to decouple the values underneath. One such tool that you can use is this. - The standard LPR/LPD sequence (except the code doing HTTP requests via reqwest's blocking API and every other code surrounding/using this logic).
- LOTS of
LazyLockusage. ALthough this is not optimal for a program that's supposed to be tiny, we've kept this pattern to reuse as much data as physically possible without hardcoding and messing up.
Mercure SSE Connection Protocol
preprintd streams real-time job notifications from the Mercure Hub (/.well-known/mercure).
- Endpoint:
https://api.preconnect.app/.well-known/mercure?topic=https%3A%2F%2Fpreconnect.app%2Fprinter - Authorization:
Bearer <subscriber-jwt>- The subscriber JWT is created by signing
{"mercure":{"subscribe":["https://preconnect.app/printer"]}}with HMAC-SHA256 usingWORKER_KEY.
- The subscriber JWT is created by signing
- Replay Support: On reconnect, pass the
Last-Event-IDheader containing the lastid:value received from the stream to receive any missed jobs.
Reference Implementation
See: https://github.com/sabbirba/preconnect/blob/main/printer.py (courtesy: @sabbirba)
License
Licensed under the GNU General Public License v3.