Cups AirPrint server with legacy printer driver support using Qemu
In this guide I’m going to explain how to setup a CUPS network printing server on an ARM-Linux which will still be able to use older or discontinued printer drivers, meant for other architectures, using Docker with Qemu platform emulation. Therefore we could use for example 32bit Linux printer drivers emulated inside the container on our ARM server. In my example I’m going to setup a Dell 1250c printer, but it should work with other devices as well.
Network printing setup
For a long time I used a Dell 1250c printer, connected to a TP-Link router running OpenWRT, as a simple network printing solution at home. Using this setup, I was able to print from every machine in the local network, as long the client operating system still had support for the legacy and proprietary Dell drivers.
Soon I wanted to replace the TP-Link router with my own home-server, a small and inexpensive ARM-computer running ArchLinux. Installing and configuring a printing server using CUPS even on this platform would be easy but requires open-source printing drivers. Officially, Dell has limited driver support for Windows and MacOS only, regarding this device. An alternative driver for the Xerox Phaser 6000 from 2011, which is compatible to this Dell printer, has Linux support but is limited to the 32bit PC architecture. I won’t be able to use it on my ARM-platform.
So somehow I have to “emulate” an 32/64bit environment with CUPS and the printing driver on the ARM board, to be able to use this printer again. I achieved this using Qemu and Docker using an experimental and lesser known container configuration.
Setting up Qemu, Docker and Cups
On the ARM board, install Docker and bootstrap an x64 ArchLinux container called archlinux-cupsd
. We’ll also need the AUR package qemu-user-static-bin
. The following commands were tested on ArchLinux ARM but in principle the setup should work on other distributions too.
pacman -S docker qemu-user-static-bin
gpasswd -a picloud docker
systemctl enable --now docker
DOCKER_CLI_EXPERIMENTAL=enabled docker run --privileged \
-v /dev/bus/usb/:/dev/bus/usb/ \
-p 631:631 \
-dit \
--restart always \
--entrypoint /usr/bin/cupsd \
--name archlinux-cupsd --platform linux/amd64 archlinux
docker exec -it archlinux-cupsd
Docker and the container will be automatically started at boot time and will forward the CUPS configuration and network printing port 631
to the host. Be sure to open this port on your firewall.
The last command will spawn a shell inside the container. We’re now going to install the CUPS daemon together with the proprietary printing drivers. Depending on the driver, you have to add custom repositories to your package manager.
[...]
[multilib]
Include = /etc/pacman.d/mirrorlist
[projectinsanity]
SigLevel = PackageOptional
Server = https://onny.project-insanity.org/archlinux
pacman -Sy cups ghostscript xerox-phaser-6000-6010
passwd
Besides installing the dependencies, we’re going to set a root password since we’ll need it later in the CUPS web admin interface.
Before starting the CUPS printing server, modify the configuration file to allow remote access. The following example is very permissive regarding network security so you might want to change this according to your needs.
[...]
Listen 0.0.0.0:631
[...]
# Restrict access to the server...
<Location />
Order allow,deny
Allow all
</Location>
# Restrict access to the admin pages...
<Location /admin>
Order allow,deny
Allow all
</Location>
[...]
Check if your USB-attached printer is recognized inside the container and check if the permissions are correct.
lsusb
[...]
# Bus 002 Device 004: ID 413c:5404 Dell 1250c Color Printer
[...]
chown root:lp /dev/bus/usb/002/004
Exit the container and if required restart it with docker start archlinux-cupsd
.
Now you can setup the printer as usual via the web configuration interface at https://<YOUR_SERVER_IP>:631/admin/
(use the local ip address of your printing server). The legacy printer drivers should be also available and working.
Setting up Avahi and AirPrint
In the last step, we’ll going to configure Avahi, a daemon which will advertise our network printer to the local network. I guess CUPS would run fine together with Avahi but without Systemd-support in Docker (and no multi-platform support in Podman) it is a bit complicated to run both in the container. So I decided to run Avahi outside of the Docker container on the host system itself.
pacman -S nss-mdns avahi
Create following Avahi-service file but be sure to replace following sections: name
, rp
, ty
, adminurl
and note
. The part adminurl
is crucial, it will correspond to the internal printer name used by CUPS. Go to https://<YOUR_SERVER_IP>:631/printers/
, click on your configured printer and write down the printer name.
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
<name>Dell 1250c</name>
<service>
<type>_ipp._tcp</type>
<subtype>_universal._sub._ipp._tcp</subtype>
<port>631</port>
<txt-record>txtver=1</txt-record>
<txt-record>qtotal=1</txt-record>
<txt-record>rp=printers/Dell_1250c_Color_Printer</txt-record>
<txt-record>ty=Dell 1250c</txt-record>
<txt-record>adminurl=http://192.168.178.2:631/printers/Dell_1250c_Color_Printer</txt-record>
<txt-record>note=Dell 1250c</txt-record>
<txt-record>priority=0</txt-record>
<txt-record>product=(GPL Ghostscript)</txt-record>
<txt-record>printer-state=3</txt-record>
<txt-record>printer-type=0x801046</txt-record>
<txt-record>Transparent=T</txt-record>
<txt-record>Binary=T</txt-record>
<txt-record>Fax=F</txt-record>
<txt-record>Color=T</txt-record>
<txt-record>Duplex=T</txt-record>
<txt-record>Staple=F</txt-record>
<txt-record>Copies=T</txt-record>
<txt-record>Collate=F</txt-record>
<txt-record>Punch=F</txt-record>
<txt-record>Bind=F</txt-record>
<txt-record>Sort=F</txt-record>
<txt-record>Scan=F</txt-record>
<txt-record>pdl=application/octet-stream,application/pdf,application/postscript,image/jpeg,image/png,image/urf</txt-record>
<txt-record>URF=W8,SRGB24,CP1,RS600</txt-record>
</service>
</service-group>
Depending on your setup you should disable multicast dns in systemd-resolved since we’re going to use Avahi together with nss-mdns.
[...]
MulticastDNS=no
[...]
Apply the changes.
chown avahi:avahi /etc/avahi/services/airprint.service
systemctl restart systemd-resolved # if used and required
systemctl enable --now avahi
Install network printer on other hosts
Your Linux guest system, which will use the network printer, will automatically detect the printer as soon CUPS and Avahi is running there. Use following command to see if your server is advertising the printer.
avahi-browse -art
+ wlan0 IPv4 Dell 1250c _ipp._tcp local
= wlan0 IPv4 Dell 1250c _ipp._tcp local
hostname = [picloud.local]
address = [192.168.178.2]
port = [631]
txt = ["URF=W8,SRGB24,CP1,RS600" "pdl=application/octet-stream,application/pdf,application/postscript,image/jpeg,image/png,image/urf" "Scan=F" "Sort=F" "Bind=F" "Punch=F" "Collate=F" "Copies=T" "Staple=F" "Duplex=T" "Color=T" "Fax=F" "Binary=T" "Transparent=T" "printer-type=0x801046" "printer-state=3" "product=(GPL Ghostscript)" "priority=0" "note=Dell 1250c" "adminurl=http://192.168.178.2:631/printers/Dell_1250c_Color_Printer" "ty=Dell_1250c_Color_Printer" "rp=printers/Dell_1250c_Color_Printer" "qtotal=1" "txtver=1"]
Discovering and using the printer without installing any drivers is possible on iOS 13, Windows 10 and the lastest Ubuntu.
Great instruction. The .service file is exactly make the printer to work without playing with the https://github.com/tjfontaine/airprint-generate/blob/master/airprint-generate.py
I also found out, in CUPS, without config Avahi above, make the printer as shared, it would also able to appear on the AirPrint using iOS device. Could this be cause by the newer version of iOS have better support on CUPS print share as the protocol?
Thanks!
Thank you so much for sharing! Just came over from HN because of the iPhone Linux articel. Great work you are doing here :-)