Skip to main content

py-printer-server

PyPI Python versions License: MIT

Remote print server for a USB-connected printer. Python 3.11+. Runs on the machine your printer is plugged into; phones and laptops on the same network upload files to it and print them.

Disclaimer. Provided AS IS, without warranty of any kind, express or implied. Use entirely at your own risk. This tool binds a network port on the machine it runs on and lets an authenticated user upload files and send them to a physical printer attached to that machine. The author accepts no liability for data loss, wasted paper or ink, hardware damage, unauthorised access or disclosure, business interruption, or consequential damages. You are responsible for setting a strong ADMIN_PASSWORD (the server refuses to start without one), limiting the server to a network you trust, verifying the printer and paper tray before printing, and being authorised to print the files you upload. Not certified for regulated, forensic, safety-critical or high-assurance use. See DISCLAIMER.md; LICENSE is the governing text and prevails where the two differ.

Install

pip install py-printer-server

Run

$env:ADMIN_PASSWORD = "your-strong-password"
py-printer-server

pps is installed as a shorter alias for the same command. No ADMIN_PASSWORD set? Get a suggestion with:

py-printer-server --generate-password

The server prints its LAN URL on startup, e.g. http://192.168.1.24:8114 -- open that from a phone on the same Wi-Fi, log in, and upload a file.

Options

--port PORT        Port (default: 8114)
--spool DIR         Spool folder for uploaded files (default: ./spool)
--jobs DIR          Archive folder for printed jobs (default: ./jobs)
--list-printers      List installed printers (hardware vs virtual) and exit
--dry-run            Log what would be printed instead of sending jobs to a printer
--generate-password  Print a strong random ADMIN_PASSWORD suggestion and exit
--no-qr              Do not print a QR code for the LAN URL on startup
--no-discovery       Do not answer UDP discovery probes from companion apps
--discovery-port     UDP port for discovery (default: 8114)
--mdns               Also advertise over mDNS (visible to every device on the LAN)
--mdns-name NAME     Instance name to advertise over mDNS (default: this hostname)
--discover           Probe the network for print servers, then exit

How it works

  1. Upload one or more files from any device on the network -- they land in a single flat spool folder. There is no folder browsing; uploading is the whole interaction.
  2. Tick the files you want (or "Print all"), pick a printer, colour/mono, A4, copies and duplex, and press Print.
  3. Printed files are moved, not deleted, into jobs\print-job-<timestamp>-<id>\, alongside a job.json recording what was printed and how -- so nothing needs re-uploading to print again. A file that failed or could not be printed stays in the spool so you can retry it; only successfully printed files are archived.

A job is reported done only when every file actually printed. A mixed result is partial, and the jobs panel shows the reason next to each file that did not print.

Only real hardware printers are shown by default (a "Show all printers" checkbox reveals software sinks like Microsoft Print to PDF).

Supported files

Type Path
Plain text / code (.txt .log .csv .md .py .json ...) Printed directly by this tool. Full control over colour, copies and duplex.
PDF and images (.pdf .png .jpg .tif .bmp .gif) Rendered and printed by this tool. Full control over colour, copies and duplex.
Office documents (.docx .xlsx .pptx .doc .xls .ppt) Same shell handoff, via Word/Excel/PowerPoint.

Colour, copies and duplex apply to text, images and PDFs, which this server renders and prints itself. Office documents are still handed to Word, Excel or PowerPoint, which build their own print settings, so those follow the printer's standing Windows defaults. The jobs list says which you got for each file.

Until 0.5.0 these settings applied to nothing: the server built the right settings and then never attached them to the print job, so everything came out at the printer's own defaults. If you set this printer's Windows default to mono as a workaround, you can set it back.

Dependencies

Two, both required: Pillow and pypdfium2, for decoding images and rendering PDF pages. They exist for one reason: per-job colour control is impossible without rendering pages ourselves, because handing a file to Windows' own handler cannot carry print settings.

Everything else is still standard library, deliberately: Win32 access is hand-written ctypes against winspool.drv/gdi32/shell32, and the HTTP server, LAN discovery, mDNS and QR code are all stdlib. pywin32 in particular stays rejected, since winspool.py already does that job.

Finding the server from an app

Typing http://192.168.1.24:8114 gets old once DHCP moves the address, so the server answers a discovery probe on UDP 8114 and replies with the URL it is reachable on. A companion Android app broadcasts a probe and opens the answer in a WebView.

