USB printers, anywhere on your network.
printstack grew out of the same idea as BLEProxy: take a physical adapter that normally has to sit right next to a computer, and make it available over the network instead. BLEProxy does that for Bluetooth -- your BLE dongle lives on one box, and clients connect to it over IP. printstack does the same trick for USB printers. Plug them into a Raspberry Pi, and an Incus container on your LAN picks them up over USB/IP and shares them through CUPS. Your laptops and phones do not need a USB cable, a driver hunt, or a printer sitting on the desk beside them.
Who is this for? Homelabbers, small-office tinkerers, and anyone who already runs Linux infrastructure and owns perfectly good USB-only printers. Maybe the printer lives in the garage, the server closet, or a shelf in the laundry room -- nowhere near the machines that actually need to print. Maybe you run Incus or LXC already and want printing to behave like the rest of your stack: reproducible, documented, and easy to rebuild. You do not need to be a CUPS guru; you do need a Pi, a host for containers, and comfort running a few bootstrap scripts.
When and why reach for it? Reach for printstack when "just share the printer on the network" is not enough -- when you want the Pi to be a dedicated USB/IP proxy, the print server to live in a container with a real LAN address, and both sides to reset themselves on a schedule so configuration drift does not accumulate. Nightly reprovisioning wipes the slate clean: the Pi reboots from cloud-init, the container is destroyed and recreated, and your printers come back without you babysitting services. Use it when you care about that operational calm more than about clicking through a consumer router's USB-sharing wizard.
| Piece | What it does |
|---|---|
| Raspberry Pi 4B | Printers plug in here. usbipd exports them on TCP 3240. |
| Your LAN | The Pi and the print server talk over ordinary Ethernet/WiFi. |
| Incus container | Attaches the remote USB devices, registers them in CUPS, and advertises them to the network. |
| Your devices | Add a printer once (Bonjour/IPP). Print. |
Both nodes are immutable: packages are baked into images ahead of time, and a 02:00 timer reprovisions everything. No creeping apt upgrade surprises on production printing night.
- Raspberry Pi 4B + SD card + USB printer(s)
- Incus host on the same LAN (storage pool, SSH from your workstation)
- A management machine with
bash--./setup.shinstalls everything else (incus,pv,xz-utils, ...)
./setup.shRegisters the printstack command (~/.local/bin/printstack), installs missing dependencies (incus, pv, xz-utils, ...), and creates ~/.printstack/{artifacts,logs,default.env}. printstack itself has no setup subcommand -- this is the one-time, pre-PATH bootstrap step; flash/up/down re-check dependencies on every run after that.
$EDITOR ~/.printstack/default.env
chmod 600 ~/.printstack/default.envOne file holds everything: SSH keys, LAN subnet, Pi hostname, SD card device (DEVICE=/dev/sdX), WiFi password, INCUS_REMOTE, container MAC address.
printstack flash --forceInsert the card, power on the Pi, connect your printers. First boot is hands-off.
./scripts/printserver-image-build.sh./scripts/printserver-bootstrap.shPrinters should appear in CUPS and on the network via Bonjour. Open http://<container-ip>:631 from a machine on the LAN to confirm.
printstack up # bring the print server online (rebuilds image if due, then reprovisions)
printstack down # take the print server offline
printstack flash # update Pi cloud-init or re-flash SD card
printstack help # full CLI referenceVirtual printers -- No hardware handy? Set ENABLE_VIRTUAL_PRINTERS=3 in ~/.printstack/default.env to stand up fake USB printers for testing.
TLS -- Set ENABLE_LETSENCRYPT=true in ~/.printstack/default.env for HTTPS on port 443 (DNS-01 via Namecheap; no inbound 80/443 required).
Firewall -- Both nodes ship with ufw. Printing is limited to PRINT_CIDRS; SSH can be restricted with SSH_CIDRS.
Multiple deployments -- Keep separate deployment files under ~/.printstack/ (e.g. home.env, office.env) and pick one with printstack -env=office.env <command>.
| Symptom | Things to check |
|---|---|
| Pi won't join WiFi | WIFI_PASSWORD in ~/.printstack/default.env; re-flash. 5 GHz needs the brcmfmac NVRAM patch (ccode=US in bootstrap output). |
| cloud-init skipped on Pi | ds=nocloud in /boot/firmware/current/cmdline.txt; fresh meta-data instance-id. |
| usbip attach fails | Pi up? nc -zv usbproxy.printstack.local 3240. Printers connected before boot? |
| CUPS printers offline | Re-run scripts/printserver-bootstrap.sh to rediscover and re-register. |
printstack.sh # CLI: flash, up, down
setup.sh # First-run bootstrap: register printstack, install deps
scripts/
install-deps.sh # Idempotent dependency installer (called by setup.sh and printstack.sh)
pi-bootstrap.sh # Pi SD card + cloud-init
printserver-bootstrap.sh # Incus container + CUPS
printserver-image-build.sh # Pre-baked container image
examples/
deployment.env.example # ~/.printstack/<name>.env template (all settings, one file)
docs/architecture.svg # Diagram above
docs/ # Project-specific agent docs
.agentstack/ # Shared agent workflow submodule (generic guidance)
~/.printstack/ # Not in the repo -- per-machine state
default.env # Deployment env (INCUS_REMOTE, SSH keys, DEVICE, MAC_ADDRESS, ...)
artifacts/ # Rendered cloud-init output (inspect after a run)
logs/ # --create-log session logs
Architecture details, bootstrap phases, gotchas, and agent workflow live in docs/ (project) and .agentstack/ (shared). Start with architecture.md if you are changing how the pieces fit together.
Inspired by BLEProxy's "adapter over IP" pattern. Built for people who want printing to be boring in the best possible way.