The beacon is gated on a shared secret derived from ADMIN_PASSWORD, the same password that logs into the web UI. A probe without a valid signature gets no reply at all, so the server does not announce itself to a port scanner, and an app has to be told the password before it can find anything.

Try it without an app, from any machine on the network that has the package installed and the same ADMIN_PASSWORD set:

py-printer-server --discover
Probing UDP 8114 for print servers sharing this ADMIN_PASSWORD...
  OFFICE-PC                http://192.168.1.24:8114  (v0.2.0)

The discovery port is deliberately independent of --port. A client has to be able to find a server whatever HTTP port it was started on, so the beacon stays on 8114 and the reply carries the real port.

The wire format is specified in PROTOCOL.md, in enough detail to implement a client without reading the Python.

Windows Firewall

This is the first thing to check when the web UI works from a phone but an app finds nothing. The prompt Windows showed on first run covered TCP; the UDP responder usually has no rule at all, and no prompt appears when the server runs without an interactive desktop session.

In an elevated PowerShell:

New-NetFirewallRule -DisplayName "py-printer-server discovery" `
  -Direction Inbound -Protocol UDP -LocalPort 8114 -Action Allow -Profile Private

That rule only applies on a Private network. Windows classifies new networks as Public, where inbound is blocked wholesale, so check and fix the profile too:

Get-NetConnectionProfile
Set-NetConnectionProfile -InterfaceAlias "Wi-Fi" -NetworkCategory Private

Other reasons a probe finds nothing: a different ADMIN_PASSWORD (silence is by design), client isolation on a guest Wi-Fi network (which blocks all traffic between devices, so the web UI will not load either), or a VPN on the phone.

mDNS

--mdns additionally advertises _pyprint._tcp so Android's NsdManager and generic DNS-SD browsers can see the server.

It is off by default and it is not gated. An mDNS advertisement is an unauthenticated broadcast by nature, so turning it on makes this machine visible to everything on the network. The advertisement is kept minimal (service type, hostname, port, and a path=/ TXT record; no printer name, version or user), and the startup banner says so whenever it is enabled. It needs inbound UDP 5353 through the firewall as well.

Security

  • The server refuses to start unless ADMIN_PASSWORD is set.
  • Login attempts are rate-limited (5 failures locks an IP out for 5 minutes).
  • A login lasts a year and survives a server restart, so a phone does not have to log in again every day. It is revocable two ways: Log out on any page ends that device's session, and changing ADMIN_PASSWORD signs out every device everywhere, because each session records which password issued it.
  • Session tokens live in sessions.json inside the spool folder. That file is hidden from the file listing and never served. Treat it as a credential: anyone who can read it can act as you until the password changes.
  • Sessions use HttpOnly, SameSite=Strict cookies with CSRF tokens on every state-changing request.
  • Windows-only: printer access is unavailable on any other OS.
  • Discovery probes must be signed with a key derived from ADMIN_PASSWORD (PBKDF2-HMAC-SHA256); an unsigned or wrongly signed probe is answered with silence, not an error.
  • Traffic is plain HTTP. Discovery is authenticated, but the login POST and the session cookie still cross the LAN in the clear, so treat this as a tool for a network you trust.

License

MIT. See LICENSE.

Metadata

Release files for py-printer-server 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for py-printer-server 0.5.0
File Size Uploaded Provenance
py_printer_server-0.5.0.tar.gz 75.5 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for py-printer-server 0.5.0
File Interpreter ABI Platform
py_printer_server-0.5.0-py3-none-any.whl Python 3 none any Details

Total release size: 154.1 kB

Release files / py_printer_server-0.5.0.tar.gz

Download URL py_printer_server-0.5.0.tar.gz
Size 75.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f38ffb1f4d9241cff1543cf8ca85bd5428036e9171a7c3abed665800b5198604
BLAKE2b-256 checksum
How to use checksums
b388d0be8dc9a923fa03da87896e60b21ff916f626e41f62f34ae83e42e8c66e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Release files / py_printer_server-0.5.0-py3-none-any.whl

Download URL py_printer_server-0.5.0-py3-none-any.whl
Size 78.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
fde296d3be85d187ea21133a4e057e94fec7721f52fd2277255f1a15af075343
BLAKE2b-256 checksum
How to use checksums
0e7f817e278faf3e2d1dc5573c903e3a9be4ba691e2f247ecba5bd0a31ab1a82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.5

Security for py-printer-server 0.5.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page